diff --git a/docs/zh/docs/advanced/additional-responses.md b/docs/zh/docs/advanced/additional-responses.md new file mode 100644 index 0000000000000..c76a92f238c37 --- /dev/null +++ b/docs/zh/docs/advanced/additional-responses.md @@ -0,0 +1,240 @@ +# Additional Responses in OpenAPI + +!!! warning + This is a rather advanced topic. + + If you are starting with **FastAPI**, you might not need this. + +You can declare additional responses, with additional status codes, media types, descriptions, etc. + +Those additional responses will be included in the OpenAPI schema, so they will also appear in the API docs. + +But for those additional responses you have to make sure you return a `Response` like `JSONResponse` directly, with your status code and content. + +## Additional Response with `model` + +You can pass to your *path operation decorators* a parameter `responses`. + +It receives a `dict`, the keys are status codes for each response, like `200`, and the values are other `dict`s with the information for each of them. + +Each of those response `dict`s can have a key `model`, containing a Pydantic model, just like `response_model`. + +**FastAPI** will take that model, generate its JSON Schema and include it in the correct place in OpenAPI. + +For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write: + +```Python hl_lines="18 22" +{!../../../docs_src/additional_responses/tutorial001.py!} +``` + +!!! note + Have in mind that you have to return the `JSONResponse` directly. + +!!! info + The `model` key is not part of OpenAPI. + + **FastAPI** will take the Pydantic model from there, generate the `JSON Schema`, and put it in the correct place. + + The correct place is: + + * In the key `content`, that has as value another JSON object (`dict`) that contains: + * A key with the media type, e.g. `application/json`, that contains as value another JSON object, that contains: + * A key `schema`, that has as the value the JSON Schema from the model, here's the correct place. + * **FastAPI** adds a reference here to the global JSON Schemas in another place in your OpenAPI instead of including it directly. This way, other applications and clients can use those JSON Schemas directly, provide better code generation tools, etc. + +The generated responses in the OpenAPI for this *path operation* will be: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +The schemas are referenced to another place inside the OpenAPI schema: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## Additional media types for the main response + +You can use this same `responses` parameter to add different media types for the same main response. + +For example, you can add an additional media type of `image/png`, declaring that your *path operation* can return a JSON object (with media type `application/json`) or a PNG image: + +```Python hl_lines="19-24 28" +{!../../../docs_src/additional_responses/tutorial002.py!} +``` + +!!! note + Notice that you have to return the image using a `FileResponse` directly. + +!!! info + Unless you specify a different media type explicitly in your `responses` parameter, FastAPI will assume the response has the same media type as the main response class (default `application/json`). + + But if you have specified a custom response class with `None` as its media type, FastAPI will use `application/json` for any additional response that has an associated model. + +## Combining information + +You can also combine response information from multiple places, including the `response_model`, `status_code`, and `responses` parameters. + +You can declare a `response_model`, using the default status code `200` (or a custom one if you need), and then declare additional information for that same response in `responses`, directly in the OpenAPI schema. + +**FastAPI** will keep the additional information from `responses`, and combine it with the JSON Schema from your model. + +For example, you can declare a response with a status code `404` that uses a Pydantic model and has a custom `description`. + +And a response with a status code `200` that uses your `response_model`, but includes a custom `example`: + +```Python hl_lines="20-31" +{!../../../docs_src/additional_responses/tutorial003.py!} +``` + +It will all be combined and included in your OpenAPI, and shown in the API docs: + + + +## Combine predefined responses and custom ones + +You might want to have some predefined responses that apply to many *path operations*, but you want to combine them with custom responses needed by each *path operation*. + +For those cases, you can use the Python technique of "unpacking" a `dict` with `**dict_to_unpack`: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +Here, `new_dict` will contain all the key-value pairs from `old_dict` plus the new key-value pair: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +You can use that technique to re-use some predefined responses in your *path operations* and combine them with additional custom ones. + +For example: + +```Python hl_lines="13-17 26" +{!../../../docs_src/additional_responses/tutorial004.py!} +``` + +## More information about OpenAPI responses + +To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification: + +* OpenAPI Responses Object, it includes the `Response Object`. +* OpenAPI Response Object, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`. diff --git a/docs/zh/docs/advanced/additional-status-codes.md b/docs/zh/docs/advanced/additional-status-codes.md index 54ec9775b6204..9981a8c7b5eec 100644 --- a/docs/zh/docs/advanced/additional-status-codes.md +++ b/docs/zh/docs/advanced/additional-status-codes.md @@ -10,25 +10,61 @@ 例如,假设你想有一个 *路径操作* 能够更新条目,并且更新成功时返回 200 「成功」 的 HTTP 状态码。 -但是你也希望它能够接受新的条目。并且当这些条目不存在时,会自动创建并返回 201 「创建」的 HTTP 状态码。 +但是你也希望它能够接受新的条目。 并且当这些条目不存在时,会自动创建并返回 201 「创建」的 HTTP 状态码。 要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。 -```Python hl_lines="4 25" -{!../../../docs_src/additional_status_codes/tutorial001.py!} -``` +=== "Python 3.10+" -!!! warning "警告" - 当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。 + ```Python hl_lines="4 25" + !!! note "技术细节" + 你也可以使用 from starlette.responses import JSONResponse。 + ``` +。 + - FastAPI 不会用模型等对该响应进行序列化。 +=== "Python 3.9+" - 确保其中有你想要的数据,且返回的值为合法的 JSON(如果你使用 `JSONResponse` 的话)。 + ```Python hl_lines="4 25" + !!! warning "警告" + 当你直接返回一个像上面例子中的 Response 对象时,它会直接返回。 + ``` + 对象时,它会直接返回。 + -!!! note "技术细节" - 你也可以使用 `from starlette.responses import JSONResponse`。  +=== "Python 3.6+" - 出于方便,**FastAPI** 为开发者提供同 `starlette.responses` 一样的 `fastapi.responses`。但是大多数可用的响应都是直接来自 Starlette。`status` 也是一样。 + ```Python hl_lines="4 26" + {!> ../../../docs_src/additional_status_codes/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 23" + {!> ../../../docs_src/additional_status_codes/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 25" + {!../../../docs_src/additional_status_codes/tutorial001.py!} + ``` + +!!! warning + When you return a `Response` directly, like in the example above, it will be returned directly. + + FastAPI 不会用模型等对该响应进行序列化。 确保其中有你想要的数据,且返回的值为合法的 JSON(如果你使用 `JSONResponse` 的话)。 + +!!! note "Technical Details" + You could also use `from starlette.responses import JSONResponse`. + + 出于方便,**FastAPI** 为开发者提供同 `starlette.responses` 一样的 `fastapi.responses`。 但是大多数可用的响应都是直接来自 Starlette。 `status` 也是一样。 ## OpenAPI 和 API 文档 diff --git a/docs/zh/docs/advanced/advanced-dependencies.md b/docs/zh/docs/advanced/advanced-dependencies.md new file mode 100644 index 0000000000000..845713c30a966 --- /dev/null +++ b/docs/zh/docs/advanced/advanced-dependencies.md @@ -0,0 +1,138 @@ +# Advanced Dependencies + +## Parameterized dependencies + +All the dependencies we have seen are a fixed function or class. + +But there could be cases where you want to be able to set parameters on the dependency, without having to declare many different functions or classes. + +Let's imagine that we want to have a dependency that checks if the query parameter `q` contains some fixed content. + +But we want to be able to parameterize that fixed content. + +## A "callable" instance + +In Python there's a way to make an instance of a class a "callable". + +Not the class itself (which is already a callable), but an instance of that class. + +To do that, we declare a method `__call__`: + +=== "Python 3.9+" + + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` + +In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later. + +## Parameterize the instance + +And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency: + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` + +In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code. + +## Create an instance + +We could create an instance of this class with: + +=== "Python 3.9+" + + ```Python hl_lines="18" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="16" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` + +And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`. + +## Use the instance as a dependency + +Then, we could use this `checker` in a `Depends(checker)`, instead of `Depends(FixedContentQueryChecker)`, because the dependency is the instance, `checker`, not the class itself. + +And when solving the dependency, **FastAPI** will call this `checker` like: + +```Python +checker(q="somequery") +``` + +...and pass whatever that returns as the value of the dependency in our *path operation function* as the parameter `fixed_content_included`: + +=== "Python 3.9+" + + ```Python hl_lines="22" + {!> ../../../docs_src/dependencies/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="21" + {!> ../../../docs_src/dependencies/tutorial011_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="20" + {!> ../../../docs_src/dependencies/tutorial011.py!} + ``` + +!!! tip + All this might seem contrived. And it might not be very clear how is it useful yet. + + These examples are intentionally simple, but show how it all works. + + In the chapters about security, there are utility functions that are implemented in this same way. + + If you understood all this, you already know how those utility tools for security work underneath. diff --git a/docs/zh/docs/advanced/async-sql-databases.md b/docs/zh/docs/advanced/async-sql-databases.md new file mode 100644 index 0000000000000..6a85f2237ea1f --- /dev/null +++ b/docs/zh/docs/advanced/async-sql-databases.md @@ -0,0 +1,169 @@ +# Async SQL (Relational) Databases + +!!! info + These docs are about to be updated. 🎉 + + The current version assumes Pydantic v1. + + The new docs will include Pydantic v2 and will use SQLModel once it is updated to use Pydantic v2 as well. + +You can also use `encode/databases` with **FastAPI** to connect to databases using `async` and `await`. + +It is compatible with: + +* PostgreSQL +* MySQL +* SQLite + +In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is. + +Later, for your production application, you might want to use a database server like **PostgreSQL**. + +!!! tip + You could adopt ideas from the section about SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), like using utility functions to perform operations in the database, independent of your **FastAPI** code. + + This section doesn't apply those ideas, to be equivalent to the counterpart in Starlette. + +## Import and set up `SQLAlchemy` + +* Import `SQLAlchemy`. +* Create a `metadata` object. +* Create a table `notes` using the `metadata` object. + +```Python hl_lines="4 14 16-22" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! tip + Notice that all this code is pure SQLAlchemy Core. + + `databases` is not doing anything here yet. + +## Import and set up `databases` + +* Import `databases`. +* Create a `DATABASE_URL`. +* Create a `database` object. + +```Python hl_lines="3 9 12" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! tip + If you were connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`. + +## Create the tables + +In this case, we are creating the tables in the same Python file, but in production, you would probably want to create them with Alembic, integrated with migrations, etc. + +Here, this section would run directly, right before starting your **FastAPI** application. + +* Create an `engine`. +* Create all the tables from the `metadata` object. + +```Python hl_lines="25-28" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +## Create models + +Create Pydantic models for: + +* Notes to be created (`NoteIn`). +* Notes to be returned (`Note`). + +```Python hl_lines="31-33 36-39" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +By creating these Pydantic models, the input data will be validated, serialized (converted), and annotated (documented). + +So, you will be able to see it all in the interactive API docs. + +## Connect and disconnect + +* Create your `FastAPI` application. +* Create event handlers to connect and disconnect from the database. + +```Python hl_lines="42 45-47 50-52" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +## Read notes + +Create the *path operation function* to read notes: + +```Python hl_lines="55-58" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! Note + Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. + +### Notice the `response_model=List[Note]` + +It uses `typing.List`. + +That documents (and validates, serializes, filters) the output data, as a `list` of `Note`s. + +## Create notes + +Create the *path operation function* to create notes: + +```Python hl_lines="61-65" +{!../../../docs_src/async_sql_databases/tutorial001.py!} +``` + +!!! Note + Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. + +### About `{**note.dict(), "id": last_record_id}` + +`note` is a Pydantic `Note` object. + +`note.dict()` returns a `dict` with its data, something like: + +```Python +{ + "text": "Some note", + "completed": False, +} +``` + +but it doesn't have the `id` field. + +So we create a new `dict`, that contains the key-value pairs from `note.dict()` with: + +```Python +{**note.dict()} +``` + +`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`. + +And then, we extend that copy `dict`, adding another key-value pair: `"id": last_record_id`: + +```Python +{**note.dict(), "id": last_record_id} +``` + +So, the final result returned would be something like: + +```Python +{ + "id": 1, + "text": "Some note", + "completed": False, +} +``` + +## Check it + +You can copy this code as is, and see the docs at http://127.0.0.1:8000/docs. + +There you can see all your API documented and interact with it: + + + +## More info + +You can read more about `encode/databases` at its GitHub page. diff --git a/docs/zh/docs/advanced/async-tests.md b/docs/zh/docs/advanced/async-tests.md new file mode 100644 index 0000000000000..9b39d70fca6a8 --- /dev/null +++ b/docs/zh/docs/advanced/async-tests.md @@ -0,0 +1,92 @@ +# Async Tests + +You have already seen how to test your **FastAPI** applications using the provided `TestClient`. Up to now, you have only seen how to write synchronous tests, without using `async` functions. + +Being able to use asynchronous functions in your tests could be useful, for example, when you're querying your database asynchronously. Imagine you want to test sending requests to your FastAPI application and then verify that your backend successfully wrote the correct data in the database, while using an async database library. + +Let's look at how we can make that work. + +## pytest.mark.anyio + +If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. AnyIO provides a neat plugin for this, that allows us to specify that some test functions are to be called asynchronously. + +## HTTPX + +Even if your **FastAPI** application uses normal `def` functions instead of `async def`, it is still an `async` application underneath. + +The `TestClient` does some magic inside to call the asynchronous FastAPI application in your normal `def` test functions, using standard pytest. But that magic doesn't work anymore when we're using it inside asynchronous functions. By running our tests asynchronously, we can no longer use the `TestClient` inside our test functions. + +The `TestClient` is based on HTTPX, and luckily, we can use it directly to test the API. + +## Example + +For a simple example, let's consider a file structure similar to the one described in [Bigger Applications](../tutorial/bigger-applications.md){.internal-link target=_blank} and [Testing](../tutorial/testing.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +The file `main.py` would have: + +```Python +{!../../../docs_src/async_tests/main.py!} +``` + +The file `test_main.py` would have the tests for `main.py`, it could look like this now: + +```Python +{!../../../docs_src/async_tests/test_main.py!} +``` + +## Run it + +You can run your tests as usual via: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## In Detail + +The marker `@pytest.mark.anyio` tells pytest that this test function should be called asynchronously: + +```Python hl_lines="7" +{!../../../docs_src/async_tests/test_main.py!} +``` + +!!! tip + Note that the test function is now `async def` instead of just `def` as before when using the `TestClient`. + +Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`. + +```Python hl_lines="9-10" +{!../../../docs_src/async_tests/test_main.py!} +``` + +This is the equivalent to: + +```Python +response = client.get('/') +``` + +...that we used to make our requests with the `TestClient`. + +!!! tip + Note that we're using async/await with the new `AsyncClient` - the request is asynchronous. + +## Other Asynchronous Function Calls + +As the testing function is now asynchronous, you can now also call (and `await`) other `async` functions apart from sending requests to your FastAPI application in your tests, exactly as you would call them anywhere else in your code. + +!!! tip + If you encounter a `RuntimeError: Task attached to a different loop` when integrating asynchronous function calls in your tests (e.g. when using MongoDB's MotorClient) Remember to instantiate objects that need an event loop only within async functions, e.g. an `'@app.on_event("startup")` callback. diff --git a/docs/zh/docs/advanced/behind-a-proxy.md b/docs/zh/docs/advanced/behind-a-proxy.md new file mode 100644 index 0000000000000..94ffd994824d4 --- /dev/null +++ b/docs/zh/docs/advanced/behind-a-proxy.md @@ -0,0 +1,346 @@ +# Behind a Proxy + +In some situations, you might need to use a **proxy** server like Traefik or Nginx with a configuration that adds an extra path prefix that is not seen by your application. + +In these cases you can use `root_path` to configure your application. + +The `root_path` is a mechanism provided by the ASGI specification (that FastAPI is built on, through Starlette). + +The `root_path` is used to handle these specific cases. + +And it's also used internally when mounting sub-applications. + +## Proxy with a stripped path prefix + +Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you add a layer on top (the proxy) that would put your **FastAPI** application under a path like `/api/v1`. + +In this case, the original path `/app` would actually be served at `/api/v1/app`. + +Even though all your code is written assuming there's just `/app`. + +And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to Uvicorn, keep your application convinced that it is serving at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. + +Up to here, everything would work as normally. + +But then, when you open the integrated docs UI (the frontend), it would expect to get the OpenAPI schema at `/openapi.json`, instead of `/api/v1/openapi.json`. + +So, the frontend (that runs in the browser) would try to reach `/openapi.json` and wouldn't be able to get the OpenAPI schema. + +Because we have a proxy with a path prefix of `/api/v1` for our app, the frontend needs to fetch the OpenAPI schema at `/api/v1/openapi.json`. + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +!!! tip + The IP `0.0.0.0` is commonly used to mean that the program listens on all the IPs available in that machine/server. + +The docs UI would also need the OpenAPI schema to declare that this API `server` is located at `/api/v1` (behind the proxy). For example: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // More stuff here + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // More stuff here + } +} +``` + +In this example, the "Proxy" could be something like **Traefik**. And the server would be something like **Uvicorn**, running your FastAPI application. + +### Providing the `root_path` + +To achieve this, you can use the command line option `--root-path` like: + +
+ +```console +$ uvicorn main:app --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +If you use Hypercorn, it also has the option `--root-path`. + +!!! note "Technical Details" + The ASGI specification defines a `root_path` for this use case. + + And the `--root-path` command line option provides that `root_path`. + +### Checking the current `root_path` + +You can get the current `root_path` used by your application for each request, it is part of the `scope` dictionary (that's part of the ASGI spec). + +Here we are including it in the message just for demonstration purposes. + +```Python hl_lines="8" +{!../../../docs_src/behind_a_proxy/tutorial001.py!} +``` + +Then, if you start Uvicorn with: + +
+ +```console +$ uvicorn main:app --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +The response would be something like: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### Setting the `root_path` in the FastAPI app + +Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app: + +```Python hl_lines="3" +{!../../../docs_src/behind_a_proxy/tutorial002.py!} +``` + +Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn. + +### About `root_path` + +Have in mind that the server (Uvicorn) won't use that `root_path` for anything else than passing it to the app. + +But if you go with your browser to http://127.0.0.1:8000/app you will see the normal response: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +So, it won't expect to be accessed at `http://127.0.0.1:8000/api/v1/app`. + +Uvicorn will expect the proxy to access Uvicorn at `http://127.0.0.1:8000/app`, and then it would be the proxy's responsibility to add the extra `/api/v1` prefix on top. + +## About proxies with a stripped path prefix + +Have in mind that a proxy with stripped path prefix is only one of the ways to configure it. + +Probably in many cases the default will be that the proxy doesn't have a stripped path prefix. + +In a case like that (without a stripped path prefix), the proxy would listen on something like `https://myawesomeapp.com`, and then if the browser goes to `https://myawesomeapp.com/api/v1/app` and your server (e.g. Uvicorn) listens on `http://127.0.0.1:8000` the proxy (without a stripped path prefix) would access Uvicorn at the same path: `http://127.0.0.1:8000/api/v1/app`. + +## Testing locally with Traefik + +You can easily run the experiment locally with a stripped path prefix using Traefik. + +Download Traefik, it's a single binary, you can extract the compressed file and run it directly from the terminal. + +Then create a file `traefik.toml` with: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +This tells Traefik to listen on port 9999 and to use another file `routes.toml`. + +!!! tip + We are using port 9999 instead of the standard HTTP port 80 so that you don't have to run it with admin (`sudo`) privileges. + +Now create that other file `routes.toml`: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +This file configures Traefik to use the path prefix `/api/v1`. + +And then it will redirect its requests to your Uvicorn running on `http://127.0.0.1:8000`. + +Now start Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +And now start your app with Uvicorn, using the `--root-path` option: + +
+ +```console +$ uvicorn main:app --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Check the responses + +Now, if you go to the URL with the port for Uvicorn: http://127.0.0.1:8000/app, you will see the normal response: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +!!! tip + Notice that even though you are accessing it at `http://127.0.0.1:8000/app` it shows the `root_path` of `/api/v1`, taken from the option `--root-path`. + +And now open the URL with the port for Traefik, including the path prefix: http://127.0.0.1:9999/api/v1/app. + +We get the same response: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +but this time at the URL with the prefix path provided by the proxy: `/api/v1`. + +Of course, the idea here is that everyone would access the app through the proxy, so the version with the path prefix `/api/v1` is the "correct" one. + +And the version without the path prefix (`http://127.0.0.1:8000/app`), provided by Uvicorn directly, would be exclusively for the _proxy_ (Traefik) to access it. + +That demonstrates how the Proxy (Traefik) uses the path prefix and how the server (Uvicorn) uses the `root_path` from the option `--root-path`. + +### Check the docs UI + +But here's the fun part. ✨ + +The "official" way to access the app would be through the proxy with the path prefix that we defined. So, as we would expect, if you try the docs UI served by Uvicorn directly, without the path prefix in the URL, it won't work, because it expects to be accessed through the proxy. + +You can check it at http://127.0.0.1:8000/docs: + + + +But if we access the docs UI at the "official" URL using the proxy with port `9999`, at `/api/v1/docs`, it works correctly! 🎉 + +You can check it at http://127.0.0.1:9999/api/v1/docs: + + + +Right as we wanted it. ✔️ + +This is because FastAPI uses this `root_path` to create the default `server` in OpenAPI with the URL provided by `root_path`. + +## Additional servers + +!!! warning + This is a more advanced use case. Feel free to skip it. + +By default, **FastAPI** will create a `server` in the OpenAPI schema with the URL for the `root_path`. + +But you can also provide other alternative `servers`, for example if you want *the same* docs UI to interact with a staging and production environments. + +If you pass a custom list of `servers` and there's a `root_path` (because your API lives behind a proxy), **FastAPI** will insert a "server" with this `root_path` at the beginning of the list. + +For example: + +```Python hl_lines="4-7" +{!../../../docs_src/behind_a_proxy/tutorial003.py!} +``` + +Will generate an OpenAPI schema like: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // More stuff here + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // More stuff here + } +} +``` + +!!! tip + Notice the auto-generated server with a `url` value of `/api/v1`, taken from the `root_path`. + +In the docs UI at http://127.0.0.1:9999/api/v1/docs it would look like: + + + +!!! tip + The docs UI will interact with the server that you select. + +### Disable automatic server from `root_path` + +If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`: + +```Python hl_lines="9" +{!../../../docs_src/behind_a_proxy/tutorial004.py!} +``` + +and then it won't include it in the OpenAPI schema. + +## Mounting a sub-application + +If you need to mount a sub-application (as described in [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}) while also using a proxy with `root_path`, you can do it normally, as you would expect. + +FastAPI will internally use the `root_path` smartly, so it will just work. ✨ diff --git a/docs/zh/docs/advanced/conditional-openapi.md b/docs/zh/docs/advanced/conditional-openapi.md new file mode 100644 index 0000000000000..add16fbec519e --- /dev/null +++ b/docs/zh/docs/advanced/conditional-openapi.md @@ -0,0 +1,58 @@ +# Conditional OpenAPI + +If you needed to, you could use settings and environment variables to configure OpenAPI conditionally depending on the environment, and even disable it entirely. + +## About security, APIs, and docs + +Hiding your documentation user interfaces in production *shouldn't* be the way to protect your API. + +That doesn't add any extra security to your API, the *path operations* will still be available where they are. + +If there's a security flaw in your code, it will still exist. + +Hiding the documentation just makes it more difficult to understand how to interact with your API, and could make it more difficult for you to debug it in production. It could be considered simply a form of Security through obscurity. + +If you want to secure your API, there are several better things you can do, for example: + +* Make sure you have well defined Pydantic models for your request bodies and responses. +* Configure any required permissions and roles using dependencies. +* Never store plaintext passwords, only password hashes. +* Implement and use well-known cryptographic tools, like Passlib and JWT tokens, etc. +* Add more granular permission controls with OAuth2 scopes where needed. +* ...etc. + +Nevertheless, you might have a very specific use case where you really need to disable the API docs for some environment (e.g. for production) or depending on configurations from environment variables. + +## Conditional OpenAPI from settings and env vars + +You can easily use the same Pydantic settings to configure your generated OpenAPI and the docs UIs. + +For example: + +```Python hl_lines="6 11" +{!../../../docs_src/conditional_openapi/tutorial001.py!} +``` + +Here we declare the setting `openapi_url` with the same default of `"/openapi.json"`. + +And then we use it when creating the `FastAPI` app. + +Then you could disable OpenAPI (including the UI docs) by setting the environment variable `OPENAPI_URL` to the empty string, like: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Then if you go to the URLs at `/openapi.json`, `/docs`, or `/redoc` you will just get a `404 Not Found` error like: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/zh/docs/advanced/custom-request-and-route.md b/docs/zh/docs/advanced/custom-request-and-route.md new file mode 100644 index 0000000000000..bfaf5c8d87877 --- /dev/null +++ b/docs/zh/docs/advanced/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Custom Request and APIRoute class + +In some cases, you may want to override the logic used by the `Request` and `APIRoute` classes. + +In particular, this may be a good alternative to logic in a middleware. + +For example, if you want to read or manipulate the request body before it is processed by your application. + +!!! danger + This is an "advanced" feature. + + If you are just starting with **FastAPI** you might want to skip this section. + +## Use cases + +Some use cases include: + +* Converting non-JSON request bodies to JSON (e.g. `msgpack`). +* Decompressing gzip-compressed request bodies. +* Automatically logging all request bodies. + +## Handling custom request body encodings + +Let's see how to make use of a custom `Request` subclass to decompress gzip requests. + +And an `APIRoute` subclass to use that custom request class. + +### Create a custom `GzipRequest` class + +!!! tip + This is a toy example to demonstrate how it works, if you need Gzip support, you can use the provided [`GzipMiddleware`](./middleware.md#gzipmiddleware){.internal-link target=_blank}. + +First, we create a `GzipRequest` class, which will overwrite the `Request.body()` method to decompress the body in the presence of an appropriate header. + +If there's no `gzip` in the header, it will not try to decompress the body. + +That way, the same route class can handle gzip compressed or uncompressed requests. + +```Python hl_lines="8-15" +{!../../../docs_src/custom_request_and_route/tutorial001.py!} +``` + +### Create a custom `GzipRoute` class + +Next, we create a custom subclass of `fastapi.routing.APIRoute` that will make use of the `GzipRequest`. + +This time, it will overwrite the method `APIRoute.get_route_handler()`. + +This method returns a function. And that function is what will receive a request and return a response. + +Here we use it to create a `GzipRequest` from the original request. + +```Python hl_lines="18-26" +{!../../../docs_src/custom_request_and_route/tutorial001.py!} +``` + +!!! note "Technical Details" + A `Request` has a `request.scope` attribute, that's just a Python `dict` containing the metadata related to the request. + + A `Request` also has a `request.receive`, that's a function to "receive" the body of the request. + + The `scope` `dict` and `receive` function are both part of the ASGI specification. + + And those two things, `scope` and `receive`, are what is needed to create a new `Request` instance. + + To learn more about the `Request` check Starlette's docs about Requests. + +The only thing the function returned by `GzipRequest.get_route_handler` does differently is convert the `Request` to a `GzipRequest`. + +Doing this, our `GzipRequest` will take care of decompressing the data (if necessary) before passing it to our *path operations*. + +After that, all of the processing logic is the same. + +But because of our changes in `GzipRequest.body`, the request body will be automatically decompressed when it is loaded by **FastAPI** when needed. + +## Accessing the request body in an exception handler + +!!! tip + To solve this same problem, it's probably a lot easier to use the `body` in a custom handler for `RequestValidationError` ([Handling Errors](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + + But this example is still valid and it shows how to interact with the internal components. + +We can also use this same approach to access the request body in an exception handler. + +All we need to do is handle the request inside a `try`/`except` block: + +```Python hl_lines="13 15" +{!../../../docs_src/custom_request_and_route/tutorial002.py!} +``` + +If an exception occurs, the`Request` instance will still be in scope, so we can read and make use of the request body when handling the error: + +```Python hl_lines="16-18" +{!../../../docs_src/custom_request_and_route/tutorial002.py!} +``` + +## Custom `APIRoute` class in a router + +You can also set the `route_class` parameter of an `APIRouter`: + +```Python hl_lines="26" +{!../../../docs_src/custom_request_and_route/tutorial003.py!} +``` + +In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response: + +```Python hl_lines="13-20" +{!../../../docs_src/custom_request_and_route/tutorial003.py!} +``` diff --git a/docs/zh/docs/advanced/custom-response.md b/docs/zh/docs/advanced/custom-response.md index 155ce28828b0b..cc557dfde50ca 100644 --- a/docs/zh/docs/advanced/custom-response.md +++ b/docs/zh/docs/advanced/custom-response.md @@ -12,8 +12,8 @@ 并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或者 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。 -!!! note "说明" - 如果你使用不带有任何媒体类型的响应类,FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。 +!!! note + If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs. ## 使用 `ORJSONResponse` @@ -21,38 +21,43 @@ 导入你想要使用的 `Response` 类(子类)然后在 *路径操作装饰器* 中声明它。 -```Python hl_lines="2 7" +For large responses, returning a `Response` directly is much faster than returning a dictionary. + +This is because by default, FastAPI will inspect every item inside and make sure it is serializable with JSON, using the same [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} explained in the tutorial. This is what allows you to return **arbitrary objects**, for example database models. + +使用 `HTMLResponse` 来从 **FastAPI** 中直接返回一个 HTML 响应。 + +```Python hl_lines="2 7" {!../../../docs_src/custom_response/tutorial001b.py!} ``` -!!! info "提示" - 参数 `response_class` 也会用来定义响应的「媒体类型」。 +!!! info + The parameter `response_class` will also be used to define the "media type" of the response. 在这个例子中,HTTP 头的 `Content-Type` 会被设置成 `application/json`。 - + 并且在 OpenAPI 文档中也会这样记录。 -!!! tip "小贴士" - `ORJSONResponse` 目前只在 FastAPI 中可用,而在 Starlette 中不可用。 - - +!!! tip + The `ORJSONResponse` is currently only available in FastAPI, not in Starlette. ## HTML 响应 -使用 `HTMLResponse` 来从 **FastAPI** 中直接返回一个 HTML 响应。 +!!! warning "警告" + *路径操作函数* 直接返回的 `Response` 不会被 OpenAPI 的文档记录(比如,`Content-Type` 不会被文档记录),并且在自动化交互文档中也是不可见的。 * 导入 `HTMLResponse`。 * 将 `HTMLResponse` 作为你的 *路径操作* 的 `response_class` 参数传入。 -```Python hl_lines="2 7" +```Python hl_lines="2 7" {!../../../docs_src/custom_response/tutorial002.py!} ``` -!!! info "提示" - 参数 `response_class` 也会用来定义响应的「媒体类型」。 +!!! info + The parameter `response_class` will also be used to define the "media type" of the response. 在这个例子中,HTTP 头的 `Content-Type` 会被设置成 `text/html`。 - + 并且在 OpenAPI 文档中也会这样记录。 ### 返回一个 `Response` @@ -61,15 +66,15 @@ 和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样: -```Python hl_lines="2 7 19" +```Python hl_lines="2 7 19" {!../../../docs_src/custom_response/tutorial003.py!} ``` -!!! warning "警告" - *路径操作函数* 直接返回的 `Response` 不会被 OpenAPI 的文档记录(比如,`Content-Type` 不会被文档记录),并且在自动化交互文档中也是不可见的。 +!!! warning + A `Response` returned directly by your *path operation function* won't be documented in OpenAPI (for example, the `Content-Type` won't be documented) and won't be visible in the automatic interactive docs. -!!! info "提示" - 当然,实际的 `Content-Type` 头,状态码等等,将来自于你返回的 `Response` 对象。 +!!! info + Of course, the actual `Content-Type` header, status code, etc, will come from the `Response` object your returned. ### OpenAPI 中的文档和重载 `Response` @@ -77,11 +82,11 @@ 接着 `response_class` 参数只会被用来文档化 OpenAPI 的 *路径操作*,你的 `Response` 用来返回响应。 -### 直接返回 `HTMLResponse` +#### 直接返回 `HTMLResponse` 比如像这样: -```Python hl_lines="7 23 21" +```Python hl_lines="7 21 23" {!../../../docs_src/custom_response/tutorial004.py!} ``` @@ -91,7 +96,7 @@ 但如果你在 `response_class` 中也传入了 `HTMLResponse`,**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML。 - + ## 可用响应 @@ -99,10 +104,10 @@ 要记得你可以使用 `Response` 来返回任何其他东西,甚至创建一个自定义的子类。 -!!! note "技术细节" - 你也可以使用 `from starlette.responses import HTMLResponse`。 +!!! note "Technical Details" + You could also use `from starlette.responses import HTMLResponse`. - **FastAPI** 提供了同 `fastapi.responses` 相同的 `starlette.responses` 只是为了方便开发者。但大多数可用的响应都直接来自 Starlette。 + **FastAPI** 提供了同 `fastapi.responses` 相同的 `starlette.responses` 只是为了方便开发者。 但大多数可用的响应都直接来自 Starlette。 ### `Response` @@ -110,15 +115,14 @@ 你可以直接返回它。 -`Response` 类接受如下参数: +It accepts the following parameters: * `content` - 一个 `str` 或者 `bytes`。 * `status_code` - 一个 `int` 类型的 HTTP 状态码。 * `headers` - 一个由字符串组成的 `dict`。 -* `media_type` - 一个给出媒体类型的 `str`,比如 `"text/html"`。 - -FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。 +* `media_type` - A `str` giving the media type. E.g. `"text/html"`. +FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。 它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。 ```Python hl_lines="1 18" {!../../../docs_src/response_directly/tutorial002.py!} @@ -146,29 +150,53 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 如上文所述,`ORJSONResponse` 是一个使用 `orjson` 的快速的可选 JSON 响应。 - ### `UJSONResponse` `UJSONResponse` 是一个使用 `ujson` 的可选 JSON 响应。 -!!! warning "警告" +!!! !!! warning "警告" 在处理某些边缘情况时,`ujson` 不如 Python 的内置实现那么谨慎。 -```Python hl_lines="2 7" +```Python hl_lines="2 7" {!../../../docs_src/custom_response/tutorial001.py!} ``` -!!! tip "小贴士" - `ORJSONResponse` 可能是一个更快的选择。 +!!! tip + It's possible that `ORJSONResponse` might be a faster alternative. ### `RedirectResponse` -返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)。 +返回 HTTP 重定向。 默认情况下使用 307 状态代码(临时重定向)。 + +!!! note "技术细节" + 你也可以使用 `from starlette.responses import HTMLResponse`。 ```Python hl_lines="2 9" {!../../../docs_src/custom_response/tutorial006.py!} ``` +--- + +!!! info "提示" + 参数 `response_class` 也会用来定义响应的「媒体类型」。 + + +```Python hl_lines="2 7 9" +{!../../../docs_src/custom_response/tutorial006b.py!} +``` + +If you do that, then you can return the URL directly from your *path operation* function. + +In this case, the `status_code` used will be the default one for the `RedirectResponse`, which is `307`. + +--- + +`media_type` - 一个给出媒体类型的 `str`,比如 `"text/html"`。 + +```Python hl_lines="2 7 9" +{!../../../docs_src/custom_response/tutorial006c.py!} +``` + ### `StreamingResponse` 采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。 @@ -181,13 +209,23 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 如果您有类似文件的对象(例如,由 `open()` 返回的对象),则可以在 `StreamingResponse` 中将其返回。 +That way, you don't have to read it all first in memory, and you can pass that generator function to the `StreamingResponse`, and return it. + 包括许多与云存储,视频处理等交互的库。 -```Python hl_lines="2 10-12 14" +```{ .python .annotate hl_lines="2 10-12 14" } {!../../../docs_src/custom_response/tutorial008.py!} ``` -!!! tip "小贴士" +1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. +2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response. +3. This `yield from` tells the function to iterate over that thing named `file_like`. And then, for each part iterated, yield that part as coming from this generator function. + + So, it is a generator function that transfers the "generating" work to something else internally. + + By doing it this way, we can put it in a `with` block, and that way, ensure that it is closed after finishing. + +!!! !!! tip "小贴士" 注意在这里,因为我们使用的是不支持 `async` 和 `await` 的标准 `open()`,我们使用普通的 `def` 声明了路径操作。 ### `FileResponse` @@ -198,7 +236,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 * `path` - 要流式传输的文件的文件路径。 * `headers` - 任何自定义响应头,传入字典类型。 -* `media_type` - 给出媒体类型的字符串。如果未设置,则文件名或路径将用于推断媒体类型。 +* `media_type` - 给出媒体类型的字符串。 如果未设置,则文件名或路径将用于推断媒体类型。 * `filename` - 如果给出,它将包含在响应的 `Content-Disposition` 中。 文件响应将包含适当的 `Content-Length`,`Last-Modified` 和 `ETag` 的响应头。 @@ -207,6 +245,64 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 {!../../../docs_src/custom_response/tutorial009.py!} ``` +`Response` 类接受如下参数: + +```Python hl_lines="2 8 10" +{!../../../docs_src/custom_response/tutorial009b.py!} +``` + +In this case, you can return the file path directly from your *path operation* function. + +## Custom response class + +!!! info "提示" + 当然,实际的 `Content-Type` 头,状态码等等,将来自于你返回的 `Response` 对象。 + +!!! tip "小贴士" + `ORJSONResponse` 可能是一个更快的选择。 + +Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`. + +You could create a `CustomORJSONResponse`. The main thing you have to do is create a `Response.render(content)` method that returns the content as `bytes`: + +```Python hl_lines="9-14 17" +{!../../../docs_src/custom_response/tutorial009c.py!} +``` + +Now instead of returning: + +```json +{"message": "Hello World"} +``` + +...this response will return: + +```json +{ + "message": "Hello World" +} +``` + +Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 + +## Default response class + +!!! note "说明" + 如果你使用不带有任何媒体类型的响应类,FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。 + +!!! info "提示" + 参数 `response_class` 也会用来定义响应的「媒体类型」。 + +!!! tip "小贴士" + `ORJSONResponse` 目前只在 FastAPI 中可用,而在 Starlette 中不可用。 + +```Python hl_lines="2 4" +{!../../../docs_src/custom_response/tutorial010.py!} +``` + +!!! tip + You can still override `response_class` in *path operations* as before. + ## 额外文档 您还可以使用 `response` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。 diff --git a/docs/zh/docs/advanced/dataclasses.md b/docs/zh/docs/advanced/dataclasses.md new file mode 100644 index 0000000000000..fe6b740aa391a --- /dev/null +++ b/docs/zh/docs/advanced/dataclasses.md @@ -0,0 +1,98 @@ +# Using Dataclasses + +FastAPI is built on top of **Pydantic**, and I have been showing you how to use Pydantic models to declare requests and responses. + +But FastAPI also supports using `dataclasses` the same way: + +```Python hl_lines="1 7-12 19-20" +{!../../../docs_src/dataclasses/tutorial001.py!} +``` + +This is still supported thanks to **Pydantic**, as it has internal support for `dataclasses`. + +So, even with the code above that doesn't use Pydantic explicitly, FastAPI is using Pydantic to convert those standard dataclasses to Pydantic's own flavor of dataclasses. + +And of course, it supports the same: + +* data validation +* data serialization +* data documentation, etc. + +This works the same way as with Pydantic models. And it is actually achieved in the same way underneath, using Pydantic. + +!!! info + Have in mind that dataclasses can't do everything Pydantic models can do. + + So, you might still need to use Pydantic models. + + But if you have a bunch of dataclasses laying around, this is a nice trick to use them to power a web API using FastAPI. 🤓 + +## Dataclasses in `response_model` + +You can also use `dataclasses` in the `response_model` parameter: + +```Python hl_lines="1 7-13 19" +{!../../../docs_src/dataclasses/tutorial002.py!} +``` + +The dataclass will be automatically converted to a Pydantic dataclass. + +This way, its schema will show up in the API docs user interface: + + + +## Dataclasses in Nested Data Structures + +You can also combine `dataclasses` with other type annotations to make nested data structures. + +In some cases, you might still have to use Pydantic's version of `dataclasses`. For example, if you have errors with the automatically generated API documentation. + +In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement: + +```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } +{!../../../docs_src/dataclasses/tutorial003.py!} +``` + +1. We still import `field` from standard `dataclasses`. + +2. `pydantic.dataclasses` is a drop-in replacement for `dataclasses`. + +3. The `Author` dataclass includes a list of `Item` dataclasses. + +4. The `Author` dataclass is used as the `response_model` parameter. + +5. You can use other standard type annotations with dataclasses as the request body. + + In this case, it's a list of `Item` dataclasses. + +6. Here we are returning a dictionary that contains `items` which is a list of dataclasses. + + FastAPI is still capable of serializing the data to JSON. + +7. Here the `response_model` is using a type annotation of a list of `Author` dataclasses. + + Again, you can combine `dataclasses` with standard type annotations. + +8. Notice that this *path operation function* uses regular `def` instead of `async def`. + + As always, in FastAPI you can combine `def` and `async def` as needed. + + If you need a refresher about when to use which, check out the section _"In a hurry?"_ in the docs about `async` and `await`. + +9. This *path operation function* is not returning dataclasses (although it could), but a list of dictionaries with internal data. + + FastAPI will use the `response_model` parameter (that includes dataclasses) to convert the response. + +You can combine `dataclasses` with other type annotations in many different combinations to form complex data structures. + +Check the in-code annotation tips above to see more specific details. + +## Learn More + +You can also combine `dataclasses` with other Pydantic models, inherit from them, include them in your own models, etc. + +To learn more, check the Pydantic docs about dataclasses. + +## Version + +This is available since FastAPI version `0.67.0`. 🔖 diff --git a/docs/zh/docs/advanced/events.md b/docs/zh/docs/advanced/events.md new file mode 100644 index 0000000000000..f62743b798255 --- /dev/null +++ b/docs/zh/docs/advanced/events.md @@ -0,0 +1,162 @@ +# Lifespan Events + +You can define logic (code) that should be executed before the application **starts up**. This means that this code will be executed **once**, **before** the application **starts receiving requests**. + +The same way, you can define logic (code) that should be executed when the application is **shutting down**. In this case, this code will be executed **once**, **after** having handled possibly **many requests**. + +Because this code is executed before the application **starts** taking requests, and right after it **finishes** handling requests, it covers the whole application **lifespan** (the word "lifespan" will be important in a second 😉). + +This can be very useful for setting up **resources** that you need to use for the whole app, and that are **shared** among requests, and/or that you need to **clean up** afterwards. For example, a database connection pool, or loading a shared machine learning model. + +## Use Case + +Let's start with an example **use case** and then see how to solve it with this. + +Let's imagine that you have some **machine learning models** that you want to use to handle requests. 🤖 + +The same models are shared among requests, so, it's not one model per request, or one per user or something similar. + +Let's imagine that loading the model can **take quite some time**, because it has to read a lot of **data from disk**. So you don't want to do it for every request. + +You could load it at the top level of the module/file, but that would also mean that it would **load the model** even if you are just running a simple automated test, then that test would be **slow** because it would have to wait for the model to load before being able to run an independent part of the code. + +That's what we'll solve, let's load the model before the requests are handled, but only right before the application starts receiving requests, not while the code is being loaded. + +## Lifespan + +You can define this *startup* and *shutdown* logic using the `lifespan` parameter of the `FastAPI` app, and a "context manager" (I'll show you what that is in a second). + +Let's start with an example and then see it in detail. + +We create an async function `lifespan()` with `yield` like this: + +```Python hl_lines="16 19" +{!../../../docs_src/events/tutorial003.py!} +``` + +Here we are simulating the expensive *startup* operation of loading the model by putting the (fake) model function in the dictionary with machine learning models before the `yield`. This code will be executed **before** the application **starts taking requests**, during the *startup*. + +And then, right after the `yield`, we unload the model. This code will be executed **after** the application **finishes handling requests**, right before the *shutdown*. This could, for example, release resources like memory or a GPU. + +!!! tip + The `shutdown` would happen when you are **stopping** the application. + + Maybe you need to start a new version, or you just got tired of running it. 🤷 + +### Lifespan function + +The first thing to notice, is that we are defining an async function with `yield`. This is very similar to Dependencies with `yield`. + +```Python hl_lines="14-19" +{!../../../docs_src/events/tutorial003.py!} +``` + +The first part of the function, before the `yield`, will be executed **before** the application starts. + +And the part after the `yield` will be executed **after** the application has finished. + +### Async Context Manager + +If you check, the function is decorated with an `@asynccontextmanager`. + +That converts the function into something called an "**async context manager**". + +```Python hl_lines="1 13" +{!../../../docs_src/events/tutorial003.py!} +``` + +A **context manager** in Python is something that you can use in a `with` statement, for example, `open()` can be used as a context manager: + +```Python +with open("file.txt") as file: + file.read() +``` + +In recent versions of Python, there's also an **async context manager**. You would use it with `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +When you create a context manager or an async context manager like above, what it does is that, before entering the `with` block, it will execute the code before the `yield`, and after exiting the `with` block, it will execute the code after the `yield`. + +In our code example above, we don't use it directly, but we pass it to FastAPI for it to use it. + +The `lifespan` parameter of the `FastAPI` app takes an **async context manager**, so we can pass our new `lifespan` async context manager to it. + +```Python hl_lines="22" +{!../../../docs_src/events/tutorial003.py!} +``` + +## Alternative Events (deprecated) + +!!! warning + The recommended way to handle the *startup* and *shutdown* is using the `lifespan` parameter of the `FastAPI` app as described above. + + You can probably skip this part. + +There's an alternative way to define this logic to be executed during *startup* and during *shutdown*. + +You can define event handlers (functions) that need to be executed before the application starts up, or when the application is shutting down. + +These functions can be declared with `async def` or normal `def`. + +### `startup` event + +To add a function that should be run before the application starts, declare it with the event `"startup"`: + +```Python hl_lines="8" +{!../../../docs_src/events/tutorial001.py!} +``` + +In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values. + +You can add more than one event handler function. + +And your application won't start receiving requests until all the `startup` event handlers have completed. + +### `shutdown` event + +To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`: + +```Python hl_lines="6" +{!../../../docs_src/events/tutorial002.py!} +``` + +Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`. + +!!! info + In the `open()` function, the `mode="a"` means "append", so, the line will be added after whatever is on that file, without overwriting the previous contents. + +!!! tip + Notice that in this case we are using a standard Python `open()` function that interacts with a file. + + So, it involves I/O (input/output), that requires "waiting" for things to be written to disk. + + But `open()` doesn't use `async` and `await`. + + So, we declare the event handler function with standard `def` instead of `async def`. + +### `startup` and `shutdown` together + +There's a high chance that the logic for your *startup* and *shutdown* is connected, you might want to start something and then finish it, acquire a resource and then release it, etc. + +Doing that in separated functions that don't share logic or variables together is more difficult as you would need to store values in global variables or similar tricks. + +Because of that, it's now recommended to instead use the `lifespan` as explained above. + +## Technical Details + +Just a technical detail for the curious nerds. 🤓 + +Underneath, in the ASGI technical specification, this is part of the Lifespan Protocol, and it defines events called `startup` and `shutdown`. + +!!! info + You can read more about the Starlette `lifespan` handlers in Starlette's Lifespan' docs. + + Including how to handle lifespan state that can be used in other areas of your code. + +## Sub Applications + +🚨 Have in mind that these lifespan events (startup and shutdown) will only be executed for the main application, not for [Sub Applications - Mounts](./sub-applications.md){.internal-link target=_blank}. diff --git a/docs/zh/docs/advanced/extending-openapi.md b/docs/zh/docs/advanced/extending-openapi.md new file mode 100644 index 0000000000000..fe3f649f93230 --- /dev/null +++ b/docs/zh/docs/advanced/extending-openapi.md @@ -0,0 +1,318 @@ +# Extending OpenAPI + +!!! warning + This is a rather advanced feature. You probably can skip it. + + If you are just following the tutorial - user guide, you can probably skip this section. + + If you already know that you need to modify the generated OpenAPI schema, continue reading. + +There are some cases where you might need to modify the generated OpenAPI schema. + +In this section you will see how. + +## The normal process + +The normal (default) process, is as follows. + +A `FastAPI` application (instance) has an `.openapi()` method that is expected to return the OpenAPI schema. + +As part of the application object creation, a *path operation* for `/openapi.json` (or for whatever you set your `openapi_url`) is registered. + +It just returns a JSON response with the result of the application's `.openapi()` method. + +By default, what the method `.openapi()` does is check the property `.openapi_schema` to see if it has contents and return them. + +If it doesn't, it generates them using the utility function at `fastapi.openapi.utils.get_openapi`. + +And that function `get_openapi()` receives as parameters: + +* `title`: The OpenAPI title, shown in the docs. +* `version`: The version of your API, e.g. `2.5.0`. +* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.1.0`. +* `summary`: A short summary of the API. +* `description`: The description of your API, this can include markdown and will be shown in the docs. +* `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`. + +!!! info + The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by FastAPI 0.99.0 and above. + +## Overriding the defaults + +Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need. + +For example, let's add ReDoc's OpenAPI extension to include a custom logo. + +### Normal **FastAPI** + +First, write all your **FastAPI** application as normally: + +```Python hl_lines="1 4 7-9" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Generate the OpenAPI schema + +Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: + +```Python hl_lines="2 15-21" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Modify the OpenAPI schema + +Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: + +```Python hl_lines="22-24" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Cache the OpenAPI schema + +You can use the property `.openapi_schema` as a "cache", to store your generated schema. + +That way, your application won't have to generate the schema every time a user opens your API docs. + +It will be generated only once, and then the same cached schema will be used for the next requests. + +```Python hl_lines="13-14 25-26" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Override the method + +Now you can replace the `.openapi()` method with your new function. + +```Python hl_lines="29" +{!../../../docs_src/extending_openapi/tutorial001.py!} +``` + +### Check it + +Once you go to http://127.0.0.1:8000/redoc you will see that you are using your custom logo (in this example, **FastAPI**'s logo): + + + +## Self-hosting JavaScript and CSS for docs + +The API docs use **Swagger UI** and **ReDoc**, and each of those need some JavaScript and CSS files. + +By default, those files are served from a CDN. + +But it's possible to customize it, you can set a specific CDN, or serve the files yourself. + +That's useful, for example, if you need your app to keep working even while offline, without open Internet access, or in a local network. + +Here you'll see how to serve those files yourself, in the same FastAPI app, and configure the docs to use them. + +### Project file structure + +Let's say your project file structure looks like this: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Now create a directory to store those static files. + +Your new file structure could look like this: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Download the files + +Download the static files needed for the docs and put them on that `static/` directory. + +You can probably right-click each link and select an option similar to `Save link as...`. + +**Swagger UI** uses the files: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +And **ReDoc** uses the file: + +* `redoc.standalone.js` + +After that, your file structure could look like: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Serve the static files + +* Import `StaticFiles`. +* "Mount" a `StaticFiles()` instance in a specific path. + +```Python hl_lines="7 11" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +### Test the static files + +Start your application and go to http://127.0.0.1:8000/static/redoc.standalone.js. + +You should see a very long JavaScript file for **ReDoc**. + +It could start with something like: + +```JavaScript +/*! + * ReDoc - OpenAPI/Swagger-generated API Reference Documentation + * ------------------------------------------------------------- + * Version: "2.0.0-rc.18" + * Repo: https://github.com/Redocly/redoc + */ +!function(e,t){"object"==typeof exports&&"object"==typeof m + +... +``` + +That confirms that you are being able to serve static files from your app, and that you placed the static files for the docs in the correct place. + +Now we can configure the app to use those static files for the docs. + +### Disable the automatic docs + +The first step is to disable the automatic docs, as those use the CDN by default. + +To disable them, set their URLs to `None` when creating your `FastAPI` app: + +```Python hl_lines="9" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +### Include the custom docs + +Now you can create the *path operations* for the custom docs. + +You can re-use FastAPI's internal functions to create the HTML pages for the docs, and pass them the needed arguments: + +* `openapi_url`: the URL where the HTML page for the docs can get the OpenAPI schema for your API. You can use here the attribute `app.openapi_url`. +* `title`: the title of your API. +* `oauth2_redirect_url`: you can use `app.swagger_ui_oauth2_redirect_url` here to use the default. +* `swagger_js_url`: the URL where the HTML for your Swagger UI docs can get the **JavaScript** file. This is the one that your own app is now serving. +* `swagger_css_url`: the URL where the HTML for your Swagger UI docs can get the **CSS** file. This is the one that your own app is now serving. + +And similarly for ReDoc... + +```Python hl_lines="2-6 14-22 25-27 30-36" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +!!! tip + The *path operation* for `swagger_ui_redirect` is a helper for when you use OAuth2. + + If you integrate your API with an OAuth2 provider, you will be able to authenticate and come back to the API docs with the acquired credentials. And interact with it using the real OAuth2 authentication. + + Swagger UI will handle it behind the scenes for you, but it needs this "redirect" helper. + +### Create a *path operation* to test it + +Now, to be able to test that everything works, create a *path operation*: + +```Python hl_lines="39-41" +{!../../../docs_src/extending_openapi/tutorial002.py!} +``` + +### Test it + +Now, you should be able to disconnect your WiFi, go to your docs at http://127.0.0.1:8000/docs, and reload the page. + +And even without Internet, you would be able to see the docs for your API and interact with it. + +## Configuring Swagger UI + +You can configure some extra Swagger UI parameters. + +To configure them, pass the `swagger_ui_parameters` argument when creating the `FastAPI()` app object or to the `get_swagger_ui_html()` function. + +`swagger_ui_parameters` receives a dictionary with the configurations passed to Swagger UI directly. + +FastAPI converts the configurations to **JSON** to make them compatible with JavaScript, as that's what Swagger UI needs. + +### Disable Syntax Highlighting + +For example, you could disable syntax highlighting in Swagger UI. + +Without changing the settings, syntax highlighting is enabled by default: + + + +But you can disable it by setting `syntaxHighlight` to `False`: + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial003.py!} +``` + +...and then Swagger UI won't show the syntax highlighting anymore: + + + +### Change the Theme + +The same way you could set the syntax highlighting theme with the key `"syntaxHighlight.theme"` (notice that it has a dot in the middle): + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial004.py!} +``` + +That configuration would change the syntax highlighting color theme: + + + +### Change Default Swagger UI Parameters + +FastAPI includes some default configuration parameters appropriate for most of the use cases. + +It includes these default configurations: + +```Python +{!../../../fastapi/openapi/docs.py[ln:7-13]!} +``` + +You can override any of them by setting a different value in the argument `swagger_ui_parameters`. + +For example, to disable `deepLinking` you could pass these settings to `swagger_ui_parameters`: + +```Python hl_lines="3" +{!../../../docs_src/extending_openapi/tutorial005.py!} +``` + +### Other Swagger UI Parameters + +To see all the other possible configurations you can use, read the official docs for Swagger UI parameters. + +### JavaScript-only settings + +Swagger UI also allows other configurations to be **JavaScript-only** objects (for example, JavaScript functions). + +FastAPI also includes these JavaScript-only `presets` settings: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +These are **JavaScript** objects, not strings, so you can't pass them from Python code directly. + +If you need to use JavaScript-only configurations like those, you can use one of the methods above. Override all the Swagger UI *path operation* and manually write any JavaScript you need. diff --git a/docs/zh/docs/advanced/generate-clients.md b/docs/zh/docs/advanced/generate-clients.md new file mode 100644 index 0000000000000..5edfccb23b1a5 --- /dev/null +++ b/docs/zh/docs/advanced/generate-clients.md @@ -0,0 +1,266 @@ +# Generate Clients + +As **FastAPI** is based on the OpenAPI specification, you get automatic compatibility with many tools, including the automatic API docs (provided by Swagger UI). + +One particular advantage that is not necessarily obvious is that you can **generate clients** (sometimes called **SDKs** ) for your API, for many different **programming languages**. + +## OpenAPI Client Generators + +There are many tools to generate clients from **OpenAPI**. + +A common tool is OpenAPI Generator. + +If you are building a **frontend**, a very interesting alternative is openapi-typescript-codegen. + +## Generate a TypeScript Frontend Client + +Let's start with a simple FastAPI application: + +=== "Python 3.9+" + + ```Python hl_lines="7-9 12-13 16-17 21" + {!> ../../../docs_src/generate_clients/tutorial001_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-11 14-15 18 19 23" + {!> ../../../docs_src/generate_clients/tutorial001.py!} + ``` + +Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`. + +### API Docs + +If you go to the API docs, you will see that it has the **schemas** for the data to be sent in requests and received in responses: + + + +You can see those schemas because they were declared with the models in the app. + +That information is available in the app's **OpenAPI schema**, and then shown in the API docs (by Swagger UI). + +And that same information from the models that is included in OpenAPI is what can be used to **generate the client code**. + +### Generate a TypeScript Client + +Now that we have the app with the models, we can generate the client code for the frontend. + +#### Install `openapi-typescript-codegen` + +You can install `openapi-typescript-codegen` in your frontend code with: + +
+ +```console +$ npm install openapi-typescript-codegen --save-dev + +---> 100% +``` + +
+ +#### Generate Client Code + +To generate the client code you can use the command line application `openapi` that would now be installed. + +Because it is installed in the local project, you probably wouldn't be able to call that command directly, but you would put it on your `package.json` file. + +It could look like this: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi --input http://localhost:8000/openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "openapi-typescript-codegen": "^0.20.1", + "typescript": "^4.6.2" + } +} +``` + +After having that NPM `generate-client` script there, you can run it with: + +
+ +```console +$ npm run generate-client + +frontend-app@1.0.0 generate-client /home/user/code/frontend-app +> openapi --input http://localhost:8000/openapi.json --output ./src/client --client axios +``` + +
+ +That command will generate code in `./src/client` and will use `axios` (the frontend HTTP library) internally. + +### Try Out the Client Code + +Now you can import and use the client code, it could look like this, notice that you get autocompletion for the methods: + + + +You will also get autocompletion for the payload to send: + + + +!!! tip + Notice the autocompletion for `name` and `price`, that was defined in the FastAPI application, in the `Item` model. + +You will have inline errors for the data that you send: + + + +The response object will also have autocompletion: + + + +## FastAPI App with Tags + +In many cases your FastAPI app will be bigger, and you will probably use tags to separate different groups of *path operations*. + +For example, you could have a section for **items** and another section for **users**, and they could be separated by tags: + +=== "Python 3.9+" + + ```Python hl_lines="21 26 34" + {!> ../../../docs_src/generate_clients/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="23 28 36" + {!> ../../../docs_src/generate_clients/tutorial002.py!} + ``` + +### Generate a TypeScript Client with Tags + +If you generate a client for a FastAPI app using tags, it will normally also separate the client code based on the tags. + +This way you will be able to have things ordered and grouped correctly for the client code: + + + +In this case you have: + +* `ItemsService` +* `UsersService` + +### Client Method Names + +Right now the generated method names like `createItemItemsPost` don't look very clean: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...that's because the client generator uses the OpenAPI internal **operation ID** for each *path operation*. + +OpenAPI requires that each operation ID is unique across all the *path operations*, so FastAPI uses the **function name**, the **path**, and the **HTTP method/operation** to generate that operation ID, because that way it can make sure that the operation IDs are unique. + +But I'll show you how to improve that next. 🤓 + +## Custom Operation IDs and Better Method Names + +You can **modify** the way these operation IDs are **generated** to make them simpler and have **simpler method names** in the clients. + +In this case you will have to ensure that each operation ID is **unique** in some other way. + +For example, you could make sure that each *path operation* has a tag, and then generate the operation ID based on the **tag** and the *path operation* **name** (the function name). + +### Custom Generate Unique ID Function + +FastAPI uses a **unique ID** for each *path operation*, it is used for the **operation ID** and also for the names of any needed custom models, for requests or responses. + +You can customize that function. It takes an `APIRoute` and outputs a string. + +For example, here it is using the first tag (you will probably have only one tag) and the *path operation* name (the function name). + +You can then pass that custom function to **FastAPI** as the `generate_unique_id_function` parameter: + +=== "Python 3.9+" + + ```Python hl_lines="6-7 10" + {!> ../../../docs_src/generate_clients/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8-9 12" + {!> ../../../docs_src/generate_clients/tutorial003.py!} + ``` + +### Generate a TypeScript Client with Custom Operation IDs + +Now if you generate the client again, you will see that it has the improved method names: + + + +As you see, the method names now have the tag and then the function name, now they don't include information from the URL path and the HTTP operation. + +### Preprocess the OpenAPI Specification for the Client Generator + +The generated code still has some **duplicated information**. + +We already know that this method is related to the **items** because that word is in the `ItemsService` (taken from the tag), but we still have the tag name prefixed in the method name too. 😕 + +We will probably still want to keep it for OpenAPI in general, as that will ensure that the operation IDs are **unique**. + +But for the generated client we could **modify** the OpenAPI operation IDs right before generating the clients, just to make those method names nicer and **cleaner**. + +We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this: + +```Python +{!../../../docs_src/generate_clients/tutorial004.py!} +``` + +With that, the operation IDs would be renamed from things like `items-get_items` to just `get_items`, that way the client generator can generate simpler method names. + +### Generate a TypeScript Client with the Preprocessed OpenAPI + +Now as the end result is in a file `openapi.json`, you would modify the `package.json` to use that local file, for example: + +```JSON hl_lines="7" +{ + "name": "frontend-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "generate-client": "openapi --input ./openapi.json --output ./src/client --client axios" + }, + "author": "", + "license": "", + "devDependencies": { + "openapi-typescript-codegen": "^0.20.1", + "typescript": "^4.6.2" + } +} +``` + +After generating the new client, you would now have **clean method names**, with all the **autocompletion**, **inline errors**, etc: + + + +## Benefits + +When using the automatically generated clients you would **autocompletion** for: + +* Methods. +* Request payloads in the body, query parameters, etc. +* Response payloads. + +You would also have **inline errors** for everything. + +And whenever you update the backend code, and **regenerate** the frontend, it would have any new *path operations* available as methods, the old ones removed, and any other change would be reflected on the generated code. 🤓 + +This also means that if something changed it will be **reflected** on the client code automatically. And if you **build** the client it will error out if you have any **mismatch** in the data used. + +So, you would **detect many errors** very early in the development cycle instead of having to wait for the errors to show up to your final users in production and then trying to debug where the problem is. ✨ diff --git a/docs/zh/docs/advanced/graphql.md b/docs/zh/docs/advanced/graphql.md new file mode 100644 index 0000000000000..1d2753fc4ce1b --- /dev/null +++ b/docs/zh/docs/advanced/graphql.md @@ -0,0 +1,59 @@ +# GraphQL + +As **FastAPI** is based on the **ASGI** standard, it's very easy to integrate any **GraphQL** library also compatible with ASGI. + +You can combine normal FastAPI *path operations* with GraphQL on the same application. + +!!! tip + **GraphQL** solves some very specific use cases. + + It has **advantages** and **disadvantages** when compared to common **web APIs**. + + Make sure you evaluate if the **benefits** for your use case compensate the **drawbacks**. 🤓 + +## GraphQL Libraries + +Here are some of the **GraphQL** libraries that have **ASGI** support. You could use them with **FastAPI**: + +* Strawberry 🍓 + * With docs for FastAPI +* Ariadne + + * With docs for Starlette (that also apply to FastAPI) +* Tartiflette + + * With Tartiflette ASGI to provide ASGI integration +* Graphene + + * With starlette-graphene3 + +## GraphQL with Strawberry + +If you need or want to work with **GraphQL**, **Strawberry** is the **recommended** library as it has the design closest to **FastAPI's** design, it's all based on **type annotations**. + +Depending on your use case, you might prefer to use a different library, but if you asked me, I would probably suggest you try **Strawberry**. + +Here's a small preview of how you could integrate Strawberry with FastAPI: + +```Python hl_lines="3 22 25-26" +{!../../../docs_src/graphql/tutorial001.py!} +``` + +You can learn more about Strawberry in the Strawberry documentation. + +And also the docs about Strawberry with FastAPI. + +## Older `GraphQLApp` from Starlette + +Previous versions of Starlette included a `GraphQLApp` class to integrate with Graphene. + +It was deprecated from Starlette, but if you have code that used it, you can easily **migrate** to starlette-graphene3, that covers the same use case and has an **almost identical interface**. + +!!! tip + If you need GraphQL, I still would recommend you check out Strawberry, as it's based on type annotations instead of custom classes and types. + +## Learn More + +You can learn more about **GraphQL** in the official GraphQL documentation. + +You can also read more about each those libraries described above in their links. diff --git a/docs/zh/docs/advanced/index.md b/docs/zh/docs/advanced/index.md index 824f91f47ba90..c042c1d32422c 100644 --- a/docs/zh/docs/advanced/index.md +++ b/docs/zh/docs/advanced/index.md @@ -6,7 +6,7 @@ 你会在接下来的章节中了解到其他的选项、配置以及额外的特性。 -!!! tip +!!! !!! tip 接下来的章节**并不一定是**「高级的」。 而且对于你的使用场景来说,解决方案很可能就在其中。 @@ -16,3 +16,9 @@ 你可能仍会用到 **FastAPI** 主教程 [教程 - 用户指南](../tutorial/){.internal-link target=_blank} 中的大多数特性。 接下来的章节我们认为你已经读过 [教程 - 用户指南](../tutorial/){.internal-link target=_blank},并且假设你已经知晓其中主要思想。 + +## TestDriven.io course + +If you would like to take an advanced-beginner course to complement this section of the docs, you might want to check: Test-Driven Development with FastAPI and Docker by **TestDriven.io**. + +They are currently donating 10% of all profits to the development of **FastAPI**. 🎉 😄 diff --git a/docs/zh/docs/advanced/middleware.md b/docs/zh/docs/advanced/middleware.md new file mode 100644 index 0000000000000..9219f1d2cb35d --- /dev/null +++ b/docs/zh/docs/advanced/middleware.md @@ -0,0 +1,99 @@ +# Advanced Middleware + +In the main tutorial you read how to add [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank} to your application. + +And then you also read how to handle [CORS with the `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +In this section we'll see how to use other middlewares. + +## Adding ASGI middlewares + +As **FastAPI** is based on Starlette and implements the ASGI specification, you can use any ASGI middleware. + +A middleware doesn't have to be made for FastAPI or Starlette to work, as long as it follows the ASGI spec. + +In general, ASGI middlewares are classes that expect to receive an ASGI app as the first argument. + +So, in the documentation for third-party ASGI middlewares they will probably tell you to do something like: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +But FastAPI (actually Starlette) provides a simpler way to do it that makes sure that the internal middlewares to handle server errors and custom exception handlers work properly. + +For that, you use `app.add_middleware()` (as in the example for CORS). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` receives a middleware class as the first argument and any additional arguments to be passed to the middleware. + +## Integrated middlewares + +**FastAPI** includes several middlewares for common use cases, we'll see next how to use them. + +!!! note "Technical Details" + For the next examples, you could also use `from starlette.middleware.something import SomethingMiddleware`. + + **FastAPI** provides several middlewares in `fastapi.middleware` just as a convenience for you, the developer. But most of the available middlewares come directly from Starlette. + +## `HTTPSRedirectMiddleware` + +Enforces that all incoming requests must either be `https` or `wss`. + +Any incoming requests to `http` or `ws` will be redirected to the secure scheme instead. + +```Python hl_lines="2 6" +{!../../../docs_src/advanced_middleware/tutorial001.py!} +``` + +## `TrustedHostMiddleware` + +Enforces that all incoming requests have a correctly set `Host` header, in order to guard against HTTP Host Header attacks. + +```Python hl_lines="2 6-8" +{!../../../docs_src/advanced_middleware/tutorial002.py!} +``` + +The following arguments are supported: + +* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains. To allow any hostname either use `allowed_hosts=["*"]` or omit the middleware. + +If an incoming request does not validate correctly then a `400` response will be sent. + +## `GZipMiddleware` + +Handles GZip responses for any request that includes `"gzip"` in the `Accept-Encoding` header. + +The middleware will handle both standard and streaming responses. + +```Python hl_lines="2 6" +{!../../../docs_src/advanced_middleware/tutorial003.py!} +``` + +The following arguments are supported: + +* `minimum_size` - Do not GZip responses that are smaller than this minimum size in bytes. Defaults to `500`. + +## Other middlewares + +There are many other ASGI middlewares. + +For example: + +* Sentry +* Uvicorn's `ProxyHeadersMiddleware` +* MessagePack + +To see other available middlewares check Starlette's Middleware docs and the ASGI Awesome List. diff --git a/docs/zh/docs/advanced/nosql-databases.md b/docs/zh/docs/advanced/nosql-databases.md new file mode 100644 index 0000000000000..f97f986c4261e --- /dev/null +++ b/docs/zh/docs/advanced/nosql-databases.md @@ -0,0 +1,163 @@ +# NoSQL (Distributed / Big Data) Databases + +!!! info + These docs are about to be updated. 🎉 + + The current version assumes Pydantic v1. + + The new docs will hopefully use Pydantic v2 and will use ODMantic with MongoDB. + +**FastAPI** can also be integrated with any NoSQL. + +Here we'll see an example using **Couchbase**, a document based NoSQL database. + +You can adapt it to any other NoSQL database like: + +* **MongoDB** +* **Cassandra** +* **CouchDB** +* **ArangoDB** +* **ElasticSearch**, etc. + +!!! tip + There is an official project generator with **FastAPI** and **Couchbase**, all based on **Docker**, including a frontend and more tools: https://github.com/tiangolo/full-stack-fastapi-couchbase + +## Import Couchbase components + +For now, don't pay attention to the rest, only the imports: + +```Python hl_lines="3-5" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## Define a constant to use as a "document type" + +We will use it later as a fixed field `type` in our documents. + +This is not required by Couchbase, but is a good practice that will help you afterwards. + +```Python hl_lines="9" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## Add a function to get a `Bucket` + +In **Couchbase**, a bucket is a set of documents, that can be of different types. + +They are generally all related to the same application. + +The analogy in the relational database world would be a "database" (a specific database, not the database server). + +The analogy in **MongoDB** would be a "collection". + +In the code, a `Bucket` represents the main entrypoint of communication with the database. + +This utility function will: + +* Connect to a **Couchbase** cluster (that might be a single machine). + * Set defaults for timeouts. +* Authenticate in the cluster. +* Get a `Bucket` instance. + * Set defaults for timeouts. +* Return it. + +```Python hl_lines="12-21" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## Create Pydantic models + +As **Couchbase** "documents" are actually just "JSON objects", we can model them with Pydantic. + +### `User` model + +First, let's create a `User` model: + +```Python hl_lines="24-28" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +We will use this model in our *path operation function*, so, we don't include in it the `hashed_password`. + +### `UserInDB` model + +Now, let's create a `UserInDB` model. + +This will have the data that is actually stored in the database. + +We don't create it as a subclass of Pydantic's `BaseModel` but as a subclass of our own `User`, because it will have all the attributes in `User` plus a couple more: + +```Python hl_lines="31-33" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +!!! note + Notice that we have a `hashed_password` and a `type` field that will be stored in the database. + + But it is not part of the general `User` model (the one we will return in the *path operation*). + +## Get the user + +Now create a function that will: + +* Take a username. +* Generate a document ID from it. +* Get the document with that ID. +* Put the contents of the document in a `UserInDB` model. + +By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your *path operation function*, you can more easily re-use it in multiple parts and also add unit tests for it: + +```Python hl_lines="36-42" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +### f-strings + +If you are not familiar with the `f"userprofile::{username}"`, it is a Python "f-string". + +Any variable that is put inside of `{}` in an f-string will be expanded / injected in the string. + +### `dict` unpacking + +If you are not familiar with the `UserInDB(**result.value)`, it is using `dict` "unpacking". + +It will take the `dict` at `result.value`, and take each of its keys and values and pass them as key-values to `UserInDB` as keyword arguments. + +So, if the `dict` contains: + +```Python +{ + "username": "johndoe", + "hashed_password": "some_hash", +} +``` + +It will be passed to `UserInDB` as: + +```Python +UserInDB(username="johndoe", hashed_password="some_hash") +``` + +## Create your **FastAPI** code + +### Create the `FastAPI` app + +```Python hl_lines="46" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +### Create the *path operation function* + +As our code is calling Couchbase and we are not using the experimental Python await support, we should declare our function with normal `def` instead of `async def`. + +Also, Couchbase recommends not using a single `Bucket` object in multiple "threads", so, we can just get the bucket directly and pass it to our utility functions: + +```Python hl_lines="49-53" +{!../../../docs_src/nosql_databases/tutorial001.py!} +``` + +## Recap + +You can integrate any third party NoSQL database, just using their standard packages. + +The same applies to any other external tool, system or API. diff --git a/docs/zh/docs/advanced/openapi-callbacks.md b/docs/zh/docs/advanced/openapi-callbacks.md new file mode 100644 index 0000000000000..14004d948e430 --- /dev/null +++ b/docs/zh/docs/advanced/openapi-callbacks.md @@ -0,0 +1,179 @@ +# OpenAPI Callbacks + +You could create an API with a *path operation* that could trigger a request to an *external API* created by someone else (probably the same developer that would be *using* your API). + +The process that happens when your API app calls the *external API* is named a "callback". Because the software that the external developer wrote sends a request to your API and then your API *calls back*, sending a request to an *external API* (that was probably created by the same developer). + +In this case, you could want to document how that external API *should* look like. What *path operation* it should have, what body it should expect, what response it should return, etc. + +## An app with callbacks + +Let's see all this with an example. + +Imagine you develop an app that allows creating invoices. + +These invoices will have an `id`, `title` (optional), `customer`, and `total`. + +The user of your API (an external developer) will create an invoice in your API with a POST request. + +Then your API will (let's imagine): + +* Send the invoice to some customer of the external developer. +* Collect the money. +* Send a notification back to the API user (the external developer). + * This will be done by sending a POST request (from *your API*) to some *external API* provided by that external developer (this is the "callback"). + +## The normal **FastAPI** app + +Let's first see how the normal API app would look like before adding the callback. + +It will have a *path operation* that will receive an `Invoice` body, and a query parameter `callback_url` that will contain the URL for the callback. + +This part is pretty normal, most of the code is probably already familiar to you: + +```Python hl_lines="9-13 36-53" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +!!! tip + The `callback_url` query parameter uses a Pydantic URL type. + +The only new thing is the `callbacks=messages_callback_router.routes` as an argument to the *path operation decorator*. We'll see what that is next. + +## Documenting the callback + +The actual callback code will depend heavily on your own API app. + +And it will probably vary a lot from one app to the next. + +It could be just one or two lines of code, like: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +But possibly the most important part of the callback is making sure that your API user (the external developer) implements the *external API* correctly, according to the data that *your API* is going to send in the request body of the callback, etc. + +So, what we will do next is add the code to document how that *external API* should look like to receive the callback from *your API*. + +That documentation will show up in the Swagger UI at `/docs` in your API, and it will let external developers know how to build the *external API*. + +This example doesn't implement the callback itself (that could be just a line of code), only the documentation part. + +!!! tip + The actual callback is just an HTTP request. + + When implementing the callback yourself, you could use something like HTTPX or Requests. + +## Write the callback documentation code + +This code won't be executed in your app, we only need it to *document* how that *external API* should look like. + +But, you already know how to easily create automatic documentation for an API with **FastAPI**. + +So we are going to use that same knowledge to document how the *external API* should look like... by creating the *path operation(s)* that the external API should implement (the ones your API will call). + +!!! tip + When writing the code to document a callback, it might be useful to imagine that you are that *external developer*. And that you are currently implementing the *external API*, not *your API*. + + Temporarily adopting this point of view (of the *external developer*) can help you feel like it's more obvious where to put the parameters, the Pydantic model for the body, for the response, etc. for that *external API*. + +### Create a callback `APIRouter` + +First create a new `APIRouter` that will contain one or more callbacks. + +```Python hl_lines="3 25" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +### Create the callback *path operation* + +To create the callback *path operation* use the same `APIRouter` you created above. + +It should look just like a normal FastAPI *path operation*: + +* It should probably have a declaration of the body it should receive, e.g. `body: InvoiceEvent`. +* And it could also have a declaration of the response it should return, e.g. `response_model=InvoiceEventReceived`. + +```Python hl_lines="16-18 21-22 28-32" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +There are 2 main differences from a normal *path operation*: + +* It doesn't need to have any actual code, because your app will never call this code. It's only used to document the *external API*. So, the function could just have `pass`. +* The *path* can contain an OpenAPI 3 expression (see more below) where it can use variables with parameters and parts of the original request sent to *your API*. + +### The callback path expression + +The callback *path* can have an OpenAPI 3 expression that can contain parts of the original request sent to *your API*. + +In this case, it's the `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +So, if your API user (the external developer) sends a request to *your API* to: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +with a JSON body of: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +Then *your API* will process the invoice, and at some point later, send a callback request to the `callback_url` (the *external API*): + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +with a JSON body containing something like: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +and it would expect a response from that *external API* with a JSON body like: + +```JSON +{ + "ok": true +} +``` + +!!! tip + Notice how the callback URL used contains the URL received as a query parameter in `callback_url` (`https://www.external.org/events`) and also the invoice `id` from inside of the JSON body (`2expen51ve`). + +### Add the callback router + +At this point you have the *callback path operation(s)* needed (the one(s) that the *external developer* should implement in the *external API*) in the callback router you created above. + +Now use the parameter `callbacks` in *your API's path operation decorator* to pass the attribute `.routes` (that's actually just a `list` of routes/*path operations*) from that callback router: + +```Python hl_lines="35" +{!../../../docs_src/openapi_callbacks/tutorial001.py!} +``` + +!!! tip + Notice that you are not passing the router itself (`invoices_callback_router`) to `callback=`, but the attribute `.routes`, as in `invoices_callback_router.routes`. + +### Check the docs + +Now you can start your app with Uvicorn and go to http://127.0.0.1:8000/docs. + +You will see your docs including a "Callback" section for your *path operation* that shows how the *external API* should look like: + + diff --git a/docs/zh/docs/advanced/openapi-webhooks.md b/docs/zh/docs/advanced/openapi-webhooks.md new file mode 100644 index 0000000000000..a41f8c820490d --- /dev/null +++ b/docs/zh/docs/advanced/openapi-webhooks.md @@ -0,0 +1,51 @@ +# OpenAPI Webhooks + +There are cases where you want to tell your API **users** that your app could call *their* app (sending a request) with some data, normally to **notify** of some type of **event**. + +This means that instead of the normal process of your users sending requests to your API, it's **your API** (or your app) that could **send requests to their system** (to their API, their app). + +This is normally called a **webhook**. + +## Webhooks steps + +The process normally is that **you define** in your code what is the message that you will send, the **body of the request**. + +You also define in some way at which **moments** your app will send those requests or events. + +And **your users** define in some way (for example in a web dashboard somewhere) the **URL** where your app should send those requests. + +All the **logic** about how to register the URLs for webhooks and the code to actually send those requests is up to you. You write it however you want to in **your own code**. + +## Documenting webhooks with **FastAPI** and OpenAPI + +With **FastAPI**, using OpenAPI, you can define the names of these webhooks, the types of HTTP operations that your app can send (e.g. `POST`, `PUT`, etc.) and the request **bodies** that your app would send. + +This can make it a lot easier for your users to **implement their APIs** to receive your **webhook** requests, they might even be able to autogenerate some of their own API code. + +!!! info + Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` and above. + +## An app with webhooks + +When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. + +```Python hl_lines="9-13 36-53" +{!../../../docs_src/openapi_webhooks/tutorial001.py!} +``` + +The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. + +!!! info + The `app.webhooks` object is actually just an `APIRouter`, the same type you would use when structuring your app with multiple files. + +Notice that with webhooks you are actually not declaring a *path* (like `/items/`), the text you pass there is just an **identifier** of the webhook (the name of the event), for example in `@app.webhooks.post("new-subscription")`, the webhook name is `new-subscription`. + +This is because it is expected that **your users** would define the actual **URL path** where they want to receive the webhook request in some other way (e.g. a web dashboard). + +### Check the docs + +Now you can start your app with Uvicorn and go to http://127.0.0.1:8000/docs. + +You will see your docs have the normal *path operations* and now also some **webhooks**: + + diff --git a/docs/zh/docs/advanced/path-operation-advanced-configuration.md b/docs/zh/docs/advanced/path-operation-advanced-configuration.md index 7da9f251e30db..b9cbd79f35f9d 100644 --- a/docs/zh/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/zh/docs/advanced/path-operation-advanced-configuration.md @@ -2,12 +2,12 @@ ## OpenAPI 的 operationId -!!! warning +!!! !!! warning 如果你并非 OpenAPI 的「专家」,你可能不需要这部分内容。 你可以在路径操作中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`。 -务必确保每个操作路径的 `operation_id` 都是唯一的。 +You would have to make sure that it is unique for each operation. ```Python hl_lines="6" {!../../../docs_src/path_operation_advanced_configuration/tutorial001.py!} @@ -19,14 +19,14 @@ 你应该在添加了所有 *路径操作* 之后执行此操作。 -```Python hl_lines="2 12 13 14 15 16 17 18 19 20 21 24" +```Python hl_lines="2 12-21 24" {!../../../docs_src/path_operation_advanced_configuration/tutorial002.py!} ``` -!!! tip +!!! !!! tip 如果你手动调用 `app.openapi()`,你应该在此之前更新 `operationId`。 -!!! warning +!!! !!! warning 如果你这样做,务必确保你的每个 *路径操作函数* 的名字唯一。 即使它们在不同的模块中(Python 文件)。 @@ -47,7 +47,146 @@ 剩余部分不会出现在文档中,但是其他工具(比如 Sphinx)可以使用剩余部分。 - -```Python hl_lines="19 20 21 22 23 24 25 26 27 28 29" +```Python hl_lines="19-29" {!../../../docs_src/path_operation_advanced_configuration/tutorial004.py!} ``` + +## Additional Responses + +You probably have seen how to declare the `response_model` and `status_code` for a *path operation*. + +That defines the metadata about the main response of a *path operation*. + +You can also declare additional responses with their models, status codes, etc. + +There's a whole chapter here in the documentation about it, you can read it at [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}. + +## OpenAPI Extra + +When you declare a *path operation* in your application, **FastAPI** automatically generates the relevant metadata about that *path operation* to be included in the OpenAPI schema. + +!!! note "Technical details" + In the OpenAPI specification it is called the Operation Object. + +It has all the information about the *path operation* and is used to generate the automatic documentation. + +It includes the `tags`, `parameters`, `requestBody`, `responses`, etc. + +This *path operation*-specific OpenAPI schema is normally generated automatically by **FastAPI**, but you can also extend it. + +!!! tip + This is a low level extension point. + + If you only need to declare additional responses, a more convenient way to do it is with [Additional Responses in OpenAPI](./additional-responses.md){.internal-link target=_blank}. + +You can extend the OpenAPI schema for a *path operation* using the parameter `openapi_extra`. + +### OpenAPI Extensions + +This `openapi_extra` can be helpful, for example, to declare [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +```Python hl_lines="6" +{!../../../docs_src/path_operation_advanced_configuration/tutorial005.py!} +``` + +If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*. + + + +And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will see your extension as part of the specific *path operation* too: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Custom OpenAPI *path operation* schema + +The dictionary in `openapi_extra` will be deeply merged with the automatically generated OpenAPI schema for the *path operation*. + +So, you could add additional data to the automatically generated schema. + +For example, you could decide to read and validate the request with your own code, without using the automatic features of FastAPI with Pydantic, but you could still want to define the request in the OpenAPI schema. + +You could do that with `openapi_extra`: + +```Python hl_lines="20-37 39-40" +{!../../../docs_src/path_operation_advanced_configuration/tutorial006.py!} +``` + +In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. + +Nevertheless, we can declare the expected schema for the request body. + +### Custom OpenAPI content type + +Using this same trick, you could use a Pydantic model to define the JSON Schema that is then included in the custom OpenAPI schema section for the *path operation*. + +And you could do this even if the data type in the request is not JSON. + +For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON: + +=== "Pydantic v2" + + ```Python hl_lines="17-22 24" + {!> ../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} + ``` + +=== "Pydantic v1" + + ```Python hl_lines="17-22 24" + {!> ../../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} + ``` + +!!! info + In Pydantic version 1 the method to get the JSON Schema for a model was called `Item.schema()`, in Pydantic version 2, the method is called `Item.model_schema_json()`. + +Nevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML. + +Then we use the request directly, and extract the body as `bytes`. This means that FastAPI won't even try to parse the request payload as JSON. + +And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content: + +=== "Pydantic v2" + + ```Python hl_lines="26-33" + {!> ../../../docs_src/path_operation_advanced_configuration/tutorial007.py!} + ``` + +=== "Pydantic v1" + + ```Python hl_lines="26-33" + {!> ../../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py!} + ``` + +!!! info + In Pydantic version 1 the method to parse and validate an object was `Item.parse_obj()`, in Pydantic version 2, the method is called `Item.model_validate()`. + +!!! tip + Here we re-use the same Pydantic model. + + But the same way, we could have validated it in some other way. diff --git a/docs/zh/docs/advanced/response-change-status-code.md b/docs/zh/docs/advanced/response-change-status-code.md index a289cf20178f0..3bc41be37ad22 100644 --- a/docs/zh/docs/advanced/response-change-status-code.md +++ b/docs/zh/docs/advanced/response-change-status-code.md @@ -24,8 +24,10 @@ {!../../../docs_src/response_change_status_code/tutorial001.py!} ``` -然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 +然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。 + +如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 **FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 -你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。 +你也可以在依赖项中声明`Response`参数,并在其中设置状态码。 但请注意,最后设置的状态码将会生效。 diff --git a/docs/zh/docs/advanced/response-cookies.md b/docs/zh/docs/advanced/response-cookies.md index 3e53c53191a4c..810119ea8a6eb 100644 --- a/docs/zh/docs/advanced/response-cookies.md +++ b/docs/zh/docs/advanced/response-cookies.md @@ -4,6 +4,8 @@ 你可以在 *路径函数* 中定义一个类型为 `Response`的参数,这样你就可以在这个临时响应对象中设置cookie了。 +And then you can set cookies in that *temporal* response object. + ```Python hl_lines="1 8-9" {!../../../docs_src/response_cookies/tutorial002.py!} ``` @@ -14,11 +16,11 @@ **FastAPI** 会使用这个 *临时* 响应对象去装在这些cookies信息 (同样还有headers和状态码等信息), 最终会将这些信息和通过`response_model`转化过的数据合并到最终的响应里。 -你也可以在depend中定义`Response`参数,并设置cookie和header。 +你还可以在直接响应`Response`时直接创建cookies。 ## 直接响应 `Response` -你还可以在直接响应`Response`时直接创建cookies。 +你也可以在depend中定义`Response`参数,并设置cookie和header。 你可以参考[Return a Response Directly](response-directly.md){.internal-link target=_blank}来创建response @@ -28,20 +30,20 @@ {!../../../docs_src/response_cookies/tutorial001.py!} ``` -!!! tip +!!! !!! tip 需要注意,如果你直接反馈一个response对象,而不是使用`Response`入参,FastAPI则会直接反馈你封装的response对象。 - 所以你需要确保你响应数据类型的正确性,如:你可以使用`JSONResponse`来兼容JSON的场景。 - + So, you will have to make sure your data is of the correct type. E.g. 所以你需要确保你响应数据类型的正确性,如:你可以使用`JSONResponse`来兼容JSON的场景。 + 同时,你也应当仅反馈通过`response_model`过滤过的数据。 ### 更多信息 -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以使用`from starlette.responses import Response` 或者 `from starlette.responses import JSONResponse`。 - 为了方便开发者,**FastAPI** 封装了相同数据类型,如`starlette.responses` 和 `fastapi.responses`。不过大部分response对象都是直接引用自Starlette。 - + 为了方便开发者,**FastAPI** 封装了相同数据类型,如`starlette.responses` 和 `fastapi.responses`。 不过大部分response对象都是直接引用自Starlette。 + 因为`Response`对象可以非常便捷的设置headers和cookies,所以 **FastAPI** 同时也封装了`fastapi.Response`。 如果你想查看所有可用的参数和选项,可以参考 Starlette帮助文档 diff --git a/docs/zh/docs/advanced/response-directly.md b/docs/zh/docs/advanced/response-directly.md index 797a878eb923b..bd26889d9f64d 100644 --- a/docs/zh/docs/advanced/response-directly.md +++ b/docs/zh/docs/advanced/response-directly.md @@ -14,14 +14,14 @@ 事实上,你可以返回任意 `Response` 或者任意 `Response` 的子类。 -!!! tip "小贴士" +!!! !!! tip "小贴士" `JSONResponse` 本身是一个 `Response` 的子类。 当你返回一个 `Response` 时,**FastAPI** 会直接传递它。 -**FastAPI** 不会用 Pydantic 模型做任何数据转换,不会将响应内容转换成任何类型,等等。 +It won't do any data conversion with Pydantic models, it won't convert the contents to any type, etc. -这种特性给你极大的可扩展性。你可以返回任何数据类型,重写任何数据声明或者校验,等等。 +这种特性给你极大的可扩展性。 你可以返回任何数据类型,重写任何数据声明或者校验,等等。 ## 在 `Response` 中使用 `jsonable_encoder` @@ -31,19 +31,18 @@ 对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据。 - -```Python hl_lines="4 6 20 21" +```Python hl_lines="6-7 21-22" {!../../../docs_src/response_directly/tutorial001.py!} ``` -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以使用 `from starlette.responses import JSONResponse`。 - 出于方便,**FastAPI** 会提供与 `starlette.responses` 相同的 `fastapi.responses` 给开发者。但是大多数可用的响应都直接来自 Starlette。 + 出于方便,**FastAPI** 会提供与 `starlette.responses` 相同的 `fastapi.responses` 给开发者。 但是大多数可用的响应都直接来自 Starlette。 ## 返回自定义 `Response` -上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而**FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict`等等。 +上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而**FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict`等等。 All that by default. 现在,让我们看看你如何才能返回一个自定义的响应。 diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md index 85dab15ac092f..6ff939767b495 100644 --- a/docs/zh/docs/advanced/response-headers.md +++ b/docs/zh/docs/advanced/response-headers.md @@ -5,35 +5,38 @@ 你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。 然后你可以在这个*临时*响应对象中设置头部。 + ```Python hl_lines="1 7-8" {!../../../docs_src/response_headers/tutorial002.py!} ``` -然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 +然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。 + +如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 **FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 -你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。 +你也可以在直接返回`Response`时添加头部。 ## 直接返回 `Response` -你也可以在直接返回`Response`时添加头部。 +You can also add headers when you return a `Response` directly. 按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: + ```Python hl_lines="10-12" {!../../../docs_src/response_headers/tutorial001.py!} ``` - -!!! 注意 "技术细节" +!!! !!! 注意 "技术细节" 你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 - **FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。 - + **FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。 但是,大多数可用的响应都直接来自Starlette。 + 由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。 ## 自定义头部 -请注意,可以使用'X-'前缀添加自定义专有头部。 +Have in mind that custom proprietary headers can be added using the 'X-' prefix. 但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。 diff --git a/docs/zh/docs/advanced/security/http-basic-auth.md b/docs/zh/docs/advanced/security/http-basic-auth.md new file mode 100644 index 0000000000000..c24dfc8c7e25d --- /dev/null +++ b/docs/zh/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,164 @@ +# HTTP Basic Auth + +For the simplest cases, you can use HTTP Basic Auth. + +In HTTP Basic Auth, the application expects a header that contains a username and a password. + +If it doesn't receive it, it returns an HTTP 401 "Unauthorized" error. + +And returns a header `WWW-Authenticate` with a value of `Basic`, and an optional `realm` parameter. + +That tells the browser to show the integrated prompt for a username and password. + +Then, when you type that username and password, the browser sends them in the header automatically. + +## Simple HTTP Basic Auth + +* Import `HTTPBasic` and `HTTPBasicCredentials`. +* Create a "`security` scheme" using `HTTPBasic`. +* Use that `security` with a dependency in your *path operation*. +* It returns an object of type `HTTPBasicCredentials`: + * It contains the `username` and `password` sent. + +=== "Python 3.9+" + + ```Python hl_lines="4 8 12" + {!> ../../../docs_src/security/tutorial006_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="2 7 11" + {!> ../../../docs_src/security/tutorial006_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 6 10" + {!> ../../../docs_src/security/tutorial006.py!} + ``` + +When you try to open the URL for the first time (or click the "Execute" button in the docs) the browser will ask you for your username and password: + + + +## Check the username + +Here's a more complete example. + +Use a dependency to check if the username and password are correct. + +For this, use the Python standard module `secrets` to check the username and password. + +`secrets.compare_digest()` needs to take `bytes` or a `str` that only contains ASCII characters (the ones in English), this means it wouldn't work with characters like `á`, as in `Sebastián`. + +To handle that, we first convert the `username` and `password` to `bytes` encoding them with UTF-8. + +Then we can use `secrets.compare_digest()` to ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. + +=== "Python 3.9+" + + ```Python hl_lines="1 12-24" + {!> ../../../docs_src/security/tutorial007_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 12-24" + {!> ../../../docs_src/security/tutorial007_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 11-21" + {!> ../../../docs_src/security/tutorial007.py!} + ``` + +This would be similar to: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +But by using the `secrets.compare_digest()` it will be secure against a type of attacks called "timing attacks". + +### Timing Attacks + +But what's a "timing attack"? + +Let's imagine some attackers are trying to guess the username and password. + +And they send a request with a username `johndoe` and a password `love123`. + +Then the Python code in your application would be equivalent to something like: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +But right at the moment Python compares the first `j` in `johndoe` to the first `s` in `stanleyjobson`, it will return `False`, because it already knows that those two strings are not the same, thinking that "there's no need to waste more computation comparing the rest of the letters". And your application will say "incorrect user or password". + +But then the attackers try with username `stanleyjobsox` and password `love123`. + +And your application code does something like: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Python will have to compare the whole `stanleyjobso` in both `stanleyjobsox` and `stanleyjobson` before realizing that both strings are not the same. So it will take some extra microseconds to reply back "incorrect user or password". + +#### The time to answer helps the attackers + +At that point, by noticing that the server took some microseconds longer to send the "incorrect user or password" response, the attackers will know that they got _something_ right, some of the initial letters were right. + +And then they can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`. + +#### A "professional" attack + +Of course, the attackers would not try all this by hand, they would write a program to do it, possibly with thousands or millions of tests per second. And would get just one extra correct letter at a time. + +But doing that, in some minutes or hours the attackers would have guessed the correct username and password, with the "help" of our application, just using the time taken to answer. + +#### Fix it with `secrets.compare_digest()` + +But in our code we are actually using `secrets.compare_digest()`. + +In short, it will take the same time to compare `stanleyjobsox` to `stanleyjobson` than it takes to compare `johndoe` to `stanleyjobson`. And the same for the password. + +That way, using `secrets.compare_digest()` in your application code, it will be safe against this whole range of security attacks. + +### Return the error + +After detecting that the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again: + +=== "Python 3.9+" + + ```Python hl_lines="26-30" + {!> ../../../docs_src/security/tutorial007_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="26-30" + {!> ../../../docs_src/security/tutorial007_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="23-27" + {!> ../../../docs_src/security/tutorial007.py!} + ``` diff --git a/docs/zh/docs/advanced/security/index.md b/docs/zh/docs/advanced/security/index.md index fdc8075c765ba..4cae9655474f7 100644 --- a/docs/zh/docs/advanced/security/index.md +++ b/docs/zh/docs/advanced/security/index.md @@ -4,7 +4,7 @@ 除 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性. -!!! tip "小贴士" +!!! !!! tip "小贴士" 接下来的章节 **并不一定是 "高级的"**. 而且对于你的使用场景来说,解决方案很可能就在其中。 diff --git a/docs/zh/docs/advanced/security/oauth2-scopes.md b/docs/zh/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 0000000000000..3f230e9e74627 --- /dev/null +++ b/docs/zh/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,598 @@ +# OAuth2 scopes + +You can use OAuth2 scopes directly with **FastAPI**, they are integrated to work seamlessly. + +This would allow you to have a more fine-grained permission system, following the OAuth2 standard, integrated into your OpenAPI application (and the API docs). + +OAuth2 with scopes is the mechanism used by many big authentication providers, like Facebook, Google, GitHub, Microsoft, Twitter, etc. They use it to provide specific permissions to users and applications. + +Every time you "log in with" Facebook, Google, GitHub, Microsoft, Twitter, that application is using OAuth2 with scopes. + +In this section you will see how to manage authentication and authorization with the same OAuth2 with scopes in your **FastAPI** application. + +!!! warning + This is a more or less advanced section. If you are just starting, you can skip it. + + You don't necessarily need OAuth2 scopes, and you can handle authentication and authorization however you want. + + But OAuth2 with scopes can be nicely integrated into your API (with OpenAPI) and your API docs. + + Nevertheless, you still enforce those scopes, or any other security/authorization requirement, however you need, in your code. + + In many cases, OAuth2 with scopes can be an overkill. + + But if you know you need it, or you are curious, keep reading. + +## OAuth2 scopes and OpenAPI + +The OAuth2 specification defines "scopes" as a list of strings separated by spaces. + +The content of each of these strings can have any format, but should not contain spaces. + +These scopes represent "permissions". + +In OpenAPI (e.g. the API docs), you can define "security schemes". + +When one of these security schemes uses OAuth2, you can also declare and use scopes. + +Each "scope" is just a string (without spaces). + +They are normally used to declare specific security permissions, for example: + +* `users:read` or `users:write` are common examples. +* `instagram_basic` is used by Facebook / Instagram. +* `https://www.googleapis.com/auth/drive` is used by Google. + +!!! info + In OAuth2 a "scope" is just a string that declares a specific permission required. + + It doesn't matter if it has other characters like `:` or if it is a URL. + + Those details are implementation specific. + + For OAuth2 they are just strings. + +## Global view + +First, let's quickly see the parts that change from the examples in the main **Tutorial - User Guide** for [OAuth2 with Password (and hashing), Bearer with JWT tokens](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Now using OAuth2 scopes: + +=== "Python 3.10+" + + ```Python hl_lines="4 8 12 46 64 105 107-115 121-124 128-134 139 155" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 155" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="2 4 8 12 47 65 106 108-116 122-125 129-135 140 156" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3 7 11 45 63 104 106-114 120-123 127-133 138 152" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 153" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 4 8 12 46 64 105 107-115 121-124 128-134 139 153" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +Now let's review those changes step by step. + +## OAuth2 Security scheme + +The first change is that now we are declaring the OAuth2 security scheme with two available scopes, `me` and `items`. + +The `scopes` parameter receives a `dict` with each scope as a key and the description as the value: + +=== "Python 3.10+" + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="63-66" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="61-64" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="62-65" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +Because we are now declaring those scopes, they will show up in the API docs when you log-in/authorize. + +And you will be able to select which scopes you want to give access to: `me` and `items`. + +This is the same mechanism used when you give permissions while logging in with Facebook, Google, GitHub, etc: + + + +## JWT token with scopes + +Now, modify the token *path operation* to return the scopes requested. + +We are still using the same `OAuth2PasswordRequestForm`. It includes a property `scopes` with a `list` of `str`, with each scope it received in the request. + +And we return the scopes as part of the JWT token. + +!!! danger + For simplicity, here we are just adding the scopes received directly to the token. + + But in your application, for security, you should make sure you only add the scopes that the user is actually able to have, or the ones you have predefined. + +=== "Python 3.10+" + + ```Python hl_lines="155" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="155" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="156" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="152" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="153" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="153" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +## Declare scopes in *path operations* and dependencies + +Now we declare that the *path operation* for `/users/me/items/` requires the scope `items`. + +For this, we import and use `Security` from `fastapi`. + +You can use `Security` to declare dependencies (just like `Depends`), but `Security` also receives a parameter `scopes` with a list of scopes (strings). + +In this case, we pass a dependency function `get_current_active_user` to `Security` (the same way we would do with `Depends`). + +But we also pass a `list` of scopes, in this case with just one scope: `items` (it could have more). + +And the dependency function `get_current_active_user` can also declare sub-dependencies, not only with `Depends` but also with `Security`. Declaring its own sub-dependency function (`get_current_user`), and more scope requirements. + +In this case, it requires the scope `me` (it could require more than one scope). + +!!! note + You don't necessarily need to add different scopes in different places. + + We are doing it here to demonstrate how **FastAPI** handles scopes declared at different levels. + +=== "Python 3.10+" + + ```Python hl_lines="4 139 170" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 139 170" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 140 171" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3 138 165" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 139 166" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 139 166" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +!!! info "Technical Details" + `Security` is actually a subclass of `Depends`, and it has just one extra parameter that we'll see later. + + But by using `Security` instead of `Depends`, **FastAPI** will know that it can declare security scopes, use them internally, and document the API with OpenAPI. + + But when you import `Query`, `Path`, `Depends`, `Security` and others from `fastapi`, those are actually functions that return special classes. + +## Use `SecurityScopes` + +Now update the dependency `get_current_user`. + +This is the one used by the dependencies above. + +Here's were we are using the same OAuth2 scheme we created before, declaring it as a dependency: `oauth2_scheme`. + +Because this dependency function doesn't have any scope requirements itself, we can use `Depends` with `oauth2_scheme`, we don't have to use `Security` when we don't need to specify security scopes. + +We also declare a special parameter of type `SecurityScopes`, imported from `fastapi.security`. + +This `SecurityScopes` class is similar to `Request` (`Request` was used to get the request object directly). + +=== "Python 3.10+" + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8 106" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7 104" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8 105" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +## Use the `scopes` + +The parameter `security_scopes` will be of type `SecurityScopes`. + +It will have a property `scopes` with a list containing all the scopes required by itself and all the dependencies that use this as a sub-dependency. That means, all the "dependants"... this might sound confusing, it is explained again later below. + +The `security_scopes` object (of class `SecurityScopes`) also provides a `scope_str` attribute with a single string, containing those scopes separated by spaces (we are going to use it). + +We create an `HTTPException` that we can re-use (`raise`) later at several points. + +In this exception, we include the scopes required (if any) as a string separated by spaces (using `scope_str`). We put that string containing the scopes in the `WWW-Authenticate` header (this is part of the spec). + +=== "Python 3.10+" + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="106 108-116" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="104 106-114" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="105 107-115" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +## Verify the `username` and data shape + +We verify that we get a `username`, and extract the scopes. + +And then we validate that data with the Pydantic model (catching the `ValidationError` exception), and if we get an error reading the JWT token or validating the data with Pydantic, we raise the `HTTPException` we created before. + +For that, we update the Pydantic model `TokenData` with a new property `scopes`. + +By validating the data with Pydantic we can make sure that we have, for example, exactly a `list` of `str` with the scopes and a `str` with the `username`. + +Instead of, for example, a `dict`, or something else, as it could break the application at some point later, making it a security risk. + +We also verify that we have a user with that username, and if not, we raise that same exception we created before. + +=== "Python 3.10+" + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="47 117-128" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="45 115-126" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="46 116-127" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +## Verify the `scopes` + +We now verify that all the scopes required, by this dependency and all the dependants (including *path operations*), are included in the scopes provided in the token received, otherwise raise an `HTTPException`. + +For this, we use `security_scopes.scopes`, that contains a `list` with all these scopes as `str`. + +=== "Python 3.10+" + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="129-135" + {!> ../../../docs_src/security/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="127-133" + {!> ../../../docs_src/security/tutorial005_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="128-134" + {!> ../../../docs_src/security/tutorial005.py!} + ``` + +## Dependency tree and scopes + +Let's review again this dependency tree and the scopes. + +As the `get_current_active_user` dependency has as a sub-dependency on `get_current_user`, the scope `"me"` declared at `get_current_active_user` will be included in the list of required scopes in the `security_scopes.scopes` passed to `get_current_user`. + +The *path operation* itself also declares a scope, `"items"`, so this will also be in the list of `security_scopes.scopes` passed to `get_current_user`. + +Here's how the hierarchy of dependencies and scopes looks like: + +* The *path operation* `read_own_items` has: + * Required scopes `["items"]` with the dependency: + * `get_current_active_user`: + * The dependency function `get_current_active_user` has: + * Required scopes `["me"]` with the dependency: + * `get_current_user`: + * The dependency function `get_current_user` has: + * No scopes required by itself. + * A dependency using `oauth2_scheme`. + * A `security_scopes` parameter of type `SecurityScopes`: + * This `security_scopes` parameter has a property `scopes` with a `list` containing all these scopes declared above, so: + * `security_scopes.scopes` will contain `["me", "items"]` for the *path operation* `read_own_items`. + * `security_scopes.scopes` will contain `["me"]` for the *path operation* `read_users_me`, because it is declared in the dependency `get_current_active_user`. + * `security_scopes.scopes` will contain `[]` (nothing) for the *path operation* `read_system_status`, because it didn't declare any `Security` with `scopes`, and its dependency, `get_current_user`, doesn't declare any `scope` either. + +!!! tip + The important and "magic" thing here is that `get_current_user` will have a different list of `scopes` to check for each *path operation*. + + All depending on the `scopes` declared in each *path operation* and each dependency in the dependency tree for that specific *path operation*. + +## More details about `SecurityScopes` + +You can use `SecurityScopes` at any point, and in multiple places, it doesn't have to be at the "root" dependency. + +It will always have the security scopes declared in the current `Security` dependencies and all the dependants for **that specific** *path operation* and **that specific** dependency tree. + +Because the `SecurityScopes` will have all the scopes declared by dependants, you can use it to verify that a token has the required scopes in a central dependency function, and then declare different scope requirements in different *path operations*. + +They will be checked independently for each *path operation*. + +## Check it + +If you open the API docs, you can authenticate and specify which scopes you want to authorize. + + + +If you don't select any scope, you will be "authenticated", but when you try to access `/users/me/` or `/users/me/items/` you will get an error saying that you don't have enough permissions. You will still be able to access `/status/`. + +And if you select the scope `me` but not the scope `items`, you will be able to access `/users/me/` but not `/users/me/items/`. + +That's what would happen to a third party application that tried to access one of these *path operations* with a token provided by a user, depending on how many permissions the user gave the application. + +## About third party integrations + +In this example we are using the OAuth2 "password" flow. + +This is appropriate when we are logging in to our own application, probably with our own frontend. + +Because we can trust it to receive the `username` and `password`, as we control it. + +But if you are building an OAuth2 application that others would connect to (i.e., if you are building an authentication provider equivalent to Facebook, Google, GitHub, etc.) you should use one of the other flows. + +The most common is the implicit flow. + +The most secure is the code flow, but is more complex to implement as it requires more steps. As it is more complex, many providers end up suggesting the implicit flow. + +!!! note + It's common that each authentication provider names their flows in a different way, to make it part of their brand. + + But in the end, they are implementing the same OAuth2 standard. + +**FastAPI** includes utilities for all these OAuth2 authentication flows in `fastapi.security.oauth2`. + +## `Security` in decorator `dependencies` + +The same way you can define a `list` of `Depends` in the decorator's `dependencies` parameter (as explained in [Dependencies in path operation decorators](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), you could also use `Security` with `scopes` there. diff --git a/docs/zh/docs/advanced/settings.md b/docs/zh/docs/advanced/settings.md index 597e99a7793c3..d37bf11e79479 100644 --- a/docs/zh/docs/advanced/settings.md +++ b/docs/zh/docs/advanced/settings.md @@ -2,13 +2,13 @@ 在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。 -这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。 +这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。 而且许多设置可能是敏感的,比如密钥。 因此,通常会将它们提供为由应用程序读取的环境变量。 ## 环境变量 -!!! tip +!!! !!! tip 如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。 环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。 @@ -29,6 +29,7 @@ Hello Wade Wilson ``` + === "Windows PowerShell" @@ -45,6 +46,7 @@ Hello Wade Wilson ``` + ### 在 Python 中读取环境变量 @@ -61,7 +63,7 @@ print(f"Hello {name} from Python") ``` !!! tip - `os.getenv()` 的第二个参数是要返回的默认值。 + The second argument to `os.getenv()` is the default value to return. 如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。 @@ -92,8 +94,6 @@ Hello Wade Wilson from Python 由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。 - - 您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。 要做到这一点,在程序本身之前的同一行创建它: @@ -116,7 +116,7 @@ Hello World from Python -!!! tip +!!! !!! tip 您可以在 Twelve-Factor App: Config 中阅读更多相关信息。 ### 类型和验证 @@ -129,7 +129,35 @@ Hello World from Python 幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即Pydantic: Settings management。 -### 创建 `Settings` 对象 +### Install `pydantic-settings` + +!!! tip + 要使其工作,您需要执行 `pip install python-dotenv`。 + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +It also comes included when you install the `all` extras with: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +!!! info + In Pydantic v1 it came included with the main package. Now it is distributed as this independent package so that you can choose to install it or not if you don't need that functionality. + +### 使用 `settings` 从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。 @@ -137,18 +165,33 @@ Hello World from Python 您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 -```Python hl_lines="2 5-8 11" -{!../../../docs_src/settings/tutorial001.py!} -``` +=== "Pydantic v2" -!!! tip + ```Python hl_lines="2 5-8 11" + !!! tip + `os.getenv()` 的第二个参数是要返回的默认值。 + ``` + +=== "Pydantic v1" + + !!! info + In Pydantic v1 you would import `BaseSettings` directly from `pydantic` instead of from `pydantic_settings`. + + ```Python hl_lines="2 5-8 11" + !!! tip + 我们稍后会讨论 @lru_cache()。 + ``` +。 + + +!!! !!! tip 如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。 然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。 -然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 +然后,它将转换和验证数据。 因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 -### 使用 `settings` +### 创建 `Settings` 对象 然后,您可以在应用程序中使用新的 `settings` 对象: @@ -158,7 +201,7 @@ Hello World from Python ### 运行服务器 -接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示: +接下来,您将运行服务器,并将配置作为环境变量传递。 例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示:
@@ -170,7 +213,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app
-!!! tip +!!! !!! tip 要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。 然后,`admin_email` 设置将为 `"deadpool@example.com"`。 @@ -183,7 +226,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app 您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。 -例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: +根据前面的示例,您的 `config.py` 文件可能如下所示: ```Python {!../../../docs_src/settings/app01/config.py!} @@ -194,8 +237,9 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app ```Python hl_lines="3 11-13" {!../../../docs_src/settings/app01/main.py!} ``` + !!! tip - 您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 + You would also need a file `__init__.py` as you saw on [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}. ## 在依赖项中使用设置 @@ -205,7 +249,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app ### 配置文件 -根据前面的示例,您的 `config.py` 文件可能如下所示: +例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: ```Python hl_lines="10" {!../../../docs_src/settings/app02/config.py!} @@ -231,15 +275,14 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app === "Python 3.6+ 非注解版本" - !!! tip + !!! !!! tip 如果可能,请尽量使用 `Annotated` 版本。 ```Python hl_lines="5 11-12" {!> ../../../docs_src/settings/app02/main.py!} ``` -!!! tip - 我们稍后会讨论 `@lru_cache()`。 +!!! 但是,由于我们在顶部使用了 `@lru_cache()` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 目前,您可以将 `get_settings()` 视为普通函数。 @@ -259,7 +302,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app === "Python 3.6+ 非注解版本" - !!! tip + !!! !!! tip 如果可能,请尽量使用 `Annotated` 版本。 ```Python hl_lines="16 18-20" @@ -284,17 +327,17 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app 这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。 -!!! tip +!!! !!! tip 以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。 但是,dotenv 文件实际上不一定要具有确切的文件名。 -Pydantic 支持使用外部库从这些类型的文件中读取。您可以在Pydantic 设置: Dotenv (.env) 支持中阅读更多相关信息。 +Pydantic 支持使用外部库从这些类型的文件中读取。 您可以在Pydantic 设置: Dotenv (.env) 支持中阅读更多相关信息。 !!! tip - 要使其工作,您需要执行 `pip install python-dotenv`。 + For this to work, you need to `pip install python-dotenv`. -### `.env` 文件 +### 从 `.env` 文件中读取设置 您可以使用以下内容创建一个名为 `.env` 的文件: @@ -303,18 +346,37 @@ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" ``` -### 从 `.env` 文件中读取设置 +### `.env` 文件 然后,您可以使用以下方式更新您的 `config.py`: -```Python hl_lines="9-10" -{!../../../docs_src/settings/app03/config.py!} -``` +=== "Pydantic v2" -在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 + ```Python hl_lines="9" + {!../../../docs_src/settings/app03/config.py!} + ``` -!!! tip - `Config` 类仅用于 Pydantic 配置。您可以在Pydantic Model Config中阅读更多相关信息。 + + !!! tip + The `model_config` attribute is used just for Pydantic configuration. You can read more at Pydantic Model Config. + +=== "Pydantic v1" + + ```Python hl_lines="9-10" + !!! tip + 您还需要一个名为 __init__.py 的文件,就像您在Bigger Applications - Multiple Files{.internal-link target=_blank}中看到的那样。 + ``` + 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 + + + !!! !!! tip + Config 类仅用于 Pydantic 配置。 您可以在Pydantic Model Config中阅读更多相关信息。 + + +!!! info + In Pydantic version 1 the configuration was done in an internal class `Config`, in Pydantic version 2 it's done in an attribute `model_config`. This attribute takes a `dict`, and to get autocompletion and inline errors you can import and use `SettingsConfigDict` to define that `dict`. + +在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 ### 使用 `lru_cache` 仅创建一次 `Settings` @@ -337,7 +399,7 @@ def get_settings(): 我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️ -但是,由于我们在顶部使用了 `@lru_cache()` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️ +But as we are using the `@lru_cache()` decorator on top, the `Settings` object will be created only once, the first time it's called. ✔️ === "Python 3.9+" @@ -353,7 +415,7 @@ def get_settings(): === "Python 3.6+ 非注解版本" - !!! tip + !!! !!! tip 如果可能,请尽量使用 `Annotated` 版本。 ```Python hl_lines="1 10" @@ -366,9 +428,10 @@ def get_settings(): `@lru_cache()` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。 -因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。 +因此,下面的函数将对每个参数组合执行一次。 然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。 例如,如果您有一个函数: + ```Python @lru_cache() def say_hi(name: str, salutation: str = "Ms."): @@ -416,15 +479,28 @@ participant execute as Execute function code ->> function: say_hi(name="Camila") function ->> code: 返回存储的结果 end + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end ``` 对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。 -这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。 +这样,它的行为几乎就像是一个全局变量。 但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。 `@lru_cache()` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在Python 文档中了解有关 `@lru_cache()` 的更多信息。 -## 小结 +## Recap 您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。 diff --git a/docs/zh/docs/advanced/sql-databases-peewee.md b/docs/zh/docs/advanced/sql-databases-peewee.md new file mode 100644 index 0000000000000..4243e96e3e948 --- /dev/null +++ b/docs/zh/docs/advanced/sql-databases-peewee.md @@ -0,0 +1,536 @@ +# SQL (Relational) Databases with Peewee + +!!! warning + If you are just starting, the tutorial [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} that uses SQLAlchemy should be enough. + + Feel free to skip this. + + Peewee is not recommended with FastAPI as it doesn't play well with anything async Python. There are several better alternatives. + +!!! info + These docs assume Pydantic v1. + + Because Pewee doesn't play well with anything async and there are better alternatives, I won't update these docs for Pydantic v2, they are kept for now only for historical purposes. + +If you are starting a project from scratch, you are probably better off with SQLAlchemy ORM ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}), or any other async ORM. + +If you already have a code base that uses Peewee ORM, you can check here how to use it with **FastAPI**. + +!!! warning "Python 3.7+ required" + You will need Python 3.7 or above to safely use Peewee with FastAPI. + +## Peewee for async + +Peewee was not designed for async frameworks, or with them in mind. + +Peewee has some heavy assumptions about its defaults and about how it should be used. + +If you are developing an application with an older non-async framework, and can work with all its defaults, **it can be a great tool**. + +But if you need to change some of the defaults, support more than one predefined database, work with an async framework (like FastAPI), etc, you will need to add quite some complex extra code to override those defaults. + +Nevertheless, it's possible to do it, and here you'll see exactly what code you have to add to be able to use Peewee with FastAPI. + +!!! note "Technical Details" + You can read more about Peewee's stand about async in Python in the docs, an issue, a PR. + +## The same app + +We are going to create the same application as in the SQLAlchemy tutorial ([SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank}). + +Most of the code is actually the same. + +So, we are going to focus only on the differences. + +## File structure + +Let's say you have a directory named `my_super_project` that contains a sub-directory called `sql_app` with a structure like this: + +``` +. +└── sql_app + ├── __init__.py + ├── crud.py + ├── database.py + ├── main.py + └── schemas.py +``` + +This is almost the same structure as we had for the SQLAlchemy tutorial. + +Now let's see what each file/module does. + +## Create the Peewee parts + +Let's refer to the file `sql_app/database.py`. + +### The standard Peewee code + +Let's first check all the normal Peewee code, create a Peewee database: + +```Python hl_lines="3 5 22" +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +!!! tip + Have in mind that if you wanted to use a different database, like PostgreSQL, you couldn't just change the string. You would need to use a different Peewee database class. + +#### Note + +The argument: + +```Python +check_same_thread=False +``` + +is equivalent to the one in the SQLAlchemy tutorial: + +```Python +connect_args={"check_same_thread": False} +``` + +...it is needed only for `SQLite`. + +!!! info "Technical Details" + + Exactly the same technical details as in [SQL (Relational) Databases](../tutorial/sql-databases.md#note){.internal-link target=_blank} apply. + +### Make Peewee async-compatible `PeeweeConnectionState` + +The main issue with Peewee and FastAPI is that Peewee relies heavily on Python's `threading.local`, and it doesn't have a direct way to override it or let you handle connections/sessions directly (as is done in the SQLAlchemy tutorial). + +And `threading.local` is not compatible with the new async features of modern Python. + +!!! note "Technical Details" + `threading.local` is used to have a "magic" variable that has a different value for each thread. + + This was useful in older frameworks designed to have one single thread per request, no more, no less. + + Using this, each request would have its own database connection/session, which is the actual final goal. + + But FastAPI, using the new async features, could handle more than one request on the same thread. And at the same time, for a single request, it could run multiple things in different threads (in a threadpool), depending on if you use `async def` or normal `def`. This is what gives all the performance improvements to FastAPI. + +But Python 3.7 and above provide a more advanced alternative to `threading.local`, that can also be used in the places where `threading.local` would be used, but is compatible with the new async features. + +We are going to use that. It's called `contextvars`. + +We are going to override the internal parts of Peewee that use `threading.local` and replace them with `contextvars`, with the corresponding updates. + +This might seem a bit complex (and it actually is), you don't really need to completely understand how it works to use it. + +We will create a `PeeweeConnectionState`: + +```Python hl_lines="10-19" +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +This class inherits from a special internal class used by Peewee. + +It has all the logic to make Peewee use `contextvars` instead of `threading.local`. + +`contextvars` works a bit differently than `threading.local`. But the rest of Peewee's internal code assumes that this class works with `threading.local`. + +So, we need to do some extra tricks to make it work as if it was just using `threading.local`. The `__init__`, `__setattr__`, and `__getattr__` implement all the required tricks for this to be used by Peewee without knowing that it is now compatible with FastAPI. + +!!! tip + This will just make Peewee behave correctly when used with FastAPI. Not randomly opening or closing connections that are being used, creating errors, etc. + + But it doesn't give Peewee async super-powers. You should still use normal `def` functions and not `async def`. + +### Use the custom `PeeweeConnectionState` class + +Now, overwrite the `._state` internal attribute in the Peewee database `db` object using the new `PeeweeConnectionState`: + +```Python hl_lines="24" +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +!!! tip + Make sure you overwrite `db._state` *after* creating `db`. + +!!! tip + You would do the same for any other Peewee database, including `PostgresqlDatabase`, `MySQLDatabase`, etc. + +## Create the database models + +Let's now see the file `sql_app/models.py`. + +### Create Peewee models for our data + +Now create the Peewee models (classes) for `User` and `Item`. + +This is the same you would do if you followed the Peewee tutorial and updated the models to have the same data as in the SQLAlchemy tutorial. + +!!! tip + Peewee also uses the term "**model**" to refer to these classes and instances that interact with the database. + + But Pydantic also uses the term "**model**" to refer to something different, the data validation, conversion, and documentation classes and instances. + +Import `db` from `database` (the file `database.py` from above) and use it here. + +```Python hl_lines="3 6-12 15-21" +{!../../../docs_src/sql_databases_peewee/sql_app/models.py!} +``` + +!!! tip + Peewee creates several magic attributes. + + It will automatically add an `id` attribute as an integer to be the primary key. + + It will chose the name of the tables based on the class names. + + For the `Item`, it will create an attribute `owner_id` with the integer ID of the `User`. But we don't declare it anywhere. + +## Create the Pydantic models + +Now let's check the file `sql_app/schemas.py`. + +!!! tip + To avoid confusion between the Peewee *models* and the Pydantic *models*, we will have the file `models.py` with the Peewee models, and the file `schemas.py` with the Pydantic models. + + These Pydantic models define more or less a "schema" (a valid data shape). + + So this will help us avoiding confusion while using both. + +### Create the Pydantic *models* / schemas + +Create all the same Pydantic models as in the SQLAlchemy tutorial: + +```Python hl_lines="16-18 21-22 25-30 34-35 38-39 42-48" +{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} +``` + +!!! tip + Here we are creating the models with an `id`. + + We didn't explicitly specify an `id` attribute in the Peewee models, but Peewee adds one automatically. + + We are also adding the magic `owner_id` attribute to `Item`. + +### Create a `PeeweeGetterDict` for the Pydantic *models* / schemas + +When you access a relationship in a Peewee object, like in `some_user.items`, Peewee doesn't provide a `list` of `Item`. + +It provides a special custom object of class `ModelSelect`. + +It's possible to create a `list` of its items with `list(some_user.items)`. + +But the object itself is not a `list`. And it's also not an actual Python generator. Because of this, Pydantic doesn't know by default how to convert it to a `list` of Pydantic *models* / schemas. + +But recent versions of Pydantic allow providing a custom class that inherits from `pydantic.utils.GetterDict`, to provide the functionality used when using the `orm_mode = True` to retrieve the values for ORM model attributes. + +We are going to create a custom `PeeweeGetterDict` class and use it in all the same Pydantic *models* / schemas that use `orm_mode`: + +```Python hl_lines="3 8-13 31 49" +{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} +``` + +Here we are checking if the attribute that is being accessed (e.g. `.items` in `some_user.items`) is an instance of `peewee.ModelSelect`. + +And if that's the case, just return a `list` with it. + +And then we use it in the Pydantic *models* / schemas that use `orm_mode = True`, with the configuration variable `getter_dict = PeeweeGetterDict`. + +!!! tip + We only need to create one `PeeweeGetterDict` class, and we can use it in all the Pydantic *models* / schemas. + +## CRUD utils + +Now let's see the file `sql_app/crud.py`. + +### Create all the CRUD utils + +Create all the same CRUD utils as in the SQLAlchemy tutorial, all the code is very similar: + +```Python hl_lines="1 4-5 8-9 12-13 16-20 23-24 27-30" +{!../../../docs_src/sql_databases_peewee/sql_app/crud.py!} +``` + +There are some differences with the code for the SQLAlchemy tutorial. + +We don't pass a `db` attribute around. Instead we use the models directly. This is because the `db` object is a global object, that includes all the connection logic. That's why we had to do all the `contextvars` updates above. + +Aso, when returning several objects, like in `get_users`, we directly call `list`, like in: + +```Python +list(models.User.select()) +``` + +This is for the same reason that we had to create a custom `PeeweeGetterDict`. But by returning something that is already a `list` instead of the `peewee.ModelSelect` the `response_model` in the *path operation* with `List[models.User]` (that we'll see later) will work correctly. + +## Main **FastAPI** app + +And now in the file `sql_app/main.py` let's integrate and use all the other parts we created before. + +### Create the database tables + +In a very simplistic way create the database tables: + +```Python hl_lines="9-11" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +### Create a dependency + +Create a dependency that will connect the database right at the beginning of a request and disconnect it at the end: + +```Python hl_lines="23-29" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +Here we have an empty `yield` because we are actually not using the database object directly. + +It is connecting to the database and storing the connection data in an internal variable that is independent for each request (using the `contextvars` tricks from above). + +Because the database connection is potentially I/O blocking, this dependency is created with a normal `def` function. + +And then, in each *path operation function* that needs to access the database we add it as a dependency. + +But we are not using the value given by this dependency (it actually doesn't give any value, as it has an empty `yield`). So, we don't add it to the *path operation function* but to the *path operation decorator* in the `dependencies` parameter: + +```Python hl_lines="32 40 47 59 65 72" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +### Context variable sub-dependency + +For all the `contextvars` parts to work, we need to make sure we have an independent value in the `ContextVar` for each request that uses the database, and that value will be used as the database state (connection, transactions, etc) for the whole request. + +For that, we need to create another `async` dependency `reset_db_state()` that is used as a sub-dependency in `get_db()`. It will set the value for the context variable (with just a default `dict`) that will be used as the database state for the whole request. And then the dependency `get_db()` will store in it the database state (connection, transactions, etc). + +```Python hl_lines="18-20" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +For the **next request**, as we will reset that context variable again in the `async` dependency `reset_db_state()` and then create a new connection in the `get_db()` dependency, that new request will have its own database state (connection, transactions, etc). + +!!! tip + As FastAPI is an async framework, one request could start being processed, and before finishing, another request could be received and start processing as well, and it all could be processed in the same thread. + + But context variables are aware of these async features, so, a Peewee database state set in the `async` dependency `reset_db_state()` will keep its own data throughout the entire request. + + And at the same time, the other concurrent request will have its own database state that will be independent for the whole request. + +#### Peewee Proxy + +If you are using a Peewee Proxy, the actual database is at `db.obj`. + +So, you would reset it with: + +```Python hl_lines="3-4" +async def reset_db_state(): + database.db.obj._state._state.set(db_state_default.copy()) + database.db.obj._state.reset() +``` + +### Create your **FastAPI** *path operations* + +Now, finally, here's the standard **FastAPI** *path operations* code. + +```Python hl_lines="32-37 40-43 46-53 56-62 65-68 71-79" +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +### About `def` vs `async def` + +The same as with SQLAlchemy, we are not doing something like: + +```Python +user = await models.User.select().first() +``` + +...but instead we are using: + +```Python +user = models.User.select().first() +``` + +So, again, we should declare the *path operation functions* and the dependency without `async def`, just with a normal `def`, as: + +```Python hl_lines="2" +# Something goes here +def read_users(skip: int = 0, limit: int = 100): + # Something goes here +``` + +## Testing Peewee with async + +This example includes an extra *path operation* that simulates a long processing request with `time.sleep(sleep_time)`. + +It will have the database connection open at the beginning and will just wait some seconds before replying back. And each new request will wait one second less. + +This will easily let you test that your app with Peewee and FastAPI is behaving correctly with all the stuff about threads. + +If you want to check how Peewee would break your app if used without modification, go the the `sql_app/database.py` file and comment the line: + +```Python +# db._state = PeeweeConnectionState() +``` + +And in the file `sql_app/main.py` file, comment the body of the `async` dependency `reset_db_state()` and replace it with a `pass`: + +```Python +async def reset_db_state(): +# database.db._state._state.set(db_state_default.copy()) +# database.db._state.reset() + pass +``` + +Then run your app with Uvicorn: + +
+ +```console +$ uvicorn sql_app.main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Open your browser at http://127.0.0.1:8000/docs and create a couple of users. + +Then open 10 tabs at http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get at the same time. + +Go to the *path operation* "Get `/slowusers/`" in all of the tabs. Use the "Try it out" button and execute the request in each tab, one right after the other. + +The tabs will wait for a bit and then some of them will show `Internal Server Error`. + +### What happens + +The first tab will make your app create a connection to the database and wait for some seconds before replying back and closing the database connection. + +Then, for the request in the next tab, your app will wait for one second less, and so on. + +This means that it will end up finishing some of the last tabs' requests earlier than some of the previous ones. + +Then one the last requests that wait less seconds will try to open a database connection, but as one of those previous requests for the other tabs will probably be handled in the same thread as the first one, it will have the same database connection that is already open, and Peewee will throw an error and you will see it in the terminal, and the response will have an `Internal Server Error`. + +This will probably happen for more than one of those tabs. + +If you had multiple clients talking to your app exactly at the same time, this is what could happen. + +And as your app starts to handle more and more clients at the same time, the waiting time in a single request needs to be shorter and shorter to trigger the error. + +### Fix Peewee with FastAPI + +Now go back to the file `sql_app/database.py`, and uncomment the line: + +```Python +db._state = PeeweeConnectionState() +``` + +And in the file `sql_app/main.py` file, uncomment the body of the `async` dependency `reset_db_state()`: + +```Python +async def reset_db_state(): + database.db._state._state.set(db_state_default.copy()) + database.db._state.reset() +``` + +Terminate your running app and start it again. + +Repeat the same process with the 10 tabs. This time all of them will wait and you will get all the results without errors. + +...You fixed it! + +## Review all the files + + Remember you should have a directory named `my_super_project` (or however you want) that contains a sub-directory called `sql_app`. + +`sql_app` should have the following files: + +* `sql_app/__init__.py`: is an empty file. + +* `sql_app/database.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/database.py!} +``` + +* `sql_app/models.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/models.py!} +``` + +* `sql_app/schemas.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/schemas.py!} +``` + +* `sql_app/crud.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/crud.py!} +``` + +* `sql_app/main.py`: + +```Python +{!../../../docs_src/sql_databases_peewee/sql_app/main.py!} +``` + +## Technical Details + +!!! warning + These are very technical details that you probably don't need. + +### The problem + +Peewee uses `threading.local` by default to store it's database "state" data (connection, transactions, etc). + +`threading.local` creates a value exclusive to the current thread, but an async framework would run all the code (e.g. for each request) in the same thread, and possibly not in order. + +On top of that, an async framework could run some sync code in a threadpool (using `asyncio.run_in_executor`), but belonging to the same request. + +This means that, with Peewee's current implementation, multiple tasks could be using the same `threading.local` variable and end up sharing the same connection and data (that they shouldn't), and at the same time, if they execute sync I/O-blocking code in a threadpool (as with normal `def` functions in FastAPI, in *path operations* and dependencies), that code won't have access to the database state variables, even while it's part of the same request and it should be able to get access to the same database state. + +### Context variables + +Python 3.7 has `contextvars` that can create a local variable very similar to `threading.local`, but also supporting these async features. + +There are several things to have in mind. + +The `ContextVar` has to be created at the top of the module, like: + +```Python +some_var = ContextVar("some_var", default="default value") +``` + +To set a value used in the current "context" (e.g. for the current request) use: + +```Python +some_var.set("new value") +``` + +To get a value anywhere inside of the context (e.g. in any part handling the current request) use: + +```Python +some_var.get() +``` + +### Set context variables in the `async` dependency `reset_db_state()` + +If some part of the async code sets the value with `some_var.set("updated in function")` (e.g. like the `async` dependency), the rest of the code in it and the code that goes after (including code inside of `async` functions called with `await`) will see that new value. + +So, in our case, if we set the Peewee state variable (with a default `dict`) in the `async` dependency, all the rest of the internal code in our app will see this value and will be able to reuse it for the whole request. + +And the context variable would be set again for the next request, even if they are concurrent. + +### Set database state in the dependency `get_db()` + +As `get_db()` is a normal `def` function, **FastAPI** will make it run in a threadpool, with a *copy* of the "context", holding the same value for the context variable (the `dict` with the reset database state). Then it can add database state to that `dict`, like the connection, etc. + +But if the value of the context variable (the default `dict`) was set in that normal `def` function, it would create a new value that would stay only in that thread of the threadpool, and the rest of the code (like the *path operation functions*) wouldn't have access to it. In `get_db()` we can only set values in the `dict`, but not the entire `dict` itself. + +So, we need to have the `async` dependency `reset_db_state()` to set the `dict` in the context variable. That way, all the code has access to the same `dict` for the database state for a single request. + +### Connect and disconnect in the dependency `get_db()` + +Then the next question would be, why not just connect and disconnect the database in the `async` dependency itself, instead of in `get_db()`? + +The `async` dependency has to be `async` for the context variable to be preserved for the rest of the request, but creating and closing the database connection is potentially blocking, so it could degrade performance if it was there. + +So we also need the normal `def` dependency `get_db()`. diff --git a/docs/zh/docs/advanced/sub-applications.md b/docs/zh/docs/advanced/sub-applications.md new file mode 100644 index 0000000000000..0d04db29dcf52 --- /dev/null +++ b/docs/zh/docs/advanced/sub-applications.md @@ -0,0 +1,73 @@ +# Sub Applications - Mounts + +If you need to have two independent FastAPI applications, with their own independent OpenAPI and their own docs UIs, you can have a main app and "mount" one (or more) sub-application(s). + +## Mounting a **FastAPI** application + +"Mounting" means adding a completely "independent" application in a specific path, that then takes care of handling everything under that path, with the _path operations_ declared in that sub-application. + +### Top-level application + +First, create the main, top-level, **FastAPI** application, and its *path operations*: + +```Python hl_lines="3 6-8" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### Sub-application + +Then, create your sub-application, and its *path operations*. + +This sub-application is just another standard FastAPI application, but this is the one that will be "mounted": + +```Python hl_lines="11 14-16" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### Mount the sub-application + +In your top-level application, `app`, mount the sub-application, `subapi`. + +In this case, it will be mounted at the path `/subapi`: + +```Python hl_lines="11 19" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### Check the automatic API docs + +Now, run `uvicorn` with the main app, if your file is `main.py`, it would be: + +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +And open the docs at http://127.0.0.1:8000/docs. + +You will see the automatic API docs for the main app, including only its own _path operations_: + + + +And then, open the docs for the sub-application, at http://127.0.0.1:8000/subapi/docs. + +You will see the automatic API docs for the sub-application, including only its own _path operations_, all under the correct sub-path prefix `/subapi`: + + + +If you try interacting with any of the two user interfaces, they will work correctly, because the browser will be able to talk to each specific app or sub-app. + +### Technical Details: `root_path` + +When you mount a sub-application as described above, FastAPI will take care of communicating the mount path for the sub-application using a mechanism from the ASGI specification called a `root_path`. + +That way, the sub-application will know to use that path prefix for the docs UI. + +And the sub-application could also have its own mounted sub-applications and everything would work correctly, because FastAPI handles all these `root_path`s automatically. + +You will learn more about the `root_path` and how to use it explicitly in the section about [Behind a Proxy](./behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/zh/docs/advanced/templates.md b/docs/zh/docs/advanced/templates.md new file mode 100644 index 0000000000000..38618aeeb09cd --- /dev/null +++ b/docs/zh/docs/advanced/templates.md @@ -0,0 +1,77 @@ +# Templates + +You can use any template engine you want with **FastAPI**. + +A common choice is Jinja2, the same one used by Flask and other tools. + +There are utilities to configure it easily that you can use directly in your **FastAPI** application (provided by Starlette). + +## Install dependencies + +Install `jinja2`: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## Using `Jinja2Templates` + +* Import `Jinja2Templates`. +* Create a `templates` object that you can re-use later. +* Declare a `Request` parameter in the *path operation* that will return a template. +* Use the `templates` you created to render and return a `TemplateResponse`, passing the `request` as one of the key-value pairs in the Jinja2 "context". + +```Python hl_lines="4 11 15-16" +{!../../../docs_src/templates/tutorial001.py!} +``` + +!!! note + Notice that you have to pass the `request` as part of the key-value pairs in the context for Jinja2. So, you also have to declare it in your *path operation*. + +!!! tip + By declaring `response_class=HTMLResponse` the docs UI will be able to know that the response will be HTML. + +!!! note "Technical Details" + You could also use `from starlette.templating import Jinja2Templates`. + + **FastAPI** provides the same `starlette.templating` as `fastapi.templating` just as a convenience for you, the developer. But most of the available responses come directly from Starlette. The same with `Request` and `StaticFiles`. + +## Writing templates + +Then you can write a template at `templates/item.html` with: + +```jinja hl_lines="7" +{!../../../docs_src/templates/templates/item.html!} +``` + +It will show the `id` taken from the "context" `dict` you passed: + +```Python +{"request": request, "id": id} +``` + +## Templates and static files + +And you can also use `url_for()` inside of the template, and use it, for example, with the `StaticFiles` you mounted. + +```jinja hl_lines="4" +{!../../../docs_src/templates/templates/item.html!} +``` + +In this example, it would link to a CSS file at `static/styles.css` with: + +```CSS hl_lines="4" +{!../../../docs_src/templates/static/styles.css!} +``` + +And because you are using `StaticFiles`, that CSS file would be served automatically by your **FastAPI** application at the URL `/static/styles.css`. + +## More details + +For more details, including how to test templates, check Starlette's docs on templates. diff --git a/docs/zh/docs/advanced/testing-database.md b/docs/zh/docs/advanced/testing-database.md new file mode 100644 index 0000000000000..3d66c7c9c7aa8 --- /dev/null +++ b/docs/zh/docs/advanced/testing-database.md @@ -0,0 +1,102 @@ +# Testing a Database + +!!! info + These docs are about to be updated. 🎉 + + The current version assumes Pydantic v1, and SQLAlchemy versions less than 2.0. + + The new docs will include Pydantic v2 and will use SQLModel (which is also based on SQLAlchemy) once it is updated to use Pydantic v2 as well. + +You can use the same dependency overrides from [Testing Dependencies with Overrides](testing-dependencies.md){.internal-link target=_blank} to alter a database for testing. + +You could want to set up a different database for testing, rollback the data after the tests, pre-fill it with some testing data, etc. + +The main idea is exactly the same you saw in that previous chapter. + +## Add tests for the SQL app + +Let's update the example from [SQL (Relational) Databases](../tutorial/sql-databases.md){.internal-link target=_blank} to use a testing database. + +All the app code is the same, you can go back to that chapter check how it was. + +The only changes here are in the new testing file. + +Your normal dependency `get_db()` would return a database session. + +In the test, you could use a dependency override to return your *custom* database session instead of the one that would be used normally. + +In this example we'll create a temporary database only for the tests. + +## File structure + +We create a new file at `sql_app/tests/test_sql_app.py`. + +So the new file structure looks like: + +``` hl_lines="9-11" +. +└── sql_app + ├── __init__.py + ├── crud.py + ├── database.py + ├── main.py + ├── models.py + ├── schemas.py + └── tests + ├── __init__.py + └── test_sql_app.py +``` + +## Create the new database session + +First, we create a new database session with the new database. + +We'll use an in-memory database that persists during the tests instead of the local file `sql_app.db`. + +But the rest of the session code is more or less the same, we just copy it. + +```Python hl_lines="8-13" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +!!! tip + You could reduce duplication in that code by putting it in a function and using it from both `database.py` and `tests/test_sql_app.py`. + + For simplicity and to focus on the specific testing code, we are just copying it. + +## Create the database + +Because now we are going to use a new database in a new file, we need to make sure we create the database with: + +```Python +Base.metadata.create_all(bind=engine) +``` + +That is normally called in `main.py`, but the line in `main.py` uses the database file `sql_app.db`, and we need to make sure we create `test.db` for the tests. + +So we add that line here, with the new file. + +```Python hl_lines="16" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +## Dependency override + +Now we create the dependency override and add it to the overrides for our app. + +```Python hl_lines="19-24 27" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +!!! tip + The code for `override_get_db()` is almost exactly the same as for `get_db()`, but in `override_get_db()` we use the `TestingSessionLocal` for the testing database instead. + +## Test the app + +Then we can just test the app as normally. + +```Python hl_lines="32-47" +{!../../../docs_src/sql_databases/sql_app/tests/test_sql_app.py!} +``` + +And all the modifications we made in the database during the tests will be in the `test.db` database instead of the main `sql_app.db`. diff --git a/docs/zh/docs/advanced/testing-dependencies.md b/docs/zh/docs/advanced/testing-dependencies.md new file mode 100644 index 0000000000000..acc6283db3609 --- /dev/null +++ b/docs/zh/docs/advanced/testing-dependencies.md @@ -0,0 +1,81 @@ +# Testing Dependencies with Overrides + +## Overriding dependencies during testing + +There are some scenarios where you might want to override a dependency during testing. + +You don't want the original dependency to run (nor any of the sub-dependencies it might have). + +Instead, you want to provide a different dependency that will be used only during tests (possibly only some specific tests), and will provide a value that can be used where the value of the original dependency was used. + +### Use cases: external service + +An example could be that you have an external authentication provider that you need to call. + +You send it a token and it returns an authenticated user. + +This provider might be charging you per request, and calling it might take some extra time than if you had a fixed mock user for tests. + +You probably want to test the external provider once, but not necessarily call it for every test that runs. + +In this case, you can override the dependency that calls that provider, and use a custom dependency that returns a mock user, only for your tests. + +### Use the `app.dependency_overrides` attribute + +For these cases, your **FastAPI** application has an attribute `app.dependency_overrides`, it is a simple `dict`. + +To override a dependency for testing, you put as a key the original dependency (a function), and as the value, your dependency override (another function). + +And then **FastAPI** will call that override instead of the original dependency. + +=== "Python 3.10+" + + ```Python hl_lines="26-27 30" + {!> ../../../docs_src/dependency_testing/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="28-29 32" + {!> ../../../docs_src/dependency_testing/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="29-30 33" + {!> ../../../docs_src/dependency_testing/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="24-25 28" + {!> ../../../docs_src/dependency_testing/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="28-29 32" + {!> ../../../docs_src/dependency_testing/tutorial001.py!} + ``` + +!!! tip + You can set a dependency override for a dependency used anywhere in your **FastAPI** application. + + The original dependency could be used in a *path operation function*, a *path operation decorator* (when you don't use the return value), a `.include_router()` call, etc. + + FastAPI will still be able to override it. + +Then you can reset your overrides (remove them) by setting `app.dependency_overrides` to be an empty `dict`: + +```Python +app.dependency_overrides = {} +``` + +!!! tip + If you want to override a dependency only during some tests, you can set the override at the beginning of the test (inside the test function) and reset it at the end (at the end of the test function). diff --git a/docs/zh/docs/advanced/testing-events.md b/docs/zh/docs/advanced/testing-events.md new file mode 100644 index 0000000000000..b24a2ccfe8546 --- /dev/null +++ b/docs/zh/docs/advanced/testing-events.md @@ -0,0 +1,7 @@ +# Testing Events: startup - shutdown + +When you need your event handlers (`startup` and `shutdown`) to run in your tests, you can use the `TestClient` with a `with` statement: + +```Python hl_lines="9-12 20-24" +{!../../../docs_src/app_testing/tutorial003.py!} +``` diff --git a/docs/zh/docs/advanced/testing-websockets.md b/docs/zh/docs/advanced/testing-websockets.md new file mode 100644 index 0000000000000..4101e5a16b70a --- /dev/null +++ b/docs/zh/docs/advanced/testing-websockets.md @@ -0,0 +1,12 @@ +# Testing WebSockets + +You can use the same `TestClient` to test WebSockets. + +For this, you use the `TestClient` in a `with` statement, connecting to the WebSocket: + +```Python hl_lines="27-31" +{!../../../docs_src/app_testing/tutorial002.py!} +``` + +!!! note + For more details, check Starlette's documentation for testing WebSockets. diff --git a/docs/zh/docs/advanced/using-request-directly.md b/docs/zh/docs/advanced/using-request-directly.md new file mode 100644 index 0000000000000..968c511aa2870 --- /dev/null +++ b/docs/zh/docs/advanced/using-request-directly.md @@ -0,0 +1,52 @@ +# Using the Request Directly + +Up to now, you have been declaring the parts of the request that you need with their types. + +Taking data from: + +* The path as parameters. +* Headers. +* Cookies. +* etc. + +And by doing so, **FastAPI** is validating that data, converting it and generating documentation for your API automatically. + +But there are situations where you might need to access the `Request` object directly. + +## Details about the `Request` object + +As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's `Request` object directly when you need to. + +It would also mean that if you get data from the `Request` object directly (for example, read the body) it won't be validated, converted or documented (with OpenAPI, for the automatic API user interface) by FastAPI. + +Although any other parameter declared normally (for example, the body with a Pydantic model) would still be validated, converted, annotated, etc. + +But there are specific cases where it's useful to get the `Request` object. + +## Use the `Request` object directly + +Let's imagine you want to get the client's IP address/host inside of your *path operation function*. + +For that you need to access the request directly. + +```Python hl_lines="1 7-8" +{!../../../docs_src/using_request_directly/tutorial001.py!} +``` + +By declaring a *path operation function* parameter with the type being the `Request` **FastAPI** will know to pass the `Request` in that parameter. + +!!! tip + Note that in this case, we are declaring a path parameter beside the request parameter. + + So, the path parameter will be extracted, validated, converted to the specified type and annotated with OpenAPI. + + The same way, you can declare any other parameter as normally, and additionally, get the `Request` too. + +## `Request` documentation + +You can read more details about the `Request` object in the official Starlette documentation site. + +!!! note "Technical Details" + You could also use `from starlette.requests import Request`. + + **FastAPI** provides it directly just as a convenience for you, the developer. But it comes directly from Starlette. diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md index a723487fdfcb4..e9665e48659c6 100644 --- a/docs/zh/docs/advanced/websockets.md +++ b/docs/zh/docs/advanced/websockets.md @@ -6,11 +6,13 @@ 首先,您需要安装 `WebSockets`: -```console +
+ $ pip install websockets ----> 100% -``` +---> 100% + +
## WebSockets 客户端 @@ -46,10 +48,10 @@ $ pip install websockets {!../../../docs_src/websockets/tutorial001.py!} ``` -!!! note "技术细节" +!!! !!! note "技术细节" 您也可以使用 `from starlette.websockets import WebSocket`。 - **FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。但它直接来自 Starlette。 + **FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。 但它直接来自 Starlette。 ## 等待消息并发送消息 @@ -65,34 +67,34 @@ $ pip install websockets 如果您的文件名为 `main.py`,请使用以下命令运行应用程序: -```console +
+ $ uvicorn main:app --reload -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + +
在浏览器中打开 http://127.0.0.1:8000。 您将看到一个简单的页面,如下所示: - + 您可以在输入框中输入消息并发送: - + 您的 **FastAPI** 应用程序将回复: - + 您可以发送(和接收)多条消息: - + 所有这些消息都将使用同一个 WebSocket 连 -接。 - ## 使用 `Depends` 和其他依赖项 在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容: @@ -124,25 +126,25 @@ $ uvicorn main:app --reload {!> ../../../docs_src/websockets/tutorial002_an.py!} ``` -=== "Python 3.10+ 非带注解版本" +=== "Python 3.6+ 非带注解版本" - !!! tip + !!! !!! tip 如果可能,请尽量使用 `Annotated` 版本。 ```Python hl_lines="66-67 79" {!> ../../../docs_src/websockets/tutorial002_py310.py!} ``` -=== "Python 3.6+ 非带注解版本" +=== "Python 3.10+ 非带注解版本" - !!! tip + !!! !!! tip 如果可能,请尽量使用 `Annotated` 版本。 ```Python hl_lines="68-69 81" {!> ../../../docs_src/websockets/tutorial002.py!} ``` -!!! info +!!! !!! info 由于这是一个 WebSocket,抛出 `HTTPException` 并不是很合理,而是抛出 `WebSocketException`。 您可以使用规范中定义的有效代码。 @@ -151,11 +153,13 @@ $ uvicorn main:app --reload 如果您的文件名为 `main.py`,请使用以下命令运行应用程序: -```console +
+ $ uvicorn main:app --reload -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` +<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) + +
在浏览器中打开 http://127.0.0.1:8000。 @@ -164,12 +168,12 @@ $ uvicorn main:app --reload * "Item ID",用于路径。 * "Token",作为查询参数。 -!!! tip +!!! !!! tip 注意,查询参数 `token` 将由依赖项处理。 通过这样,您可以连接 WebSocket,然后发送和接收消息: - + ## 处理断开连接和多个客户端 @@ -190,7 +194,7 @@ $ uvicorn main:app --reload 尝试以下操作: * 使用多个浏览器选项卡打开应用程序。 -* 从这些选项卡中发送消息。 +* Write messages from them. * 然后关闭其中一个选项卡。 这将引发 `WebSocketDisconnect` 异常,并且所有其他客户端都会收到类似以下的消息: @@ -199,16 +203,16 @@ $ uvicorn main:app --reload Client #1596980209979 left the chat ``` -!!! tip +!!! !!! tip 上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。 但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。 - + 如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。 ## 更多信息 要了解更多选项,请查看 Starlette 的文档: -* [WebSocket 类](https://www.starlette.io/websockets/) +* WebSocket 类 * [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。 diff --git a/docs/zh/docs/advanced/wsgi.md b/docs/zh/docs/advanced/wsgi.md index ad71280fc6180..e39b8eb3d26c9 100644 --- a/docs/zh/docs/advanced/wsgi.md +++ b/docs/zh/docs/advanced/wsgi.md @@ -12,7 +12,7 @@ 之后将其挂载到某一个路径下。 -```Python hl_lines="2-3 22" +```Python hl_lines="2-3 23" {!../../../docs_src/wsgi/tutorial001.py!} ``` diff --git a/docs/zh/docs/alternatives.md b/docs/zh/docs/alternatives.md new file mode 100644 index 0000000000000..b9f184cccfe55 --- /dev/null +++ b/docs/zh/docs/alternatives.md @@ -0,0 +1,398 @@ +# Alternatives, Inspiration and Comparisons + +What inspired **FastAPI**, how it compares to other alternatives and what it learned from them. + +## Intro + +**FastAPI** wouldn't exist if not for the previous work of others. + +There have been many tools created before that have helped inspire its creation. + +I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins, and tools. + +But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints). + +## Previous tools + +### Django + +It's the most popular Python framework and is widely trusted. It is used to build systems like Instagram. + +It's relatively tightly coupled with relational databases (like MySQL or PostgreSQL), so, having a NoSQL database (like Couchbase, MongoDB, Cassandra, etc) as the main store engine is not very easy. + +It was created to generate the HTML in the backend, not to create APIs used by a modern frontend (like React, Vue.js and Angular) or by other systems (like IoT devices) communicating with it. + +### Django REST Framework + +Django REST framework was created to be a flexible toolkit for building Web APIs using Django underneath, to improve its API capabilities. + +It is used by many companies including Mozilla, Red Hat and Eventbrite. + +It was one of the first examples of **automatic API documentation**, and this was specifically one of the first ideas that inspired "the search for" **FastAPI**. + +!!! note + Django REST Framework was created by Tom Christie. The same creator of Starlette and Uvicorn, on which **FastAPI** is based. + + +!!! check "Inspired **FastAPI** to" Have an automatic API documentation web user interface. + +### Flask + +Flask is a "microframework", it doesn't include database integrations nor many of the things that come by default in Django. + +This simplicity and flexibility allow doing things like using NoSQL databases as the main data storage system. + +As it is very simple, it's relatively intuitive to learn, although the documentation gets somewhat technical at some points. + +It is also commonly used for other applications that don't necessarily need a database, user management, or any of the many features that come pre-built in Django. Although many of these features can be added with plug-ins. + +This decoupling of parts, and being a "microframework" that could be extended to cover exactly what is needed was a key feature that I wanted to keep. + +Given the simplicity of Flask, it seemed like a good match for building APIs. The next thing to find was a "Django REST Framework" for Flask. + +!!! check "Inspired **FastAPI** to" Be a micro-framework. Making it easy to mix and match the tools and parts needed. + + Have a simple and easy to use routing system. + + +### Requests + +**FastAPI** is not actually an alternative to **Requests**. Their scope is very different. + +It would actually be common to use Requests *inside* of a FastAPI application. + +But still, FastAPI got quite some inspiration from Requests. + +**Requests** is a library to *interact* with APIs (as a client), while **FastAPI** is a library to *build* APIs (as a server). + +They are, more or less, at opposite ends, complementing each other. + +Requests has a very simple and intuitive design, it's very easy to use, with sensible defaults. But at the same time, it's very powerful and customizable. + +That's why, as said in the official website: + +> Requests is one of the most downloaded Python packages of all time + +The way you use it is very simple. For example, to do a `GET` request, you would write: + +```Python +response = requests.get("http://example.com/some/url") +``` + +The FastAPI counterpart API *path operation* could look like: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +See the similarities in `requests.get(...)` and `@app.get(...)`. + +!!! check "Inspired **FastAPI** to" + * Have a simple and intuitive API. + * Use HTTP method names (operations) directly, in a straightforward and intuitive way. + * Have sensible defaults, but powerful customizations. + + +### Swagger / OpenAPI + +The main feature I wanted from Django REST Framework was the automatic API documentation. + +Then I found that there was a standard to document APIs, using JSON (or YAML, an extension of JSON) called Swagger. + +And there was a web user interface for Swagger APIs already created. So, being able to generate Swagger documentation for an API would allow using this web user interface automatically. + +At some point, Swagger was given to the Linux Foundation, to be renamed OpenAPI. + +That's why when talking about version 2.0 it's common to say "Swagger", and for version 3+ "OpenAPI". + +!!! check "Inspired **FastAPI** to" Adopt and use an open standard for API specifications, instead of a custom schema. + + And integrate standards-based user interface tools: + + * Swagger UI + * ReDoc + + These two were chosen for being fairly popular and stable, but doing a quick search, you could find dozens of additional alternative user interfaces for OpenAPI (that you can use with **FastAPI**). + +### Flask REST frameworks + +There are several Flask REST frameworks, but after investing the time and work into investigating them, I found that many are discontinued or abandoned, with several standing issues that made them unfit. + +### Marshmallow + +One of the main features needed by API systems is data "serialization" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc. + +Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data. + +Without a data validation system, you would have to do all the checks by hand, in code. + +These features are what Marshmallow was built to provide. It is a great library, and I have used it a lot before. + +But it was created before there existed Python type hints. So, to define every schema you need to use specific utils and classes provided by Marshmallow. + +!!! check "Inspired **FastAPI** to" Use code to define "schemas" that provide data types and validation, automatically. + +### Webargs + +Another big feature required by APIs is parsing data from incoming requests. + +Webargs is a tool that was made to provide that on top of several frameworks, including Flask. + +It uses Marshmallow underneath to do the data validation. And it was created by the same developers. + +It's a great tool and I have used it a lot too, before having **FastAPI**. + +!!! info + Webargs was created by the same Marshmallow developers. + +!!! check "Inspired **FastAPI** to" Have automatic validation of incoming request data. + +### APISpec + +Marshmallow and Webargs provide validation, parsing and serialization as plug-ins. + +But documentation is still missing. Then APISpec was created. + +It is a plug-in for many frameworks (and there's a plug-in for Starlette too). + +The way it works is that you write the definition of the schema using YAML format inside the docstring of each function handling a route. + +And it generates OpenAPI schemas. + +That's how it works in Flask, Starlette, Responder, etc. + +But then, we have again the problem of having a micro-syntax, inside of a Python string (a big YAML). + +The editor can't help much with that. And if we modify parameters or Marshmallow schemas and forget to also modify that YAML docstring, the generated schema would be obsolete. + +!!! info + APISpec was created by the same Marshmallow developers. + + +!!! check "Inspired **FastAPI** to" Support the open standard for APIs, OpenAPI. + +### Flask-apispec + +It's a Flask plug-in, that ties together Webargs, Marshmallow and APISpec. + +It uses the information from Webargs and Marshmallow to automatically generate OpenAPI schemas, using APISpec. + +It's a great tool, very under-rated. It should be way more popular than many Flask plug-ins out there. It might be due to its documentation being too concise and abstract. + +This solved having to write YAML (another syntax) inside of Python docstrings. + +This combination of Flask, Flask-apispec with Marshmallow and Webargs was my favorite backend stack until building **FastAPI**. + +Using it led to the creation of several Flask full-stack generators. These are the main stack I (and several external teams) have been using up to now: + +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb + +And these same full-stack generators were the base of the [**FastAPI** Project Generators](project-generation.md){.internal-link target=_blank}. + +!!! info + Flask-apispec was created by the same Marshmallow developers. + +!!! check "Inspired **FastAPI** to" Generate the OpenAPI schema automatically, from the same code that defines serialization and validation. + +### NestJS (and Angular) + +This isn't even Python, NestJS is a JavaScript (TypeScript) NodeJS framework inspired by Angular. + +It achieves something somewhat similar to what can be done with Flask-apispec. + +It has an integrated dependency injection system, inspired by Angular two. It requires pre-registering the "injectables" (like all the other dependency injection systems I know), so, it adds to the verbosity and code repetition. + +As the parameters are described with TypeScript types (similar to Python type hints), editor support is quite good. + +But as TypeScript data is not preserved after compilation to JavaScript, it cannot rely on the types to define validation, serialization and documentation at the same time. Due to this and some design decisions, to get validation, serialization and automatic schema generation, it's needed to add decorators in many places. So, it becomes quite verbose. + +It can't handle nested models very well. So, if the JSON body in the request is a JSON object that has inner fields that in turn are nested JSON objects, it cannot be properly documented and validated. + +!!! check "Inspired **FastAPI** to" Use Python types to have great editor support. + + Have a powerful dependency injection system. Find a way to minimize code repetition. + +### Sanic + +It was one of the first extremely fast Python frameworks based on `asyncio`. It was made to be very similar to Flask. + +!!! note "Technical Details" + It used `uvloop` instead of the default Python `asyncio` loop. That's what made it so fast. + + It clearly inspired Uvicorn and Starlette, that are currently faster than Sanic in open benchmarks. + +!!! check "Inspired **FastAPI** to" Find a way to have a crazy performance. + + That's why **FastAPI** is based on Starlette, as it is the fastest framework available (tested by third-party benchmarks). + +### Falcon + +Falcon is another high performance Python framework, it is designed to be minimal, and work as the foundation of other frameworks like Hug. + +It is designed to have functions that receive two parameters, one "request" and one "response". Then you "read" parts from the request, and "write" parts to the response. Because of this design, it is not possible to declare request parameters and bodies with standard Python type hints as function parameters. + +So, data validation, serialization, and documentation, have to be done in code, not automatically. Or they have to be implemented as a framework on top of Falcon, like Hug. This same distinction happens in other frameworks that are inspired by Falcon's design, of having one request object and one response object as parameters. + +!!! check "Inspired **FastAPI** to" Find ways to get great performance. + + Along with Hug (as Hug is based on Falcon) inspired **FastAPI** to declare a `response` parameter in functions. + + Although in FastAPI it's optional, and is used mainly to set headers, cookies, and alternative status codes. + +### Molten + +I discovered Molten in the first stages of building **FastAPI**. And it has quite similar ideas: + +* Based on Python type hints. +* Validation and documentation from these types. +* Dependency Injection system. + +It doesn't use a data validation, serialization and documentation third-party library like Pydantic, it has its own. So, these data type definitions would not be reusable as easily. + +It requires a little bit more verbose configurations. And as it is based on WSGI (instead of ASGI), it is not designed to take advantage of the high-performance provided by tools like Uvicorn, Starlette and Sanic. + +The dependency injection system requires pre-registration of the dependencies and the dependencies are solved based on the declared types. So, it's not possible to declare more than one "component" that provides a certain type. + +Routes are declared in a single place, using functions declared in other places (instead of using decorators that can be placed right on top of the function that handles the endpoint). This is closer to how Django does it than to how Flask (and Starlette) does it. It separates in the code things that are relatively tightly coupled. + +!!! check "Inspired **FastAPI** to" Define extra validations for data types using the "default" value of model attributes. This improves editor support, and it was not available in Pydantic before. + + This actually inspired updating parts of Pydantic, to support the same validation declaration style (all this functionality is now already available in Pydantic). + +### Hug + +Hug was one of the first frameworks to implement the declaration of API parameter types using Python type hints. This was a great idea that inspired other tools to do the same. + +It used custom types in its declarations instead of standard Python types, but it was still a huge step forward. + +It also was one of the first frameworks to generate a custom schema declaring the whole API in JSON. + +It was not based on a standard like OpenAPI and JSON Schema. So it wouldn't be straightforward to integrate it with other tools, like Swagger UI. But again, it was a very innovative idea. + +It has an interesting, uncommon feature: using the same framework, it's possible to create APIs and also CLIs. + +As it is based on the previous standard for synchronous Python web frameworks (WSGI), it can't handle Websockets and other things, although it still has high performance too. + +!!! info + Hug was created by Timothy Crosley, the same creator of `isort`, a great tool to automatically sort imports in Python files. + +!!! check "Ideas inspired in **FastAPI**" Hug inspired parts of APIStar, and was one of the tools I found most promising, alongside APIStar. + + Hug helped inspiring **FastAPI** to use Python type hints to declare parameters, and to generate a schema defining the API automatically. + + Hug inspired **FastAPI** to declare a `response` parameter in functions to set headers and cookies. + +### APIStar (<= 0.5) + +Right before deciding to build **FastAPI** I found **APIStar** server. It had almost everything I was looking for and had a great design. + +It was one of the first implementations of a framework using Python type hints to declare parameters and requests that I ever saw (before NestJS and Molten). I found it more or less at the same time as Hug. But APIStar used the OpenAPI standard. + +It had automatic data validation, data serialization and OpenAPI schema generation based on the same type hints in several places. + +Body schema definitions didn't use the same Python type hints like Pydantic, it was a bit more similar to Marshmallow, so, editor support wouldn't be as good, but still, APIStar was the best available option. + +It had the best performance benchmarks at the time (only surpassed by Starlette). + +At first, it didn't have an automatic API documentation web UI, but I knew I could add Swagger UI to it. + +It had a dependency injection system. It required pre-registration of components, as other tools discussed above. But still, it was a great feature. + +I was never able to use it in a full project, as it didn't have security integration, so, I couldn't replace all the features I was having with the full-stack generators based on Flask-apispec. I had in my backlog of projects to create a pull request adding that functionality. + +But then, the project's focus shifted. + +It was no longer an API web framework, as the creator needed to focus on Starlette. + +Now APIStar is a set of tools to validate OpenAPI specifications, not a web framework. + +!!! info + APIStar was created by Tom Christie. The same guy that created: + + * Django REST Framework + * Starlette (in which **FastAPI** is based) + * Uvicorn (used by Starlette and **FastAPI**) + +!!! check "Inspired **FastAPI** to" Exist. + + The idea of declaring multiple things (data validation, serialization and documentation) with the same Python types, that at the same time provided great editor support, was something I considered a brilliant idea. + + And after searching for a long time for a similar framework and testing many different alternatives, APIStar was the best option available. + + Then APIStar stopped to exist as a server and Starlette was created, and was a new better foundation for such a system. That was the final inspiration to build **FastAPI**. + + I consider **FastAPI** a "spiritual successor" to APIStar, while improving and increasing the features, typing system, and other parts, based on the learnings from all these previous tools. + +## Used by **FastAPI** + +### Pydantic + +Pydantic is a library to define data validation, serialization and documentation (using JSON Schema) based on Python type hints. + +That makes it extremely intuitive. + +It is comparable to Marshmallow. Although it's faster than Marshmallow in benchmarks. And as it is based on the same Python type hints, the editor support is great. + +!!! check "**FastAPI** uses it to" Handle all the data validation, data serialization and automatic model documentation (based on JSON Schema). + + **FastAPI** then takes that JSON Schema data and puts it in OpenAPI, apart from all the other things it does. + +### Starlette + +Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high-performance asyncio services. + +It is very simple and intuitive. It's designed to be easily extensible, and have modular components. + +It has: + +* Seriously impressive performance. +* WebSocket support. +* In-process background tasks. +* Startup and shutdown events. +* Test client built on HTTPX. +* CORS, GZip, Static Files, Streaming responses. +* Session and Cookie support. +* 100% test coverage. +* 100% type annotated codebase. +* Few hard dependencies. + +Starlette is currently the fastest Python framework tested. Only surpassed by Uvicorn, which is not a framework, but a server. + +Starlette provides all the basic web microframework functionality. + +But it doesn't provide automatic data validation, serialization or documentation. + +That's one of the main things that **FastAPI** adds on top, all based on Python type hints (using Pydantic). That, plus the dependency injection system, security utilities, OpenAPI schema generation, etc. + +!!! note "Technical Details" + ASGI is a new "standard" being developed by Django core team members. It is still not a "Python standard" (a PEP), although they are in the process of doing that. + + Nevertheless, it is already being used as a "standard" by several tools. This greatly improves interoperability, as you could switch Uvicorn for any other ASGI server (like Daphne or Hypercorn), or you could add ASGI compatible tools, like `python-socketio`. + +!!! check "**FastAPI** uses it to" Handle all the core web parts. Adding features on top. + + The class `FastAPI` itself inherits directly from the class `Starlette`. + + So, anything that you can do with Starlette, you can do it directly with **FastAPI**, as it is basically Starlette on steroids. + +### Uvicorn + +Uvicorn is a lightning-fast ASGI server, built on uvloop and httptools. + +It is not a web framework, but a server. For example, it doesn't provide tools for routing by paths. That's something that a framework like Starlette (or **FastAPI**) would provide on top. + +It is the recommended server for Starlette and **FastAPI**. + +!!! check "**FastAPI** recommends it as" The main web server to run **FastAPI** applications. + + You can combine it with Gunicorn, to have an asynchronous multi-process server. + + Check more details in the [Deployment](deployment/index.md){.internal-link target=_blank} section. + +## Benchmarks and speed + +To understand, compare, and see the difference between Uvicorn, Starlette and FastAPI, check the section about [Benchmarks](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/zh/docs/async.md b/docs/zh/docs/async.md new file mode 100644 index 0000000000000..3ac62a7795ea9 --- /dev/null +++ b/docs/zh/docs/async.md @@ -0,0 +1,430 @@ +# Concurrency and async / await + +Details about the `async def` syntax for *path operation functions* and some background about asynchronous code, concurrency, and parallelism. + +## In a hurry? + +TL;DR: + +If you are using third party libraries that tell you to call them with `await`, like: + +```Python +results = await some_library() +``` + +Then, declare your *path operation functions* with `async def` like: + +```Python hl_lines="2" +@app.get('/') +async def read_results(): + results = await some_library() + return results +``` + +!!! note + You can only use `await` inside of functions created with `async def`. + +--- + +If you are using a third party library that communicates with something (a database, an API, the file system, etc.) and doesn't have support for using `await`, (this is currently the case for most database libraries), then declare your *path operation functions* as normally, with just `def`, like: + +```Python hl_lines="2" +@app.get('/') +def results(): + results = some_library() + return results +``` + +--- + +If your application (somehow) doesn't have to communicate with anything else and wait for it to respond, use `async def`. + +--- + +If you just don't know, use normal `def`. + +--- + +**Note**: You can mix `def` and `async def` in your *path operation functions* as much as you need and define each one using the best option for you. FastAPI will do the right thing with them. + +Anyway, in any of the cases above, FastAPI will still work asynchronously and be extremely fast. + +But by following the steps above, it will be able to do some performance optimizations. + +## Technical Details + +Modern versions of Python have support for **"asynchronous code"** using something called **"coroutines"**, with **`async` and `await`** syntax. + +Let's see that phrase by parts in the sections below: + +* **Asynchronous Code** +* **`async` and `await`** +* **Coroutines** + +## Asynchronous Code + +Asynchronous code just means that the language 💬 has a way to tell the computer / program 🤖 that at some point in the code, it 🤖 will have to wait for *something else* to finish somewhere else. Let's say that *something else* is called "slow-file" 📝. + +So, during that time, the computer can go and do some other work, while "slow-file" 📝 finishes. + +Then the computer / program 🤖 will come back every time it has a chance because it's waiting again, or whenever it 🤖 finished all the work it had at that point. And it 🤖 will see if any of the tasks it was waiting for have already finished, doing whatever it had to do. + +Next, it 🤖 takes the first task to finish (let's say, our "slow-file" 📝) and continues whatever it had to do with it. + +That "wait for something else" normally refers to I/O operations that are relatively "slow" (compared to the speed of the processor and the RAM memory), like waiting for: + +* the data from the client to be sent through the network +* the data sent by your program to be received by the client through the network +* the contents of a file in the disk to be read by the system and given to your program +* the contents your program gave to the system to be written to disk +* a remote API operation +* a database operation to finish +* a database query to return the results +* etc. + +As the execution time is consumed mostly by waiting for I/O operations, they call them "I/O bound" operations. + +It's called "asynchronous" because the computer / program doesn't have to be "synchronized" with the slow task, waiting for the exact moment that the task finishes, while doing nothing, to be able to take the task result and continue the work. + +Instead of that, by being an "asynchronous" system, once finished, the task can wait in line a little bit (some microseconds) for the computer / program to finish whatever it went to do, and then come back to take the results and continue working with them. + +For "synchronous" (contrary to "asynchronous") they commonly also use the term "sequential", because the computer / program follows all the steps in sequence before switching to a different task, even if those steps involve waiting. + +### Concurrency and Burgers + +This idea of **asynchronous** code described above is also sometimes called **"concurrency"**. It is different from **"parallelism"**. + +**Concurrency** and **parallelism** both relate to "different things happening more or less at the same time". + +But the details between *concurrency* and *parallelism* are quite different. + +To see the difference, imagine the following story about burgers: + +### Concurrent Burgers + +You go with your crush to get fast food, you stand in line while the cashier takes the orders from the people in front of you. 😍 + + + +Then it's your turn, you place your order of 2 very fancy burgers for your crush and you. 🍔🍔 + + + +The cashier says something to the cook in the kitchen so they know they have to prepare your burgers (even though they are currently preparing the ones for the previous clients). + + + +You pay. 💸 + +The cashier gives you the number of your turn. + + + +While you are waiting, you go with your crush and pick a table, you sit and talk with your crush for a long time (as your burgers are very fancy and take some time to prepare). + +As you are sitting at the table with your crush, while you wait for the burgers, you can spend that time admiring how awesome, cute and smart your crush is ✨😍✨. + + + +While waiting and talking to your crush, from time to time, you check the number displayed on the counter to see if it's your turn already. + +Then at some point, it finally is your turn. You go to the counter, get your burgers and come back to the table. + + + +You and your crush eat the burgers and have a nice time. ✨ + + + +!!! info + Beautiful illustrations by Ketrina Thompson. 🎨 + +--- + +Imagine you are the computer / program 🤖 in that story. + +While you are at the line, you are just idle 😴, waiting for your turn, not doing anything very "productive". But the line is fast because the cashier is only taking the orders (not preparing them), so that's fine. + +Then, when it's your turn, you do actual "productive" work, you process the menu, decide what you want, get your crush's choice, pay, check that you give the correct bill or card, check that you are charged correctly, check that the order has the correct items, etc. + +But then, even though you still don't have your burgers, your work with the cashier is "on pause" ⏸, because you have to wait 🕙 for your burgers to be ready. + +But as you go away from the counter and sit at the table with a number for your turn, you can switch 🔀 your attention to your crush, and "work" ⏯ 🤓 on that. Then you are again doing something very "productive" as is flirting with your crush 😍. + +Then the cashier 💁 says "I'm finished with doing the burgers" by putting your number on the counter's display, but you don't jump like crazy immediately when the displayed number changes to your turn number. You know no one will steal your burgers because you have the number of your turn, and they have theirs. + +So you wait for your crush to finish the story (finish the current work ⏯ / task being processed 🤓), smile gently and say that you are going for the burgers ⏸. + +Then you go to the counter 🔀, to the initial task that is now finished ⏯, pick the burgers, say thanks and take them to the table. That finishes that step / task of interaction with the counter ⏹. That in turn, creates a new task, of "eating burgers" 🔀 ⏯, but the previous one of "getting burgers" is finished ⏹. + +### Parallel Burgers + +Now let's imagine these aren't "Concurrent Burgers", but "Parallel Burgers". + +You go with your crush to get parallel fast food. + +You stand in line while several (let's say 8) cashiers that at the same time are cooks take the orders from the people in front of you. + +Everyone before you is waiting for their burgers to be ready before leaving the counter because each of the 8 cashiers goes and prepares the burger right away before getting the next order. + + + +Then it's finally your turn, you place your order of 2 very fancy burgers for your crush and you. + +You pay 💸. + + + +The cashier goes to the kitchen. + +You wait, standing in front of the counter 🕙, so that no one else takes your burgers before you do, as there are no numbers for turns. + + + +As you and your crush are busy not letting anyone get in front of you and take your burgers whenever they arrive, you cannot pay attention to your crush. 😞 + +This is "synchronous" work, you are "synchronized" with the cashier/cook 👨‍🍳. You have to wait 🕙 and be there at the exact moment that the cashier/cook 👨‍🍳 finishes the burgers and gives them to you, or otherwise, someone else might take them. + + + +Then your cashier/cook 👨‍🍳 finally comes back with your burgers, after a long time waiting 🕙 there in front of the counter. + + + +You take your burgers and go to the table with your crush. + +You just eat them, and you are done. ⏹ + + + +There was not much talk or flirting as most of the time was spent waiting 🕙 in front of the counter. 😞 + +!!! info + Beautiful illustrations by Ketrina Thompson. 🎨 + +--- + +In this scenario of the parallel burgers, you are a computer / program 🤖 with two processors (you and your crush), both waiting 🕙 and dedicating their attention ⏯ to be "waiting on the counter" 🕙 for a long time. + +The fast food store has 8 processors (cashiers/cooks). While the concurrent burgers store might have had only 2 (one cashier and one cook). + +But still, the final experience is not the best. 😞 + +--- + +This would be the parallel equivalent story for burgers. 🍔 + +For a more "real life" example of this, imagine a bank. + +Up to recently, most of the banks had multiple cashiers 👨‍💼👨‍💼👨‍💼👨‍💼 and a big line 🕙🕙🕙🕙🕙🕙🕙🕙. + +All of the cashiers doing all the work with one client after the other 👨‍💼⏯. + +And you have to wait 🕙 in the line for a long time or you lose your turn. + +You probably wouldn't want to take your crush 😍 with you to do errands at the bank 🏦. + +### Burger Conclusion + +In this scenario of "fast food burgers with your crush", as there is a lot of waiting 🕙, it makes a lot more sense to have a concurrent system ⏸🔀⏯. + +This is the case for most of the web applications. + +Many, many users, but your server is waiting 🕙 for their not-so-good connection to send their requests. + +And then waiting 🕙 again for the responses to come back. + +This "waiting" 🕙 is measured in microseconds, but still, summing it all, it's a lot of waiting in the end. + +That's why it makes a lot of sense to use asynchronous ⏸🔀⏯ code for web APIs. + +This kind of asynchronicity is what made NodeJS popular (even though NodeJS is not parallel) and that's the strength of Go as a programming language. + +And that's the same level of performance you get with **FastAPI**. + +And as you can have parallelism and asynchronicity at the same time, you get higher performance than most of the tested NodeJS frameworks and on par with Go, which is a compiled language closer to C (all thanks to Starlette). + +### Is concurrency better than parallelism? + +Nope! That's not the moral of the story. + +Concurrency is different than parallelism. And it is better on **specific** scenarios that involve a lot of waiting. Because of that, it generally is a lot better than parallelism for web application development. But not for everything. + +So, to balance that out, imagine the following short story: + +> You have to clean a big, dirty house. + +*Yep, that's the whole story*. + +--- + +There's no waiting 🕙 anywhere, just a lot of work to be done, on multiple places of the house. + +You could have turns as in the burgers example, first the living room, then the kitchen, but as you are not waiting 🕙 for anything, just cleaning and cleaning, the turns wouldn't affect anything. + +It would take the same amount of time to finish with or without turns (concurrency) and you would have done the same amount of work. + +But in this case, if you could bring the 8 ex-cashier/cooks/now-cleaners, and each one of them (plus you) could take a zone of the house to clean it, you could do all the work in **parallel**, with the extra help, and finish much sooner. + +In this scenario, each one of the cleaners (including you) would be a processor, doing their part of the job. + +And as most of the execution time is taken by actual work (instead of waiting), and the work in a computer is done by a CPU, they call these problems "CPU bound". + +--- + +Common examples of CPU bound operations are things that require complex math processing. + +For example: + +* **Audio** or **image processing**. +* **Computer vision**: an image is composed of millions of pixels, each pixel has 3 values / colors, processing that normally requires computing something on those pixels, all at the same time. +* **Machine Learning**: it normally requires lots of "matrix" and "vector" multiplications. Think of a huge spreadsheet with numbers and multiplying all of them together at the same time. +* **Deep Learning**: this is a sub-field of Machine Learning, so, the same applies. It's just that there is not a single spreadsheet of numbers to multiply, but a huge set of them, and in many cases, you use a special processor to build and / or use those models. + +### Concurrency + Parallelism: Web + Machine Learning + +With **FastAPI** you can take the advantage of concurrency that is very common for web development (the same main attraction of NodeJS). + +But you can also exploit the benefits of parallelism and multiprocessing (having multiple processes running in parallel) for **CPU bound** workloads like those in Machine Learning systems. + +That, plus the simple fact that Python is the main language for **Data Science**, Machine Learning and especially Deep Learning, make FastAPI a very good match for Data Science / Machine Learning web APIs and applications (among many others). + +To see how to achieve this parallelism in production see the section about [Deployment](deployment/index.md){.internal-link target=_blank}. + +## `async` and `await` + +Modern versions of Python have a very intuitive way to define asynchronous code. This makes it look just like normal "sequential" code and do the "awaiting" for you at the right moments. + +When there is an operation that will require waiting before giving the results and has support for these new Python features, you can code it like: + +```Python +burgers = await get_burgers(2) +``` + +The key here is the `await`. It tells Python that it has to wait ⏸ for `get_burgers(2)` to finish doing its thing 🕙 before storing the results in `burgers`. With that, Python will know that it can go and do something else 🔀 ⏯ in the meanwhile (like receiving another request). + +For `await` to work, it has to be inside a function that supports this asynchronicity. To do that, you just declare it with `async def`: + +```Python hl_lines="1" +async def get_burgers(number: int): + # Do some asynchronous stuff to create the burgers + return burgers +``` + +...instead of `def`: + +```Python hl_lines="2" +# This is not asynchronous +def get_sequential_burgers(number: int): + # Do some sequential stuff to create the burgers + return burgers +``` + +With `async def`, Python knows that, inside that function, it has to be aware of `await` expressions, and that it can "pause" ⏸ the execution of that function and go do something else 🔀 before coming back. + +When you want to call an `async def` function, you have to "await" it. So, this won't work: + +```Python +# This won't work, because get_burgers was defined with: async def +burgers = get_burgers(2) +``` + +--- + +So, if you are using a library that tells you that you can call it with `await`, you need to create the *path operation functions* that uses it with `async def`, like in: + +```Python hl_lines="2-3" +@app.get('/burgers') +async def read_burgers(): + burgers = await get_burgers(2) + return burgers +``` + +### More technical details + +You might have noticed that `await` can only be used inside of functions defined with `async def`. + +But at the same time, functions defined with `async def` have to be "awaited". So, functions with `async def` can only be called inside of functions defined with `async def` too. + +So, about the egg and the chicken, how do you call the first `async` function? + +If you are working with **FastAPI** you don't have to worry about that, because that "first" function will be your *path operation function*, and FastAPI will know how to do the right thing. + +But if you want to use `async` / `await` without FastAPI, you can do it as well. + +### Write your own async code + +Starlette (and **FastAPI**) are based on AnyIO, which makes it compatible with both Python's standard library asyncio and Trio. + +In particular, you can directly use AnyIO for your advanced concurrency use cases that require more advanced patterns in your own code. + +And even if you were not using FastAPI, you could also write your own async applications with AnyIO to be highly compatible and get its benefits (e.g. *structured concurrency*). + +### Other forms of asynchronous code + +This style of using `async` and `await` is relatively new in the language. + +But it makes working with asynchronous code a lot easier. + +This same syntax (or almost identical) was also included recently in modern versions of JavaScript (in Browser and NodeJS). + +But before that, handling asynchronous code was quite more complex and difficult. + +In previous versions of Python, you could have used threads or Gevent. But the code is way more complex to understand, debug, and think about. + +In previous versions of NodeJS / Browser JavaScript, you would have used "callbacks". Which leads to callback hell. + +## Coroutines + +**Coroutine** is just the very fancy term for the thing returned by an `async def` function. Python knows that it is something like a function that it can start and that it will end at some point, but that it might be paused ⏸ internally too, whenever there is an `await` inside of it. + +But all this functionality of using asynchronous code with `async` and `await` is many times summarized as using "coroutines". It is comparable to the main key feature of Go, the "Goroutines". + +## Conclusion + +Let's see the same phrase from above: + +> Modern versions of Python have support for **"asynchronous code"** using something called **"coroutines"**, with **`async` and `await`** syntax. + +That should make more sense now. ✨ + +All that is what powers FastAPI (through Starlette) and what makes it have such an impressive performance. + +## Very Technical Details + +!!! warning + You can probably skip this. + + These are very technical details of how **FastAPI** works underneath. + + If you have quite some technical knowledge (co-routines, threads, blocking, etc.) and are curious about how FastAPI handles `async def` vs normal `def`, go ahead. + +### Path operation functions + +When you declare a *path operation function* with normal `def` instead of `async def`, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server). + +If you are coming from another async framework that does not work in the way described above and you are used to defining trivial compute-only *path operation functions* with plain `def` for a tiny performance gain (about 100 nanoseconds), please note that in **FastAPI** the effect would be quite opposite. In these cases, it's better to use `async def` unless your *path operation functions* use code that performs blocking I/O. + +Still, in both situations, chances are that **FastAPI** will [still be faster](/#performance){.internal-link target=_blank} than (or at least comparable to) your previous framework. + +### Dependencies + +The same applies for [dependencies](/tutorial/dependencies/index.md){.internal-link target=_blank}. If a dependency is a standard `def` function instead of `async def`, it is run in the external threadpool. + +### Sub-dependencies + +You can have multiple dependencies and [sub-dependencies](/tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} requiring each other (as parameters of the function definitions), some of them might be created with `async def` and some with normal `def`. It would still work, and the ones created with normal `def` would be called on an external thread (from the threadpool) instead of being "awaited". + +### Other utility functions + +Any other utility function that you call directly can be created with normal `def` or `async def` and FastAPI won't affect the way you call it. + +This is in contrast to the functions that FastAPI calls for you: *path operation functions* and dependencies. + +If your utility function is a normal function with `def`, it will be called directly (as you write it in your code), not in a threadpool, if the function is created with `async def` then you should `await` for that function when you call it in your code. + +--- + +Again, these are very technical details that would probably be useful if you came searching for them. + +Otherwise, you should be good with the guidelines from the section above: In a hurry?. diff --git a/docs/zh/docs/benchmarks.md b/docs/zh/docs/benchmarks.md index 71e8d483822ac..44ff3a3870998 100644 --- a/docs/zh/docs/benchmarks.md +++ b/docs/zh/docs/benchmarks.md @@ -1,6 +1,6 @@ -# 基准测试 +# Benchmarks -第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*) +第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。 (*) 但是在查看基准得分和对比时,请注意以下几点。 @@ -10,7 +10,7 @@ 具体来说,是将 Uvicorn,Starlette 和 FastAPI 一起比较(在许多其它工具中)。 -该工具解决的问题最简单,它将获得更好的性能。而且大多数基准测试并未测试该工具提供的其他功能。 +该工具解决的问题最简单,它将获得更好的性能。 而且大多数基准测试并未测试该工具提供的其他功能。 层次结构如下: @@ -20,15 +20,15 @@ * **Uvicorn**: * 具有最佳性能,因为除了服务器本身外,它没有太多额外的代码。 - * 您不会直接在 Uvicorn 中编写应用程序。这意味着您的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果您这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。 - * 如果要对比与 Uvicorn 对标的服务器,请将其与 Daphne,Hypercorn,uWSGI等应用服务器进行比较。 + * 您不会直接在 Uvicorn 中编写应用程序。 这意味着您的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。 如果您这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。 + * 如果要对比与 Uvicorn 对标的服务器,请将其与 Daphne,Hypercorn,uWSGI等应用服务器进行比较。 Application servers. * **Starlette**: - * 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。 + * 在 Uvicorn 后使用 Starlette,性能会略有下降。 实际上,Starlette 使用 Uvicorn运行。 因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。 * 但它为您提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。 - * 如果想对比与 Starlette 对标的开发框架,请将其与 Sanic,Flask,Django 等网络框架(或微框架)进行比较。 + * 如果想对比与 Starlette 对标的开发框架,请将其与 Sanic,Flask,Django 等网络框架(或微框架)进行比较。 Web frameworks (or microframeworks). * **FastAPI**: * 与 Starlette 使用 Uvicorn 一样,由于 **FastAPI** 使用 Starlette,因此 FastAPI 不能比 Starlette 更快。 - * FastAPI 在 Starlette 基础上提供了更多功能。例如在开发 API 时,所需的数据验证和序列化功能。FastAPI 可以帮助您自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。 - * 如果您不使用 FastAPI 而直接使用 Starlette(或诸如 Sanic,Flask,Responder 等其它工具),您则要自己实现所有的数据验证和序列化。那么最终您的应用程序会和使用 FastAPI 构建的程序有相同的开销。一般这种数据验证和序列化的操作在您应用程序的代码中会占很大比重。 + * FastAPI 在 Starlette 基础上提供了更多功能。 例如在开发 API 时,所需的数据验证和序列化功能。 FastAPI 可以帮助您自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。 + * 如果您不使用 FastAPI 而直接使用 Starlette(或诸如 Sanic,Flask,Responder 等其它工具),您则要自己实现所有的数据验证和序列化。 那么最终您的应用程序会和使用 FastAPI 构建的程序有相同的开销。 一般这种数据验证和序列化的操作在您应用程序的代码中会占很大比重。 * 因此,通过使用 FastAPI 意味着您可以节省开发时间,减少编码错误,用更少的编码实现其功能,并且相比不使用 FastAPI 您很大可能会获得相同或更好的性能(因为那样您必须在代码中实现所有相同的功能)。 - * 如果您想对比与 FastAPI 对标的开发框架,请与能够提供数据验证,序列化和带有自动文档生成的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证,序列化和自动化文档的 Flask-apispec,NestJS,Molten 等。 + * 如果您想对比与 FastAPI 对标的开发框架,请与能够提供数据验证,序列化和带有自动文档生成的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证,序列化和自动化文档的 Flask-apispec,NestJS,Molten 等。 Frameworks with integrated automatic data validation, serialization and documentation. diff --git a/docs/zh/docs/contributing.md b/docs/zh/docs/contributing.md index 4ebd673150b25..46f349f032fab 100644 --- a/docs/zh/docs/contributing.md +++ b/docs/zh/docs/contributing.md @@ -32,6 +32,7 @@ $ python -m venv env $ source ./env/bin/activate ``` + === "Windows PowerShell" @@ -42,18 +43,20 @@ $ python -m venv env $ .\env\Scripts\Activate.ps1 ``` + === "Windows Bash" Or if you use Bash for Windows (e.g. Git Bash): - +
```console $ source ./env/Scripts/activate ``` +
要检查操作是否成功,运行: @@ -68,6 +71,7 @@ $ python -m venv env some/directory/fastapi/env/bin/pip ``` + === "Windows PowerShell" @@ -80,12 +84,24 @@ $ python -m venv env some/directory/fastapi/env/bin/pip ``` + 如果显示 `pip` 程序文件位于 `env/bin/pip` 则说明激活成功。 🎉 +Make sure you have the latest pip version on your virtual environment to avoid errors on the next steps: -!!! tip +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +!!! !!! tip 每一次你在该环境下使用 `pip` 安装了新软件包时,请再次激活该环境。 这样可以确保你在使用由该软件包安装的终端程序时使用的是当前虚拟环境中的程序,而不是其他的可能是全局安装的程序。 @@ -108,12 +124,17 @@ $ pip install -r requirements.txt #### 使用本地 FastAPI -如果你创建一个导入并使用 FastAPI 的 Python 文件,然后使用虚拟环境中的 Python 运行它,它将使用你本地的 FastAPI 源码。 - 并且如果你更改该本地 FastAPI 的源码,由于它是通过 `-e` 安装的,当你再次运行那个 Python 文件,它将使用你刚刚编辑过的最新版本的 FastAPI。 +如果你创建一个导入并使用 FastAPI 的 Python 文件,然后使用虚拟环境中的 Python 运行它,它将使用你本地的 FastAPI 源码。 + 这样,你不必再去重新"安装"你的本地版本即可测试所有更改。 +!!! note "Technical Details" + This only happens when you install using this included `requiements.txt` instead of installing `pip install fastapi` directly. + + That is because inside of the `requirements.txt` file, the local version of FastAPI is marked to be installed in "editable" mode, with the `-e` option. + ### 格式化 你可以运行下面的脚本来格式化和清理所有代码: @@ -130,20 +151,6 @@ $ bash scripts/format.sh 为了使整理正确进行,你需要在当前环境中安装本地的 FastAPI,即在运行上述段落中的命令时添加 `-e`。 -### 格式化导入 - -还有另一个脚本可以格式化所有导入,并确保你没有未使用的导入代码: - -
- -```console -$ bash scripts/format-imports.sh -``` - -
- -由于它依次运行了多个命令,并修改和还原了许多文件,所以运行时间会更长一些,因此经常地使用 `scripts/format.sh` 然后仅在提交前执行 `scripts/format-imports.sh` 会更好一些。 - ## 文档 首先,请确保按上述步骤设置好环境,这将安装所有需要的依赖。 @@ -152,7 +159,7 @@ $ bash scripts/format-imports.sh 并且在 `./scripts/docs.py` 中还有适用的额外工具/脚本来处理翻译。 -!!! tip +!!! !!! tip 你不需要去了解 `./scripts/docs.py` 中的代码,只需在命令行中使用它即可。 所有文档均在 `./docs/en/` 目录中以 Markdown 文件格式保存。 @@ -171,7 +178,7 @@ $ bash scripts/format-imports.sh 这有助于确保: -* 文档始终是最新的。 +* The documentation is up to date. * 文档示例可以直接运行。 * 绝大多数特性既在文档中得以阐述,又通过测试覆盖进行保障。 @@ -193,6 +200,22 @@ $ python ./scripts/docs.py live 这样,你可以编辑文档/源文件并实时查看更改。 +!!! tip + Alternatively, you can perform the same steps that scripts does manually. + + Go into the language directory, for the main docs in English it's at `docs/en/`: + + ```console + docs/es/docs/mkdocs.yml + ``` + + + Then run `mkdocs` in that directory: + + ```console + $ mkdocs serve --dev-addr 8008 + ``` + #### Typer CLI (可选) 本指引向你展示了如何直接用 `python` 程序运行 `./scripts/docs.py` 中的脚本。 @@ -209,6 +232,8 @@ $ typer --install-completion zsh completion installed in /home/user/.bashrc. Completion will take effect once you restart the terminal. ``` +Completion will take effect once you restart the terminal. +``` @@ -230,7 +255,7 @@ $ uvicorn tutorial001:app --reload ### 翻译 -非常感谢你能够参与文档的翻译!这项工作需要社区的帮助才能完成。 🌎 🚀 +非常感谢你能够参与文档的翻译! 这项工作需要社区的帮助才能完成。 🌎 🚀 以下是参与帮助翻译的步骤。 @@ -238,22 +263,24 @@ $ uvicorn tutorial001:app --reload * 在当前 已有的 pull requests 中查找你使用的语言,添加要求修改或同意合并的评审意见。 -!!! tip +!!! !!! tip 你可以为已有的 pull requests 添加包含修改建议的评论。 详情可查看关于 添加 pull request 评审意见 以同意合并或要求修改的文档。 -* 在 issues 中查找是否有对你所用语言所进行的协作翻译。 +* Check if there's a GitHub Discussion to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion. -* 每翻译一个页面新增一个 pull request。这将使其他人更容易对其进行评审。 +* 每翻译一个页面新增一个 pull request。 这将使其他人更容易对其进行评审。 对于我(译注:作者使用西班牙语和英语)不懂的语言,我将在等待其他人评审翻译之后将其合并。 * 你还可以查看是否有你所用语言的翻译,并对其进行评审,这将帮助我了解翻译是否正确以及能否将其合并。 + * 在 issues 中查找是否有对你所用语言所进行的协作翻译。 + * Or you can filter the existing PRs by the ones with the label for your language, for example, for Spanish, the label is `lang-es`. -* 使用相同的 Python 示例并且仅翻译文档中的文本。无需进行任何其他更改示例也能正常工作。 +* 使用相同的 Python 示例并且仅翻译文档中的文本。 无需进行任何其他更改示例也能正常工作。 -* 使用相同的图片、文件名以及链接地址。无需进行任何其他调整来让它们兼容。 +* 使用相同的图片、文件名以及链接地址。 无需进行任何其他调整来让它们兼容。 * 你可以从 ISO 639-1 代码列表 表中查找你想要翻译语言的两位字母代码。 @@ -261,9 +288,9 @@ $ uvicorn tutorial001:app --reload 假设你想将某个页面翻译成已经翻译了一些页面的语言,例如西班牙语。 -对于西班牙语来说,它的两位字母代码是 `es`。所以西班牙语翻译的目录位于 `docs/es/`。 +对于西班牙语来说,它的两位字母代码是 `es`。 所以西班牙语翻译的目录位于 `docs/es/`。 -!!! tip +!!! !!! tip 主要("官方")语言是英语,位于 `docs/en/`目录。 现在为西班牙语文档运行实时服务器: @@ -281,11 +308,27 @@ $ python ./scripts/docs.py live es -现在你可以访问 http://127.0.0.1:8008 实时查看你所做的更改。 +!!! tip + Alternatively, you can perform the same steps that scripts does manually. + + Go into the language directory, for the Spanish translations it's at `docs/es/`: + + ```console + ```console +$ bash scripts/format-imports.sh +``` + ``` + + + Then run `mkdocs` in that directory: + + ```console + $ mkdocs serve --dev-addr 8008 + ``` -如果你查看 FastAPI 的线上文档网站,会看到每种语言都有所有页面。但是某些页面并未被翻译并且会有一处关于缺少翻译的提示。 +现在你可以访问 http://127.0.0.1:8008 实时查看你所做的更改。 -但是当你像上面这样在本地运行文档时,你只会看到已经翻译的页面。 +如果你查看 FastAPI 的线上文档网站,会看到每种语言都有所有页面。 但是某些页面并未被翻译并且会有一处关于缺少翻译的提示。 现在假设你要为 [Features](features.md){.internal-link target=_blank} 章节添加翻译。 @@ -301,49 +344,9 @@ docs/en/docs/features.md docs/es/docs/features.md ``` -!!! tip +!!! !!! tip 注意路径和文件名的唯一变化是语言代码,从 `en` 更改为 `es`。 -* 现在打开位于英语文档目录下的 MkDocs 配置文件: - -``` -docs/en/docs/mkdocs.yml -``` - -* 在配置文件中找到 `docs/features.md` 所在的位置。结果像这样: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -* 打开你正在编辑的语言目录中的 MkDocs 配置文件,例如: - -``` -docs/es/docs/mkdocs.yml -``` - -* 将其添加到与英语文档完全相同的位置,例如: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -如果配置文件中还有其他条目,请确保你所翻译的新条目和它们之间的顺序与英文版本完全相同。 - 打开浏览器,现在你将看到文档展示了你所加入的新章节。 🎉 现在,你可以将它全部翻译完并在保存文件后进行预览。 @@ -373,41 +376,20 @@ Updating en 现在,你可以在编辑器中查看新创建的目录 `docs/ht/`。 -!!! tip - 在添加实际的翻译之前,仅以此创建首个 pull request 来设定新语言的配置。 - - 这样当你在翻译第一个页面时,其他人可以帮助翻译其他页面。🚀 - -首先翻译文档主页 `docs/ht/index.md`。 - -然后,你可以根据上面的"已有语言"的指引继续进行翻译。 - -##### 不支持的新语言 +这将在 `./docs_build/` 目录中为每一种语言生成全部的文档。 -如果在运行实时服务器脚本时收到关于不支持该语言的错误,类似于: - -``` - raise TemplateNotFound(template) -jinja2.exceptions.TemplateNotFound: partials/language/xx.html +```yaml +docs/en/docs/mkdocs.yml ``` -这意味着文档的主题不支持该语言(在这种例子中,编造的语言代码是 `xx`)。 - -但是别担心,你可以将主题语言设置为英语,然后翻译文档的内容。 - -如果你需要这么做,编辑新语言目录下的 `mkdocs.yml`,它将有类似下面的内容: +!!! tip + You could also simply create that file with those contents manually. -```YAML hl_lines="5" -site_name: FastAPI -# More stuff -theme: - # More stuff - language: xx -``` +首先翻译文档主页 `docs/ht/index.md`。 -将其中的 language 项从 `xx`(你的语言代码)更改为 `en`。 +You can continue with the previous instructions for an "Existing Language" for that process. -然后,你就可以再次启动实时服务器了。 +这样当你在翻译第一个页面时,其他人可以帮助翻译其他页面。 🚀 #### 预览结果 @@ -433,8 +415,6 @@ Copying en index.md to README.md -这将在 `./docs_build/` 目录中为每一种语言生成全部的文档。还包括添加所有缺少翻译的文件,并带有一条"此文件还没有翻译"的提醒。但是你不需要对该目录执行任何操作。 - 然后,它针对每种语言构建独立的 MkDocs 站点,将它们组合在一起,并在 `./site/` 目录中生成最终的输出。 然后你可以使用命令 `serve` 来运行生成的站点: @@ -449,6 +429,10 @@ Warning: this is a very simple server. For development, use mkdocs serve instead This is here only to preview a site with translations already built. Make sure you run the build-all command first. Serving at: http://127.0.0.1:8008 +``` For development, use mkdocs serve instead. +This is here only to preview a site with translations already built. +Make sure you run the build-all command first. +Serving at: http://127.0.0.1:8008 ``` diff --git a/docs/zh/docs/deployment/concepts.md b/docs/zh/docs/deployment/concepts.md new file mode 100644 index 0000000000000..72f55860c3093 --- /dev/null +++ b/docs/zh/docs/deployment/concepts.md @@ -0,0 +1,311 @@ +# Deployments Concepts + +When deploying a **FastAPI** application, or actually, any type of web API, there are several concepts that you probably care about, and using them you can find the **most appropriate** way to **deploy your application**. + +Some of the important concepts are: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +We'll see how they would affect **deployments**. + +In the end, the ultimate objective is to be able to **serve your API clients** in a way that is **secure**, to **avoid disruptions**, and to use the **compute resources** (for example remote servers/virtual machines) as efficiently as possible. 🚀 + +I'll tell you a bit more about these **concepts** here, and that would hopefully give you the **intuition** you would need to decide how to deploy your API in very different environments, possibly even in **future** ones that don't exist yet. + +By considering these concepts, you will be able to **evaluate and design** the best way to deploy **your own APIs**. + +In the next chapters, I'll give you more **concrete recipes** to deploy FastAPI applications. + +But for now, let's check these important **conceptual ideas**. These concepts also apply to any other type of web API. 💡 + +## Security - HTTPS + +In the [previous chapter about HTTPS](./https.md){.internal-link target=_blank} we learned about how HTTPS provides encryption for your API. + +We also saw that HTTPS is normally provided by a component **external** to your application server, a **TLS Termination Proxy**. + +And there has to be something in charge of **renewing the HTTPS certificates**, it could be the same component or it could be something different. + +### Example Tools for HTTPS + +Some of the tools you could use as a TLS Termination Proxy are: + +* Traefik + * Automatically handles certificates renewals ✨ +* Caddy + * Automatically handles certificates renewals ✨ +* Nginx + * With an external component like Certbot for certificate renewals +* HAProxy + * With an external component like Certbot for certificate renewals +* Kubernetes with an Ingress Controller like Nginx + * With an external component like cert-manager for certificate renewals +* Handled internally by a cloud provider as part of their services (read below 👇) + +Another option is that you could use a **cloud service** that does more of the work including setting up HTTPS. It could have some restrictions or charge you more, etc. But in that case, you wouldn't have to set up a TLS Termination Proxy yourself. + +I'll show you some concrete examples in the next chapters. + +--- + +Then the next concepts to consider are all about the program running your actual API (e.g. Uvicorn). + +## Program and Process + +We will talk a lot about the running "**process**", so it's useful to have clarity about what it means, and what's the difference with the word "**program**". + +### What is a Program + +The word **program** is commonly used to describe many things: + +* The **code** that you write, the **Python files**. +* The **file** that can be **executed** by the operating system, for example: `python`, `python.exe` or `uvicorn`. +* A particular program while it is **running** on the operating system, using the CPU, and storing things on memory. This is also called a **process**. + +### What is a Process + +The word **process** is normally used in a more specific way, only referring to the thing that is running in the operating system (like in the last point above): + +* A particular program while it is **running** on the operating system. + * This doesn't refer to the file, nor to the code, it refers **specifically** to the thing that is being **executed** and managed by the operating system. +* Any program, any code, **can only do things** when it is being **executed**. So, when there's a **process running**. +* The process can be **terminated** (or "killed") by you, or by the operating system. At that point, it stops running/being executed, and it can **no longer do things**. +* Each application that you have running on your computer has some process behind it, each running program, each window, etc. And there are normally many processes running **at the same time** while a computer is on. +* There can be **multiple processes** of the **same program** running at the same time. + +If you check out the "task manager" or "system monitor" (or similar tools) in your operating system, you will be able to see many of those processes running. + +And, for example, you will probably see that there are multiple processes running the same browser program (Firefox, Chrome, Edge, etc). They normally run one process per tab, plus some other extra processes. + + + +--- + +Now that we know the difference between the terms **process** and **program**, let's continue talking about deployments. + +## Running on Startup + +In most cases, when you create a web API, you want it to be **always running**, uninterrupted, so that your clients can always access it. This is of course, unless you have a specific reason why you want it to run only in certain situations, but most of the time you want it constantly running and **available**. + +### In a Remote Server + +When you set up a remote server (a cloud server, a virtual machine, etc.) the simplest thing you can do is to run Uvicorn (or similar) manually, the same way you do when developing locally. + +And it will work and will be useful **during development**. + +But if your connection to the server is lost, the **running process** will probably die. + +And if the server is restarted (for example after updates, or migrations from the cloud provider) you probably **won't notice it**. And because of that, you won't even know that you have to restart the process manually. So, your API will just stay dead. 😱 + +### Run Automatically on Startup + +In general, you will probably want the server program (e.g. Uvicorn) to be started automatically on server startup, and without needing any **human intervention**, to have a process always running with your API (e.g. Uvicorn running your FastAPI app). + +### Separate Program + +To achieve this, you will normally have a **separate program** that would make sure your application is run on startup. And in many cases, it would also make sure other components or applications are also run, for example, a database. + +### Example Tools to Run at Startup + +Some examples of the tools that can do this job are: + +* Docker +* Kubernetes +* Docker Compose +* Docker in Swarm Mode +* Systemd +* Supervisor +* Handled internally by a cloud provider as part of their services +* Others... + +I'll give you more concrete examples in the next chapters. + +## Restarts + +Similar to making sure your application is run on startup, you probably also want to make sure it is **restarted** after failures. + +### We Make Mistakes + +We, as humans, make **mistakes**, all the time. Software almost *always* has **bugs** hidden in different places. 🐛 + +And we as developers keep improving the code as we find those bugs and as we implement new features (possibly adding new bugs too 😅). + +### Small Errors Automatically Handled + +When building web APIs with FastAPI, if there's an error in our code, FastAPI will normally contain it to the single request that triggered the error. 🛡 + +The client will get a **500 Internal Server Error** for that request, but the application will continue working for the next requests instead of just crashing completely. + +### Bigger Errors - Crashes + +Nevertheless, there might be cases where we write some code that **crashes the entire application** making Uvicorn and Python crash. 💥 + +And still, you would probably not want the application to stay dead because there was an error in one place, you probably want it to **continue running** at least for the *path operations* that are not broken. + +### Restart After Crash + +But in those cases with really bad errors that crash the running **process**, you would want an external component that is in charge of **restarting** the process, at least a couple of times... + +!!! tip + ...Although if the whole application is just **crashing immediately** it probably doesn't make sense to keep restarting it forever. But in those cases, you will probably notice it during development, or at least right after deployment. + + So let's focus on the main cases, where it could crash entirely in some particular cases **in the future**, and it still makes sense to restart it. + +You would probably want to have the thing in charge of restarting your application as an **external component**, because by that point, the same application with Uvicorn and Python already crashed, so there's nothing in the same code of the same app that could do anything about it. + +### Example Tools to Restart Automatically + +In most cases, the same tool that is used to **run the program on startup** is also used to handle automatic **restarts**. + +For example, this could be handled by: + +* Docker +* Kubernetes +* Docker Compose +* Docker in Swarm Mode +* Systemd +* Supervisor +* Handled internally by a cloud provider as part of their services +* Others... + +## Replication - Processes and Memory + +With a FastAPI application, using a server program like Uvicorn, running it once in **one process** can serve multiple clients concurrently. + +But in many cases, you will want to run several worker processes at the same time. + +### Multiple Processes - Workers + +If you have more clients than what a single process can handle (for example if the virtual machine is not too big) and you have **multiple cores** in the server's CPU, then you could have **multiple processes** running with the same application at the same time, and distribute all the requests among them. + +When you run **multiple processes** of the same API program, they are commonly called **workers**. + +### Worker Processes and Ports + +Remember from the docs [About HTTPS](./https.md){.internal-link target=_blank} that only one process can be listening on one combination of port and IP address in a server? + +This is still true. + +So, to be able to have **multiple processes** at the same time, there has to be a **single process listening on a port** that then transmits the communication to each worker process in some way. + +### Memory per Process + +Now, when the program loads things in memory, for example, a machine learning model in a variable, or the contents of a large file in a variable, all that **consumes a bit of the memory (RAM)** of the server. + +And multiple processes normally **don't share any memory**. This means that each running process has its own things, variables, and memory. And if you are consuming a large amount of memory in your code, **each process** will consume an equivalent amount of memory. + +### Server Memory + +For example, if your code loads a Machine Learning model with **1 GB in size**, when you run one process with your API, it will consume at least 1 GB of RAM. And if you start **4 processes** (4 workers), each will consume 1 GB of RAM. So in total, your API will consume **4 GB of RAM**. + +And if your remote server or virtual machine only has 3 GB of RAM, trying to load more than 4 GB of RAM will cause problems. 🚨 + +### Multiple Processes - An Example + +In this example, there's a **Manager Process** that starts and controls two **Worker Processes**. + +This Manager Process would probably be the one listening on the **port** in the IP. And it would transmit all the communication to the worker processes. + +Those worker processes would be the ones running your application, they would perform the main computations to receive a **request** and return a **response**, and they would load anything you put in variables in RAM. + + + +And of course, the same machine would probably have **other processes** running as well, apart from your application. + +An interesting detail is that the percentage of the **CPU used** by each process can **vary** a lot over time, but the **memory (RAM)** normally stays more or less **stable**. + +If you have an API that does a comparable amount of computations every time and you have a lot of clients, then the **CPU utilization** will probably *also be stable* (instead of constantly going up and down quickly). + +### Examples of Replication Tools and Strategies + +There can be several approaches to achieve this, and I'll tell you more about specific strategies in the next chapters, for example when talking about Docker and containers. + +The main constraint to consider is that there has to be a **single** component handling the **port** in the **public IP**. And then it has to have a way to **transmit** the communication to the replicated **processes/workers**. + +Here are some possible combinations and strategies: + +* **Gunicorn** managing **Uvicorn workers** + * Gunicorn would be the **process manager** listening on the **IP** and **port**, the replication would be by having **multiple Uvicorn worker processes** +* **Uvicorn** managing **Uvicorn workers** + * One Uvicorn **process manager** would listen on the **IP** and **port**, and it would start **multiple Uvicorn worker processes** +* **Kubernetes** and other distributed **container systems** + * Something in the **Kubernetes** layer would listen on the **IP** and **port**. The replication would be by having **multiple containers**, each with **one Uvicorn process** running +* **Cloud services** that handle this for you + * The cloud service will probably **handle replication for you**. It would possibly let you define **a process to run**, or a **container image** to use, in any case, it would most probably be **a single Uvicorn process**, and the cloud service would be in charge of replicating it. + +!!! tip + Don't worry if some of these items about **containers**, Docker, or Kubernetes don't make a lot of sense yet. + + I'll tell you more about container images, Docker, Kubernetes, etc. in a future chapter: [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank}. + +## Previous Steps Before Starting + +There are many cases where you want to perform some steps **before starting** your application. + +For example, you might want to run **database migrations**. + +But in most cases, you will want to perform these steps only **once**. + +So, you will want to have a **single process** to perform those **previous steps**, before starting the application. + +And you will have to make sure that it's a single process running those previous steps *even* if afterwards, you start **multiple processes** (multiple workers) for the application itself. If those steps were run by **multiple processes**, they would **duplicate** the work by running it on **parallel**, and if the steps were something delicate like a database migration, they could cause conflicts with each other. + +Of course, there are some cases where there's no problem in running the previous steps multiple times, in that case, it's a lot easier to handle. + +!!! tip + Also, have in mind that depending on your setup, in some cases you **might not even need any previous steps** before starting your application. + + In that case, you wouldn't have to worry about any of this. 🤷 + +### Examples of Previous Steps Strategies + +This will **depend heavily** on the way you **deploy your system**, and it would probably be connected to the way you start programs, handling restarts, etc. + +Here are some possible ideas: + +* An "Init Container" in Kubernetes that runs before your app container +* A bash script that runs the previous steps and then starts your application + * You would still need a way to start/restart *that* bash script, detect errors, etc. + +!!! tip + I'll give you more concrete examples for doing this with containers in a future chapter: [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank}. + +## Resource Utilization + +Your server(s) is (are) a **resource**, you can consume or **utilize**, with your programs, the computation time on the CPUs, and the RAM memory available. + +How much of the system resources do you want to be consuming/utilizing? It might be easy to think "not much", but in reality, you will probably want to consume **as much as possible without crashing**. + +If you are paying for 3 servers but you are using only a little bit of their RAM and CPU, you are probably **wasting money** 💸, and probably **wasting server electric power** 🌎, etc. + +In that case, it could be better to have only 2 servers and use a higher percentage of their resources (CPU, memory, disk, network bandwidth, etc). + +On the other hand, if you have 2 servers and you are using **100% of their CPU and RAM**, at some point one process will ask for more memory, and the server will have to use the disk as "memory" (which can be thousands of times slower), or even **crash**. Or one process might need to do some computation and would have to wait until the CPU is free again. + +In this case, it would be better to get **one extra server** and run some processes on it so that they all have **enough RAM and CPU time**. + +There's also the chance that for some reason you have a **spike** of usage of your API. Maybe it went viral, or maybe some other services or bots start using it. And you might want to have extra resources to be safe in those cases. + +You could put an **arbitrary number** to target, for example, something **between 50% to 90%** of resource utilization. The point is that those are probably the main things you will want to measure and use to tweak your deployments. + +You can use simple tools like `htop` to see the CPU and RAM used in your server or the amount used by each process. Or you can use more complex monitoring tools, which may be distributed across servers, etc. + +## Recap + +You have been reading here some of the main concepts that you would probably need to have in mind when deciding how to deploy your application: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +Understanding these ideas and how to apply them should give you the intuition necessary to take any decisions when configuring and tweaking your deployments. 🤓 + +In the next sections, I'll give you more concrete examples of possible strategies you can follow. 🚀 diff --git a/docs/zh/docs/deployment/deta.md b/docs/zh/docs/deployment/deta.md new file mode 100644 index 0000000000000..6b1b4690ecc8d --- /dev/null +++ b/docs/zh/docs/deployment/deta.md @@ -0,0 +1,393 @@ +# Deploy FastAPI on Deta Space + +In this section you will learn how to easily deploy a **FastAPI** application on Deta Space, for free. 🎁 + +It will take you about **10 minutes** to deploy an API that you can use. After that, you can optionally release it to anyone. + +Let's dive in. + +!!! info + Deta is a **FastAPI** sponsor. 🎉 + +## A simple **FastAPI** app + +* To start, create an empty directory with the name of your app, for example `./fastapi-deta/`, and then navigate into it. + +```console +$ mkdir fastapi-deta +$ cd fastapi-deta +``` + +### FastAPI code + +* Create a `main.py` file with: + +```Python +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int): + return {"item_id": item_id} +``` + +### Requirements + +Now, in the same directory create a file `requirements.txt` with: + +```text +fastapi +uvicorn[standard] +``` + +### Directory structure + +You will now have a directory `./fastapi-deta/` with two files: + +``` +. +└── main.py +└── requirements.txt +``` + +## Create a free **Deta Space** account + +Next, create a free account on Deta Space, you just need an email and password. + +You don't even need a credit card, but make sure **Developer Mode** is enabled when you sign up. + + +## Install the CLI + +Once you have your account, install the Deta Space CLI: + +=== "Linux, macOS" + +
+ + ```console + $ curl -fsSL https://get.deta.dev/space-cli.sh | sh + ``` + + +
+ +=== "Windows PowerShell" + +
+ + ```console + $ iwr https://get.deta.dev/space-cli.ps1 -useb | iex + ``` + + +
+ +After installing it, open a new terminal so that the installed CLI is detected. + +In a new terminal, confirm that it was correctly installed with: + +
+ +```console +$ space --help + +Deta command line interface for managing deta micros. +Complete documentation available at https://deta.space/docs + +Usage: + space [flags] + space [command] + +Available Commands: + help Help about any command + link link code to project + login login to space + new create new project + push push code for project + release create release for a project + validate validate spacefile in dir + version Space CLI version +... +``` + +
+ +!!! tip + If you have problems installing the CLI, check the official Deta Space Documentation. + +## Login with the CLI + +In order to authenticate your CLI with Deta Space, you will need an access token. + +To obtain this token, open your Deta Space Canvas, open the **Teletype** (command bar at the bottom of the Canvas), and then click on **Settings**. From there, select **Generate Token** and copy the resulting token. + + + +Now run `space login` from the Space CLI. Upon pasting the token into the CLI prompt and pressing enter, you should see a confirmation message. + +
+ +```console +$ space login + +To authenticate the Space CLI with your Space account, generate a new access token in your Space settings and paste it below: + +# Enter access token (41 chars) >$ ***************************************** + +👍 Login Successful! +``` + +
+ +## Create a new project in Space + +Now that you've authenticated with the Space CLI, use it to create a new Space Project: + +```console +$ space new + +# What is your project's name? >$ fastapi-deta +``` + +The Space CLI will ask you to name the project, we will call ours `fastapi-deta`. + +Then, it will try to automatically detect which framework or language you are using, showing you what it finds. In our case it will identify the Python app with the following message, prompting you to confirm: + +```console +⚙️ No Spacefile found, trying to auto-detect configuration ... +👇 Deta detected the following configuration: + +Micros: +name: fastapi-deta + L src: . + L engine: python3.9 + +# Do you want to bootstrap "fastapi-deta" with this configuration? (y/n)$ y +``` + +After you confirm, your project will be created in Deta Space inside a special app called Builder. Builder is a toolbox that helps you to create and manage your apps in Deta Space. + +The CLI will also create a `Spacefile` locally in the `fastapi-deta` directory. The Spacefile is a configuration file which tells Deta Space how to run your app. The `Spacefile` for your app will be as follows: + +```yaml +v: 0 +micros: + - name: fastapi-deta + src: . + engine: python3.9 +``` + +It is a `yaml` file, and you can use it to add features like scheduled tasks or modify how your app functions, which we'll do later. To learn more, read the `Spacefile` documentation. + +!!! tip + The Space CLI will also create a hidden `.space` folder in your local directory to link your local environment with Deta Space. This folder should not be included in your version control and will automatically be added to your `.gitignore` file, if you have initialized a Git repository. + +## Define the run command in the Spacefile + +The `run` command in the Spacefile tells Space what command should be executed to start your app. In this case it would be `uvicorn main:app`. + +```diff +v: 0 +micros: + - name: fastapi-deta + src: . + engine: python3.9 ++ run: uvicorn main:app +``` + +## Deploy to Deta Space + +To get your FastAPI live in the cloud, use one more CLI command: + +
+ +```console +$ space push + +---> 100% + +build complete... created revision: satyr-jvjk + +✔ Successfully pushed your code and created a new Revision! +ℹ Updating your development instance with the latest Revision, it will be available on your Canvas shortly. +``` +
+ +This command will package your code, upload all the necessary files to Deta Space, and run a remote build of your app, resulting in a **revision**. Whenever you run `space push` successfully, a live instance of your API is automatically updated with the latest revision. + +!!! tip + You can manage your revisions by opening your project in the Builder app. The live copy of your API will be visible under the **Develop** tab in Builder. + +## Check it + +The live instance of your API will also be added automatically to your Canvas (the dashboard) on Deta Space. + + + +Click on the new app called `fastapi-deta`, and it will open your API in a new browser tab on a URL like `https://fastapi-deta-gj7ka8.deta.app/`. + +You will get a JSON response from your FastAPI app: + +```JSON +{ + "Hello": "World" +} +``` + +And now you can head over to the `/docs` of your API. For this example, it would be `https://fastapi-deta-gj7ka8.deta.app/docs`. + + + +## Enable public access + +Deta will handle authentication for your account using cookies. By default, every app or API that you `push` or install to your Space is personal - it's only accessible to you. + +But you can also make your API public using the `Spacefile` from earlier. + +With a `public_routes` parameter, you can specify which paths of your API should be available to the public. + +Set your `public_routes` to `"*"` to open every route of your API to the public: + +```yaml +v: 0 +micros: + - name: fastapi-deta + src: . + engine: python3.9 + public_routes: + - "/*" +``` + +Then run `space push` again to update your live API on Deta Space. + +Once it deploys, you can share your URL with anyone and they will be able to access your API. 🚀 + +## HTTPS + +Congrats! You deployed your FastAPI app to Deta Space! 🎉 🍰 + +Also, notice that Deta Space correctly handles HTTPS for you, so you don't have to take care of that and can be sure that your users will have a secure encrypted connection. ✅ 🔒 + +## Create a release + +Space also allows you to publish your API. When you publish it, anyone else can install their own copy of your API, in their own Deta Space cloud. + +To do so, run `space release` in the Space CLI to create an **unlisted release**: + +
+ +```console +$ space release + +# Do you want to use the latest revision (buzzard-hczt)? (y/n)$ y + +~ Creating a Release with the latest Revision + +---> 100% + +creating release... +publishing release in edge locations.. +completed... +released: fastapi-deta-exp-msbu +https://deta.space/discovery/r/5kjhgyxewkdmtotx + + Lift off -- successfully created a new Release! + Your Release is available globally on 5 Deta Edges + Anyone can install their own copy of your app. +``` +
+ +This command publishes your revision as a release and gives you a link. Anyone you give this link to can install your API. + + +You can also make your app publicly discoverable by creating a **listed release** with `space release --listed` in the Space CLI: + +
+ +```console +$ space release --listed + +# Do you want to use the latest revision (buzzard-hczt)? (y/n)$ y + +~ Creating a listed Release with the latest Revision ... + +creating release... +publishing release in edge locations.. +completed... +released: fastapi-deta-exp-msbu +https://deta.space/discovery/@user/fastapi-deta + + Lift off -- successfully created a new Release! + Your Release is available globally on 5 Deta Edges + Anyone can install their own copy of your app. + Listed on Discovery for others to find! +``` +
+ +This will allow anyone to find and install your app via Deta Discovery. Read more about releasing your app in the docs. + +## Check runtime logs + +Deta Space also lets you inspect the logs of every app you build or install. + +Add some logging functionality to your app by adding a `print` statement to your `main.py` file. + +```py +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int): + print(item_id) + return {"item_id": item_id} +``` + +The code within the `read_item` function includes a print statement that will output the `item_id` that is included in the URL. Send a request to your _path operation_ `/items/{item_id}` from the docs UI (which will have a URL like `https://fastapi-deta-gj7ka8.deta.app/docs`), using an ID like `5` as an example. + +Now go to your Space's Canvas. Click on the context menu (`...`) of your live app instance, and then click on **View Logs**. Here you can view your app's logs, sorted by time. + + + +## Learn more + +At some point, you will probably want to store some data for your app in a way that persists through time. For that you can use Deta Base and Deta Drive, both of which have a generous **free tier**. + +You can also read more in the Deta Space Documentation. + +!!! tip + If you have any Deta related questions, comments, or feedback, head to the Deta Discord server. + + +## Deployment Concepts + +Coming back to the concepts we discussed in [Deployments Concepts](./concepts.md){.internal-link target=_blank}, here's how each of them would be handled with Deta Space: + +- **HTTPS**: Handled by Deta Space, they will give you a subdomain and handle HTTPS automatically. +- **Running on startup**: Handled by Deta Space, as part of their service. +- **Restarts**: Handled by Deta Space, as part of their service. +- **Replication**: Handled by Deta Space, as part of their service. +- **Authentication**: Handled by Deta Space, as part of their service. +- **Memory**: Limit predefined by Deta Space, you could contact them to increase it. +- **Previous steps before starting**: Can be configured using the `Spacefile`. + +!!! note + Deta Space is designed to make it easy and free to build cloud applications for yourself. Then you can optionally share them with anyone. + + It can simplify several use cases, but at the same time, it doesn't support others, like using external databases (apart from Deta's own NoSQL database system), custom virtual machines, etc. + + You can read more details in the Deta Space Documentation to see if it's the right choice for you. diff --git a/docs/zh/docs/deployment/docker.md b/docs/zh/docs/deployment/docker.md new file mode 100644 index 0000000000000..fac4650a2626e --- /dev/null +++ b/docs/zh/docs/deployment/docker.md @@ -0,0 +1,698 @@ +# FastAPI in Containers - Docker + +When deploying FastAPI applications a common approach is to build a **Linux container image**. It's normally done using **Docker**. You can then deploy that container image in one of a few possible ways. + +Using Linux containers has several advantages including **security**, **replicability**, **simplicity**, and others. + +!!! tip + In a hurry and already know this stuff? Jump to the [`Dockerfile` below 👇](#build-a-docker-image-for-fastapi). + +
+Dockerfile Preview 👀 + +```Dockerfile +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] + +# If running behind a proxy like Nginx or Traefik add --proxy-headers +# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] +``` + +
+ +## What is a Container + +Containers (mainly Linux containers) are a very **lightweight** way to package applications including all their dependencies and necessary files while keeping them isolated from other containers (other applications or components) in the same system. + +Linux containers run using the same Linux kernel of the host (machine, virtual machine, cloud server, etc). This just means that they are very lightweight (compared to full virtual machines emulating an entire operating system). + +This way, containers consume **little resources**, an amount comparable to running the processes directly (a virtual machine would consume much more). + +Containers also have their own **isolated** running processes (commonly just one process), file system, and network, simplifying deployment, security, development, etc. + +## What is a Container Image + +A **container** is run from a **container image**. + +A container image is a **static** version of all the files, environment variables, and the default command/program that should be present in a container. **Static** here means that the container **image** is not running, it's not being executed, it's only the packaged files and metadata. + +In contrast to a "**container image**" that is the stored static contents, a "**container**" normally refers to the running instance, the thing that is being **executed**. + +When the **container** is started and running (started from a **container image**) it could create or change files, environment variables, etc. Those changes will exist only in that container, but would not persist in the underlying container image (would not be saved to disk). + +A container image is comparable to the **program** file and contents, e.g. `python` and some file `main.py`. + +And the **container** itself (in contrast to the **container image**) is the actual running instance of the image, comparable to a **process**. In fact, a container is running only when it has a **process running** (and normally it's only a single process). The container stops when there's no process running in it. + +## Container Images + +Docker has been one of the main tools to create and manage **container images** and **containers**. + +And there's a public Docker Hub with pre-made **official container images** for many tools, environments, databases, and applications. + +For example, there's an official Python Image. + +And there are many other images for different things like databases, for example for: + +* PostgreSQL +* MySQL +* MongoDB +* Redis, etc. + +By using a pre-made container image it's very easy to **combine** and use different tools. For example, to try out a new database. In most cases, you can use the **official images**, and just configure them with environment variables. + +That way, in many cases you can learn about containers and Docker and re-use that knowledge with many different tools and components. + +So, you would run **multiple containers** with different things, like a database, a Python application, a web server with a React frontend application, and connect them together via their internal network. + +All the container management systems (like Docker or Kubernetes) have these networking features integrated into them. + +## Containers and Processes + +A **container image** normally includes in its metadata the default program or command that should be run when the **container** is started and the parameters to be passed to that program. Very similar to what would be if it was in the command line. + +When a **container** is started, it will run that command/program (although you can override it and make it run a different command/program). + +A container is running as long as the **main process** (command or program) is running. + +A container normally has a **single process**, but it's also possible to start subprocesses from the main process, and that way you will have **multiple processes** in the same container. + +But it's not possible to have a running container without **at least one running process**. If the main process stops, the container stops. + +## Build a Docker Image for FastAPI + +Okay, let's build something now! 🚀 + +I'll show you how to build a **Docker image** for FastAPI **from scratch**, based on the **official Python** image. + +This is what you would want to do in **most cases**, for example: + +* Using **Kubernetes** or similar tools +* When running on a **Raspberry Pi** +* Using a cloud service that would run a container image for you, etc. + +### Package Requirements + +You would normally have the **package requirements** for your application in some file. + +It would depend mainly on the tool you use to **install** those requirements. + +The most common way to do it is to have a file `requirements.txt` with the package names and their versions, one per line. + +You would of course use the same ideas you read in [About FastAPI versions](./versions.md){.internal-link target=_blank} to set the ranges of versions. + +For example, your `requirements.txt` could look like: + +``` +fastapi>=0.68.0,<0.69.0 +pydantic>=1.8.0,<2.0.0 +uvicorn>=0.15.0,<0.16.0 +``` + +And you would normally install those package dependencies with `pip`, for example: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic uvicorn +``` + +
+ +!!! info + There are other formats and tools to define and install package dependencies. + + I'll show you an example using Poetry later in a section below. 👇 + +### Create the **FastAPI** Code + +* Create an `app` directory and enter it. +* Create an empty file `__init__.py`. +* Create a `main.py` file with: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +### Dockerfile + +Now in the same project directory create a file `Dockerfile` with: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 + +# (2) +WORKDIR /code + +# (3) +COPY ./requirements.txt /code/requirements.txt + +# (4) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5) +COPY ./app /code/app + +# (6) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Start from the official Python base image. + +2. Set the current working directory to `/code`. + + This is where we'll put the `requirements.txt` file and the `app` directory. + +3. Copy the file with the requirements to the `/code` directory. + + Copy **only** the file with the requirements first, not the rest of the code. + + As this file **doesn't change often**, Docker will detect it and use the **cache** for this step, enabling the cache for the next step too. + +4. Install the package dependencies in the requirements file. + + The `--no-cache-dir` option tells `pip` to not save the downloaded packages locally, as that is only if `pip` was going to be run again to install the same packages, but that's not the case when working with containers. + + !!! note + The `--no-cache-dir` is only related to `pip`, it has nothing to do with Docker or containers. + + The `--upgrade` option tells `pip` to upgrade the packages if they are already installed. + + Because the previous step copying the file could be detected by the **Docker cache**, this step will also **use the Docker cache** when available. + + Using the cache in this step will **save** you a lot of **time** when building the image again and again during development, instead of **downloading and installing** all the dependencies **every time**. + +5. Copy the `./app` directory inside the `/code` directory. + + As this has all the code which is what **changes most frequently** the Docker **cache** won't be used for this or any **following steps** easily. + + So, it's important to put this **near the end** of the `Dockerfile`, to optimize the container image build times. + +6. Set the **command** to run the `uvicorn` server. + + `CMD` takes a list of strings, each of these strings is what you would type in the command line separated by spaces. + + This command will be run from the **current working directory**, the same `/code` directory you set above with `WORKDIR /code`. + + Because the program will be started at `/code` and inside of it is the directory `./app` with your code, **Uvicorn** will be able to see and **import** `app` from `app.main`. + +!!! tip + Review what each line does by clicking each number bubble in the code. 👆 + +You should now have a directory structure like: + +``` +. +├── app +│   ├── __init__.py +│ └── main.py +├── Dockerfile +└── requirements.txt +``` + +#### Behind a TLS Termination Proxy + +If you are running your container behind a TLS Termination Proxy (load balancer) like Nginx or Traefik, add the option `--proxy-headers`, this will tell Uvicorn to trust the headers sent by that proxy telling it that the application is running behind HTTPS, etc. + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +#### Docker Cache + +There's an important trick in this `Dockerfile`, we first copy the **file with the dependencies alone**, not the rest of the code. Let me tell you why is that. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker and other tools **build** these container images **incrementally**, adding **one layer on top of the other**, starting from the top of the `Dockerfile` and adding any files created by each of the instructions of the `Dockerfile`. + +Docker and similar tools also use an **internal cache** when building the image, if a file hasn't changed since the last time building the container image, then it will **re-use the same layer** created the last time, instead of copying the file again and creating a new layer from scratch. + +Just avoiding the copy of files doesn't necessarily improve things too much, but because it used the cache for that step, it can **use the cache for the next step**. For example, it could use the cache for the instruction that installs dependencies with: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +The file with the package requirements **won't change frequently**. So, by copying only that file, Docker will be able to **use the cache** for that step. + +And then, Docker will be able to **use the cache for the next step** that downloads and install those dependencies. And here's where we **save a lot of time**. ✨ ...and avoid boredom waiting. 😪😆 + +Downloading and installing the package dependencies **could take minutes**, but using the **cache** would **take seconds** at most. + +And as you would be building the container image again and again during development to check that your code changes are working, there's a lot of accumulated time this would save. + +Then, near the end of the `Dockerfile`, we copy all the code. As this is what **changes most frequently**, we put it near the end, because almost always, anything after this step will not be able to use the cache. + +```Dockerfile +COPY ./app /code/app +``` + +### Build the Docker Image + +Now that all the files are in place, let's build the container image. + +* Go to the project directory (in where your `Dockerfile` is, containing your `app` directory). +* Build your FastAPI image: + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +!!! tip + Notice the `.` at the end, it's equivalent to `./`, it tells Docker the directory to use to build the container image. + + In this case, it's the same current directory (`.`). + +### Start the Docker Container + +* Run a container based on your image: + +
+ +```console +$ docker run -d --name mycontainer -p 80:80 myimage +``` + +
+ +## Check it + +You should be able to check it in your Docker container's URL, for example: http://192.168.99.100/items/5?q=somequery or http://127.0.0.1/items/5?q=somequery (or equivalent, using your Docker host). + +You will see something like: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## Interactive API docs + +Now you can go to http://192.168.99.100/docs or http://127.0.0.1/docs (or equivalent, using your Docker host). + +You will see the automatic interactive API documentation (provided by Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## Alternative API docs + +And you can also go to http://192.168.99.100/redoc or http://127.0.0.1/redoc (or equivalent, using your Docker host). + +You will see the alternative automatic documentation (provided by ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Build a Docker Image with a Single-File FastAPI + +If your FastAPI is a single file, for example, `main.py` without an `./app` directory, your file structure could look like this: + +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` + +Then you would just have to change the corresponding paths to copy the file inside the `Dockerfile`: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1) +COPY ./main.py /code/ + +# (2) +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. Copy the `main.py` file to the `/code` directory directly (without any `./app` directory). + +2. Run Uvicorn and tell it to import the `app` object from `main` (instead of importing from `app.main`). + +Then adjust the Uvicorn command to use the new module `main` instead of `app.main` to import the FastAPI object `app`. + +## Deployment Concepts + +Let's talk again about some of the same [Deployment Concepts](./concepts.md){.internal-link target=_blank} in terms of containers. + +Containers are mainly a tool to simplify the process of **building and deploying** an application, but they don't enforce a particular approach to handle these **deployment concepts**, and there are several possible strategies. + +The **good news** is that with each different strategy there's a way to cover all of the deployment concepts. 🎉 + +Let's review these **deployment concepts** in terms of containers: + +* HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +## HTTPS + +If we focus just on the **container image** for a FastAPI application (and later the running **container**), HTTPS normally would be handled **externally** by another tool. + +It could be another container, for example with Traefik, handling **HTTPS** and **automatic** acquisition of **certificates**. + +!!! tip + Traefik has integrations with Docker, Kubernetes, and others, so it's very easy to set up and configure HTTPS for your containers with it. + +Alternatively, HTTPS could be handled by a cloud provider as one of their services (while still running the application in a container). + +## Running on Startup and Restarts + +There is normally another tool in charge of **starting and running** your container. + +It could be **Docker** directly, **Docker Compose**, **Kubernetes**, a **cloud service**, etc. + +In most (or all) cases, there's a simple option to enable running the container on startup and enabling restarts on failures. For example, in Docker, it's the command line option `--restart`. + +Without using containers, making applications run on startup and with restarts can be cumbersome and difficult. But when **working with containers** in most cases that functionality is included by default. ✨ + +## Replication - Number of Processes + +If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Gunicorn with workers) in each container. + +One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**. + +In those cases, you would probably want to build a **Docker image from scratch** as [explained above](#dockerfile), installing your dependencies, and running **a single Uvicorn process** instead of running something like Gunicorn with Uvicorn workers. + +### Load Balancer + +When using containers, you would normally have some component **listening on the main port**. It could possibly be another container that is also a **TLS Termination Proxy** to handle **HTTPS** or some similar tool. + +As this component would take the **load** of requests and distribute that among the workers in a (hopefully) **balanced** way, it is also commonly called a **Load Balancer**. + +!!! tip + The same **TLS Termination Proxy** component used for HTTPS would probably also be a **Load Balancer**. + +And when working with containers, the same system you use to start and manage them would already have internal tools to transmit the **network communication** (e.g. HTTP requests) from that **load balancer** (that could also be a **TLS Termination Proxy**) to the container(s) with your app. + +### One Load Balancer - Multiple Worker Containers + +When working with **Kubernetes** or similar distributed container management systems, using their internal networking mechanisms would allow the single **load balancer** that is listening on the main **port** to transmit communication (requests) to possibly **multiple containers** running your app. + +Each of these containers running your app would normally have **just one process** (e.g. a Uvicorn process running your FastAPI application). They would all be **identical containers**, running the same thing, but each with its own process, memory, etc. That way you would take advantage of **parallelization** in **different cores** of the CPU, or even in **different machines**. + +And the distributed container system with the **load balancer** would **distribute the requests** to each one of the containers with your app **in turns**. So, each request could be handled by one of the multiple **replicated containers** running your app. + +And normally this **load balancer** would be able to handle requests that go to *other* apps in your cluster (e.g. to a different domain, or under a different URL path prefix), and would transmit that communication to the right containers for *that other* application running in your cluster. + +### One Process per Container + +In this type of scenario, you probably would want to have **a single (Uvicorn) process per container**, as you would already be handling replication at the cluster level. + +So, in this case, you **would not** want to have a process manager like Gunicorn with Uvicorn workers, or Uvicorn using its own Uvicorn workers. You would want to have just a **single Uvicorn process** per container (but probably multiple containers). + +Having another process manager inside the container (as would be with Gunicorn or Uvicorn managing Uvicorn workers) would only add **unnecessary complexity** that you are most probably already taking care of with your cluster system. + +### Containers with Multiple Processes and Special Cases + +Of course, there are **special cases** where you could want to have **a container** with a **Gunicorn process manager** starting several **Uvicorn worker processes** inside. + +In those cases, you can use the **official Docker image** that includes **Gunicorn** as a process manager running multiple **Uvicorn worker processes**, and some default settings to adjust the number of workers based on the current CPU cores automatically. I'll tell you more about it below in [Official Docker Image with Gunicorn - Uvicorn](#official-docker-image-with-gunicorn-uvicorn). + +Here are some examples of when that could make sense: + +#### A Simple App + +You could want a process manager in the container if your application is **simple enough** that you don't need (at least not yet) to fine-tune the number of processes too much, and you can just use an automated default (with the official Docker image), and you are running it on a **single server**, not a cluster. + +#### Docker Compose + +You could be deploying to a **single server** (not a cluster) with **Docker Compose**, so you wouldn't have an easy way to manage replication of containers (with Docker Compose) while preserving the shared network and **load balancing**. + +Then you could want to have **a single container** with a **process manager** starting **several worker processes** inside. + +#### Prometheus and Other Reasons + +You could also have **other reasons** that would make it easier to have a **single container** with **multiple processes** instead of having **multiple containers** with **a single process** in each of them. + +For example (depending on your setup) you could have some tool like a Prometheus exporter in the same container that should have access to **each of the requests** that come. + +In this case, if you had **multiple containers**, by default, when Prometheus came to **read the metrics**, it would get the ones for **a single container each time** (for the container that handled that particular request), instead of getting the **accumulated metrics** for all the replicated containers. + +Then, in that case, it could be simpler to have **one container** with **multiple processes**, and a local tool (e.g. a Prometheus exporter) on the same container collecting Prometheus metrics for all the internal processes and exposing those metrics on that single container. + +--- + +The main point is, **none** of these are **rules written in stone** that you have to blindly follow. You can use these ideas to **evaluate your own use case** and decide what is the best approach for your system, checking out how to manage the concepts of: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +## Memory + +If you run **a single process per container** you will have a more or less well-defined, stable, and limited amount of memory consumed by each of those containers (more than one if they are replicated). + +And then you can set those same memory limits and requirements in your configurations for your container management system (for example in **Kubernetes**). That way it will be able to **replicate the containers** in the **available machines** taking into account the amount of memory needed by them, and the amount available in the machines in the cluster. + +If your application is **simple**, this will probably **not be a problem**, and you might not need to specify hard memory limits. But if you are **using a lot of memory** (for example with **machine learning** models), you should check how much memory you are consuming and adjust the **number of containers** that runs in **each machine** (and maybe add more machines to your cluster). + +If you run **multiple processes per container** (for example with the official Docker image) you will have to make sure that the number of processes started doesn't **consume more memory** than what is available. + +## Previous Steps Before Starting and Containers + +If you are using containers (e.g. Docker, Kubernetes), then there are two main approaches you can use. + +### Multiple Containers + +If you have **multiple containers**, probably each one running a **single process** (for example, in a **Kubernetes** cluster), then you would probably want to have a **separate container** doing the work of the **previous steps** in a single container, running a single process, **before** running the replicated worker containers. + +!!! info + If you are using Kubernetes, this would probably be an Init Container. + +If in your use case there's no problem in running those previous steps **multiple times in parallel** (for example if you are not running database migrations, but just checking if the database is ready yet), then you could also just put them in each container right before starting the main process. + +### Single Container + +If you have a simple setup, with a **single container** that then starts multiple **worker processes** (or also just one process), then you could run those previous steps in the same container, right before starting the process with the app. The official Docker image supports this internally. + +## Official Docker Image with Gunicorn - Uvicorn + +There is an official Docker image that includes Gunicorn running with Uvicorn workers, as detailed in a previous chapter: [Server Workers - Gunicorn with Uvicorn](./server-workers.md){.internal-link target=_blank}. + +This image would be useful mainly in the situations described above in: [Containers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases). + +* tiangolo/uvicorn-gunicorn-fastapi. + +!!! warning + There's a high chance that you **don't** need this base image or any other similar one, and would be better off by building the image from scratch as [described above in: Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). + +This image has an **auto-tuning** mechanism included to set the **number of worker processes** based on the CPU cores available. + +It has **sensible defaults**, but you can still change and update all the configurations with **environment variables** or configuration files. + +It also supports running **previous steps before starting** with a script. + +!!! tip + To see all the configurations and options, go to the Docker image page: tiangolo/uvicorn-gunicorn-fastapi. + +### Number of Processes on the Official Docker Image + +The **number of processes** on this image is **computed automatically** from the CPU **cores** available. + +This means that it will try to **squeeze** as much **performance** from the CPU as possible. + +You can also adjust it with the configurations using **environment variables**, etc. + +But it also means that as the number of processes depends on the CPU the container is running, the **amount of memory consumed** will also depend on that. + +So, if your application consumes a lot of memory (for example with machine learning models), and your server has a lot of CPU cores **but little memory**, then your container could end up trying to use more memory than what is available, and degrading performance a lot (or even crashing). 🚨 + +### Create a `Dockerfile` + +Here's how you would create a `Dockerfile` based on this image: + +```Dockerfile +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +### Bigger Applications + +If you followed the section about creating [Bigger Applications with Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}, your `Dockerfile` might instead look like: + +```Dockerfile hl_lines="7" +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app/app +``` + +### When to Use + +You should probably **not** use this official base image (or any other similar one) if you are using **Kubernetes** (or others) and you are already setting **replication** at the cluster level, with multiple **containers**. In those cases, you are better off **building an image from scratch** as described above: [Build a Docker Image for FastAPI](#build-a-docker-image-for-fastapi). + +This image would be useful mainly in the special cases described above in [Containers with Multiple Processes and Special Cases](#containers-with-multiple-processes-and-special-cases). For example, if your application is **simple enough** that setting a default number of processes based on the CPU works well, you don't want to bother with manually configuring the replication at the cluster level, and you are not running more than one container with your app. Or if you are deploying with **Docker Compose**, running on a single server, etc. + +## Deploy the Container Image + +After having a Container (Docker) Image there are several ways to deploy it. + +For example: + +* With **Docker Compose** in a single server +* With a **Kubernetes** cluster +* With a Docker Swarm Mode cluster +* With another tool like Nomad +* With a cloud service that takes your container image and deploys it + +## Docker Image with Poetry + +If you use Poetry to manage your project's dependencies, you could use Docker multi-stage building: + +```{ .dockerfile .annotate } +# (1) +FROM python:3.9 as requirements-stage + +# (2) +WORKDIR /tmp + +# (3) +RUN pip install poetry + +# (4) +COPY ./pyproject.toml ./poetry.lock* /tmp/ + +# (5) +RUN poetry export -f requirements.txt --output requirements.txt --without-hashes + +# (6) +FROM python:3.9 + +# (7) +WORKDIR /code + +# (8) +COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt + +# (9) +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (10) +COPY ./app /code/app + +# (11) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +1. This is the first stage, it is named `requirements-stage`. + +2. Set `/tmp` as the current working directory. + + Here's where we will generate the file `requirements.txt` + +3. Install Poetry in this Docker stage. + +4. Copy the `pyproject.toml` and `poetry.lock` files to the `/tmp` directory. + + Because it uses `./poetry.lock*` (ending with a `*`), it won't crash if that file is not available yet. + +5. Generate the `requirements.txt` file. + +6. This is the final stage, anything here will be preserved in the final container image. + +7. Set the current working directory to `/code`. + +8. Copy the `requirements.txt` file to the `/code` directory. + + This file only lives in the previous Docker stage, that's why we use `--from-requirements-stage` to copy it. + +9. Install the package dependencies in the generated `requirements.txt` file. + +10. Copy the `app` directory to the `/code` directory. + +11. Run the `uvicorn` command, telling it to use the `app` object imported from `app.main`. + +!!! tip + Click the bubble numbers to see what each line does. + +A **Docker stage** is a part of a `Dockerfile` that works as a **temporary container image** that is only used to generate some files to be used later. + +The first stage will only be used to **install Poetry** and to **generate the `requirements.txt`** with your project dependencies from Poetry's `pyproject.toml` file. + +This `requirements.txt` file will be used with `pip` later in the **next stage**. + +In the final container image **only the final stage** is preserved. The previous stage(s) will be discarded. + +When using Poetry, it would make sense to use **Docker multi-stage builds** because you don't really need to have Poetry and its dependencies installed in the final container image, you **only need** to have the generated `requirements.txt` file to install your project dependencies. + +Then in the next (and final) stage you would build the image more or less in the same way as described before. + +### Behind a TLS Termination Proxy - Poetry + +Again, if you are running your container behind a TLS Termination Proxy (load balancer) like Nginx or Traefik, add the option `--proxy-headers` to the command: + +```Dockerfile +CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +``` + +## Recap + +Using container systems (e.g. with **Docker** and **Kubernetes**) it becomes fairly straightforward to handle all the **deployment concepts**: + +* HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +In most cases, you probably won't want to use any base image, and instead **build a container image from scratch** one based on the official Python Docker image. + +Taking care of the **order** of instructions in the `Dockerfile` and the **Docker cache** you can **minimize build times**, to maximize your productivity (and avoid boredom). 😎 + +In certain special cases, you might want to use the official Docker image for FastAPI. 🤓 diff --git a/docs/zh/docs/deployment/https.md b/docs/zh/docs/deployment/https.md new file mode 100644 index 0000000000000..75b7d85132831 --- /dev/null +++ b/docs/zh/docs/deployment/https.md @@ -0,0 +1,190 @@ +# About HTTPS + +It is easy to assume that HTTPS is something that is just "enabled" or not. + +But it is way more complex than that. + +!!! tip + If you are in a hurry or don't care, continue with the next sections for step by step instructions to set everything up with different techniques. + +To **learn the basics of HTTPS**, from a consumer perspective, check https://howhttps.works/. + +Now, from a **developer's perspective**, here are several things to have in mind while thinking about HTTPS: + +* For HTTPS, **the server** needs to **have "certificates"** generated by a **third party**. + * Those certificates are actually **acquired** from the third party, not "generated". +* Certificates have a **lifetime**. + * They **expire**. + * And then they need to be **renewed**, **acquired again** from the third party. +* The encryption of the connection happens at the **TCP level**. + * That's one layer **below HTTP**. + * So, the **certificate and encryption** handling is done **before HTTP**. +* **TCP doesn't know about "domains"**. Only about IP addresses. + * The information about the **specific domain** requested goes in the **HTTP data**. +* The **HTTPS certificates** "certify" a **certain domain**, but the protocol and encryption happen at the TCP level, **before knowing** which domain is being dealt with. +* **By default**, that would mean that you can only have **one HTTPS certificate per IP address**. + * No matter how big your server is or how small each application you have on it might be. + * There is a **solution** to this, however. +* There's an **extension** to the **TLS** protocol (the one handling the encryption at the TCP level, before HTTP) called **SNI**. + * This SNI extension allows one single server (with a **single IP address**) to have **several HTTPS certificates** and serve **multiple HTTPS domains/applications**. + * For this to work, a **single** component (program) running on the server, listening on the **public IP address**, must have **all the HTTPS certificates** in the server. +* **After** obtaining a secure connection, the communication protocol is **still HTTP**. + * The contents are **encrypted**, even though they are being sent with the **HTTP protocol**. + +It is a common practice to have **one program/HTTP server** running on the server (the machine, host, etc.) and **managing all the HTTPS parts**: receiving the **encrypted HTTPS requests**, sending the **decrypted HTTP requests** to the actual HTTP application running in the same server (the **FastAPI** application, in this case), take the **HTTP response** from the application, **encrypt it** using the appropriate **HTTPS certificate** and sending it back to the client using **HTTPS**. This server is often called a **TLS Termination Proxy**. + +Some of the options you could use as a TLS Termination Proxy are: + +* Traefik (that can also handle certificate renewals) +* Caddy (that can also handle certificate renewals) +* Nginx +* HAProxy + +## Let's Encrypt + +Before Let's Encrypt, these **HTTPS certificates** were sold by trusted third parties. + +The process to acquire one of these certificates used to be cumbersome, require quite some paperwork and the certificates were quite expensive. + +But then **Let's Encrypt** was created. + +It is a project from the Linux Foundation. It provides **HTTPS certificates for free**, in an automated way. These certificates use all the standard cryptographic security, and are short-lived (about 3 months), so the **security is actually better** because of their reduced lifespan. + +The domains are securely verified and the certificates are generated automatically. This also allows automating the renewal of these certificates. + +The idea is to automate the acquisition and renewal of these certificates so that you can have **secure HTTPS, for free, forever**. + +## HTTPS for Developers + +Here's an example of how an HTTPS API could look like, step by step, paying attention mainly to the ideas important for developers. + +### Domain Name + +It would probably all start by you **acquiring** some **domain name**. Then, you would configure it in a DNS server (possibly your same cloud provider). + +You would probably get a cloud server (a virtual machine) or something similar, and it would have a fixed **public IP address**. + +In the DNS server(s) you would configure a record (an "`A record`") to point **your domain** to the public **IP address of your server**. + +You would probably do this just once, the first time, when setting everything up. + +!!! tip + This Domain Name part is way before HTTPS, but as everything depends on the domain and the IP address, it's worth mentioning it here. + +### DNS + +Now let's focus on all the actual HTTPS parts. + +First, the browser would check with the **DNS servers** what is the **IP for the domain**, in this case, `someapp.example.com`. + +The DNS servers would tell the browser to use some specific **IP address**. That would be the public IP address used by your server, that you configured in the DNS servers. + + + +### TLS Handshake Start + +The browser would then communicate with that IP address on **port 443** (the HTTPS port). + +The first part of the communication is just to establish the connection between the client and the server and to decide the cryptographic keys they will use, etc. + + + +This interaction between the client and the server to establish the TLS connection is called the **TLS handshake**. + +### TLS with SNI Extension + +**Only one process** in the server can be listening on a specific **port** in a specific **IP address**. There could be other processes listening on other ports in the same IP address, but only one for each combination of IP address and port. + +TLS (HTTPS) uses the specific port `443` by default. So that's the port we would need. + +As only one process can be listening on this port, the process that would do it would be the **TLS Termination Proxy**. + +The TLS Termination Proxy would have access to one or more **TLS certificates** (HTTPS certificates). + +Using the **SNI extension** discussed above, the TLS Termination Proxy would check which of the TLS (HTTPS) certificates available it should use for this connection, using the one that matches the domain expected by the client. + +In this case, it would use the certificate for `someapp.example.com`. + + + +The client already **trusts** the entity that generated that TLS certificate (in this case Let's Encrypt, but we'll see about that later), so it can **verify** that the certificate is valid. + +Then, using the certificate, the client and the TLS Termination Proxy **decide how to encrypt** the rest of the **TCP communication**. This completes the **TLS Handshake** part. + +After this, the client and the server have an **encrypted TCP connection**, this is what TLS provides. And then they can use that connection to start the actual **HTTP communication**. + +And that's what **HTTPS** is, it's just plain **HTTP** inside a **secure TLS connection** instead of a pure (unencrypted) TCP connection. + +!!! tip + Notice that the encryption of the communication happens at the **TCP level**, not at the HTTP level. + +### HTTPS Request + +Now that the client and server (specifically the browser and the TLS Termination Proxy) have an **encrypted TCP connection**, they can start the **HTTP communication**. + +So, the client sends an **HTTPS request**. This is just an HTTP request through an encrypted TLS connection. + + + +### Decrypt the Request + +The TLS Termination Proxy would use the encryption agreed to **decrypt the request**, and would transmit the **plain (decrypted) HTTP request** to the process running the application (for example a process with Uvicorn running the FastAPI application). + + + +### HTTP Response + +The application would process the request and send a **plain (unencrypted) HTTP response** to the TLS Termination Proxy. + + + +### HTTPS Response + +The TLS Termination Proxy would then **encrypt the response** using the cryptography agreed before (that started with the certificate for `someapp.example.com`), and send it back to the browser. + +Next, the browser would verify that the response is valid and encrypted with the right cryptographic key, etc. It would then **decrypt the response** and process it. + + + +The client (browser) will know that the response comes from the correct server because it is using the cryptography they agreed using the **HTTPS certificate** before. + +### Multiple Applications + +In the same server (or servers), there could be **multiple applications**, for example, other API programs or a database. + +Only one process can be handling the specific IP and port (the TLS Termination Proxy in our example) but the other applications/processes can be running on the server(s) too, as long as they don't try to use the same **combination of public IP and port**. + + + +That way, the TLS Termination Proxy could handle HTTPS and certificates for **multiple domains**, for multiple applications, and then transmit the requests to the right application in each case. + +### Certificate Renewal + +At some point in the future, each certificate would **expire** (about 3 months after acquiring it). + +And then, there would be another program (in some cases it's another program, in some cases it could be the same TLS Termination Proxy) that would talk to Let's Encrypt, and renew the certificate(s). + + + +The **TLS certificates** are **associated with a domain name**, not with an IP address. + +So, to renew the certificates, the renewal program needs to **prove** to the authority (Let's Encrypt) that it indeed **"owns" and controls that domain**. + +To do that, and to accommodate different application needs, there are several ways it can do it. Some popular ways are: + +* **Modify some DNS records**. + * For this, the renewal program needs to support the APIs of the DNS provider, so, depending on the DNS provider you are using, this might or might not be an option. +* **Run as a server** (at least during the certificate acquisition process) on the public IP address associated with the domain. + * As we said above, only one process can be listening on a specific IP and port. + * This is one of the reasons why it's very useful when the same TLS Termination Proxy also takes care of the certificate renewal process. + * Otherwise, you might have to stop the TLS Termination Proxy momentarily, start the renewal program to acquire the certificates, then configure them with the TLS Termination Proxy, and then restart the TLS Termination Proxy. This is not ideal, as your app(s) will not be available during the time that the TLS Termination Proxy is off. + +All this renewal process, while still serving the app, is one of the main reasons why you would want to have a **separate system to handle HTTPS** with a TLS Termination Proxy instead of just using the TLS certificates with the application server directly (e.g. Uvicorn). + +## Recap + +Having **HTTPS** is very important, and quite **critical** in most cases. Most of the effort you as a developer have to put around HTTPS is just about **understanding these concepts** and how they work. + +But once you know the basic information of **HTTPS for developers** you can easily combine and configure different tools to help you manage everything in a simple way. + +In some of the next chapters, I'll show you several concrete examples of how to set up **HTTPS** for **FastAPI** applications. 🔒 diff --git a/docs/zh/docs/deployment/index.md b/docs/zh/docs/deployment/index.md new file mode 100644 index 0000000000000..6c43d8abbe4db --- /dev/null +++ b/docs/zh/docs/deployment/index.md @@ -0,0 +1,21 @@ +# Deployment + +Deploying a **FastAPI** application is relatively easy. + +## What Does Deployment Mean + +To **deploy** an application means to perform the necessary steps to make it **available to the users**. + +For a **web API**, it normally involves putting it in a **remote machine**, with a **server program** that provides good performance, stability, etc, so that your **users** can **access** the application efficiently and without interruptions or problems. + +This is in contrast to the **development** stages, where you are constantly changing the code, breaking it and fixing it, stopping and restarting the development server, etc. + +## Deployment Strategies + +There are several ways to do it depending on your specific use case and the tools that you use. + +You could **deploy a server** yourself using a combination of tools, you could use a **cloud service** that does part of the work for you, or other possible options. + +I will show you some of the main concepts you should probably have in mind when deploying a **FastAPI** application (although most of it applies to any other type of web application). + +You will see more details to have in mind and some of the techniques to do it in the next sections. ✨ diff --git a/docs/zh/docs/deployment/manually.md b/docs/zh/docs/deployment/manually.md new file mode 100644 index 0000000000000..8238fa607ab30 --- /dev/null +++ b/docs/zh/docs/deployment/manually.md @@ -0,0 +1,141 @@ +# Run a Server Manually - Uvicorn + +The main thing you need to run a **FastAPI** application in a remote server machine is an ASGI server program like **Uvicorn**. + +There are 3 main alternatives: + +* Uvicorn: a high performance ASGI server. +* Hypercorn: an ASGI server compatible with HTTP/2 and Trio among other features. +* Daphne: the ASGI server built for Django Channels. + +## Server Machine and Server Program + +There's a small detail about names to have in mind. 💡 + +The word "**server**" is commonly used to refer to both the remote/cloud computer (the physical or virtual machine) and also the program that is running on that machine (e.g. Uvicorn). + +Just have that in mind when you read "server" in general, it could refer to one of those two things. + +When referring to the remote machine, it's common to call it **server**, but also **machine**, **VM** (virtual machine), **node**. Those all refer to some type of remote machine, normally running Linux, where you run programs. + +## Install the Server Program + +You can install an ASGI compatible server with: + +=== "Uvicorn" + + * Uvicorn, a lightning-fast ASGI server, built on uvloop and httptools. + +
+B1B + + + +
+ + !!! tip + By adding the `standard`, Uvicorn will install and use some recommended extra dependencies. + + That including `uvloop`, the high-performance drop-in replacement for `asyncio`, that provides the big concurrency performance boost. + +=== "Hypercorn" + + * Hypercorn, an ASGI server also compatible with HTTP/2. + +
+B2B + + + +
+ + ...or any other ASGI server. + +## Run the Server Program + +You can then run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.: + +=== "Uvicorn" + +
+ + ```console + $ uvicorn main:app --host 0.0.0.0 --port 80 + + INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) + ``` + + +
+ +=== "Hypercorn" + +
+ + ```console + $ hypercorn main:app --bind 0.0.0.0:80 + + Running on 0.0.0.0:8080 over http (CTRL + C to quit) + ``` + + +
+ +!!! warning + Remember to remove the `--reload` option if you were using it. + + The `--reload` option consumes much more resources, is more unstable, etc. + + It helps a lot during **development**, but you **shouldn't** use it in **production**. + +## Hypercorn with Trio + +Starlette and **FastAPI** are based on AnyIO, which makes them compatible with both Python's standard library asyncio and Trio. + +Nevertheless, Uvicorn is currently only compatible with asyncio, and it normally uses `uvloop`, the high-performance drop-in replacement for `asyncio`. + +But if you want to directly use **Trio**, then you can use **Hypercorn** as it supports it. ✨ + +### Install Hypercorn with Trio + +First you need to install Hypercorn with Trio support: + +
+ +```console +$ pip install "hypercorn[trio]" +---> 100% +``` + +
+ +### Run with Trio + +Then you can pass the command line option `--worker-class` with the value `trio`: + +
+ +```console +$ hypercorn main:app --worker-class trio +``` + +
+ +And that will start Hypercorn with your app using Trio as the backend. + +Now you can use Trio internally in your app. Or even better, you can use AnyIO, to keep your code compatible with both Trio and asyncio. 🎉 + +## Deployment Concepts + +These examples run the server program (e.g Uvicorn), starting **a single process**, listening on all the IPs (`0.0.0.0`) on a predefined port (e.g. `80`). + +This is the basic idea. But you will probably want to take care of some additional things, like: + +* Security - HTTPS +* Running on startup +* Restarts +* Replication (the number of processes running) +* Memory +* Previous steps before starting + +I'll tell you more about each of these concepts, how to think about them, and some concrete examples with strategies to handle them in the next chapters. 🚀 diff --git a/docs/zh/docs/deployment/server-workers.md b/docs/zh/docs/deployment/server-workers.md new file mode 100644 index 0000000000000..4ccd9d9f69a8b --- /dev/null +++ b/docs/zh/docs/deployment/server-workers.md @@ -0,0 +1,178 @@ +# Server Workers - Gunicorn with Uvicorn + +Let's check back those deployment concepts from before: + +* Security - HTTPS +* Running on startup +* Restarts +* **Replication (the number of processes running)** +* Memory +* Previous steps before starting + +Up to this point, with all the tutorials in the docs, you have probably been running a **server program** like Uvicorn, running a **single process**. + +When deploying applications you will probably want to have some **replication of processes** to take advantage of **multiple cores** and to be able to handle more requests. + +As you saw in the previous chapter about [Deployment Concepts](./concepts.md){.internal-link target=_blank}, there are multiple strategies you can use. + +Here I'll show you how to use **Gunicorn** with **Uvicorn worker processes**. + +!!! info + If you are using containers, for example with Docker or Kubernetes, I'll tell you more about that in the next chapter: [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank}. + + In particular, when running on **Kubernetes** you will probably **not** want to use Gunicorn and instead run **a single Uvicorn process per container**, but I'll tell you about it later in that chapter. + +## Gunicorn with Uvicorn Workers + +**Gunicorn** is mainly an application server using the **WSGI standard**. That means that Gunicorn can serve applications like Flask and Django. Gunicorn by itself is not compatible with **FastAPI**, as FastAPI uses the newest **ASGI standard**. + +But Gunicorn supports working as a **process manager** and allowing users to tell it which specific **worker process class** to use. Then Gunicorn would start one or more **worker processes** using that class. + +And **Uvicorn** has a **Gunicorn-compatible worker class**. + +Using that combination, Gunicorn would act as a **process manager**, listening on the **port** and the **IP**. And it would **transmit** the communication to the worker processes running the **Uvicorn class**. + +And then the Gunicorn-compatible **Uvicorn worker** class would be in charge of converting the data sent by Gunicorn to the ASGI standard for FastAPI to use it. + +## Install Gunicorn and Uvicorn + +
+ +```console +$ pip install "uvicorn[standard]" gunicorn + +---> 100% +``` + +
+ +That will install both Uvicorn with the `standard` extra packages (to get high performance) and Gunicorn. + +## Run Gunicorn with Uvicorn Workers + +Then you can run Gunicorn with: + +
+ +```console +$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80 + +[19499] [INFO] Starting gunicorn 20.1.0 +[19499] [INFO] Listening at: http://0.0.0.0:80 (19499) +[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker +[19511] [INFO] Booting worker with pid: 19511 +[19513] [INFO] Booting worker with pid: 19513 +[19514] [INFO] Booting worker with pid: 19514 +[19515] [INFO] Booting worker with pid: 19515 +[19511] [INFO] Started server process [19511] +[19511] [INFO] Waiting for application startup. +[19511] [INFO] Application startup complete. +[19513] [INFO] Started server process [19513] +[19513] [INFO] Waiting for application startup. +[19513] [INFO] Application startup complete. +[19514] [INFO] Started server process [19514] +[19514] [INFO] Waiting for application startup. +[19514] [INFO] Application startup complete. +[19515] [INFO] Started server process [19515] +[19515] [INFO] Waiting for application startup. +[19515] [INFO] Application startup complete. +``` + +
+ +Let's see what each of those options mean: + +* `main:app`: This is the same syntax used by Uvicorn, `main` means the Python module named "`main`", so, a file `main.py`. And `app` is the name of the variable that is the **FastAPI** application. + * You can imagine that `main:app` is equivalent to a Python `import` statement like: + + ```Python + from main import app + ``` + + * So, the colon in `main:app` would be equivalent to the Python `import` part in `from main import app`. +* `--workers`: The number of worker processes to use, each will run a Uvicorn worker, in this case, 4 workers. +* `--worker-class`: The Gunicorn-compatible worker class to use in the worker processes. + * Here we pass the class that Gunicorn can import and use with: + + ```Python + import uvicorn.workers.UvicornWorker + ``` + +* `--bind`: This tells Gunicorn the IP and the port to listen to, using a colon (`:`) to separate the IP and the port. + * If you were running Uvicorn directly, instead of `--bind 0.0.0.0:80` (the Gunicorn option) you would use `--host 0.0.0.0` and `--port 80`. + +In the output, you can see that it shows the **PID** (process ID) of each process (it's just a number). + +You can see that: + +* The Gunicorn **process manager** starts with PID `19499` (in your case it will be a different number). +* Then it starts `Listening at: http://0.0.0.0:80`. +* Then it detects that it has to use the worker class at `uvicorn.workers.UvicornWorker`. +* And then it starts **4 workers**, each with its own PID: `19511`, `19513`, `19514`, and `19515`. + +Gunicorn would also take care of managing **dead processes** and **restarting** new ones if needed to keep the number of workers. So that helps in part with the **restart** concept from the list above. + +Nevertheless, you would probably also want to have something outside making sure to **restart Gunicorn** if necessary, and also to **run it on startup**, etc. + +## Uvicorn with Workers + +Uvicorn also has an option to start and run several **worker processes**. + +Nevertheless, as of now, Uvicorn's capabilities for handling worker processes are more limited than Gunicorn's. So, if you want to have a process manager at this level (at the Python level), then it might be better to try with Gunicorn as the process manager. + +In any case, you would run it like this: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +The only new option here is `--workers` telling Uvicorn to start 4 worker processes. + +You can also see that it shows the **PID** of each process, `27365` for the parent process (this is the **process manager**) and one for each worker process: `27368`, `27369`, `27370`, and `27367`. + +## Deployment Concepts + +Here you saw how to use **Gunicorn** (or Uvicorn) managing **Uvicorn worker processes** to **parallelize** the execution of the application, take advantage of **multiple cores** in the CPU, and be able to serve **more requests**. + +From the list of deployment concepts from above, using workers would mainly help with the **replication** part, and a little bit with the **restarts**, but you still need to take care of the others: + +* **Security - HTTPS** +* **Running on startup** +* ***Restarts*** +* Replication (the number of processes running) +* **Memory** +* **Previous steps before starting** + +## Containers and Docker + +In the next chapter about [FastAPI in Containers - Docker](./docker.md){.internal-link target=_blank} I'll tell some strategies you could use to handle the other **deployment concepts**. + +I'll also show you the **official Docker image** that includes **Gunicorn with Uvicorn workers** and some default configurations that can be useful for simple cases. + +There I'll also show you how to **build your own image from scratch** to run a single Uvicorn process (without Gunicorn). It is a simple process and is probably what you would want to do when using a distributed container management system like **Kubernetes**. + +## Recap + +You can use **Gunicorn** (or also Uvicorn) as a process manager with Uvicorn workers to take advantage of **multi-core CPUs**, to run **multiple processes in parallel**. + +You could use these tools and ideas if you are setting up **your own deployment system** while taking care of the other deployment concepts yourself. + +Check out the next chapter to learn about **FastAPI** with containers (e.g. Docker and Kubernetes). You will see that those tools have simple ways to solve the other **deployment concepts** as well. ✨ diff --git a/docs/zh/docs/deployment/versions.md b/docs/zh/docs/deployment/versions.md new file mode 100644 index 0000000000000..4be9385ddf530 --- /dev/null +++ b/docs/zh/docs/deployment/versions.md @@ -0,0 +1,87 @@ +# About FastAPI versions + +**FastAPI** is already being used in production in many applications and systems. And the test coverage is kept at 100%. But its development is still moving quickly. + +New features are added frequently, bugs are fixed regularly, and the code is still continuously improving. + +That's why the current versions are still `0.x.x`, this reflects that each version could potentially have breaking changes. This follows the Semantic Versioning conventions. + +You can create production applications with **FastAPI** right now (and you have probably been doing it for some time), you just have to make sure that you use a version that works correctly with the rest of your code. + +## Pin your `fastapi` version + +The first thing you should do is to "pin" the version of **FastAPI** you are using to the specific latest version that you know works correctly for your application. + +For example, let's say you are using version `0.45.0` in your app. + +If you use a `requirements.txt` file you could specify the version with: + +```txt +fastapi==0.45.0 +``` + +that would mean that you would use exactly the version `0.45.0`. + +Or you could also pin it with: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +that would mean that you would use the versions `0.45.0` or above, but less than `0.46.0`, for example, a version `0.45.2` would still be accepted. + +If you use any other tool to manage your installations, like Poetry, Pipenv, or others, they all have a way that you can use to define specific versions for your packages. + +## Available versions + +You can see the available versions (e.g. to check what is the current latest) in the [Release Notes](../release-notes.md){.internal-link target=_blank}. + +## About versions + +Following the Semantic Versioning conventions, any version below `1.0.0` could potentially add breaking changes. + +FastAPI also follows the convention that any "PATCH" version change is for bug fixes and non-breaking changes. + +!!! tip + The "PATCH" is the last number, for example, in `0.2.3`, the PATCH version is `3`. + +So, you should be able to pin to a version like: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +Breaking changes and new features are added in "MINOR" versions. + +!!! tip + The "MINOR" is the number in the middle, for example, in `0.2.3`, the MINOR version is `2`. + +## Upgrading the FastAPI versions + +You should add tests for your app. + +With **FastAPI** it's very easy (thanks to Starlette), check the docs: [Testing](../tutorial/testing.md){.internal-link target=_blank} + +After you have tests, then you can upgrade the **FastAPI** version to a more recent one, and make sure that all your code is working correctly by running your tests. + +If everything is working, or after you make the necessary changes, and all your tests are passing, then you can pin your `fastapi` to that new recent version. + +## About Starlette + +You shouldn't pin the version of `starlette`. + +Different versions of **FastAPI** will use a specific newer version of Starlette. + +So, you can just let **FastAPI** use the correct Starlette version. + +## About Pydantic + +Pydantic includes the tests for **FastAPI** with its own tests, so new versions of Pydantic (above `1.0.0`) are always compatible with FastAPI. + +You can pin Pydantic to any version above `1.0.0` that works for you and below `2.0.0`. + +For example: + +```txt +pydantic>=1.2.0,<2.0.0 +``` diff --git a/docs/zh/docs/external-links.md b/docs/zh/docs/external-links.md new file mode 100644 index 0000000000000..0c91470bc0a03 --- /dev/null +++ b/docs/zh/docs/external-links.md @@ -0,0 +1,91 @@ +# External Links and Articles + +**FastAPI** has a great community constantly growing. + +There are many posts, articles, tools, and projects, related to **FastAPI**. + +Here's an incomplete list of some of them. + +!!! tip + If you have an article, project, tool, or anything related to **FastAPI** that is not yet listed here, create a Pull Request adding it. + +## Articles + +### English + +{% if external_links %} +{% for article in external_links.articles.english %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +### Japanese + +{% if external_links %} +{% for article in external_links.articles.japanese %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +### Vietnamese + +{% if external_links %} +{% for article in external_links.articles.vietnamese %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +### Russian + +{% if external_links %} +{% for article in external_links.articles.russian %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +### German + +{% if external_links %} +{% for article in external_links.articles.german %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +### Taiwanese + +{% if external_links %} +{% for article in external_links.articles.taiwanese %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +## Podcasts + +{% if external_links %} +{% for article in external_links.podcasts.english %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +## Talks + +{% if external_links %} +{% for article in external_links.talks.english %} + +* {{ article.title }} by {{ article.author }}. +{% endfor %} +{% endif %} + +## Projects + +Latest GitHub projects with the topic `fastapi`: + +
+
diff --git a/docs/zh/docs/fastapi-people.md b/docs/zh/docs/fastapi-people.md index 5d7b0923f33c4..2c1862773a014 100644 --- a/docs/zh/docs/fastapi-people.md +++ b/docs/zh/docs/fastapi-people.md @@ -2,9 +2,9 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋友。 -## 创建者 & 维护者 +## Creator - Maintainer -嘿! 👋 +向他们致以掌声。 👏 🙇 这就是我: @@ -18,13 +18,13 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋 {% endif %} -我是 **FastAPI** 的创建者和维护者. 你能在 [帮助 FastAPI - 获取帮助 - 与作者联系](help-fastapi.md#connect-with-the-author){.internal-link target=_blank} 阅读有关此内容的更多信息。 +我是 **FastAPI** 的创建者和维护者. 你能在 [帮助 FastAPI - 获取帮助 - 与作者联系](help-fastapi.md#connect-with-the-author){.internal-link target=_blank} 阅读有关此内容的更多信息。 You can read more about that in [Help FastAPI - Get Help - Connect with the author](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}. ...但是在这里我想向您展示社区。 --- -**FastAPI** 得到了社区的大力支持。因此我想突出他们的贡献。 +**FastAPI** 得到了社区的大力支持。 因此我想突出他们的贡献。 这些人: @@ -32,11 +32,11 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋 * [创建 Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}。 * 审核 Pull Requests, 对于 [翻译](contributing.md#translations){.internal-link target=_blank} 尤为重要。 -向他们致以掌声。 👏 🙇 +他们贡献了源代码,文档,翻译等。 📦 ## 上个月最活跃的用户 -上个月这些用户致力于 [帮助他人解决 GitHub 的 issues](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}。 +还有很多其他贡献者(超过100个),你可以在 FastAPI GitHub 贡献者页面 中看到他们。 👷 {% if people %}
@@ -54,7 +54,7 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋 这些用户一直以来致力于 [帮助他人解决 GitHub 的 issues](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}。 -他们通过帮助许多人而被证明是专家。✨ +他们通过帮助许多人而被证明是专家。 ✨ {% if people %}
@@ -72,7 +72,7 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋 这些用户 [创建了最多已被合并的 Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}。 -他们贡献了源代码,文档,翻译等。 📦 +They have contributed source code, documentation, translations, etc. 📦 {% if people %}
@@ -84,7 +84,7 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋
{% endif %} -还有很多其他贡献者(超过100个),你可以在 FastAPI GitHub 贡献者页面 中看到他们。👷 +以下是 **赞助商** 。 😎 ## 杰出审核者 @@ -92,7 +92,7 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋 ### 翻译审核 -我只会说少数几种语言(而且还不是很流利 😅)。所以,具备[能力去批准文档翻译](contributing.md#translations){.internal-link target=_blank} 是这些评审者们。如果没有它们,就不会有多语言文档。 +我只会说少数几种语言(而且还不是很流利 😅)。 所以,具备[能力去批准文档翻译](contributing.md#translations){.internal-link target=_blank} 是这些评审者们。 如果没有它们,就不会有多语言文档。 --- @@ -110,7 +110,7 @@ FastAPI 有一个非常棒的社区,它欢迎来自各个领域和背景的朋 ## 赞助商 -以下是 **赞助商** 。😎 +These are the **Sponsors**. 😎 他们主要通过GitHub Sponsors支持我在 **FastAPI** (和其他项目)的工作。 diff --git a/docs/zh/docs/features.md b/docs/zh/docs/features.md index 2db7f852a9e83..b1a214a1224e6 100644 --- a/docs/zh/docs/features.md +++ b/docs/zh/docs/features.md @@ -6,15 +6,14 @@ ### 基于开放标准 - * 用于创建 API 的 OpenAPI 包含了路径操作,请求参数,请求体,安全性等的声明。 * 使用 JSON Schema (因为 OpenAPI 本身就是基于 JSON Schema 的)自动生成数据模型文档。 -* 经过了缜密的研究后围绕这些标准而设计。并非狗尾续貂。 +* 经过了缜密的研究后围绕这些标准而设计。 并非狗尾续貂。 * 这也允许了在很多语言中自动**生成客户端代码**。 ### 自动生成文档 -交互式 API 文档以及具探索性 web 界面。因为该框架是基于 OpenAPI,所以有很多可选项,FastAPI 默认自带两个交互式 API 文档。 +交互式 API 文档以及具探索性 web 界面。 因为该框架是基于 OpenAPI,所以有很多可选项,FastAPI 默认自带两个交互式 API 文档。 * Swagger UI,可交互式操作,能在浏览器中直接调用和测试你的 API 。 @@ -26,7 +25,7 @@ ### 更主流的 Python -全部都基于标准的 **Python 3.6 类型**声明(感谢 Pydantic )。没有新的语法需要学习。只需要标准的 Python 。 +全部都基于标准的 **Python 3.6 类型**声明(感谢 Pydantic )。 没有新的语法需要学习。 只需要标准的 Python 。 如果你需要2分钟来学习如何使用 Python 类型(即使你不使用 FastAPI ),看看这个简短的教程:[Python Types](python-types.md){.internal-link target=_blank}。 @@ -64,8 +63,7 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` - -!!! info +!!! !!! info `**second_user_data` 意思是: 直接将`second_user_data`字典的键和值直接作为key-value参数传递,等同于:`User(id=4, name="Mary", joined="2018-11-30")` @@ -76,7 +74,7 @@ my_second_user: User = User(**second_user_data) 在最近的 Python 开发者调查中,我们能看到 被使用最多的功能是"自动补全"。 -整个 **FastAPI** 框架就是基于这一点的。任何地方都可以进行自动补全。 +整个 **FastAPI** 框架就是基于这一点的。 任何地方都可以进行自动补全。 你几乎不需要经常回来看文档。 @@ -90,21 +88,19 @@ my_second_user: User = User(**second_user_data) ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) -你将能进行代码补全,这是在之前你可能曾认为不可能的事。例如,在来自请求 JSON 体(可能是嵌套的)中的键 `price`。 +你将能进行代码补全,这是在之前你可能曾认为不可能的事。 例如,在来自请求 JSON 体(可能是嵌套的)中的键 `price`。 不会再输错键名,来回翻看文档,或者来回滚动寻找你最后使用的 `username` 或者 `user_name` 。 +### Short - -### 简洁 - -任何类型都有合理的**默认值**,任何和地方都有可选配置。所有的参数被微调,来满足你的需求,定义成你需要的 API。 +任何类型都有合理的**默认值**,任何和地方都有可选配置。 所有的参数被微调,来满足你的需求,定义成你需要的 API。 但是默认情况下,一切都能**“顺利工作”**。 ### 验证 -* 校验大部分(甚至所有?)的 Python **数据类型**,包括: +* Validation for most (or all?) 校验大部分(甚至所有?)的 Python **数据类型**,包括: * JSON 对象 (`dict`). * JSON 数组 (`list`) 定义成员类型。 * 字符串 (`str`) 字段, 定义最小或最大长度。 @@ -120,14 +116,14 @@ my_second_user: User = User(**second_user_data) ### 安全性及身份验证 -集成了安全性和身份认证。杜绝数据库或者数据模型的渗透风险。 +集成了安全性和身份认证。 杜绝数据库或者数据模型的渗透风险。 OpenAPI 中定义的安全模式,包括: * HTTP 基本认证。 -* **OAuth2** (也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。 +* **OAuth2** (也使用 **JWT tokens**)。 在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。 * API 密钥,在: - * 请求头。 + * Headers. * 查询参数。 * Cookies, 等等。 @@ -135,8 +131,6 @@ OpenAPI 中定义的安全模式,包括: 所有的这些都是可复用的工具和组件,可以轻松与你的系统,数据仓库,关系型以及 NoSQL 数据库等等集成。 - - ### 依赖注入 FastAPI 有一个使用非常简单,但是非常强大的依赖注入系统。 @@ -162,13 +156,14 @@ FastAPI 有一个使用非常简单,但是非常强大的Pydantic 完全兼容(并基于)。所以,你有的其他的 Pydantic 代码也能正常工作。 +**FastAPI** 和 Starlette 完全兼容(并基于)。 So, any additional Pydantic code you have, will also work. 兼容包括基于 Pydantic 的外部库, 例如用与数据库的 ORMs, ODMs。 -这也意味着在很多情况下,你可以将从请求中获得的相同对象**直接传到数据库**,因为所有的验证都是自动的。 - 反之亦然,在很多情况下,你也可以将从数据库中获取的对象**直接传到客户端**。 +这也意味着在很多情况下,你可以将从请求中获得的相同对象**直接传到数据库**,因为所有的验证都是自动的。 + 通过 **FastAPI** 你可以获得所有 **Pydantic** (FastAPI 基于 Pydantic 做了所有的数据处理): * **更简单**: @@ -194,8 +189,6 @@ FastAPI 有一个使用非常简单,但是非常强大的Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。🎉 +Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。 🎉 知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。 ## 为 FastAPI 投票 -* 在 Slant 上为 **FastAPI** 投票 -* 在 AlternativeTo 上为 **FastAPI** 投票 +* Vote for **FastAPI** in Slant. +* Vote for **FastAPI** in AlternativeTo. +* Say you use **FastAPI** on StackShare. ## 在 GitHub 上帮助其他人解决问题 -您可以查看现有 issues,并尝试帮助其他人解决问题,说不定您能解决这些问题呢。🤓 +You can try and help others with their questions in: -如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#experts){.internal-link target=_blank}。🎉 +* GitHub Discussions +* GitHub Issues + +如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#experts){.internal-link target=_blank}。 🎉 + +**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。 😉 + +Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗 + +The idea is for the **FastAPI** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other. + +--- + +Here's how to help others with questions (in discussions or issues): + +### Understand the question + +* **Star** 以后,其它用户就能更容易找到 FastAPI,并了解到已经有其他用户在使用它了。 + +* 如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。 + +* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**. + +* contributing.md#translations + +### 创建 Issue + +For most of the cases and most of the questions there's something related to the person's **original code**. + +In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**. + +* You can ask them to provide a minimal, reproducible, example, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better. + +* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first. + +### Suggest solutions + +* 给我买杯咖啡 ☕️ 以示感谢 😄 + +* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do. + +### Ask to close + +另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。 😄 + +* Now, if that solved their problem, you can ask them to: + + * 在 **Twitter** 上关注我 + * 在 **GitHub** 上关注我 ## 监听 GitHub 资源库 -您可以在 GitHub 上「监听」FastAPI(点击右上角的 "watch" 按钮): https://github.com/tiangolo/fastapi. 👀 +您还可以在 GitHub 上 **Watch** FastAPI,(点击右上角的 **Watch** 按钮)https://github.com/tiangolo/fastapi。 👀 -如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。 +If you select "Watching" instead of "Releases only" you will receive notifications when someone creates a new issue or question. You can also specify that you only want to be notified about new issues, or discussions, or PRs, etc. 然后您就可以尝试并帮助他们解决问题。 -## 创建 Issue +## Ask Questions 您可以在 GitHub 资源库中创建 Issue,例如: * 提出**问题**或**意见** * 提出新**特性**建议 -**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。😉 +当然您也可以成为 FastAPI 的金牌或银牌赞助商。 🏅🎉 + +## Review Pull Requests + +You can help me review pull requests from others. + +谢谢! 🚀 + +--- -## 创建 PR +Here's what to have in mind and how to review a pull request: + +### Understand the problem + +* First, make sure you **understand the problem** that the pull request is trying to solve. It might have a longer discussion in a GitHub Discussion or issue. + +* There's also a good chance that the pull request is not actually needed because the problem can be solved in a **different way**. Then you can suggest or ask about that. + +### Don't worry about style + +* Don't worry too much about things like commit message styles, I will squash and merge customizing the commit manually. + +* Also don't worry about style rules, there are already automatized tools checking that. + +And if there's any other style or consistency need, I'll ask directly for that, or I'll add commits on top with the needed changes. + +### Check the code + +* 您可以选择只关注发布(**Releases only**)。 + +* Then **comment** saying that you did that, that's how I will know you really checked it. + +!!! info + Unfortunately, I can't simply trust PRs that just have several approvals. + + Several times it has happened that there are PRs with 3, 5 or more approvals, probably because the description is appealing, but when I check the PRs, they are actually broken, have a bug, or don't solve the problem they claim to solve. 😅 + + So, it's really important that you actually read and run the code, and let me know in the comments that you did. 🤓 + +* If the PR can be simplified in a way, you can ask for that, but there's no need to be too picky, there might be a lot of subjective points of view (and I will have my own as well 🙈), so it's better if you can focus on the fundamental things. + +### Tests + +* 创建 PR + +* Check that the tests **fail** before the PR. 🚨 + +* Then check that the tests **pass** after the PR. ✅ + +* Many PRs don't have tests, you can **remind** them to add tests, or you can even **suggest** some tests yourself. That's one of the things that consume most time and you can help a lot with that. + +* Then also comment what you tried, that way I'll know that you checked it. 🤓 + +## Create a Pull Request 您可以创建 PR 为源代码做[贡献](contributing.md){.internal-link target=_blank},例如: -* 修改文档错别字 +* To fix a typo you found on the documentation. * 编辑这个文件,分享 FastAPI 的文章、视频、博客,不论是您自己的,还是您看到的都成 * 注意,添加的链接要放在对应区块的开头 * [翻译文档](contributing.md#translations){.internal-link target=_blank} - * 审阅别人翻译的文档 -* 添加新的文档内容 + * You can also help to review the translations created by others. +* To propose new documentation sections. * 修复现有问题/Bug + * Make sure to add tests. * 添加新功能 + * Make sure to add tests. + * Make sure to add documentation if it's relevant. + +## 在 AlternativeTo 上为 **FastAPI** 投票 + +Help me maintain **FastAPI**! 🤓 + +There's a lot of work to do, and for most of it, **YOU** can do it. + +The main tasks that you can do right now are: + +* [Help others with questions in GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (see the section above). +* [Review Pull Requests](#review-pull-requests){.internal-link target=_blank} (see the section above). + +Those two tasks are what **consume time the most**. That's the main work of maintaining FastAPI. + +If you can help me with that, **you are helping me maintain FastAPI** and making sure it keeps **advancing faster and better**. 🚀 ## 加入聊天 快加入 👥 Discord 聊天服务器 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。 -!!! tip "提示" - - 如有问题,请在 GitHub Issues 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank}的帮助。 +!!! 如有问题,请在 {\[--lt--]}a href="https://github.com/tiangolo/fastapi/issues/new/choose" class="external-link" target="_blank">GitHub Issues</a> 里提问,在这里更容易得到 [FastAPI 专家\](fastapi-people.md#experts){.internal-link target=_blank}的帮助。 - 聊天室仅供闲聊。 + Use the chat only for other general conversations. 我们之前还使用过 Gitter chat,但它不支持频道等高级功能,聊天也比较麻烦,所以现在推荐使用 Discord。 @@ -120,19 +236,19 @@ 注意,聊天室更倾向于“闲聊”,经常有人会提出一些笼统得让人难以回答的问题,所以在这里提问一般没人回答。 -GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。但在聊天室里,我就没功夫这么做了。😅 +GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。 而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。 但在聊天室里,我就没功夫这么做了。 😅 -聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。 +聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。 只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#experts){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。 -另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。😄 +On the other side, there are thousands of users in the chat systems, so there's a high chance you'll find someone to talk to there, almost all the time. 😄 ## 赞助作者 您还可以通过 GitHub 赞助商资助本项目的作者(就是我)。 -给我买杯咖啡 ☕️ 以示感谢 😄 +There you could buy me a coffee ☕️ to say thanks. 😄 -当然您也可以成为 FastAPI 的金牌或银牌赞助商。🏅🎉 +And you can also become a Silver or Gold sponsor for FastAPI. 🏅🎉 ## 赞助 FastAPI 使用的工具 @@ -145,4 +261,4 @@ GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获 --- -谢谢!🚀 +Thanks! 🚀 diff --git a/docs/zh/docs/history-design-future.md b/docs/zh/docs/history-design-future.md new file mode 100644 index 0000000000000..ef6ea883213c2 --- /dev/null +++ b/docs/zh/docs/history-design-future.md @@ -0,0 +1,76 @@ +# History, Design and Future + +Some time ago, a **FastAPI** user asked: + +> What’s the history of this project? It seems to have come from nowhere to awesome in a few weeks [...] + +Here's a little bit of that history. + +## Alternatives + +I have been creating APIs with complex requirements for several years (Machine Learning, distributed systems, asynchronous jobs, NoSQL databases, etc), leading several teams of developers. + +As part of that, I needed to investigate, test and use many alternatives. + +The history of **FastAPI** is in great part the history of its predecessors. + +As said in the section [Alternatives](alternatives.md){.internal-link target=_blank}: +
+ **FastAPI** wouldn't exist if not for the previous work of others. + + There have been many tools created before that have helped inspire its creation. + + I have been avoiding the creation of a new framework for several years. First I tried to solve all the features covered by **FastAPI** using many different frameworks, plug-ins, and tools. + + But at some point, there was no other option than creating something that provided all these features, taking the best ideas from previous tools, and combining them in the best way possible, using language features that weren't even available before (Python 3.6+ type hints). +
+ +## Investigation + +By using all the previous alternatives I had the chance to learn from all of them, take ideas, and combine them in the best way I could find for myself and the teams of developers I have worked with. + +For example, it was clear that ideally it should be based on standard Python type hints. + +Also, the best approach was to use already existing standards. + +So, before even starting to code **FastAPI**, I spent several months studying the specs for OpenAPI, JSON Schema, OAuth2, etc. Understanding their relationship, overlap, and differences. + +## Design + +Then I spent some time designing the developer "API" I wanted to have as a user (as a developer using FastAPI). + +I tested several ideas in the most popular Python editors: PyCharm, VS Code, Jedi based editors. + +By the last Python Developer Survey, that covers about 80% of the users. + +It means that **FastAPI** was specifically tested with the editors used by 80% of the Python developers. And as most of the other editors tend to work similarly, all its benefits should work for virtually all editors. + +That way I could find the best ways to reduce code duplication as much as possible, to have completion everywhere, type and error checks, etc. + +All in a way that provided the best development experience for all the developers. + +## Requirements + +After testing several alternatives, I decided that I was going to use **Pydantic** for its advantages. + +Then I contributed to it, to make it fully compliant with JSON Schema, to support different ways to define constraint declarations, and to improve editor support (type checks, autocompletion) based on the tests in several editors. + +During the development, I also contributed to **Starlette**, the other key requirement. + +## Development + +By the time I started creating **FastAPI** itself, most of the pieces were already in place, the design was defined, the requirements and tools were ready, and the knowledge about the standards and specifications was clear and fresh. + +## Future + +By this point, it's already clear that **FastAPI** with its ideas is being useful for many people. + +It is being chosen over previous alternatives for suiting many use cases better. + +Many developers and teams already depend on **FastAPI** for their projects (including me and my team). + +But still, there are many improvements and features to come. + +**FastAPI** has a great future ahead. + +And [your help](help-fastapi.md){.internal-link target=_blank} is greatly appreciated. diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md index 1de2a8d36d09a..ebd74bc8f00d2 100644 --- a/docs/zh/docs/index.md +++ b/docs/zh/docs/index.md @@ -2,43 +2,45 @@ FastAPI

- FastAPI 框架,高性能,易于学习,高效编码,生产可用 + FastAPI framework, high performance, easy to learn, fast to code, ready for production

- - Test + + Test - - Coverage + + Coverage Package version + + Supported Python versions +

--- -**文档**: https://fastapi.tiangolo.com +**Documentation**: https://fastapi.tiangolo.com -**源码**: https://github.com/tiangolo/fastapi +**Source Code**: https://github.com/tiangolo/fastapi --- -FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 3.6+ 并基于标准的 Python 类型提示。 +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. -关键特性: +The key features are: -* **快速**:可与 **NodeJS** 和 **Go** 并肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。 +* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). +* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * +* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Easy**: Designed to be easy to use and learn. Less time reading docs. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Robust**: Get production-ready code. With automatic interactive documentation. +* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. -* **高效编码**:提高功能开发速度约 200% 至 300%。* -* **更少 bug**:减少约 40% 的人为(开发者)导致错误。* -* **智能**:极佳的编辑器支持。处处皆可自动补全,减少调试时间。 -* **简单**:设计的易于使用和学习,阅读文档的时间更短。 -* **简短**:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。 -* **健壮**:生产可用级别的代码。还有自动生成的交互式文档。 -* **标准化**:基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema。 - -* 根据对某个构建线上应用的内部开发团队所进行的测试估算得出。 +* estimation based on tests on an internal development team, building production applications. ## Sponsors @@ -57,64 +59,70 @@ FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框 Other sponsors -## 评价 +## Opinions -「_[...] 最近我一直在使用 **FastAPI**。[...] 实际上我正在计划将其用于我所在的**微软**团队的所有**机器学习服务**。其中一些服务正被集成进核心 **Windows** 产品和一些 **Office** 产品。_」 +"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" -
Kabir Khan - 微软 (ref)
+
Kabir Khan - Microsoft (ref)
--- -「_我们选择了 **FastAPI** 来创建用于获取**预测结果**的 **REST** 服务。[用于 Ludwig]_」 +"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" -
Piero Molino,Yaroslav Dudin 和 Sai Sumanth Miryala - Uber (ref)
+
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
--- -「_**Netflix** 非常高兴地宣布,正式开源我们的**危机管理**编排框架:**Dispatch**![使用 **FastAPI** 构建]_」 +"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" -
Kevin Glisson,Marc Vilanova,Forest Monsen - Netflix (ref)
+
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -「_**FastAPI** 让我兴奋的欣喜若狂。它太棒了!_」 +"_I’m over the moon excited about **FastAPI**. It’s so fun!_" -
Brian Okken - Python Bytes 播客主持人 (ref)
+
Brian Okken - Python Bytes podcast host (ref)
--- -「_老实说,你的作品看起来非常可靠和优美。在很多方面,这就是我想让 **Hug** 成为的样子 - 看到有人实现了它真的很鼓舞人心。_」 +"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" -
Timothy Crosley - Hug 作者 (ref)
+
Timothy Crosley - Hug creator (ref)
--- -「_如果你正打算学习一个**现代框架**用来构建 REST API,来看下 **FastAPI** [...] 它快速、易用且易于学习 [...]_」 +"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" + +"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" + +
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
+ +--- -「_我们已经将 **API** 服务切换到了 **FastAPI** [...] 我认为你会喜欢它的 [...]_」 +"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" -
Ines Montani - Matthew Honnibal - Explosion AI 创始人 - spaCy 作者 (ref) - (ref)
+
Deon Pillsbury - Cisco (ref)
--- -## **Typer**,命令行中的 FastAPI +## **Typer**, the FastAPI of CLIs -如果你正在开发一个在终端中运行的命令行应用而不是 web API,不妨试下 **Typer**。 +If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. -**Typer** 是 FastAPI 的小同胞。它想要成为**命令行中的 FastAPI**。 ⌨️ 🚀 +**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 -## 依赖 +## Requirements -Python 3.6 及更高版本 +Python 3.7+ -FastAPI 站在以下巨人的肩膀之上: +FastAPI stands on the shoulders of giants: -* Starlette 负责 web 部分。 -* Pydantic 负责数据部分。 +* Starlette for the web parts. +* Pydantic for the data parts. -## 安装 +## Installation
@@ -126,7 +134,7 @@ $ pip install fastapi
-你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn。 +You will also need an ASGI server, for production such as Uvicorn or Hypercorn.
@@ -138,11 +146,11 @@ $ pip install "uvicorn[standard]"
-## 示例 +## Example -### 创建 +### Create it -* 创建一个 `main.py` 文件并写入以下内容: +* Create a file `main.py` with: ```Python from typing import Union @@ -163,9 +171,9 @@ def read_item(item_id: int, q: Union[str, None] = None): ```
-或者使用 async def... +Or use async def... -如果你的代码里会出现 `async` / `await`,请使用 `async def`: +If your code uses `async` / `await`, use `async def`: ```Python hl_lines="9 14" from typing import Union @@ -187,13 +195,13 @@ async def read_item(item_id: int, q: Union[str, None] = None): **Note**: -如果你不知道是否会用到,可以查看文档的 _"In a hurry?"_ 章节中 关于 `async` 和 `await` 的部分。 +If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs.
-### 运行 +### Run it -通过以下命令运行服务器: +Run the server with:
@@ -210,54 +218,54 @@ INFO: Application startup complete.
-关于 uvicorn main:app --reload 命令...... +About the command uvicorn main:app --reload... - `uvicorn main:app` 命令含义如下: +The command `uvicorn main:app` refers to: -* `main`:`main.py` 文件(一个 Python "模块")。 -* `app`:在 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。 -* `--reload`:让服务器在更新代码后重新启动。仅在开发时使用该选项。 +* `main`: the file `main.py` (the Python "module"). +* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. +* `--reload`: make the server restart after code changes. Only do this for development.
-### 检查 +### Check it -使用浏览器访问 http://127.0.0.1:8000/items/5?q=somequery。 +Open your browser at http://127.0.0.1:8000/items/5?q=somequery. -你将会看到如下 JSON 响应: +You will see the JSON response as: ```JSON {"item_id": 5, "q": "somequery"} ``` -你已经创建了一个具有以下功能的 API: +You already created an API that: -* 通过 _路径_ `/` 和 `/items/{item_id}` 接受 HTTP 请求。 -* 以上 _路径_ 都接受 `GET` 操作(也被称为 HTTP _方法_)。 -* `/items/{item_id}` _路径_ 有一个 _路径参数_ `item_id` 并且应该为 `int` 类型。 -* `/items/{item_id}` _路径_ 有一个可选的 `str` 类型的 _查询参数_ `q`。 +* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. +* Both _paths_ take `GET` operations (also known as HTTP _methods_). +* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. +* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. -### 交互式 API 文档 +### Interactive API docs -现在访问 http://127.0.0.1:8000/docs。 +Now go to http://127.0.0.1:8000/docs. -你会看到自动生成的交互式 API 文档(由 Swagger UI生成): +You will see the automatic interactive API documentation (provided by Swagger UI): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### 可选的 API 文档 +### Alternative API docs -访问 http://127.0.0.1:8000/redoc。 +And now, go to http://127.0.0.1:8000/redoc. -你会看到另一个自动生成的文档(由 ReDoc 生成): +You will see the alternative automatic documentation (provided by ReDoc): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## 示例升级 +## Example upgrade -现在修改 `main.py` 文件来从 `PUT` 请求中接收请求体。 +Now modify the file `main.py` to receive a body from a `PUT` request. -我们借助 Pydantic 来使用标准的 Python 类型声明请求体。 +Declare the body using standard Python types, thanks to Pydantic. ```Python hl_lines="4 9-12 25-27" from typing import Union @@ -289,173 +297,174 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -服务器将会自动重载(因为在上面的步骤中你向 `uvicorn` 命令添加了 `--reload` 选项)。 +The server should reload automatically (because you added `--reload` to the `uvicorn` command above). -### 交互式 API 文档升级 +### Interactive API docs upgrade -访问 http://127.0.0.1:8000/docs。 +Now go to http://127.0.0.1:8000/docs. -* 交互式 API 文档将会自动更新,并加入新的请求体: +* The interactive API documentation will be automatically updated, including the new body: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* 点击「Try it out」按钮,之后你可以填写参数并直接调用 API: +* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* 然后点击「Execute」按钮,用户界面将会和 API 进行通信,发送参数,获取结果并在屏幕上展示: +* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### 可选文档升级 +### Alternative API docs upgrade -访问 http://127.0.0.1:8000/redoc。 +And now, go to http://127.0.0.1:8000/redoc. -* 可选文档同样会体现新加入的请求参数和请求体: +* The alternative documentation will also reflect the new query parameter and body: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### 总结 +### Recap -总的来说,你就像声明函数的参数类型一样只声明了**一次**请求参数、请求体等的类型。 +In summary, you declare **once** the types of parameters, body, etc. as function parameters. -你使用了标准的现代 Python 类型来完成声明。 +You do that with standard modern Python types. -你不需要去学习新的语法、了解特定库的方法或类,等等。 +You don't have to learn a new syntax, the methods or classes of a specific library, etc. -只需要使用标准的 **Python 3.6 及更高版本**。 +Just standard **Python 3.7+**. -举个例子,比如声明 `int` 类型: +For example, for an `int`: ```Python item_id: int ``` -或者一个更复杂的 `Item` 模型: +or for a more complex `Item` model: ```Python item: Item ``` -......在进行一次声明之后,你将获得: - -* 编辑器支持,包括: - * 自动补全 - * 类型检查 -* 数据校验: - * 在校验失败时自动生成清晰的错误信息 - * 对多层嵌套的 JSON 对象依然执行校验 -* 转换 来自网络请求的输入数据为 Python 数据类型。包括以下数据: - * JSON - * 路径参数 - * 查询参数 - * Cookies - * 请求头 - * 表单 - * 文件 -* 转换 输出的数据:转换 Python 数据类型为供网络传输的 JSON 数据: - * 转换 Python 基础类型 (`str`、 `int`、 `float`、 `bool`、 `list` 等) - * `datetime` 对象 - * `UUID` 对象 - * 数据库模型 - * ......以及更多其他类型 -* 自动生成的交互式 API 文档,包括两种可选的用户界面: - * Swagger UI - * ReDoc +...and with that single declaration you get: + +* Editor support, including: + * Completion. + * Type checks. +* Validation of data: + * Automatic and clear errors when the data is invalid. + * Validation even for deeply nested JSON objects. +* Conversion of input data: coming from the network to Python data and types. Reading from: + * JSON. + * Path parameters. + * Query parameters. + * Cookies. + * Headers. + * Forms. + * Files. +* Conversion of output data: converting from Python data and types to network data (as JSON): + * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). + * `datetime` objects. + * `UUID` objects. + * Database models. + * ...and many more. +* Automatic interactive API documentation, including 2 alternative user interfaces: + * Swagger UI. + * ReDoc. --- -回到前面的代码示例,**FastAPI** 将会: - -* 校验 `GET` 和 `PUT` 请求的路径中是否含有 `item_id`。 -* 校验 `GET` 和 `PUT` 请求中的 `item_id` 是否为 `int` 类型。 - * 如果不是,客户端将会收到清晰有用的错误信息。 -* 检查 `GET` 请求中是否有命名为 `q` 的可选查询参数(比如 `http://127.0.0.1:8000/items/foo?q=somequery`)。 - * 因为 `q` 被声明为 `= None`,所以它是可选的。 - * 如果没有 `None` 它将会是必需的 (如 `PUT` 例子中的请求体)。 -* 对于访问 `/items/{item_id}` 的 `PUT` 请求,将请求体读取为 JSON 并: - * 检查是否有必需属性 `name` 并且值为 `str` 类型 。 - * 检查是否有必需属性 `price` 并且值为 `float` 类型。 - * 检查是否有可选属性 `is_offer`, 如果有的话值应该为 `bool` 类型。 - * 以上过程对于多层嵌套的 JSON 对象同样也会执行 -* 自动对 JSON 进行转换或转换成 JSON。 -* 通过 OpenAPI 文档来记录所有内容,可被用于: - * 交互式文档系统 - * 许多编程语言的客户端代码自动生成系统 -* 直接提供 2 种交互式文档 web 界面。 +Coming back to the previous code example, **FastAPI** will: + +* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. +* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. + * If it is not, the client will see a useful, clear error. +* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. + * As the `q` parameter is declared with `= None`, it is optional. + * Without the `None` it would be required (as is the body in the case with `PUT`). +* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: + * Check that it has a required attribute `name` that should be a `str`. + * Check that it has a required attribute `price` that has to be a `float`. + * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. + * All this would also work for deeply nested JSON objects. +* Convert from and to JSON automatically. +* Document everything with OpenAPI, that can be used by: + * Interactive documentation systems. + * Automatic client code generation systems, for many languages. +* Provide 2 interactive documentation web interfaces directly. --- -虽然我们才刚刚开始,但其实你已经了解了这一切是如何工作的。 +We just scratched the surface, but you already get the idea of how it all works. -尝试更改下面这行代码: +Try changing the line with: ```Python return {"item_name": item.name, "item_id": item_id} ``` -......从: +...from: ```Python ... "item_name": item.name ... ``` -......改为: +...to: ```Python ... "item_price": item.price ... ``` -......注意观察编辑器是如何自动补全属性并且还知道它们的类型: +...and see how your editor will auto-complete the attributes and know their types: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -教程 - 用户指南 中有包含更多特性的更完整示例。 +For a more complete example including more features, see the Tutorial - User Guide. -**剧透警告**: 教程 - 用户指南中的内容有: +**Spoiler alert**: the tutorial - user guide includes: -* 对来自不同地方的参数进行声明,如:**请求头**、**cookies**、**form 表单**以及**上传的文件**。 -* 如何设置**校验约束**如 `maximum_length` 或者 `regex`。 -* 一个强大并易于使用的 **依赖注入** 系统。 -* 安全性和身份验证,包括通过 **JWT 令牌**和 **HTTP 基本身份认证**来支持 **OAuth2**。 -* 更进阶(但同样简单)的技巧来声明 **多层嵌套 JSON 模型** (借助 Pydantic)。 -* 许多额外功能(归功于 Starlette)比如: +* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. +* How to set **validation constraints** as `maximum_length` or `regex`. +* A very powerful and easy to use **Dependency Injection** system. +* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. +* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). +* **GraphQL** integration with Strawberry and other libraries. +* Many extra features (thanks to Starlette) as: * **WebSockets** - * **GraphQL** - * 基于 HTTPX 和 `pytest` 的极其简单的测试 + * extremely easy tests based on HTTPX and `pytest` * **CORS** * **Cookie Sessions** - * ......以及更多 + * ...and more. -## 性能 +## Performance -独立机构 TechEmpower 所作的基准测试结果显示,基于 Uvicorn 运行的 **FastAPI** 程序是 最快的 Python web 框架之一,仅次于 Starlette 和 Uvicorn 本身(FastAPI 内部使用了它们)。(*) +Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) -想了解更多,请查阅 基准测试 章节。 +To understand more about it, see the section Benchmarks. -## 可选依赖 +## Optional Dependencies -用于 Pydantic: +Used by Pydantic: -* email_validator - 用于 email 校验。 +* email_validator - for email validation. +* pydantic-settings - for settings management. +* pydantic-extra-types - for extra types to be used with Pydantic. -用于 Starlette: +Used by Starlette: -* httpx - 使用 `TestClient` 时安装。 -* jinja2 - 使用默认模板配置时安装。 -* python-multipart - 需要通过 `request.form()` 对表单进行「解析」时安装。 -* itsdangerous - 需要 `SessionMiddleware` 支持时安装。 -* pyyaml - 使用 Starlette 提供的 `SchemaGenerator` 时安装(有 FastAPI 你可能并不需要它)。 -* graphene - 需要 `GraphQLApp` 支持时安装。 -* ujson - 使用 `UJSONResponse` 时安装。 +* httpx - Required if you want to use the `TestClient`. +* jinja2 - Required if you want to use the default template configuration. +* python-multipart - Required if you want to support form "parsing", with `request.form()`. +* itsdangerous - Required for `SessionMiddleware` support. +* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). +* ujson - Required if you want to use `UJSONResponse`. -用于 FastAPI / Starlette: +Used by FastAPI / Starlette: -* uvicorn - 用于加载和运行你的应用程序的服务器。 -* orjson - 使用 `ORJSONResponse` 时安装。 +* uvicorn - for the server that loads and serves your application. +* orjson - Required if you want to use `ORJSONResponse`. -你可以通过 `pip install fastapi[all]` 命令来安装以上所有依赖。 +You can install all of these with `pip install "fastapi[all]"`. -## 许可协议 +## License -该项目遵循 MIT 许可协议。 +This project is licensed under the terms of the MIT license. diff --git a/docs/zh/docs/newsletter.md b/docs/zh/docs/newsletter.md new file mode 100644 index 0000000000000..782db1353c8d0 --- /dev/null +++ b/docs/zh/docs/newsletter.md @@ -0,0 +1,5 @@ +# FastAPI and friends newsletter + + + + diff --git a/docs/zh/docs/project-generation.md b/docs/zh/docs/project-generation.md new file mode 100644 index 0000000000000..8ba34fa11200d --- /dev/null +++ b/docs/zh/docs/project-generation.md @@ -0,0 +1,84 @@ +# Project Generation - Template + +You can use a project generator to get started, as it includes a lot of the initial set up, security, database and some API endpoints already done for you. + +A project generator will always have a very opinionated setup that you should update and adapt for your own needs, but it might be a good starting point for your project. + +## Full Stack FastAPI PostgreSQL + +GitHub: https://github.com/tiangolo/full-stack-fastapi-postgresql + +### Full Stack FastAPI PostgreSQL - Features + +* Full **Docker** integration (Docker based). +* Docker Swarm Mode deployment. +* **Docker Compose** integration and optimization for local development. +* **Production ready** Python web server using Uvicorn and Gunicorn. +* Python **FastAPI** backend: + * **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). + * **Intuitive**: Great editor support. Completion everywhere. Less time debugging. + * **Easy**: Designed to be easy to use and learn. Less time reading docs. + * **Short**: Minimize code duplication. Multiple features from each parameter declaration. + * **Robust**: Get production-ready code. With automatic interactive documentation. + * **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI and JSON Schema. + * **Many other features** including automatic validation, serialization, interactive documentation, authentication with OAuth2 JWT tokens, etc. +* **Secure password** hashing by default. +* **JWT token** authentication. +* **SQLAlchemy** models (independent of Flask extensions, so they can be used with Celery workers directly). +* Basic starting models for users (modify and remove as you need). +* **Alembic** migrations. +* **CORS** (Cross Origin Resource Sharing). +* **Celery** worker that can import and use models and code from the rest of the backend selectively. +* REST backend tests based on **Pytest**, integrated with Docker, so you can test the full API interaction, independent on the database. As it runs in Docker, it can build a new data store from scratch each time (so you can use ElasticSearch, MongoDB, CouchDB, or whatever you want, and just test that the API works). +* Easy Python integration with **Jupyter Kernels** for remote or in-Docker development with extensions like Atom Hydrogen or Visual Studio Code Jupyter. +* **Vue** frontend: + * Generated with Vue CLI. + * **JWT Authentication** handling. + * Login view. + * After login, main dashboard view. + * Main dashboard with user creation and edition. + * Self user edition. + * **Vuex**. + * **Vue-router**. + * **Vuetify** for beautiful material design components. + * **TypeScript**. + * Docker server based on **Nginx** (configured to play nicely with Vue-router). + * Docker multi-stage building, so you don't need to save or commit compiled code. + * Frontend tests ran at build time (can be disabled too). + * Made as modular as possible, so it works out of the box, but you can re-generate with Vue CLI or create it as you need, and re-use what you want. +* **PGAdmin** for PostgreSQL database, you can modify it to use PHPMyAdmin and MySQL easily. +* **Flower** for Celery jobs monitoring. +* Load balancing between frontend and backend with **Traefik**, so you can have both under the same domain, separated by path, but served by different containers. +* Traefik integration, including Let's Encrypt **HTTPS** certificates automatic generation. +* GitLab **CI** (continuous integration), including frontend and backend testing. + +## Full Stack FastAPI Couchbase + +GitHub: https://github.com/tiangolo/full-stack-fastapi-couchbase + +⚠️ **WARNING** ⚠️ + +If you are starting a new project from scratch, check the alternatives here. + +For example, the project generator Full Stack FastAPI PostgreSQL might be a better alternative, as it is actively maintained and used. And it includes all the new features and improvements. + +You are still free to use the Couchbase-based generator if you want to, it should probably still work fine, and if you already have a project generated with it that's fine as well (and you probably already updated it to suit your needs). + +You can read more about it in the docs for the repo. + +## Full Stack FastAPI MongoDB + +...might come later, depending on my time availability and other factors. 😅 🎉 + +## Machine Learning models with spaCy and FastAPI + +GitHub: https://github.com/microsoft/cookiecutter-spacy-fastapi + +### Machine Learning models with spaCy and FastAPI - Features + +* **spaCy** NER model integration. +* **Azure Cognitive Search** request format built in. +* **Production ready** Python web server using Uvicorn and Gunicorn. +* **Azure DevOps** Kubernetes (AKS) CI/CD deployment built in. +* **Multilingual** Easily choose one of spaCy's built in languages during project setup. +* **Easily extensible** to other model frameworks (Pytorch, Tensorflow), not just spaCy. diff --git a/docs/zh/docs/python-types.md b/docs/zh/docs/python-types.md index 6cdb4b58838d7..34350d70befba 100644 --- a/docs/zh/docs/python-types.md +++ b/docs/zh/docs/python-types.md @@ -1,139 +1,139 @@ -# Python 类型提示简介 +# Python Types Intro -**Python 3.6+ 版本**加入了对"类型提示"的支持。 +Python has support for optional "type hints" (also called "type annotations"). -这些**"类型提示"**是一种新的语法(在 Python 3.6 版本加入)用来声明一个变量的类型。 +These **"type hints"** or annotations are a special syntax that allow declaring the type of a variable. -通过声明变量的类型,编辑器和一些工具能给你提供更好的支持。 +By declaring types for your variables, editors and tools can give you better support. -这只是一个关于 Python 类型提示的**快速入门 / 复习**。它仅涵盖与 **FastAPI** 一起使用所需的最少部分...实际上只有很少一点。 +This is just a **quick tutorial / refresher** about Python type hints. It covers only the minimum necessary to use them with **FastAPI**... which is actually very little. -整个 **FastAPI** 都基于这些类型提示构建,它们带来了许多优点和好处。 +**FastAPI** is all based on these type hints, they give it many advantages and benefits. -但即使你不会用到 **FastAPI**,了解一下类型提示也会让你从中受益。 +But even if you never use **FastAPI**, you would benefit from learning a bit about them. !!! note - 如果你已经精通 Python,并且了解关于类型提示的一切知识,直接跳到下一章节吧。 + If you are a Python expert, and you already know everything about type hints, skip to the next chapter. -## 动机 +## Motivation -让我们从一个简单的例子开始: +Let's start with a simple example: ```Python {!../../../docs_src/python_types/tutorial001.py!} ``` -运行这段程序将输出: +Calling this program outputs: ``` John Doe ``` -这个函数做了下面这些事情: +The function does the following: -* 接收 `first_name` 和 `last_name` 参数。 -* 通过 `title()` 将每个参数的第一个字母转换为大写形式。 -* 中间用一个空格来拼接它们。 +* Takes a `first_name` and `last_name`. +* Converts the first letter of each one to upper case with `title()`. +* Concatenates them with a space in the middle. ```Python hl_lines="2" {!../../../docs_src/python_types/tutorial001.py!} ``` -### 修改示例 +### Edit it -这是一个非常简单的程序。 +It's a very simple program. -现在假设你将从头开始编写这段程序。 +But now imagine that you were writing it from scratch. -在某一时刻,你开始定义函数,并且准备好了参数...。 +At some point you would have started the definition of the function, you had the parameters ready... -现在你需要调用一个"将第一个字母转换为大写形式的方法"。 +But then you have to call "that method that converts the first letter to upper case". -等等,那个方法是什么来着?`upper`?还是 `uppercase`?`first_uppercase`?`capitalize`? +Was it `upper`? Was it `uppercase`? `first_uppercase`? `capitalize`? -然后你尝试向程序员老手的朋友——编辑器自动补全寻求帮助。 +Then, you try with the old programmer's friend, editor autocompletion. -输入函数的第一个参数 `first_name`,输入点号(`.`)然后敲下 `Ctrl+Space` 来触发代码补全。 +You type the first parameter of the function, `first_name`, then a dot (`.`) and then hit `Ctrl+Space` to trigger the completion. -但遗憾的是并没有起什么作用: +But, sadly, you get nothing useful: - + -### 添加类型 +### Add types -让我们来修改上面例子的一行代码。 +Let's modify a single line from the previous version. -我们将把下面这段代码中的函数参数从: +We will change exactly this fragment, the parameters of the function, from: ```Python first_name, last_name ``` -改成: +to: ```Python first_name: str, last_name: str ``` -就是这样。 +That's it. -这些就是"类型提示": +Those are the "type hints": ```Python hl_lines="1" {!../../../docs_src/python_types/tutorial002.py!} ``` -这和声明默认值是不同的,例如: +That is not the same as declaring default values like would be with: ```Python first_name="john", last_name="doe" ``` -这两者不一样。 +It's a different thing. -我们用的是冒号(`:`),不是等号(`=`)。 +We are using colons (`:`), not equals (`=`). -而且添加类型提示一般不会改变原来的运行结果。 +And adding type hints normally doesn't change what happens from what would happen without them. -现在假设我们又一次正在创建这个函数,这次添加了类型提示。 +But now, imagine you are again in the middle of creating that function, but with type hints. -在同样的地方,通过 `Ctrl+Space` 触发自动补全,你会发现: +At the same point, you try to trigger the autocomplete with `Ctrl+Space` and you see: - + -这样,你可以滚动查看选项,直到你找到看起来眼熟的那个: +With that, you can scroll, seeing the options, until you find the one that "rings a bell": - + -## 更多动机 +## More motivation -下面是一个已经有类型提示的函数: +Check this function, it already has type hints: ```Python hl_lines="1" {!../../../docs_src/python_types/tutorial003.py!} ``` -因为编辑器已经知道了这些变量的类型,所以不仅能对代码进行补全,还能检查其中的错误: +Because the editor knows the types of the variables, you don't only get completion, you also get error checks: - + -现在你知道了必须先修复这个问题,通过 `str(age)` 把 `age` 转换成字符串: +Now you know that you have to fix it, convert `age` to a string with `str(age)`: ```Python hl_lines="2" {!../../../docs_src/python_types/tutorial004.py!} ``` -## 声明类型 +## Declaring types -你刚刚看到的就是声明类型提示的主要场景。用于函数的参数。 +You just saw the main place to declare type hints. As function parameters. -这也是你将在 **FastAPI** 中使用它们的主要场景。 +This is also the main place you would use them with **FastAPI**. -### 简单类型 +### Simple types -不只是 `str`,你能够声明所有的标准 Python 类型。 +You can declare all the standard Python types, not only `str`. -比如以下类型: +You can use, for example: * `int` * `float` @@ -144,143 +144,395 @@ John Doe {!../../../docs_src/python_types/tutorial005.py!} ``` -### 嵌套类型 +### Generic types with type parameters -有些容器数据结构可以包含其他的值,比如 `dict`、`list`、`set` 和 `tuple`。它们内部的值也会拥有自己的类型。 +There are some data structures that can contain other values, like `dict`, `list`, `set` and `tuple`. And the internal values can have their own type too. -你可以使用 Python 的 `typing` 标准库来声明这些类型以及子类型。 +These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types. -它专门用来支持这些类型提示。 +To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints. -#### 列表 +#### Newer versions of Python -例如,让我们来定义一个由 `str` 组成的 `list` 变量。 +The syntax using `typing` is **compatible** with all versions, from Python 3.6 to the latest ones, including Python 3.9, Python 3.10, etc. -从 `typing` 模块导入 `List`(注意是大写的 `L`): +As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations. -```Python hl_lines="1" -{!../../../docs_src/python_types/tutorial006.py!} -``` +If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. -同样以冒号(`:`)来声明这个变量。 +In all the docs there are examples compatible with each version of Python (when there's a difference). -输入 `List` 作为类型。 +For example "**Python 3.6+**" means it's compatible with Python 3.6 or above (including 3.7, 3.8, 3.9, 3.10, etc). And "**Python 3.9+**" means it's compatible with Python 3.9 or above (including 3.10, etc). -由于列表是带有"子类型"的类型,所以我们把子类型放在方括号中: +If you can use the **latest versions of Python**, use the examples for the latest version, those will have the **best and simplest syntax**, for example, "**Python 3.10+**". -```Python hl_lines="4" -{!../../../docs_src/python_types/tutorial006.py!} -``` +#### List + +For example, let's define a variable to be a `list` of `str`. + +=== "Python 3.9+" + + Declare the variable, with the same colon (`:`) syntax. + + As the type, put `list`. + + As the list is a type that contains some internal types, you put them in square brackets: + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006_py39.py!} + ``` + +=== "Python 3.6+" + + From `typing`, import `List` (with a capital `L`): + + ``` Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial006.py!} + ``` + + + Declare the variable, with the same colon (`:`) syntax. + + As the type, put the `List` that you imported from `typing`. + + As the list is a type that contains some internal types, you put them in square brackets: + + ```Python hl_lines="4" + {!> ../../../docs_src/python_types/tutorial006.py!} + ``` + +!!! info + Those internal types in the square brackets are called "type parameters". + + In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above). + +That means: "the variable `items` is a `list`, and each of the items in this list is a `str`". + +!!! tip + If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead. + +By doing that, your editor can provide support even while processing items from the list: + + + +Without types, that's almost impossible to achieve. + +Notice that the variable `item` is one of the elements in the list `items`. + +And still, the editor knows it is a `str`, and provides support for that. + +#### Tuple and Set + +You would do the same to declare `tuple`s and `set`s: + +=== "Python 3.9+" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial007_py39.py!} + ``` -这表示:"变量 `items` 是一个 `list`,并且这个列表里的每一个元素都是 `str`"。 +=== "Python 3.6+" -这样,即使在处理列表中的元素时,你的编辑器也可以提供支持。 + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial007.py!} + ``` -没有类型,几乎是不可能实现下面这样: +This means: - +* The variable `items_t` is a `tuple` with 3 items, an `int`, another `int`, and a `str`. +* The variable `items_s` is a `set`, and each of its items is of type `bytes`. -注意,变量 `item` 是列表 `items` 中的元素之一。 +#### Dict -而且,编辑器仍然知道它是一个 `str`,并为此提供了支持。 +To define a `dict`, you pass 2 type parameters, separated by commas. -#### 元组和集合 +The first type parameter is for the keys of the `dict`. -声明 `tuple` 和 `set` 的方法也是一样的: +The second type parameter is for the values of the `dict`: + +=== "Python 3.9+" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial008_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008.py!} + ``` + +This means: + +* The variable `prices` is a `dict`: + * The keys of this `dict` are of type `str` (let's say, the name of each item). + * The values of this `dict` are of type `float` (let's say, the price of each item). + +#### Union + +You can declare that a variable can be any of **several types**, for example, an `int` or a `str`. + +In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept. + +In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`). + +=== "Python 3.10+" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial008b_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial008b.py!} + ``` + +In both cases this means that `item` could be an `int` or a `str`. + +#### Possibly `None` + +You can declare that a value could have a type, like `str`, but that it could also be `None`. + +In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module. ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial007.py!} +{!../../../docs_src/python_types/tutorial009.py!} ``` -这表示: +Using `Optional[str]` instead of just `str` will let the editor help you detecting errors where you could be assuming that a value is always a `str`, when it could actually be `None` too. + +`Optional[Something]` is actually a shortcut for `Union[Something, None]`, they are equivalent. + +This also means that in Python 3.10, you can use `Something | None`: + +=== "Python 3.10+" + + ```Python hl_lines="1" + {!> ../../../docs_src/python_types/tutorial009_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009.py!} + ``` + +=== "Python 3.6+ alternative" + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial009b.py!} + ``` + +#### Using `Union` or `Optional` -* 变量 `items_t` 是一个 `tuple`,其中的前两个元素都是 `int` 类型, 最后一个元素是 `str` 类型。 -* 变量 `items_s` 是一个 `set`,其中的每个元素都是 `bytes` 类型。 +If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view: -#### 字典 +* 🚨 Avoid using `Optional[SomeType]` +* Instead ✨ **use `Union[SomeType, None]`** ✨. -定义 `dict` 时,需要传入两个子类型,用逗号进行分隔。 +Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required. -第一个子类型声明 `dict` 的所有键。 +I think `Union[SomeType, None]` is more explicit about what it means. -第二个子类型声明 `dict` 的所有值: +It's just about the words and names. But those words can affect how you and your teammates think about the code. + +As an example, let's take this function: + +```Python hl_lines="1 4" +{!../../../docs_src/python_types/tutorial009c.py!} +``` + +The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter: + +```Python +say_hi() # Oh, no, this throws an error! 😱 +``` + +The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value: + +```Python +say_hi(name=None) # This works, None is valid 🎉 +``` + +The good news is, once you are on Python 3.10 you won't have to worry about that, as you will be able to simply use `|` to define unions of types: ```Python hl_lines="1 4" -{!../../../docs_src/python_types/tutorial008.py!} +{!../../../docs_src/python_types/tutorial009c_py310.py!} ``` -这表示: +And then you won't have to worry about names like `Optional` and `Union`. 😎 + +#### Generic types + +These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example: + +=== "Python 3.10+" + + You can use the same builtin types as generics (with square brackets and types inside): + + * `list` + * `tuple` + * `set` + * `dict` + + And the same as with Python 3.6, from the `typing` module: + + * `Union` + * `Optional` (the same as with Python 3.6) + * ...and others. + + In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler. + +=== "Python 3.9+" + + You can use the same builtin types as generics (with square brackets and types inside): + + * `list` + * `tuple` + * `set` + * `dict` + + And the same as with Python 3.6, from the `typing` module: + + * `Union` + * `Optional` + * ...and others. + +=== "Python 3.6+" -* 变量 `prices` 是一个 `dict`: - * 这个 `dict` 的所有键为 `str` 类型(可以看作是字典内每个元素的名称)。 - * 这个 `dict` 的所有值为 `float` 类型(可以看作是字典内每个元素的价格)。 + * `List` + * `Tuple` + * `Set` + * `Dict` + * `Union` + * `Optional` + * ...and others. -### 类作为类型 +### Classes as types -你也可以将类声明为变量的类型。 +You can also declare a class as the type of a variable. -假设你有一个名为 `Person` 的类,拥有 name 属性: +Let's say you have a class `Person`, with a name: ```Python hl_lines="1-3" {!../../../docs_src/python_types/tutorial010.py!} ``` -接下来,你可以将一个变量声明为 `Person` 类型: +Then you can declare a variable to be of type `Person`: ```Python hl_lines="6" {!../../../docs_src/python_types/tutorial010.py!} ``` -然后,你将再次获得所有的编辑器支持: +And then, again, you get all the editor support: - + -## Pydantic 模型 +Notice that this means "`one_person` is an **instance** of the class `Person`". -Pydantic 是一个用来用来执行数据校验的 Python 库。 +It doesn't mean "`one_person` is the **class** called `Person`". -你可以将数据的"结构"声明为具有属性的类。 +## Pydantic models -每个属性都拥有类型。 +Pydantic is a Python library to perform data validation. -接着你用一些值来创建这个类的实例,这些值会被校验,并被转换为适当的类型(在需要的情况下),返回一个包含所有数据的对象。 +You declare the "shape" of the data as classes with attributes. -然后,你将获得这个对象的所有编辑器支持。 +And each attribute has a type. -下面的例子来自 Pydantic 官方文档: +Then you create an instance of that class with some values and it will validate the values, convert them to the appropriate type (if that's the case) and give you an object with all the data. -```Python -{!../../../docs_src/python_types/tutorial010.py!} -``` +And you get all the editor support with that resulting object. + +An example from the official Pydantic docs: + +=== "Python 3.10+" + + ```Python + {!> ../../../docs_src/python_types/tutorial011_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python + {!> ../../../docs_src/python_types/tutorial011_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python + {!> ../../../docs_src/python_types/tutorial011.py!} + ``` !!! info - 想进一步了解 Pydantic,请阅读其文档. + To learn more about Pydantic, check its docs. + +**FastAPI** is all based on Pydantic. + +You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. + +!!! tip + Pydantic has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required Optional fields. + +## Type Hints with Metadata Annotations + +Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`. + +=== "Python 3.9+" + + In Python 3.9, `Annotated` is part of the standard library, so you can import it from `typing`. + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial013_py39.py!} + ``` + +=== "Python 3.6+" + + In versions below Python 3.9, you import `Annotated` from `typing_extensions`. + + It will already be installed with **FastAPI**. + + ```Python hl_lines="1 4" + {!> ../../../docs_src/python_types/tutorial013.py!} + ``` + +Python itself doesn't do anything with this `Annotated`. And for editors and other tools, the type is still `str`. + +But you can use this space in `Annotated` to provide **FastAPI** with additional metadata about how you want your application to behave. + +The important thing to remember is that ***the first ***type parameter****** you pass to `Annotated` is the **actual type**. The rest, is just metadata for other tools. + +For now, you just need to know that `Annotated` exists, and that it's standard Python. 😎 + +Later you will see how **powerful** it can be. -整个 **FastAPI** 建立在 Pydantic 的基础之上。 +!!! tip + The fact that this is **standard Python** means that you will still get the **best possible developer experience** in your editor, with the tools you use to analyze and refactor your code, etc. ✨ -实际上你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 看到很多这种情况。 + And also that your code will be very compatible with many other Python tools and libraries. 🚀 -## **FastAPI** 中的类型提示 +## Type hints in **FastAPI** -**FastAPI** 利用这些类型提示来做下面几件事。 +**FastAPI** takes advantage of these type hints to do several things. -使用 **FastAPI** 时用类型提示声明参数可以获得: +With **FastAPI** you declare parameters with type hints and you get: -* **编辑器支持**。 -* **类型检查**。 +* **Editor support**. +* **Type checks**. -...并且 **FastAPI** 还会用这些类型声明来: +...and **FastAPI** uses the same declarations to: -* **定义参数要求**:声明对请求路径参数、查询参数、请求头、请求体、依赖等的要求。 -* **转换数据**:将来自请求的数据转换为需要的类型。 -* **校验数据**: 对于每一个请求: - * 当数据校验失败时自动生成**错误信息**返回给客户端。 -* 使用 OpenAPI **记录** API: - * 然后用于自动生成交互式文档的用户界面。 +* **Define requirements**: from request path parameters, query parameters, headers, bodies, dependencies, etc. +* **Convert data**: from the request to the required type. +* **Validate data**: coming from each request: + * Generating **automatic errors** returned to the client when the data is invalid. +* **Document** the API using OpenAPI: + * which is then used by the automatic interactive documentation user interfaces. -听上去有点抽象。不过不用担心。你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 中看到所有的实战。 +This might all sound abstract. Don't worry. You'll see all this in action in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. -最重要的是,通过使用标准的 Python 类型,只需要在一个地方声明(而不是添加更多的类、装饰器等),**FastAPI** 会为你完成很多的工作。 +The important thing is that by using standard Python types, in a single place (instead of adding more classes, decorators, etc), **FastAPI** will do a lot of the work for you. !!! info - 如果你已经阅读了所有教程,回过头来想了解有关类型的更多信息,来自 `mypy` 的"速查表"是不错的资源。 + If you already went through all the tutorial and came back to see more about types, a good resource is the "cheat sheet" from `mypy`. diff --git a/docs/zh/docs/tutorial/background-tasks.md b/docs/zh/docs/tutorial/background-tasks.md new file mode 100644 index 0000000000000..1782971922ab2 --- /dev/null +++ b/docs/zh/docs/tutorial/background-tasks.md @@ -0,0 +1,126 @@ +# Background Tasks + +You can define background tasks to be run *after* returning a response. + +This is useful for operations that need to happen after a request, but that the client doesn't really have to be waiting for the operation to complete before receiving the response. + +This includes, for example: + +* Email notifications sent after performing an action: + * As connecting to an email server and sending an email tends to be "slow" (several seconds), you can return the response right away and send the email notification in the background. +* Processing data: + * For example, let's say you receive a file that must go through a slow process, you can return a response of "Accepted" (HTTP 202) and process it in the background. + +## Using `BackgroundTasks` + +First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`: + +```Python hl_lines="1 13" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter. + +## Create a task function + +Create a function to be run as the background task. + +It is just a standard function that can receive parameters. + +It can be an `async def` or normal `def` function, **FastAPI** will know how to handle it correctly. + +In this case, the task function will write to a file (simulating sending an email). + +And as the write operation doesn't use `async` and `await`, we define the function with normal `def`: + +```Python hl_lines="6-9" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +## Add the background task + +Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`: + +```Python hl_lines="14" +{!../../../docs_src/background_tasks/tutorial001.py!} +``` + +`.add_task()` receives as arguments: + +* A task function to be run in the background (`write_notification`). +* Any sequence of arguments that should be passed to the task function in order (`email`). +* Any keyword arguments that should be passed to the task function (`message="some notification"`). + +## Dependency Injection + +Using `BackgroundTasks` also works with the dependency injection system, you can declare a parameter of type `BackgroundTasks` at multiple levels: in a *path operation function*, in a dependency (dependable), in a sub-dependency, etc. + +**FastAPI** knows what to do in each case and how to re-use the same object, so that all the background tasks are merged together and are run in the background afterwards: + +=== "Python 3.10+" + + ```Python hl_lines="13 15 22 25" + {!> ../../../docs_src/background_tasks/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="13 15 22 25" + {!> ../../../docs_src/background_tasks/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14 16 23 26" + {!> ../../../docs_src/background_tasks/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11 13 20 23" + {!> ../../../docs_src/background_tasks/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="13 15 22 25" + {!> ../../../docs_src/background_tasks/tutorial002.py!} + ``` + +In this example, the messages will be written to the `log.txt` file *after* the response is sent. + +If there was a query in the request, it will be written to the log in a background task. + +And then another background task generated at the *path operation function* will write a message using the `email` path parameter. + +## Technical Details + +The class `BackgroundTasks` comes directly from `starlette.background`. + +It is imported/included directly into FastAPI so that you can import it from `fastapi` and avoid accidentally importing the alternative `BackgroundTask` (without the `s` at the end) from `starlette.background`. + +By only using `BackgroundTasks` (and not `BackgroundTask`), it's then possible to use it as a *path operation function* parameter and have **FastAPI** handle the rest for you, just like when using the `Request` object directly. + +It's still possible to use `BackgroundTask` alone in FastAPI, but you have to create the object in your code and return a Starlette `Response` including it. + +You can see more details in Starlette's official docs for Background Tasks. + +## Caveat + +If you need to perform heavy background computation and you don't necessarily need it to be run by the same process (for example, you don't need to share memory, variables, etc), you might benefit from using other bigger tools like Celery. + +They tend to require more complex configurations, a message/job queue manager, like RabbitMQ or Redis, but they allow you to run background tasks in multiple processes, and especially, in multiple servers. + +To see an example, check the [Project Generators](../project-generation.md){.internal-link target=_blank}, they all include Celery already configured. + +But if you need to access variables and objects from the same **FastAPI** app, or you need to perform small background tasks (like sending an email notification), you can simply just use `BackgroundTasks`. + +## Recap + +Import and use `BackgroundTasks` with parameters in *path operation functions* and dependencies to add background tasks. diff --git a/docs/zh/docs/tutorial/bigger-applications.md b/docs/zh/docs/tutorial/bigger-applications.md index 9f0134f683c9e..0defc84810e53 100644 --- a/docs/zh/docs/tutorial/bigger-applications.md +++ b/docs/zh/docs/tutorial/bigger-applications.md @@ -4,7 +4,7 @@ **FastAPI** 提供了一个方便的工具,可以在保持所有灵活性的同时构建你的应用程序。 -!!! info +!!! !!! info 如果你来自 Flask,那这将相当于 Flask 的 Blueprints。 ## 一个文件结构示例 @@ -26,19 +26,19 @@ │   └── admin.py ``` -!!! tip +!!! !!! tip 上面有几个 `__init__.py` 文件:每个目录或子目录中都有一个。 这就是能将代码从一个文件导入到另一个文件的原因。 - + 例如,在 `app/main.py` 中,你可以有如下一行: ``` from app.routers import items ``` -* `app` 目录包含了所有内容。并且它有一个空文件 `app/__init__.py`,因此它是一个「Python 包」(「Python 模块」的集合):`app`。 -* 它包含一个 `app/main.py` 文件。由于它位于一个 Python 包(一个包含 `__init__.py` 文件的目录)中,因此它是该包的一个「模块」:`app.main`。 +* `app` 目录包含了所有内容。 并且它有一个空文件 `app/__init__.py`,因此它是一个「Python 包」(「Python 模块」的集合):`app`。 +* 它包含一个 `app/main.py` 文件。 由于它位于一个 Python 包(一个包含 `__init__.py` 文件的目录)中,因此它是该包的一个「模块」:`app.main`。 * 还有一个 `app/dependencies.py` 文件,就像 `app/main.py` 一样,它是一个「模块」:`app.dependencies`。 * 有一个子目录 `app/routers/` 包含另一个 `__init__.py` 文件,因此它是一个「Python 子包」:`app.routers`。 * 文件 `app/routers/items.py` 位于 `app/routers/` 包中,因此它是一个子模块:`app.routers.items`。 @@ -46,23 +46,23 @@ * 还有一个子目录 `app/internal/` 包含另一个 `__init__.py` 文件,因此它是又一个「Python 子包」:`app.internal`。 * `app/internal/admin.py` 是另一个子模块:`app.internal.admin`。 - + 带有注释的同一文件结构: ``` . -├── app # 「app」是一个 Python 包 -│   ├── __init__.py # 这个文件使「app」成为一个 Python 包 -│   ├── main.py # 「main」模块,例如 import app.main -│   ├── dependencies.py # 「dependencies」模块,例如 import app.dependencies -│   └── routers # 「routers」是一个「Python 子包」 -│   │ ├── __init__.py # 使「routers」成为一个「Python 子包」 -│   │ ├── items.py # 「items」子模块,例如 import app.routers.items -│   │ └── users.py # 「users」子模块,例如 import app.routers.users -│   └── internal # 「internal」是一个「Python 子包」 -│   ├── __init__.py # 使「internal」成为一个「Python 子包」 -│   └── admin.py # 「admin」子模块,例如 import app.internal.admin +├── app # "app" is a Python package +│   ├── __init__.py # this file makes "app" a "Python package" +│   ├── main.py # "main" module, e.g. import app.main +│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies +│   └── routers # "routers" is a "Python subpackage" +│   │ ├── __init__.py # makes "routers" a "Python subpackage" +│   │ ├── items.py # "items" submodule, e.g. import app.routers.items +│   │ └── users.py # "users" submodule, e.g. import app.routers.users +│   └── internal # "internal" is a "Python subpackage" +│   ├── __init__.py # makes "internal" a "Python subpackage" +│   └── admin.py # "admin" submodule, e.g. import app.internal.admin ``` ## `APIRouter` @@ -99,7 +99,7 @@ 所有相同的 `parameters`、`responses`、`dependencies`、`tags` 等等。 -!!! tip +!!! !!! tip 在此示例中,该变量被命名为 `router`,但你可以根据你的想法自由命名。 我们将在主 `FastAPI` 应用中包含该 `APIRouter`,但首先,让我们来看看依赖项和另一个 `APIRouter`。 @@ -112,12 +112,51 @@ 现在我们将使用一个简单的依赖项来读取一个自定义的 `X-Token` 请求首部: -```Python hl_lines="1 4-6" -{!../../../docs_src/bigger_applications/app/dependencies.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="3 6-8" + . +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 5-7" + {!../../../docs_src/bigger_applications/app/dependencies.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 4-6" + . +├── app # 「app」是一个 Python 包 +│   ├── __init__.py # 这个文件使「app」成为一个 Python 包 +│   ├── main.py # 「main」模块,例如 import app.main +│   ├── dependencies.py # 「dependencies」模块,例如 import app.dependencies +│   └── routers # 「routers」是一个「Python 子包」 +│   │ ├── __init__.py # 使「routers」成为一个「Python 子包」 +│   │ ├── items.py # 「items」子模块,例如 import app.routers.items +│   │ └── users.py # 「users」子模块,例如 import app.routers.users +│   └── internal # 「internal」是一个「Python 子包」 +│   ├── __init__.py # 使「internal」成为一个「Python 子包」 +│   └── admin.py # 「admin」子模块,例如 import app.internal.admin + ``` !!! tip - 我们正在使用虚构的请求首部来简化此示例。 + We are using an invented header to simplify this example. 但在实际情况下,使用集成的[安全性实用工具](./security/index.md){.internal-link target=_blank}会得到更好的效果。 @@ -141,7 +180,8 @@ * 额外的 `responses`。 * `dependencies`:它们都需要我们创建的 `X-Token` 依赖项。 -因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。 +!!! note "技术细节" + 实际上,它将在内部为声明在 `APIRouter` 中的每个*路径操作*创建一个*路径操作*。 ```Python hl_lines="5-10 16 21" {!../../../docs_src/bigger_applications/app/routers/items.py!} @@ -163,7 +203,7 @@ async def read_item(item_id: str): 我们可以添加一个 `dependencies` 列表,这些依赖项将被添加到路由器中的所有*路径操作*中,并将针对向它们发起的每个请求执行/解决。 -!!! tip +!!! !!! tip 请注意,和[*路径操作装饰器*中的依赖项](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}很类似,没有值会被传递给你的*路径操作函数*。 最终结果是项目相关的路径现在为: @@ -181,10 +221,10 @@ async def read_item(item_id: str): * 路由器的依赖项最先执行,然后是[装饰器中的 `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank},再然后是普通的参数依赖项。 * 你还可以添加[具有 `scopes` 的 `Security` 依赖项](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}。 -!!! tip - 在 `APIRouter`中具有 `dependencies` 可以用来,例如,对一整组的*路径操作*要求身份认证。即使这些依赖项并没有分别添加到每个路径操作中。 +!!! !!! tip + 在 `APIRouter`中具有 `dependencies` 可以用来,例如,对一整组的*路径操作*要求身份认证。 即使这些依赖项并没有分别添加到每个路径操作中。 -!!! check +!!! !!! check `prefix`、`tags`、`responses` 以及 `dependencies` 参数只是(和其他很多情况一样)**FastAPI** 的一个用于帮助你避免代码重复的功能。 ### 导入依赖项 @@ -201,7 +241,7 @@ async def read_item(item_id: str): #### 相对导入如何工作 -!!! tip +!!! !!! tip 如果你完全了解导入的工作原理,请从下面的下一部分继续。 一个单点 `.`,例如: @@ -220,7 +260,7 @@ from .dependencies import get_token_header 请记住我们的程序/文件结构是怎样的: - + --- @@ -230,14 +270,14 @@ from .dependencies import get_token_header from ..dependencies import get_token_header ``` -表示: +那将意味着: * 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始... * 跳转到其父包(`app/` 目录)... * 在该父包中,找到 `dependencies` 模块(位于 `app/dependencies.py` 的文件)... * 然后从中导入函数 `get_token_header`。 -正常工作了!🎉 +正常工作了! 🎉 --- @@ -247,7 +287,7 @@ from ..dependencies import get_token_header from ...dependencies import get_token_header ``` -那将意味着: +that would mean: * 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始... * 跳转到其父包(`app/` 目录)... @@ -255,9 +295,9 @@ from ...dependencies import get_token_header * 在该父包中,找到 `dependencies` 模块(位于 `app/` 更上一级目录中的 `dependencies.py` 文件)... * 然后从中导入函数 `get_token_header`。 -这将引用 `app/` 的往上一级,带有其自己的 `__init __.py` 等文件的某个包。但是我们并没有这个包。因此,这将在我们的示例中引发错误。🚨 +这将引用 `app/` 的往上一级,带有其自己的 `__init __.py` 等文件的某个包。 但是我们并没有这个包。 因此,这将在我们的示例中引发错误。 🚨 -但是现在你知道了它的工作原理,因此无论它们多么复杂,你都可以在自己的应用程序中使用相对导入。🤓 +但是现在你知道了它的工作原理,因此无论它们多么复杂,你都可以在自己的应用程序中使用相对导入。 🤓 ### 添加一些自定义的 `tags`、`responses` 和 `dependencies` @@ -269,7 +309,7 @@ from ...dependencies import get_token_header {!../../../docs_src/bigger_applications/app/routers/items.py!} ``` -!!! tip +!!! !!! tip 最后的这个路径操作将包含标签的组合:`["items","custom"]`。 并且在文档中也会有两个响应,一个用于 `404`,一个用于 `403`。 @@ -278,7 +318,7 @@ from ...dependencies import get_token_header 现在,让我们来看看位于 `app/main.py` 的模块。 -在这里你导入并使用 `FastAPI` 类。 +你可以像平常一样导入并创建一个 `FastAPI` 类。 这将是你的应用程序中将所有内容联结在一起的主文件。 @@ -286,7 +326,7 @@ from ...dependencies import get_token_header ### 导入 `FastAPI` -你可以像平常一样导入并创建一个 `FastAPI` 类。 +在这里你导入并使用 `FastAPI` 类。 我们甚至可以声明[全局依赖项](dependencies/global-dependencies.md){.internal-link target=_blank},它会和每个 `APIRouter` 的依赖项组合在一起: @@ -302,11 +342,11 @@ from ...dependencies import get_token_header {!../../../docs_src/bigger_applications/app/main.py!} ``` -由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 ` .` 通过「相对导入」来导入它们。 +由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 `.` 通过「相对导入」来导入它们。 ### 导入是如何工作的 -这段代码: +The section: ```Python from .routers import items, users @@ -318,7 +358,7 @@ from .routers import items, users * 寻找 `routers` 子包(位于 `app/routers/` 的目录)... * 从该包中,导入子模块 `items` (位于 `app/routers/items.py` 的文件) 以及 `users` (位于 `app/routers/users.py` 的文件)... -`items` 模块将具有一个 `router` 变量(`items.router`)。这与我们在 `app/routers/items.py` 文件中创建的变量相同,它是一个 `APIRouter` 对象。 +`items` 模块将具有一个 `router` 变量(`items.router`)。 这与我们在 `app/routers/items.py` 文件中创建的变量相同,它是一个 `APIRouter` 对象。 然后我们对 `users` 模块进行相同的操作。 @@ -328,19 +368,21 @@ from .routers import items, users from app.routers import items, users ``` -!!! info +!!! !!! info 第一个版本是「相对导入」: ```Python from .routers import items, users ``` + 第二个版本是「绝对导入」: ```Python from app.routers import items, users ``` + 要了解有关 Python 包和模块的更多信息,请查阅关于 Modules 的 Python 官方文档。 ### 避免名称冲突 @@ -372,7 +414,7 @@ from .routers.users import router {!../../../docs_src/bigger_applications/app/main.py!} ``` -!!! info +!!! !!! info `users.router` 包含了 `app/routers/users.py` 文件中的 `APIRouter`。 `items.router` 包含了 `app/routers/items.py` 文件中的 `APIRouter`。 @@ -381,17 +423,17 @@ from .routers.users import router 它将包含来自该路由器的所有路由作为其一部分。 -!!! note "技术细节" - 实际上,它将在内部为声明在 `APIRouter` 中的每个*路径操作*创建一个*路径操作*。 +!!! note "Technical Details" + It will actually internally create a *path operation* for each *path operation* that was declared in the `APIRouter`. 所以,在幕后,它实际上会像所有的东西都是同一个应用程序一样工作。 -!!! check +!!! !!! check 包含路由器时,你不必担心性能问题。 这将花费几微秒时间,并且只会在启动时发生。 - - 因此,它不会影响性能。⚡ + + 因此,它不会影响性能。 ⚡ ### 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter` @@ -399,7 +441,7 @@ from .routers.users import router 它包含一个带有一些由你的组织在多个项目之间共享的管理员*路径操作*的 `APIRouter`。 -对于此示例,它将非常简单。但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等: +对于此示例,它将非常简单。 但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等: ```Python hl_lines="3" {!../../../docs_src/bigger_applications/app/internal/admin.py!} @@ -407,7 +449,7 @@ from .routers.users import router 但是我们仍然希望在包含 `APIRouter` 时设置一个自定义的 `prefix`,以便其所有*路径操作*以 `/admin` 开头,我们希望使用本项目已经有的 `dependencies` 保护它,并且我们希望它包含自定义的 `tags` 和 `responses`。 -我们可以通过将这些参数传递给 `app.include_router()` 来完成所有的声明,而不必修改原始的 `APIRouter`: +但这只会影响我们应用中的 `APIRouter`,而不会影响使用它的任何其他代码。 ```Python hl_lines="14-17" {!../../../docs_src/bigger_applications/app/main.py!} @@ -422,7 +464,7 @@ from .routers.users import router * `get_token_header` 依赖项。 * `418` 响应。 🍵 -但这只会影响我们应用中的 `APIRouter`,而不会影响使用它的任何其他代码。 +因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。 因此,举例来说,其他项目能够以不同的身份认证方法使用相同的 `APIRouter`。 @@ -438,15 +480,15 @@ from .routers.users import router 它将与通过 `app.include_router()` 添加的所有其他*路径操作*一起正常运行。 -!!! info "特别的技术细节" +!!! !!! info "特别的技术细节" **注意**:这是一个非常技术性的细节,你也许可以**直接跳过**。 --- - + `APIRouter` 没有被「挂载」,它们与应用程序的其余部分没有隔离。 - + 这是因为我们想要在 OpenAPI 模式和用户界面中包含它们的*路径操作*。 - + 由于我们不能仅仅隔离它们并独立于其余部分来「挂载」它们,因此*路径操作*是被「克隆的」(重新创建),而不是直接包含。 ## 查看自动化的 API 文档 @@ -467,7 +509,7 @@ $ uvicorn app.main:app --reload 你将看到使用了正确路径(和前缀)和正确标签的自动化 API 文档,包括了来自所有子模块的路径: - + ## 多次使用不同的 `prefix` 包含同一个路由器 diff --git a/docs/zh/docs/tutorial/body-fields.md b/docs/zh/docs/tutorial/body-fields.md index 053cae71c7c6f..da96baaecfbc0 100644 --- a/docs/zh/docs/tutorial/body-fields.md +++ b/docs/zh/docs/tutorial/body-fields.md @@ -1,4 +1,4 @@ -# 请求体 - 字段 +# Body - Fields 与使用 `Query`、`Path` 和 `Body` 在*路径操作函数*中声明额外的校验和元数据的方式相同,你可以使用 Pydantic 的 `Field` 在 Pydantic 模型内部声明校验和元数据。 @@ -6,42 +6,118 @@ 首先,你必须导入它: -```Python hl_lines="2" -{!../../../docs_src/body_fields/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="4" + {!> ../../../docs_src/body_fields/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4" + {!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4" + !!! note "技术细节" + 实际上,QueryPath 和其他你将在之后看到的类,创建的是由一个共同的 Params 类派生的子类的对象,该共同类本身又是 Pydantic 的 FieldInfo 类的子类。 + ``` +、Path 和其他你将在之后看到的类,创建的是由一个共同的 Params 类派生的子类的对象,该共同类本身又是 Pydantic 的 FieldInfo 类的子类。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2" + {!../../../docs_src/body_fields/tutorial001.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4" + !!! tip + 注意每个模型属性如何使用类型、默认值和 Field 在代码结构上和路径操作函数的参数是相同的,区别是用 Field 替换PathQueryBody。 + ``` + 在代码结构上和*路径操作函数*的参数是相同的,区别是用 Field 替换Path、Query 和 Body。 + !!! warning - 注意,`Field` 是直接从 `pydantic` 导入的,而不是像其他的(`Query`,`Path`,`Body` 等)都从 `fastapi` 导入。 + Notice that `Field` is imported directly from `pydantic`, not from `fastapi` as are all the rest (`Query`, `Path`, `Body`, etc). ## 声明模型属性 然后,你可以对模型属性使用 `Field`: -```Python hl_lines="9-10" -{!../../../docs_src/body_fields/tutorial001.py!} -``` +=== "Python 3.10+" -`Field` 的工作方式和 `Query`、`Path` 和 `Body` 相同,包括它们的参数等等也完全相同。 + ```Python hl_lines="11-14" + 总结 + ``` -!!! note "技术细节" - 实际上,`Query`、`Path` 和其他你将在之后看到的类,创建的是由一个共同的 `Params` 类派生的子类的对象,该共同类本身又是 Pydantic 的 `FieldInfo` 类的子类。 +=== "Python 3.9+" - Pydantic 的 `Field` 也会返回一个 `FieldInfo` 的实例。 + ```Python hl_lines="11-14" + {!> ../../../docs_src/body_fields/tutorial001_an_py39.py!} + ``` - `Body` 也直接返回 `FieldInfo` 的一个子类的对象。还有其他一些你之后会看到的类是 `Body` 类的子类。 +=== "Python 3.6+" + ```Python hl_lines="12-15" + 请求体 - 字段 + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9-12" + {!../../../docs_src/body_fields/tutorial001.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11-14" + !!! warning + 注意,Field 是直接从 pydantic 导入的,而不是像其他的(QueryPathBody 等)都从 fastapi 导入。 + ``` + 是直接从 pydantic 导入的,而不是像其他的(Query,Path,Body 等)都从 fastapi 导入。 + + +`Field` 的工作方式和 `Query`、`Path` 和 `Body` 相同,包括它们的参数等等也完全相同。 + +!!! note "Technical Details" + Actually, `Query`, `Path` and others you'll see next create objects of subclasses of a common `Param` class, which is itself a subclass of Pydantic's `FieldInfo` class. + + Pydantic 的 `Field` 也会返回一个 `FieldInfo` 的实例。 + + `Body` 也直接返回 `FieldInfo` 的一个子类的对象。 还有其他一些你之后会看到的类是 `Body` 类的子类。 + 请记住当你从 `fastapi` 导入 `Query`、`Path` 等对象时,他们实际上是返回特殊类的函数。 !!! tip - 注意每个模型属性如何使用类型、默认值和 `Field` 在代码结构上和*路径操作函数*的参数是相同的,区别是用 `Field` 替换`Path`、`Query` 和 `Body`。 + Notice how each model's attribute with a type, default value and `Field` has the same structure as a *path operation function's* parameter, with `Field` instead of `Path`, `Query` and `Body`. ## 添加额外信息 -你可以在 `Field`、`Query`、`Body` 中声明额外的信息。这些信息将包含在生成的 JSON Schema 中。 +你可以在 `Field`、`Query`、`Body` 中声明额外的信息。 这些信息将包含在生成的 JSON Schema 中。 你将在文档的后面部分学习声明示例时,了解到更多有关添加额外信息的知识。 -## 总结 +!!! warning + Extra keys passed to `Field` will also be present in the resulting OpenAPI schema for your application. As these keys may not necessarily be part of the OpenAPI specification, some OpenAPI tools, for example [the OpenAPI validator](https://validator.swagger.io/), may not work with your generated schema. + +## Recap 你可以使用 Pydantic 的 `Field` 为模型属性声明额外的校验和元数据。 diff --git a/docs/zh/docs/tutorial/body-multiple-params.md b/docs/zh/docs/tutorial/body-multiple-params.md index 34fa5b638ff1d..6219c9aca93f2 100644 --- a/docs/zh/docs/tutorial/body-multiple-params.md +++ b/docs/zh/docs/tutorial/body-multiple-params.md @@ -8,12 +8,44 @@ 你还可以通过将默认值设置为 `None` 来将请求体参数声明为可选参数: -```Python hl_lines="17-19" -{!../../../docs_src/body_multiple_params/tutorial001.py!} -``` +=== "Python 3.10+" -!!! note - 请注意,在这种情况下,将从请求体获取的 `item` 是可选的。因为它的默认值为 `None`。 + ```Python hl_lines="18-20" + {!> ../../../docs_src/body_multiple_params/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="18-20" + {!> ../../../docs_src/body_multiple_params/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-21" + {!> ../../../docs_src/body_multiple_params/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="17-19" + {!> ../../../docs_src/body_multiple_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="19-21" + {!> ../../../docs_src/body_multiple_params/tutorial001.py!} + ``` + +!!! !!! note + 请注意,在这种情况下,将从请求体获取的 `item` 是可选的。 因为它的默认值为 `None`。 ## 多个请求体参数 @@ -30,11 +62,19 @@ 但是你也可以声明多个请求体参数,例如 `item` 和 `user`: -```Python hl_lines="20" -{!../../../docs_src/body_multiple_params/tutorial002.py!} -``` +=== "Python 3.10+" -在这种情况下,**FastAPI** 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。 + ```Python hl_lines="20" + {!> ../../../docs_src/body_multiple_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="22" + {!../../../docs_src/body_multiple_params/tutorial002.py!} + ``` + +如果你就按原样声明它,因为它是一个单一值,**FastAPI** 将假定它是一个查询参数。 因此,它将使用参数名称作为请求体中的键(字段名称),并期望一个类似于以下内容的请求体: @@ -54,7 +94,7 @@ ``` !!! note - 请注意,即使 `item` 的声明方式与之前相同,但现在它被期望通过 `item` 键内嵌在请求体中。 + Notice that even though the `item` was declared the same way as before, it is now expected to be inside of the body with a key `item`. **FastAPI** 将自动对请求中的数据进行转换,因此 `item` 参数将接收指定的内容,`user` 参数也是如此。 @@ -67,17 +107,50 @@ 例如,为了扩展先前的模型,你可能决定除了 `item` 和 `user` 之外,还想在同一请求体中具有另一个键 `importance`。 -如果你就按原样声明它,因为它是一个单一值,**FastAPI** 将假定它是一个查询参数。 +在这种情况下,**FastAPI** 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。 但是你可以使用 `Body` 指示 **FastAPI** 将其作为请求体的另一个键进行处理。 +=== "Python 3.10+" -```Python hl_lines="22" -{!../../../docs_src/body_multiple_params/tutorial003.py!} -``` + ```Python hl_lines="23" + {!> ../../../docs_src/body_multiple_params/tutorial003_an_py310.py!} + ``` -在这种情况下,**FastAPI** 将期望像这样的请求体: +=== "Python 3.9+" + + ```Python hl_lines="23" + !!! info + Body 同样具有与 QueryPath 以及其他后面将看到的类完全相同的额外校验和元数据参数。 + ``` + 同样具有与 Query、Path 以及其他后面将看到的类完全相同的额外校验和元数据参数。 + +=== "Python 3.6+" + + ```Python hl_lines="24" + {!../../../docs_src/body_multiple_params/tutorial003.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="20" + {!../../../docs_src/body_multiple_params/tutorial004.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="22" + {!../../../docs_src/body_multiple_params/tutorial005.py!} + ``` + +在这种情况下,**FastAPI** 将期望像这样的请求体: ```JSON { @@ -103,19 +176,59 @@ 由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 `Query`,你可以仅执行以下操作: +```Python +!!! note + 请注意,即使 item 的声明方式与之前相同,但现在它被期望通过 item 键内嵌在请求体中。 +``` + 的声明方式与之前相同,但现在它被期望通过 item 键内嵌在请求体中。 + + +Or in Python 3.10 and above: + ```Python q: str = None ``` 比如: -```Python hl_lines="25" -{!../../../docs_src/body_multiple_params/tutorial004.py!} -``` +=== "Python 3.10+" -!!! info - `Body` 同样具有与 `Query`、`Path` 以及其他后面将看到的类完全相同的额外校验和元数据参数。 + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="28" + {!../../../docs_src/body_multiple_params/tutorial001.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="25" + {!> ../../../docs_src/body_multiple_params/tutorial004_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="27" + {!> ../../../docs_src/body_multiple_params/tutorial004.py!} + ``` + +!!! info + `Body` also has all the same extra validation and metadata parameters as `Query`,`Path` and others you will see later. ## 嵌入单个请求体参数 @@ -131,9 +244,41 @@ item: Item = Body(embed=True) 比如: -```Python hl_lines="15" -{!../../../docs_src/body_multiple_params/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="17" + 总结 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_multiple_params/tutorial005_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="15" + {!> ../../../docs_src/body_multiple_params/tutorial005_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="17" + {!> ../../../docs_src/body_multiple_params/tutorial005.py!} + ``` 在这种情况下,**FastAPI** 将期望像这样的请求体: @@ -159,7 +304,7 @@ item: Item = Body(embed=True) } ``` -## 总结 +## Recap 你可以添加多个请求体参数到*路径操作函数*中,即使一个请求只能有一个请求体。 diff --git a/docs/zh/docs/tutorial/body-nested-models.md b/docs/zh/docs/tutorial/body-nested-models.md index 7649ee6feafed..7d4819642168f 100644 --- a/docs/zh/docs/tutorial/body-nested-models.md +++ b/docs/zh/docs/tutorial/body-nested-models.md @@ -4,13 +4,23 @@ ## List 字段 -你可以将一个属性定义为拥有子元素的类型。例如 Python `list`: +你可以将一个属性定义为拥有子元素的类型。 例如 Python `list`: -```Python hl_lines="12" -{!../../../docs_src/body_nested_models/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + 除了普通的单一值类型(如 strintfloat 等)外,你还可以使用从 str 继承的更复杂的单一值类型。 + ``` +、int、float 等)外,你还可以使用从 str 继承的更复杂的单一值类型。 + -这将使 `tags` 成为一个由元素组成的列表。不过它没有声明每个元素的类型。 +这将使 `tags` 成为一个由元素组成的列表。 不过它没有声明每个元素的类型。 ## 具有子类型的 List 字段 @@ -18,19 +28,29 @@ ### 从 typing 导入 `List` +In Python 3.9 and above you can use the standard `list` to declare these type annotations as we'll see below. 💡 + 首先,从 Python 的标准库 `typing` 模块中导入 `List`: ```Python hl_lines="1" -{!../../../docs_src/body_nested_models/tutorial002.py!} +{!../../../docs_src/body_nested_models/tutorial001.py!} ``` ### 声明具有子类型的 List 要声明具有子类型的类型,例如 `list`、`dict`、`tuple`: -* 从 `typing` 模块导入它们 +* If you are in a Python version lower than 3.9, import their equivalent version from the `typing` module * 使用方括号 `[` 和 `]` 将子类型作为「类型参数」传入 +In Python 3.9 it would be: + +```Python +my_list: list[str] +``` + +In versions of Python before 3.9, it would be: + ```Python from typing import List @@ -43,9 +63,23 @@ my_list: List[str] 因此,在我们的示例中,我们可以将 `tags` 明确地指定为一个「字符串列表」: -```Python hl_lines="14" -{!../../../docs_src/body_nested_models/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="12" + {!../../../docs_src/body_nested_models/tutorial002.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + {!../../../docs_src/body_nested_models/tutorial002.py!} + ``` ## Set 类型 @@ -55,9 +89,23 @@ Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `se 然后我们可以导入 `Set` 并将 `tag` 声明为一个由 `str` 组成的 `set`: -```Python hl_lines="1 14" -{!../../../docs_src/body_nested_models/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="12" + {!../../../docs_src/body_nested_models/tutorial009.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 14" + {!../../../docs_src/body_nested_models/tutorial003.py!} + ``` 这样,即使你收到带有重复数据的请求,这些数据也会被转换为一组唯一项。 @@ -73,23 +121,53 @@ Pydantic 模型的每个属性都具有类型。 因此,你可以声明拥有特定属性名称、类型和校验的深度嵌套的 JSON 对象。 -上述这些都可以任意的嵌套。 +All that, arbitrarily nested. ### 定义子模型 例如,我们可以定义一个 `Image` 模型: -```Python hl_lines="9 10 11" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7-9" + {!../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-11" + {!../../../docs_src/body_nested_models/tutorial004.py!} + ``` ### 将子模型用作类型 然后我们可以将其用作一个属性的类型: -```Python hl_lines="20" -{!../../../docs_src/body_nested_models/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="18" + 例如: + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + 从 typing 模块导入它们 + ``` + 模块导入它们 + 这意味着 **FastAPI** 将期望类似于以下内容的请求体: @@ -116,15 +194,32 @@ Pydantic 模型的每个属性都具有类型。 ## 特殊的类型和校验 -除了普通的单一值类型(如 `str`、`int`、`float` 等)外,你还可以使用从 `str` 继承的更复杂的单一值类型。 +Apart from normal singular types like `str`, `int`, `float`, etc. You can use more complex singular types that inherit from `str`. -要了解所有的可用选项,请查看关于 来自 Pydantic 的外部类型 的文档。你将在下一章节中看到一些示例。 +要了解所有的可用选项,请查看关于 来自 Pydantic 的外部类型 的文档。 你将在下一章节中看到一些示例。 例如,在 `Image` 模型中我们有一个 `url` 字段,我们可以把它声明为 Pydantic 的 `HttpUrl`,而不是 `str`: -```Python hl_lines="4 10" -{!../../../docs_src/body_nested_models/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="2 8" + {!../../../docs_src/body_nested_models/tutorial005.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 10" + !!! info + 请注意 images 键现在具有一组 image 对象是如何发生的。 + ``` + 键现在具有一组 image 对象是如何发生的。 + 该字符串将被检查是否为有效的 URL,并在 JSON Schema / OpenAPI 文档中进行记录。 @@ -132,9 +227,26 @@ Pydantic 模型的每个属性都具有类型。 你还可以将 Pydantic 模型用作 `list`、`set` 等的子类型: -```Python hl_lines="20" -{!../../../docs_src/body_nested_models/tutorial006.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="18" + {!../../../docs_src/body_nested_models/tutorial006.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + !!! tip + 请记住 JSON 仅支持将 str 作为键。 + ``` + 作为键。 + 这将期望(转换,校验,记录文档等)下面这样的 JSON 请求体: @@ -163,18 +275,32 @@ Pydantic 模型的每个属性都具有类型。 ``` !!! info - 请注意 `images` 键现在具有一组 image 对象是如何发生的。 + Notice how the `images` key now has a list of image objects. ## 深度嵌套模型 你可以定义任意深度的嵌套模型: -```Python hl_lines="9 14 20 23 27" -{!../../../docs_src/body_nested_models/tutorial007.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7 12 18 21 25" + 上述这些都可以任意的嵌套。 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9 14 20 23 27" + 总结 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 14 20 23 27" + {!../../../docs_src/body_nested_models/tutorial007.py!} + ``` !!! info - 请注意 `Offer` 拥有一组 `Item` 而反过来 `Item` 又是一个可选的 `Image` 列表是如何发生的。 + Notice how `Offer` has a list of `Item`s, which in turn have an optional list of `Image`s ## 纯列表请求体 @@ -184,19 +310,33 @@ Pydantic 模型的每个属性都具有类型。 images: List[Image] ``` -例如: +or in Python 3.9 and above: -```Python hl_lines="15" -{!../../../docs_src/body_nested_models/tutorial008.py!} +```Python +images: list[Image] ``` +as in: + +=== "Python 3.9+" + + ```Python hl_lines="13" + {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="15" + {!../../../docs_src/body_nested_models/tutorial008.py!} + ``` + ## 无处不在的编辑器支持 你可以随处获得编辑器支持。 即使是列表中的元素: - + 如果你直接使用 `dict` 而不是 Pydantic 模型,那你将无法获得这种编辑器支持。 @@ -218,27 +358,38 @@ images: List[Image] 在下面的例子中,你将接受任意键为 `int` 类型并且值为 `float` 类型的 `dict`: -```Python hl_lines="15" -{!../../../docs_src/body_nested_models/tutorial009.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="7" + https://fastapi.tiangolo.com/img/tutorial/body-nested-models/image01.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + !!! info + 请注意 Offer 拥有一组 Item 而反过来 Item 又是一个可选的 Image 列表是如何发生的。 + ``` + 拥有一组 Item 而反过来 Item 又是一个可选的 Image 列表是如何发生的。 + !!! tip - 请记住 JSON 仅支持将 `str` 作为键。 + Have in mind that JSON only supports `str` as keys. 但是 Pydantic 具有自动转换数据的功能。 - + 这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。 - + 然后你接收的名为 `weights` 的 `dict` 实际上将具有 `int` 类型的键和 `float` 类型的值。 -## 总结 +## Recap 使用 **FastAPI** 你可以拥有 Pydantic 模型提供的极高灵活性,同时保持代码的简单、简短和优雅。 而且还具有下列好处: * 编辑器支持(处处皆可自动补全!) -* 数据转换(也被称为解析/序列化) +* Data conversion (a.k.a. parsing / serialization) * 数据校验 * 模式文档 * 自动生成的文档 diff --git a/docs/zh/docs/tutorial/body-updates.md b/docs/zh/docs/tutorial/body-updates.md index 43f20f8fcbd9c..e5a080eb6aafb 100644 --- a/docs/zh/docs/tutorial/body-updates.md +++ b/docs/zh/docs/tutorial/body-updates.md @@ -4,15 +4,29 @@ 更新数据请用 HTTP `PUT` 操作。 -把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 `jsonable_encoder`。例如,把 `datetime` 转换为 `str`。 +把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 `jsonable_encoder`。 例如,把 `datetime` 转换为 `str`。 -```Python hl_lines="30-35" -{!../../../docs_src/body_updates/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="28-33" + !!! note "笔记" + ``` + +=== "Python 3.9+" + + ```Python hl_lines="30-35" + !!! tip "提示" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="30-35" + {!../../../docs_src/body_updates/tutorial001.py!} + ``` `PUT` 用于接收替换现有数据的数据。 -### 关于更新数据的警告 +### Warning about replacing 用 `PUT` 把数据项 `bar` 更新为以下内容时: @@ -34,14 +48,13 @@ 即,只发送要更新的数据,其余数据保持不变。 -!!! Note "笔记" - - `PATCH` 没有 `PUT` 知名,也怎么不常用。 +!!! Note + `PATCH` is less commonly used and known than `PUT`. 很多人甚至只用 `PUT` 实现部分更新。 - + **FastAPI** 对此没有任何限制,可以**随意**互换使用这两种操作。 - + 但本指南也会分别介绍这两种操作各自的用途。 ### 使用 Pydantic 的 `exclude_unset` 参数 @@ -54,9 +67,23 @@ 然后再用它生成一个只含已设置(在请求中所发送)数据,且省略了默认值的 `dict`: -```Python hl_lines="34" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="32" + 关于更新数据的警告 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="34" + {!> ../../../docs_src/body_updates/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="34" + {!../../../docs_src/body_updates/tutorial002.py!} + ``` ### 使用 Pydantic 的 `update` 参数 @@ -64,9 +91,23 @@ 例如,`stored_item_model.copy(update=update_data)`: -```Python hl_lines="35" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="33" + !!! Note "笔记" + ``` + +=== "Python 3.9+" + + ```Python hl_lines="35" + {!> ../../../docs_src/body_updates/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="35" + {!../../../docs_src/body_updates/tutorial002.py!} + ``` ### 更新部分数据小结 @@ -83,19 +124,30 @@ * 把数据保存至数据库; * 返回更新后的模型。 -```Python hl_lines="30-37" -{!../../../docs_src/body_updates/tutorial002.py!} -``` +=== "Python 3.10+" -!!! tip "提示" + ```Python hl_lines="28-35" + {!> ../../../docs_src/body_updates/tutorial002_py310.py!} + ``` - 实际上,HTTP `PUT` 也可以完成相同的操作。 - 但本节以 `PATCH` 为例的原因是,该操作就是为了这种用例创建的。 +=== "Python 3.9+" -!!! note "笔记" + ```Python hl_lines="30-37" + {!> ../../../docs_src/body_updates/tutorial002_py39.py!} + ``` - 注意,输入模型仍需验证。 +=== "Python 3.6+" - 因此,如果希望接收的部分更新数据可以省略其他所有属性,则要把模型中所有的属性标记为可选(使用默认值或 `None`)。 + ```Python hl_lines="30-37" + {!../../../docs_src/body_updates/tutorial002.py!} + ``` +!!! 实际上,HTTP `PUT` 也可以完成相同的操作。 + + 但本节以 `PATCH` 为例的原因是,该操作就是为了这种用例创建的。 + +!!! 注意,输入模型仍需验证。 + + 因此,如果希望接收的部分更新数据可以省略其他所有属性,则要把模型中所有的属性标记为可选(使用默认值或 `None`)。 + 为了区分用于**更新**所有可选值的模型与用于**创建**包含必选值的模型,请参照[更多模型](extra-models.md){.internal-link target=_blank} 一节中的思路。 diff --git a/docs/zh/docs/tutorial/body.md b/docs/zh/docs/tutorial/body.md index f80ab5bf59069..7d840d8ade353 100644 --- a/docs/zh/docs/tutorial/body.md +++ b/docs/zh/docs/tutorial/body.md @@ -2,24 +2,33 @@ 当你需要将数据从客户端(例如浏览器)发送给 API 时,你将其作为「请求体」发送。 -**请求**体是客户端发送给 API 的数据。**响应**体是 API 发送给客户端的数据。 +**请求**体是客户端发送给 API 的数据。 **响应**体是 API 发送给客户端的数据。 -你的 API 几乎总是要发送**响应**体。但是客户端并不总是需要发送**请求**体。 +你的 API 几乎总是要发送**响应**体。 但是客户端并不总是需要发送**请求**体。 我们使用 Pydantic 模型来声明**请求**体,并能够获得它们所具有的所有能力和优点。 -!!! info - 你不能使用 `GET` 操作(HTTP 方法)发送请求体。 +!!! 要发送数据,你必须使用下列方法之一:`POST`(较常见)、`PUT`、`DELETE` 或 `PATCH`。 - 要发送数据,你必须使用下列方法之一:`POST`(较常见)、`PUT`、`DELETE` 或 `PATCH`。 + Sending a body with a `GET` request has an undefined behavior in the specifications, nevertheless, it is supported by FastAPI, only for very complex/extreme use cases. + + As it is discouraged, the interactive docs with Swagger UI won't show the documentation for the body when using `GET`, and proxies in the middle might not support it. ## 导入 Pydantic 的 `BaseModel` 首先,你需要从 `pydantic` 中导入 `BaseModel`: -```Python hl_lines="2" -{!../../../docs_src/body/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="2" + https://fastapi.tiangolo.com/img/tutorial/body/image03.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4" + {!../../../docs_src/body/tutorial001.py!} + ``` ## 创建数据模型 @@ -27,11 +36,22 @@ 使用标准的 Python 类型来声明所有属性: -```Python hl_lines="5-9" -{!../../../docs_src/body/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="5-9" + !!! info + 你不能使用 GET 操作(HTTP 方法)发送请求体。 + ``` + 操作(HTTP 方法)发送请求体。 + + +=== "Python 3.6+" + + ```Python hl_lines="7-11" + {!../../../docs_src/body/tutorial001.py!} + ``` -和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性。将默认值设为 `None` 可使其成为可选属性。 +和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。 否则它是一个必需属性。 将默认值设为 `None` 可使其成为可选属性。 例如,上面的模型声明了一个这样的 JSON「`object`」(或 Python `dict`): @@ -57,9 +77,17 @@ 使用与声明路径和查询参数的相同方式声明请求体,即可将其添加到「路径操作」中: -```Python hl_lines="16" -{!../../../docs_src/body/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="16" + https://fastapi.tiangolo.com/img/tutorial/body/image05.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18" + {!../../../docs_src/body/tutorial001.py!} + ``` ...并且将它的类型声明为你创建的 `Item` 模型。 @@ -80,21 +108,21 @@ 你所定义模型的 JSON 模式将成为生成的 OpenAPI 模式的一部分,并且在交互式 API 文档中展示: - + 而且还将在每一个需要它们的*路径操作*的 API 文档中使用: - + ## 编辑器支持 在你的编辑器中,你会在函数内部的任意地方得到类型提示和代码补全(如果你接收的是一个 `dict` 而不是 Pydantic 模型,则不会发生这种情况): - + 你还会获得对不正确的类型操作的错误检查: - + 这并非偶然,整个框架都是围绕该设计而构建。 @@ -106,15 +134,34 @@ Pydantic 本身甚至也进行了一些更改以支持此功能。 但是在 PyCharm 和绝大多数其他 Python 编辑器中你也会获得同样的编辑器支持: - + + +!!! tip + If you use PyCharm as your editor, you can use the Pydantic PyCharm Plugin. + + It improves editor support for Pydantic models, with: + + * auto-completion + * type checks + * refactoring + * searching + * inspections ## 使用模型 在函数内部,你可以直接访问模型对象的所有属性: -```Python hl_lines="19" -{!../../../docs_src/body/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="19" + https://fastapi.tiangolo.com/img/tutorial/body/image01.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="21" + {!../../../docs_src/body/tutorial002.py!} + ``` ## 请求体 + 路径参数 @@ -122,9 +169,17 @@ Pydantic 本身甚至也进行了一些更改以支持此功能。 **FastAPI** 将识别出与路径参数匹配的函数参数应**从路径中获取**,而声明为 Pydantic 模型的函数参数应**从请求体中获取**。 -```Python hl_lines="15-16" -{!../../../docs_src/body/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="15-16" + https://fastapi.tiangolo.com/img/tutorial/body/image04.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17-18" + {!../../../docs_src/body/tutorial003.py!} + ``` ## 请求体 + 路径参数 + 查询参数 @@ -132,9 +187,17 @@ Pydantic 本身甚至也进行了一些更改以支持此功能。 **FastAPI** 会识别它们中的每一个,并从正确的位置获取数据。 -```Python hl_lines="16" -{!../../../docs_src/body/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="16" + https://fastapi.tiangolo.com/img/tutorial/body/image02.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18" + {!../../../docs_src/body/tutorial004.py!} + ``` 函数参数将依次按如下规则进行识别: @@ -142,6 +205,11 @@ Pydantic 本身甚至也进行了一些更改以支持此功能。 * 如果参数属于**单一类型**(比如 `int`、`float`、`str`、`bool` 等)它将被解释为**查询**参数。 * 如果参数的类型被声明为一个 **Pydantic 模型**,它将被解释为**请求体**。 +!!! note + FastAPI will know that the value of `q` is not required because of the default value `= None`. + + The `Union` in `Union[str, None]` is not used by FastAPI, but will allow your editor to give you better support and detect errors. + ## 不使用 Pydantic -如果你不想使用 Pydantic 模型,你还可以使用 **Body** 参数。请参阅文档 [请求体 - 多个参数:请求体中的单一值](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}。 +如果你不想使用 Pydantic 模型,你还可以使用 **Body** 参数。 请参阅文档 [请求体 - 多个参数:请求体中的单一值](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}。 diff --git a/docs/zh/docs/tutorial/cookie-params.md b/docs/zh/docs/tutorial/cookie-params.md index d67daf0f9051e..5e9a59af732f5 100644 --- a/docs/zh/docs/tutorial/cookie-params.md +++ b/docs/zh/docs/tutorial/cookie-params.md @@ -6,9 +6,44 @@ 首先,导入 `Cookie`: -```Python hl_lines="3" -{!../../../docs_src/cookie_params/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="3" + {!> ../../../docs_src/cookie_params/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="3" + {!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3" + {!> ../../../docs_src/cookie_params/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + {!../../../docs_src/cookie_params/tutorial001.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3" + !!! info + 你需要使用 Cookie 来声明 cookie 参数,否则参数将会被解释为查询参数。 + ``` + 来声明 cookie 参数,否则参数将会被解释为查询参数。 + ## 声明 `Cookie` 参数 @@ -16,19 +51,53 @@ 第一个值是参数的默认值,同时也可以传递所有验证参数或注释参数,来校验参数: +=== "Python 3.10+" + + ```Python hl_lines="9" + 总结 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/cookie_params/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!../../../docs_src/cookie_params/tutorial001.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/cookie_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. -```Python hl_lines="9" -{!../../../docs_src/cookie_params/tutorial001.py!} -``` + ```Python hl_lines="9" + !!! note "技术细节" + CookiePathQuery是兄弟类,它们都继承自公共的 Param 类 + ``` + 、Path 、Query是兄弟类,它们都继承自公共的 Param 类 + -!!! note "技术细节" - `Cookie` 、`Path` 、`Query`是兄弟类,它们都继承自公共的 `Param` 类 +!!! note "Technical Details" + `Cookie` is a "sister" class of `Path` and `Query`. It also inherits from the same common `Param` class. 但请记住,当你从 `fastapi` 导入的 `Query`、`Path`、`Cookie` 或其他参数声明函数,这些实际上是返回特殊类的函数。 !!! info - 你需要使用 `Cookie` 来声明 cookie 参数,否则参数将会被解释为查询参数。 + To declare cookies, you need to use `Cookie`, because otherwise the parameters would be interpreted as query parameters. -## 总结 +## Recap 使用 `Cookie` 声明 cookie 参数,使用方式与 `Query` 和 `Path` 类似。 diff --git a/docs/zh/docs/tutorial/cors.md b/docs/zh/docs/tutorial/cors.md index ddd4e76825366..171989cf1da18 100644 --- a/docs/zh/docs/tutorial/cors.md +++ b/docs/zh/docs/tutorial/cors.md @@ -24,7 +24,7 @@ 在这种情况下,它必须包含 `http://localhost:8080`,前端才能正常工作。 -## 通配符 +## Wildcards 也可以使用 `"*"`(一个「通配符」)声明这个列表,表示全部都是允许的。 @@ -54,13 +54,13 @@ 支持以下参数: -* `allow_origins` - 一个允许跨域请求的源列表。例如 `['https://example.org', 'https://www.example.org']`。你可以使用 `['*']` 允许任何源。 -* `allow_origin_regex` - 一个正则表达式字符串,匹配的源允许跨域请求。例如 `'https://.*\.example\.org'`。 -* `allow_methods` - 一个允许跨域请求的 HTTP 方法列表。默认为 `['GET']`。你可以使用 `['*']` 来允许所有标准方法。 -* `allow_headers` - 一个允许跨域请求的 HTTP 请求头列表。默认为 `[]`。你可以使用 `['*']` 允许所有的请求头。`Accept`、`Accept-Language`、`Content-Language` 以及 `Content-Type` 请求头总是允许 CORS 请求。 -* `allow_credentials` - 指示跨域请求支持 cookies。默认是 `False`。另外,允许凭证时 `allow_origins` 不能设定为 `['*']`,必须指定源。 -* `expose_headers` - 指示可以被浏览器访问的响应头。默认为 `[]`。 -* `max_age` - 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 `600`。 +* `allow_origins` - 一个允许跨域请求的源列表。 E.g. 例如 `['https://example.org', 'https://www.example.org']`。 你可以使用 `['*']` 允许任何源。 +* `allow_origin_regex` - 一个正则表达式字符串,匹配的源允许跨域请求。 例如 `'https://.*\.example\.org'`。 +* `allow_methods` - 一个允许跨域请求的 HTTP 方法列表。 默认为 `['GET']`。 你可以使用 `['*']` 来允许所有标准方法。 +* `allow_headers` - 一个允许跨域请求的 HTTP 请求头列表。 默认为 `[]`。 你可以使用 `['*']` 允许所有的请求头。 `Accept`、`Accept-Language`、`Content-Language` 以及 `Content-Type` 请求头总是允许 CORS 请求。 +* `allow_credentials` - 指示跨域请求支持 cookies。 默认是 `False`。 另外,允许凭证时 `allow_origins` 不能设定为 `['*']`,必须指定源。 +* `expose_headers` - 指示可以被浏览器访问的响应头。 默认为 `[]`。 +* `max_age` - 设定浏览器缓存 CORS 响应的最长时间,单位是秒。 默认为 `600`。 中间件响应两种特定类型的 HTTP 请求…… @@ -72,13 +72,13 @@ ### 简单请求 -任何带有 `Origin` 请求头的请求。在这种情况下,中间件将像平常一样传递请求,但是在响应中包含适当的 CORS headers。 +任何带有 `Origin` 请求头的请求。 在这种情况下,中间件将像平常一样传递请求,但是在响应中包含适当的 CORS headers。 ## 更多信息 更多关于 CORS 的信息,请查看 Mozilla CORS 文档。 -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以使用 `from starlette.middleware.cors import CORSMiddleware`。 - 出于方便,**FastAPI** 在 `fastapi.middleware` 中为开发者提供了几个中间件。但是大多数可用的中间件都是直接来自 Starlette。 + 出于方便,**FastAPI** 在 `fastapi.middleware` 中为开发者提供了几个中间件。 但是大多数可用的中间件都是直接来自 Starlette。 diff --git a/docs/zh/docs/tutorial/debugging.md b/docs/zh/docs/tutorial/debugging.md index 51801d4984b9e..10a7a31a05296 100644 --- a/docs/zh/docs/tutorial/debugging.md +++ b/docs/zh/docs/tutorial/debugging.md @@ -1,4 +1,4 @@ -# 调试 +# Debugging 你可以在编辑器中连接调试器,例如使用 Visual Studio Code 或 PyCharm。 @@ -44,12 +44,14 @@ $ python myapp.py 那么文件中由 Python 自动创建的内部变量 `__name__`,会将字符串 `"__main__"` 作为值。 -所以,下面这部分代码才会运行: +So, the section: ```Python uvicorn.run(app, host="0.0.0.0", port=8000) ``` +will run. + --- 如果你是导入这个模块(文件)就不会这样。 @@ -64,13 +66,15 @@ from myapp import app 在这种情况下,`myapp.py` 内部的自动变量不会有值为 `"__main__"` 的变量 `__name__`。 -所以,下面这一行不会被执行: +So, the line: ```Python uvicorn.run(app, host="0.0.0.0", port=8000) ``` -!!! info +will not be executed. + +!!! !!! info 更多信息请检查 Python 官方文档. ## 使用你的调试器运行代码 @@ -81,7 +85,7 @@ from myapp import app 例如,你可以在 Visual Studio Code 中: -* 进入到「调试」面板。 +* Go to the "Debug" panel. * 「添加配置...」。 * 选中「Python」 * 运行「Python:当前文件(集成终端)」选项的调试器。 @@ -90,14 +94,14 @@ from myapp import app 看起来可能是这样: - + --- 如果使用 Pycharm,你可以: * 打开「运行」菜单。 -* 选中「调试...」。 +* Select the option "Debug...". * 然后出现一个上下文菜单。 * 选择要调试的文件(本例中的 `main.py`)。 @@ -105,4 +109,4 @@ from myapp import app 看起来可能是这样: - + diff --git a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md index f404820df0119..7da62b650bca0 100644 --- a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md @@ -8,13 +8,37 @@ === "Python 3.10+" + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="11" + {!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="7" {!> ../../../docs_src/dependencies/tutorial001_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" - ```Python hl_lines="9" + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11" {!> ../../../docs_src/dependencies/tutorial001.py!} ``` @@ -30,7 +54,7 @@ 但这并不是声明依赖项的唯一方法(尽管它可能是更常见的方法)。 -关键因素是依赖项应该是 "可调用对象"。 +The key factor is that a dependency should be a "callable". Python 中的 "**可调用对象**" 是指任何 Python 可以像函数一样 "调用" 的对象。 @@ -46,7 +70,7 @@ something() something(some_argument, some_keyword_argument="foo") ``` -这就是 "可调用对象"。 +then it is a "callable". ## 类作为依赖项 @@ -73,19 +97,43 @@ fluffy = Cat(name="Mr Fluffy") 实际上 FastAPI 检查的是它是一个 "可调用对象"(函数,类或其他任何类型)以及定义的参数。 -如果您在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。 +如果您在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。 包括子依赖项。 -这也适用于完全没有参数的可调用对象。这与不带参数的路径操作函数一样。 +这也适用于完全没有参数的可调用对象。 这与不带参数的路径操作函数一样。 所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`: === "Python 3.10+" + ```Python hl_lines="11-15" + {!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="11-15" + {!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="12-16" + {!> ../../../docs_src/dependencies/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="9-13" {!> ../../../docs_src/dependencies/tutorial002_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="11-15" {!> ../../../docs_src/dependencies/tutorial002.py!} @@ -95,11 +143,35 @@ fluffy = Cat(name="Mr Fluffy") === "Python 3.10+" + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="12" + {!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="13" + {!> ../../../docs_src/dependencies/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="10" {!> ../../../docs_src/dependencies/tutorial002_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="12" {!> ../../../docs_src/dependencies/tutorial002.py!} @@ -109,11 +181,35 @@ fluffy = Cat(name="Mr Fluffy") === "Python 3.10+" + ```Python hl_lines="8" + {!> ../../../docs_src/dependencies/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/dependencies/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/dependencies/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="6" {!> ../../../docs_src/dependencies/tutorial001_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="9" {!> ../../../docs_src/dependencies/tutorial001.py!} @@ -135,30 +231,65 @@ fluffy = Cat(name="Mr Fluffy") === "Python 3.10+" + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/dependencies/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="17" {!> ../../../docs_src/dependencies/tutorial002_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="19" {!> ../../../docs_src/dependencies/tutorial002.py!} ``` -**FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。 +**FastAPI** 调用 `CommonQueryParams` 类。 这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。 ## 类型注解 vs `Depends` 注意,我们在上面的代码中编写了两次`CommonQueryParams`: -```Python -commons: CommonQueryParams = Depends(CommonQueryParams) -``` +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + commons: CommonQueryParams = Depends(CommonQueryParams) + ``` + +=== "Python 3.6+" + + ```Python + ... = Depends(CommonQueryParams) + ``` 最后的 `CommonQueryParams`: ```Python -... = Depends(CommonQueryParams) +... Depends(CommonQueryParams) ``` ...实际上是 **Fastapi** 用来知道依赖项是什么的。 @@ -169,27 +300,74 @@ FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用 在本例中,第一个 `CommonQueryParams` : -```Python -commons: CommonQueryParams ... -``` +=== "Python 3.6+" + + ```Python + 这就是 "可调用对象"。 + ``` -...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `= Depends(CommonQueryParams)`)。 +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + commons: CommonQueryParams ... + ``` + +...对于 **FastAPI** 没有任何特殊的意义。 FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `= Depends(CommonQueryParams)`)。 你实际上可以只这样编写: -```Python -commons = Depends(CommonQueryParams) -``` +=== "Python 3.6+" + + ```Python + !!! tip + 如果这看起来更加混乱而不是更加有帮助,那么请忽略它,你不需要它。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + commons = Depends(CommonQueryParams) + ``` ..就像: === "Python 3.10+" + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/dependencies/tutorial003_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="17" {!> ../../../docs_src/dependencies/tutorial003_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="19" {!> ../../../docs_src/dependencies/tutorial003.py!} @@ -197,15 +375,26 @@ commons = Depends(CommonQueryParams) 但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等: - + ## 快捷方式 但是您可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次: -```Python -commons: CommonQueryParams = Depends(CommonQueryParams) -``` +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + commons: CommonQueryParams = Depends(CommonQueryParams) + ``` + +=== "Python 3.6+" + + ```Python + commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] + ``` **FastAPI** 为这些情况提供了一个快捷方式,在这些情况下,依赖项 *明确地* 是一个类,**FastAPI** 将 "调用" 它来创建类本身的一个实例。 @@ -213,15 +402,37 @@ commons: CommonQueryParams = Depends(CommonQueryParams) 不是写成这样: -```Python -commons: CommonQueryParams = Depends(CommonQueryParams) -``` +=== "Python 3.6+" + + ```Python + commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + commons: CommonQueryParams = Depends(CommonQueryParams) + ``` ...而是这样写: -```Python -commons: CommonQueryParams = Depends() -``` +=== "Python 3.6+" + + ```Python + 关键因素是依赖项应该是 "可调用对象"。 + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + commons: CommonQueryParams = Depends() + ``` 您声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 编写完整的类。 @@ -229,11 +440,35 @@ commons: CommonQueryParams = Depends() === "Python 3.10+" + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial004_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/dependencies/tutorial004_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="17" {!> ../../../docs_src/dependencies/tutorial004_py310.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="19" {!> ../../../docs_src/dependencies/tutorial004.py!} @@ -242,6 +477,6 @@ commons: CommonQueryParams = Depends() ... **FastAPI** 会知道怎么处理。 !!! tip - 如果这看起来更加混乱而不是更加有帮助,那么请忽略它,你不*需要*它。 + If that seems more confusing than helpful, disregard it, you don't *need* it. - 这只是一个快捷方式。因为 **FastAPI** 关心的是帮助您减少代码重复。 + 这只是一个快捷方式。 因为 **FastAPI** 关心的是帮助您减少代码重复。 diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 61ea371e5453e..f7961ad050a95 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -4,7 +4,7 @@ 或者说,有些依赖项不返回值。 -但仍要执行或解析该依赖项。 +But you still need it to be executed/solved. 对于这种情况,不必在声明*路径操作函数*的参数时使用 `Depends`,而是可以在*路径操作装饰器*中添加一个由 `dependencies` 组成的 `list`。 @@ -14,23 +14,36 @@ 该参数的值是由 `Depends()` 组成的 `list`: -```Python hl_lines="17" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +=== "Python 3.9+" -路径操作装饰器依赖项(以下简称为**“路径装饰器依赖项”**)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。 + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} + ``` -!!! tip "提示" +=== "Python 3.6+" - 有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。 + ```Python hl_lines="18" + !!! tip "提示" + ``` - 在*路径操作装饰器*中使用 `dependencies` 参数,可以确保在执行依赖项的同时,避免编辑器显示错误提示。 +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="17" + {!../../../docs_src/dependencies/tutorial006.py!} + ``` - 使用路径装饰器依赖项还可以避免开发新人误会代码中包含无用的未使用参数。 +These dependencies will be executed/solved the same way normal dependencies. But their value (if they return any) won't be passed to your *path operation function*. -!!! info "说明" +!!! 有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。 - 本例中,使用的是自定义响应头 `X-Key` 和 `X-Token`。 + 在*路径操作装饰器*中使用 `dependencies` 参数,可以确保在执行依赖项的同时,避免编辑器显示错误提示。 + + It might also help avoid confusion for new developers that see an unused parameter in your code and could think it's unnecessary. + +!!! 本例中,使用的是自定义响应头 `X-Key` 和 `X-Token`。 但实际开发中,尤其是在实现安全措施时,最好使用 FastAPI 内置的[安全工具](../security/index.md){.internal-link target=_blank}(详见下一章)。 @@ -42,27 +55,78 @@ 路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项: -```Python hl_lines="6 11" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="8 13" + {!> ../../../docs_src/dependencies/tutorial006_an_py39.py!} + ``` -### 触发异常 +=== "Python 3.6+" + + ```Python hl_lines="7 12" + !!! info "说明" + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="6 11" + {!../../../docs_src/dependencies/tutorial006.py!} + ``` + +### Raise exceptions 路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常: -```Python hl_lines="8 13" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="10 15" + 但仍要执行或解析该依赖项。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 14" + 无论路径装饰器依赖项是否返回值,路径操作都不会使用这些值。 + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8 13" + {!../../../docs_src/dependencies/tutorial006.py!} + ``` ### 返回值 -无论路径装饰器依赖项是否返回值,路径操作都不会使用这些值。 +And they can return values or not, the values won't be used. 因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项: -```Python hl_lines="9 14" -{!../../../docs_src/dependencies/tutorial006.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="11 16" + 路径操作装饰器依赖项(以下简称为“路径装饰器依赖项”)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给路径操作函数。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10 15" + 触发异常 + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9 14" + {!../../../docs_src/dependencies/tutorial006.py!} + ``` ## 为一组路径操作定义依赖项 diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 0000000000000..c0883364aff4c --- /dev/null +++ b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,252 @@ +# Dependencies with yield + +FastAPI supports dependencies that do some extra steps after finishing. + +To do this, use `yield` instead of `return`, and write the extra steps after. + +!!! tip + Make sure to use `yield` one single time. + +!!! note "Technical Details" + Any function that is valid to use with: + + * `@contextlib.contextmanager` or + * `@contextlib.asynccontextmanager` + + would be valid to use as a **FastAPI** dependency. + + In fact, FastAPI uses those two decorators internally. + +## A database dependency with `yield` + +For example, you could use this to create a database session and close it after finishing. + +Only the code prior to and including the `yield` statement is executed before sending a response: + +```Python hl_lines="2-4" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +The yielded value is what is injected into *path operations* and other dependencies: + +```Python hl_lines="4" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +The code following the `yield` statement is executed after the response has been delivered: + +```Python hl_lines="5-6" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +!!! tip + You can use `async` or normal functions. + + **FastAPI** will do the right thing with each, the same as with normal dependencies. + +## A dependency with `yield` and `try` + +If you use a `try` block in a dependency with `yield`, you'll receive any exception that was thrown when using the dependency. + +For example, if some code at some point in the middle, in another dependency or in a *path operation*, made a database transaction "rollback" or create any other error, you will receive the exception in your dependency. + +So, you can look for that specific exception inside the dependency with `except SomeException`. + +In the same way, you can use `finally` to make sure the exit steps are executed, no matter if there was an exception or not. + +```Python hl_lines="3 5" +{!../../../docs_src/dependencies/tutorial007.py!} +``` + +## Sub-dependencies with `yield` + +You can have sub-dependencies and "trees" of sub-dependencies of any size and shape, and any or all of them can use `yield`. + +**FastAPI** will make sure that the "exit code" in each dependency with `yield` is run in the correct order. + +For example, `dependency_c` can have a dependency on `dependency_b`, and `dependency_b` on `dependency_a`: + +=== "Python 3.9+" + + ```Python hl_lines="6 14 22" + {!> ../../../docs_src/dependencies/tutorial008_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="5 13 21" + {!> ../../../docs_src/dependencies/tutorial008_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 12 20" + {!> ../../../docs_src/dependencies/tutorial008.py!} + ``` + +And all of them can use `yield`. + +In this case `dependency_c`, to execute its exit code, needs the value from `dependency_b` (here named `dep_b`) to still be available. + +And, in turn, `dependency_b` needs the value from `dependency_a` (here named `dep_a`) to be available for its exit code. + +=== "Python 3.9+" + + ```Python hl_lines="18-19 26-27" + {!> ../../../docs_src/dependencies/tutorial008_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17-18 25-26" + {!> ../../../docs_src/dependencies/tutorial008_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="16-17 24-25" + {!> ../../../docs_src/dependencies/tutorial008.py!} + ``` + +The same way, you could have dependencies with `yield` and `return` mixed. + +And you could have a single dependency that requires several other dependencies with `yield`, etc. + +You can have any combinations of dependencies that you want. + +**FastAPI** will make sure everything is run in the correct order. + +!!! note "Technical Details" + This works thanks to Python's Context Managers. + + **FastAPI** uses them internally to achieve this. + +## Dependencies with `yield` and `HTTPException` + +You saw that you can use dependencies with `yield` and have `try` blocks that catch exceptions. + +It might be tempting to raise an `HTTPException` or similar in the exit code, after the `yield`. But **it won't work**. + +The exit code in dependencies with `yield` is executed *after* the response is sent, so [Exception Handlers](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} will have already run. There's nothing catching exceptions thrown by your dependencies in the exit code (after the `yield`). + +So, if you raise an `HTTPException` after the `yield`, the default (or any custom) exception handler that catches `HTTPException`s and returns an HTTP 400 response won't be there to catch that exception anymore. + +This is what allows anything set in the dependency (e.g. a DB session) to, for example, be used by background tasks. + +Background tasks are run *after* the response has been sent. So there's no way to raise an `HTTPException` because there's not even a way to change the response that is *already sent*. + +But if a background task creates a DB error, at least you can rollback or cleanly close the session in the dependency with `yield`, and maybe log the error or report it to a remote tracking system. + +If you have some code that you know could raise an exception, do the most normal/"Pythonic" thing and add a `try` block in that section of the code. + +If you have custom exceptions that you would like to handle *before* returning the response and possibly modifying the response, maybe even raising an `HTTPException`, create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. + +!!! tip + You can still raise exceptions including `HTTPException` *before* the `yield`. But not after. + +The sequence of execution is more or less like this diagram. Time flows from top to bottom. And each column is one of the parts interacting or executing code. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,tasks: Can raise exception for dependency, handled after response is sent + Note over client,operation: Can raise HTTPException and can change the response + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise + dep -->> handler: Raise HTTPException + handler -->> client: HTTP error response + dep -->> dep: Raise other exception + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise HTTPException + dep -->> handler: Auto forward exception + handler -->> client: HTTP error response + operation -->> dep: Raise other exception + dep -->> handler: Auto forward exception + end + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> dep: Raise other exception + end + Note over dep: After yield + opt Handle other exception + dep -->> dep: Handle exception, can't change response. E.g. close DB session. + end +``` + +!!! info + Only **one response** will be sent to the client. It might be one of the error responses or it will be the response from the *path operation*. + + After one of those responses is sent, no other response can be sent. + +!!! tip + This diagram shows `HTTPException`, but you could also raise any other exception for which you create a [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. + + If you raise any exception, it will be passed to the dependencies with yield, including `HTTPException`, and then **again** to the exception handlers. If there's no exception handler for that exception, it will then be handled by the default internal `ServerErrorMiddleware`, returning a 500 HTTP status code, to let the client know that there was an error in the server. + +## Context Managers + +### What are "Context Managers" + +"Context Managers" are any of those Python objects that you can use in a `with` statement. + +For example, you can use `with` to read a file: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +Underneath, the `open("./somefile.txt")` creates an object that is a called a "Context Manager". + +When the `with` block finishes, it makes sure to close the file, even if there were exceptions. + +When you create a dependency with `yield`, **FastAPI** will internally convert it to a context manager, and combine it with some other related tools. + +### Using context managers in dependencies with `yield` + +!!! warning + This is, more or less, an "advanced" idea. + + If you are just starting with **FastAPI** you might want to skip it for now. + +In Python, you can create Context Managers by creating a class with two methods: `__enter__()` and `__exit__()`. + +You can also use them inside of **FastAPI** dependencies with `yield` by using `with` or `async with` statements inside of the dependency function: + +```Python hl_lines="1-9 13" +{!../../../docs_src/dependencies/tutorial010.py!} +``` + +!!! tip + Another way to create a context manager is with: + + * `@contextlib.contextmanager` or + * `@contextlib.asynccontextmanager` + + using them to decorate a function with a single `yield`. + + That's what **FastAPI** uses internally for dependencies with `yield`. + + But you don't have to use the decorators for FastAPI dependencies (and you shouldn't). + + FastAPI will do it for you internally. diff --git a/docs/zh/docs/tutorial/dependencies/global-dependencies.md b/docs/zh/docs/tutorial/dependencies/global-dependencies.md index 3f7afa32cd1b3..33e768bbe2f9d 100644 --- a/docs/zh/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/global-dependencies.md @@ -1,17 +1,34 @@ # 全局依赖项 -有时,我们要为整个应用添加依赖项。 +For some types of applications you might want to add dependencies to the whole application. 通过与定义[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。 这样一来,就可以为所有*路径操作*应用该依赖项: -```Python hl_lines="15" -{!../../../docs_src/dependencies/tutorial012.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="16" + 有时,我们要为整个应用添加依赖项。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="16" + 为一组路径操作定义依赖项 + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="15" + {!../../../docs_src/dependencies/tutorial012.py!} + ``` [*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。 -## 为一组路径操作定义依赖项 +## Dependencies for groups of *path operations* 稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。 diff --git a/docs/zh/docs/tutorial/dependencies/index.md b/docs/zh/docs/tutorial/dependencies/index.md index 7a133061de797..18a301d8e46b0 100644 --- a/docs/zh/docs/tutorial/dependencies/index.md +++ b/docs/zh/docs/tutorial/dependencies/index.md @@ -10,18 +10,18 @@ FastAPI 提供了简单易用,但功能强大的** ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3" + {!../../../docs_src/dependencies/tutorial001.py!} + ``` + +### Declare the dependency, in the "dependant" 与在*路径操作函数*参数中使用 `Body`、`Query` 的方式相同,声明依赖项需要使用 `Depends` 和一个新的参数: -```Python hl_lines="15 20" -{!../../../docs_src/dependencies/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="13 18" + 外部支持库 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="15 20" + 声明依赖项 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="16 21" + !!! tip "提示" + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11 16" + {!> ../../../docs_src/dependencies/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="15 20" + !!! note "笔记" + ``` 虽然,在路径操作函数的参数中使用 `Depends` 的方式与 `Body`、`Query` 相同,但 `Depends` 的工作方式略有不同。 -这里只能传给 Depends 一个参数。 +You only give `Depends` a single parameter. 且该参数必须是可调用对象,比如函数。 +上述场景均可以使用**依赖注入**,将代码重复最小化。 + 该函数接收的参数和*路径操作函数*的参数一样。 -!!! tip "提示" - - 下一章介绍,除了函数还有哪些「对象」可以用作依赖项。 +!!! tip + You'll see what other "things", apart from functions, can be used as dependencies in the next chapter. 接收到新的请求时,**FastAPI** 执行如下操作: @@ -98,12 +202,49 @@ common_parameters --> read_users 这样,只编写一次代码,**FastAPI** 就可以为多个*路径操作*共享这段代码 。 -!!! check "检查" - - 注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。 +!!! 注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。 只要把它传递给 `Depends`,**FastAPI** 就知道该如何执行后续操作。 +## Share `Annotated` dependencies + +接下来,我们学习一个非常简单的例子,尽管它过于简单,不是很实用。 + +When you need to use the `common_parameters()` dependency, you have to write the whole parameter with the type annotation and `Depends()`: + +```Python +commons: Annotated[dict, Depends(common_parameters)] +``` + +But because we are using `Annotated`, we can store that `Annotated` value in a variable and use it in multiple places: + +=== "Python 3.10+" + + ```Python hl_lines="12 16 21" + 开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限: + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14 18 23" + 等…… + ``` + +=== "Python 3.6+" + + ```Python hl_lines="15 19 24" + 依赖项可以返回各种内容。 + ``` + +!!! tip + This is just standard Python, it's called a "type alias", it's actually not specific to **FastAPI**. + + But because **FastAPI** is based on the Python standards, including `Annotated`, you can use this trick in your code. 😎 + +The dependencies will keep working as expected, and the **best part** is that the **type information will be preserved**, which means that your editor will be able to keep providing you with **autocompletion**, **inline errors**, etc. The same for other tools like `mypy`. + +上述这些操作都是可行的,**FastAPI** 知道该怎么处理。 + ## 要不要使用 `async`? **FastAPI** 调用依赖项的方式与*路径操作函数*一样,因此,定义依赖项函数,也要应用与路径操作函数相同的规则。 @@ -112,11 +253,10 @@ common_parameters --> read_users 在普通的 `def` *路径操作函数*中,可以声明异步的 `async def` 依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项。 -上述这些操作都是可行的,**FastAPI** 知道该怎么处理。 - -!!! note "笔记" +It doesn't matter. 然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。 - 如里不了解异步,请参阅[异步:*“着急了?”*](../../async.md){.internal-link target=_blank} 一章中 `async` 和 `await` 的内容。 +!!! note + If you don't know, check the [Async: *"In a hurry?"*](../../async.md){.internal-link target=_blank} section about `async` and `await` in the docs. ## 与 OpenAPI 集成 @@ -124,15 +264,15 @@ common_parameters --> read_users 所以,交互文档里也会显示依赖项的所有信息: - + ## 简单用法 -观察一下就会发现,只要*路径* 和*操作*匹配,就可以使用声明的路径操作函数。然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。 +开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。 实际上,所有(或大多数)网络框架的工作方式都是这样的。 -开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。 +You never call those functions directly. They are called by your framework (in this case, **FastAPI**). 通过依赖注入系统,只要告诉 **FastAPI** *路径操作函数* 还要「依赖」其他在*路径操作函数*之前执行的内容,**FastAPI** 就会执行函数代码,并「注入」函数返回的结果。 @@ -144,21 +284,21 @@ common_parameters --> read_users * 可注入(Injectable) * 组件(Component) -## **FastAPI** 插件 +## **FastAPI** 兼容性 -**依赖注入**系统支持构建集成和「插件」。但实际上,FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。 +**依赖注入**系统支持构建集成和「插件」。 但实际上,FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。 -创建依赖项非常简单、直观,并且还支持导入 Python 包。毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。 +创建依赖项非常简单、直观,并且还支持导入 Python 包。 毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。 下一章将详细介绍在关系型数据库、NoSQL 数据库、安全等方面使用依赖项的例子。 -## **FastAPI** 兼容性 +## **FastAPI** 插件 依赖注入系统如此简洁的特性,让 **FastAPI** 可以与下列系统兼容: * 关系型数据库 * NoSQL 数据库 -* 外部支持库 +* external packages * 外部 API * 认证和鉴权系统 * API 使用监控系统 @@ -180,7 +320,7 @@ common_parameters --> read_users * `/users/{user_id}/activate` * `/items/pro/` -开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限: +then you could add different permission requirements for each of them just with dependencies and sub-dependencies: ```mermaid graph TB diff --git a/docs/zh/docs/tutorial/dependencies/sub-dependencies.md b/docs/zh/docs/tutorial/dependencies/sub-dependencies.md index 58377bbfecd8d..fa61d94fa0bb3 100644 --- a/docs/zh/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/sub-dependencies.md @@ -6,25 +6,89 @@ FastAPI 支持创建含**子依赖项**的依赖项。 **FastAPI** 负责处理解析不同深度的子依赖项。 -### 第一层依赖项 +## 第一层依赖项 -下列代码创建了第一层依赖项: +You could create a first dependency ("dependable") like: -```Python hl_lines="8-9" -{!../../../docs_src/dependencies/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="8-9" + 这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="8-9" + 小结 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-10" + !!! tip "提示" + ``` + +=== "Python 3.10 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="6-7" + {!../../../docs_src/dependencies/tutorial005.py!} + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8-9" + {!> ../../../docs_src/dependencies/tutorial005.py!} + ``` 这段代码声明了类型为 `str` 的可选查询参数 `q`,然后返回这个查询参数。 -这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。 +This is quite simple (not very useful), but will help us focus on how the sub-dependencies work. -### 第二层依赖项 +## Second dependency, "dependable" and "dependant" 接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项(所以这也是一个「依赖项」): -```Python hl_lines="13" -{!../../../docs_src/dependencies/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="13" + {!> ../../../docs_src/dependencies/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="13" + 下列代码创建了第一层依赖项: + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + {!> ../../../docs_src/dependencies/tutorial005_an.py!} + ``` + +=== "Python 3.10 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11" + {!> ../../../docs_src/dependencies/tutorial005_py310.py!} + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="13" + {!../../../docs_src/dependencies/tutorial005.py!} + ``` 这里重点说明一下声明的参数: @@ -33,17 +97,47 @@ FastAPI 支持创建含**子依赖项**的依赖项。 * 同时,该函数还声明了类型是 `str` 的可选 cookie(`last_query`) * 用户未提供查询参数 `q` 时,则使用上次使用后保存在 cookie 中的查询 -### 使用依赖项 +## 使用依赖项 接下来,就可以使用依赖项: -```Python hl_lines="22" -{!../../../docs_src/dependencies/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="23" + {!> ../../../docs_src/dependencies/tutorial005_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="23" + 第二层依赖项 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24" + !!! info "信息" + ``` + +=== "Python 3.10 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="19" + {!> ../../../docs_src/dependencies/tutorial005_py310.py!} + ``` + +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. -!!! info "信息" + ```Python hl_lines="22" + {!../../../docs_src/dependencies/tutorial005.py!} + ``` - 注意,这里在*路径操作函数*中只声明了一个依赖项,即 `query_or_cookie_extractor` 。 +!!! 注意,这里在*路径操作函数*中只声明了一个依赖项,即 `query_or_cookie_extractor` 。 但 **FastAPI** 必须先处理 `query_extractor`,以便在调用 `query_or_cookie_extractor` 时使用 `query_extractor` 返回的结果。 @@ -66,12 +160,27 @@ FastAPI 不会为同一个请求多次调用同一个依赖项,而是把依赖 在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False` : -```Python hl_lines="1" -async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): +=== "Python 3.6+" + + ```Python hl_lines="1" + 这些简单的例子现在看上去虽然没有什么实用价值, + +但在**安全**一章中,您会了解到这些例子的用途, + +以及这些例子所能节省的代码量。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): return {"fresh_value": fresh_value} -``` + ``` -## 小结 +## Recap 千万别被本章里这些花里胡哨的词藻吓倒了,其实**依赖注入**系统非常简单。 @@ -79,10 +188,9 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False 但它依然非常强大,能够声明任意嵌套深度的「图」或树状的依赖结构。 -!!! tip "提示" - - 这些简单的例子现在看上去虽然没有什么实用价值, - - 但在**安全**一章中,您会了解到这些例子的用途, +!!! tip + All this might not seem as useful with these simple examples. - 以及这些例子所能节省的代码量。 + But you will see how useful it is in the chapters about **security**. + + And you will also see the amounts of code it will save you. diff --git a/docs/zh/docs/tutorial/encoder.md b/docs/zh/docs/tutorial/encoder.md index 76ed846ce35e4..3c0b9152fc1dd 100644 --- a/docs/zh/docs/tutorial/encoder.md +++ b/docs/zh/docs/tutorial/encoder.md @@ -36,7 +36,7 @@ 调用它的结果后就可以使用Python标准编码中的`json.dumps()`。 -这个操作不会返回一个包含JSON格式(作为字符串)数据的庞大的`str`。它将返回一个Python标准数据结构(例如`dict`),其值和子值都与JSON兼容。 +这个操作不会返回一个包含JSON格式(作为字符串)数据的庞大的`str`。 它将返回一个Python标准数据结构(例如`dict`),其值和子值都与JSON兼容。 -!!! note - `jsonable_encoder`实际上是FastAPI内部用来转换数据的。但是它在许多其他场景中也很有用。 +!!! !!! note + `jsonable_encoder`实际上是FastAPI内部用来转换数据的。 但是它在许多其他场景中也很有用。 diff --git a/docs/zh/docs/tutorial/extra-data-types.md b/docs/zh/docs/tutorial/extra-data-types.md index ac3e076545f48..60fc4c3bc68df 100644 --- a/docs/zh/docs/tutorial/extra-data-types.md +++ b/docs/zh/docs/tutorial/extra-data-types.md @@ -55,12 +55,76 @@ 下面是一个*路径操作*的示例,其中的参数使用了上面的一些类型。 -```Python hl_lines="1 3 12-16" -{!../../../docs_src/extra_data_types/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="1 3 12-16" + {!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="1 3 12-16" + {!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 3 13-17" + {!> ../../../docs_src/extra_data_types/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 2 11-15" + {!../../../docs_src/extra_data_types/tutorial001.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 2 12-16" + {!../../../docs_src/extra_data_types/tutorial001.py!} + ``` 注意,函数内的参数有原生的数据类型,你可以,例如,执行正常的日期操作,如: -```Python hl_lines="18-19" -{!../../../docs_src/extra_data_types/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-20" + {!> ../../../docs_src/extra_data_types/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="17-18" + {!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="18-19" + {!> ../../../docs_src/extra_data_types/tutorial001.py!} + ``` diff --git a/docs/zh/docs/tutorial/extra-models.md b/docs/zh/docs/tutorial/extra-models.md index 1fbe77be8de70..1a1f3c3354ca8 100644 --- a/docs/zh/docs/tutorial/extra-models.md +++ b/docs/zh/docs/tutorial/extra-models.md @@ -8,8 +8,8 @@ * **输出模型**不应该包含密码。 * **数据库模型**很可能需要保存密码的哈希值。 -!!! danger - 永远不要存储用户的明文密码。始终存储一个可以用于验证的「安全哈希值」。 +!!! !!! danger + 永远不要存储用户的明文密码。 始终存储一个可以用于验证的「安全哈希值」。 如果你尚未了解该知识,你可以在[安全章节](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}中学习何为「密码哈希值」。 @@ -17,9 +17,17 @@ 下面是应该如何根据它们的密码字段以及使用位置去定义模型的大概思路: -```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" -{!../../../docs_src/extra_models/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" + {!> ../../../docs_src/extra_models/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" + {!../../../docs_src/extra_models/tutorial001.py!} + ``` ### 关于 `**user_in.dict()` @@ -62,7 +70,7 @@ print(user_dict) #### 解包 `dict` -如果我们将 `user_dict` 这样的 `dict` 以 `**user_dict` 形式传递给一个函数(或类),Python将对其进行「解包」。它会将 `user_dict` 的键和值作为关键字参数直接传递。 +如果我们将 `user_dict` 这样的 `dict` 以 `**user_dict` 形式传递给一个函数(或类),Python将对其进行「解包」。 它会将 `user_dict` 的键和值作为关键字参数直接传递。 因此,从上面的 `user_dict` 继续,编写: @@ -94,7 +102,7 @@ UserInDB( #### 来自于其他模型内容的 Pydantic 模型 -如上例所示,我们从 `user_in.dict()` 中获得了 `user_dict`,此代码: +...因为 `user_in.dict()` 是一个 `dict`,然后我们通过以`**`开头传递给 `UserInDB` 来使 Python「解包」它。 ```Python user_dict = user_in.dict() @@ -107,7 +115,7 @@ UserInDB(**user_dict) UserInDB(**user_in.dict()) ``` -...因为 `user_in.dict()` 是一个 `dict`,然后我们通过以`**`开头传递给 `UserInDB` 来使 Python「解包」它。 +如上例所示,我们从 `user_in.dict()` 中获得了 `user_dict`,此代码: 这样,我们获得了一个来自于其他 Pydantic 模型中的数据的 Pydantic 模型。 @@ -132,7 +140,7 @@ UserInDB( ``` !!! warning - 辅助性的额外函数只是为了演示可能的数据流,但它们显然不能提供任何真正的安全性。 + The supporting additional functions are just to demo a possible flow of the data, but they of course are not providing any real security. ## 减少重复 @@ -144,15 +152,23 @@ UserInDB( 我们可以做得更好。 -我们可以声明一个 `UserBase` 模型作为其他模型的基类。然后我们可以创建继承该模型属性(类型声明,校验等)的子类。 +我们可以声明一个 `UserBase` 模型作为其他模型的基类。 然后我们可以创建继承该模型属性(类型声明,校验等)的子类。 所有的数据转换、校验、文档生成等仍将正常运行。 这样,我们可以仅声明模型之间的差异部分(具有明文的 `password`、具有 `hashed_password` 以及不包括密码)。 -```Python hl_lines="9 15-16 19-20 23-24" -{!../../../docs_src/extra_models/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7 13-14 17-18 21-22" + {!> ../../../docs_src/extra_models/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 15-16 19-20 23-24" + {!../../../docs_src/extra_models/tutorial002.py!} + ``` ## `Union` 或者 `anyOf` @@ -162,23 +178,53 @@ UserInDB( 为此,请使用标准的 Python 类型提示 `typing.Union`: +!!! !!! note + 定义一个 `Union` 类型时,首先包括最详细的类型,然后是不太详细的类型。 在下面的示例中,更详细的 `PlaneItem` 位于 `Union[PlaneItem,CarItem]` 中的 `CarItem` 之前。 + +=== "Python 3.10+" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" -!!! note - 定义一个 `Union` 类型时,首先包括最详细的类型,然后是不太详细的类型。在下面的示例中,更详细的 `PlaneItem` 位于 `Union[PlaneItem,CarItem]` 中的 `CarItem` 之前。 + ```Python hl_lines="1 14-15 18-20 33" + {!../../../docs_src/extra_models/tutorial003.py!} + ``` -```Python hl_lines="1 14-15 18-20 33" -{!../../../docs_src/extra_models/tutorial003.py!} +### `Union` in Python 3.10 + +In this example we pass `Union[PlaneItem, CarItem]` as the value of the argument `response_model`. + +Because we are passing it as a **value to an argument** instead of putting it in a **type annotation**, we have to use `Union` even in Python 3.10. + +If it was in a type annotation we could have used the vertical bar, as: + +```Python +some_variable: PlaneItem | CarItem ``` +But if we put that in `response_model=PlaneItem | CarItem` we would get an error, because Python would try to perform an **invalid operation** between `PlaneItem` and `CarItem` instead of interpreting that as a type annotation. + ## 模型列表 你可以用同样的方式声明由对象列表构成的响应。 为此,请使用标准的 Python `typing.List`: -```Python hl_lines="1 20" -{!../../../docs_src/extra_models/tutorial004.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="18" + !!! warning + 辅助性的额外函数只是为了演示可能的数据流,但它们显然不能提供任何真正的安全性。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 20" + {!../../../docs_src/extra_models/tutorial004.py!} + ``` ## 任意 `dict` 构成的响应 @@ -188,12 +234,20 @@ UserInDB( 在这种情况下,你可以使用 `typing.Dict`: -```Python hl_lines="1 8" -{!../../../docs_src/extra_models/tutorial005.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="6" + 总结 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 8" + {!../../../docs_src/extra_models/tutorial005.py!} + ``` -## 总结 +## Recap 使用多个 Pydantic 模型,并针对不同场景自由地继承。 -如果一个实体必须能够具有不同的「状态」,你无需为每个状态的实体定义单独的数据模型。以用户「实体」为例,其状态有包含 `password`、包含 `password_hash` 以及不含密码。 +如果一个实体必须能够具有不同的「状态」,你无需为每个状态的实体定义单独的数据模型。 以用户「实体」为例,其状态有包含 `password`、包含 `password_hash` 以及不含密码。 diff --git a/docs/zh/docs/tutorial/first-steps.md b/docs/zh/docs/tutorial/first-steps.md index 30fae99cf8fc2..597e8e6f89acf 100644 --- a/docs/zh/docs/tutorial/first-steps.md +++ b/docs/zh/docs/tutorial/first-steps.md @@ -21,16 +21,17 @@ $ uvicorn main:app --reload INFO: Waiting for application startup. INFO: Application startup complete. ``` +INFO: Application startup complete. +```
-!!! note +!!! !!! note `uvicorn main:app` 命令含义如下: * `main`:`main.py` 文件(一个 Python「模块」)。 * `app`:在 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。 - * `--reload`:让服务器在更新代码后重新启动。仅在开发时使用该选项。 - + * `--reload`:让服务器在更新代码后重新启动。 仅在开发时使用该选项。 在输出中,会有一行信息像下面这样: @@ -38,7 +39,6 @@ $ uvicorn main:app --reload INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` - 该行显示了你的应用在本机所提供服务的 URL 地址。 ### 查看 @@ -73,23 +73,23 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) #### 「模式」 -「模式」是对事物的一种定义或描述。它并非具体的实现代码,而只是抽象的描述。 +「模式」是对事物的一种定义或描述。 它并非具体的实现代码,而只是抽象的描述。 #### API「模式」 -在这种场景下,OpenAPI 是一种规定如何定义 API 模式的规范。 +In this case, OpenAPI is a specification that dictates how to define a schema of your API. 定义的 OpenAPI 模式将包括你的 API 路径,以及它们可能使用的参数等等。 #### 数据「模式」 -「模式」这个术语也可能指的是某些数据比如 JSON 的结构。 +The term "schema" might also refer to the shape of some data, like a JSON content. 在这种情况下,它可以表示 JSON 的属性及其具有的数据类型,等等。 #### OpenAPI 和 JSON Schema -OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送和接收的数据的定义(或称为「模式」),这些定义通过 JSON 数据模式标准 **JSON Schema** 所生成。 +OpenAPI 为你的 API 定义 API 模式。 该模式中包含了你的 API 发送和接收的数据的定义(或称为「模式」),这些定义通过 JSON 数据模式标准 **JSON Schema** 所生成。 #### 查看 `openapi.json` @@ -122,13 +122,13 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送 #### OpenAPI 的用途 -驱动 FastAPI 内置的 2 个交互式文档系统的正是 OpenAPI 模式。 +The OpenAPI schema is what powers the two interactive documentation systems included. -并且还有数十种替代方案,它们全部都基于 OpenAPI。你可以轻松地将这些替代方案中的任何一种添加到使用 **FastAPI** 构建的应用程序中。 +并且还有数十种替代方案,它们全部都基于 OpenAPI。 你可以轻松地将这些替代方案中的任何一种添加到使用 **FastAPI** 构建的应用程序中。 -你还可以使用它自动生成与你的 API 进行通信的客户端代码。例如 web 前端,移动端或物联网嵌入程序。 +你还可以使用它自动生成与你的 API 进行通信的客户端代码。 例如 web 前端,移动端或物联网嵌入程序。 -## 分步概括 +## Recap, step by step ### 步骤 1:导入 `FastAPI` @@ -138,7 +138,7 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送 `FastAPI` 是一个为你的 API 提供了所有功能的 Python 类。 -!!! note "技术细节" +!!! !!! note "技术细节" `FastAPI` 是直接从 `Starlette` 继承的类。 你可以通过 `FastAPI` 使用所有的 Starlette 的功能。 @@ -201,7 +201,7 @@ https://example.com/items/foo /items/foo ``` -!!! info +!!! !!! info 「路径」也通常被称为「端点」或「路由」。 开发 API 时,「路径」是用来分离「关注点」和「资源」的主要手段。 @@ -239,7 +239,7 @@ https://example.com/items/foo 因此,在 OpenAPI 中,每一个 HTTP 方法都被称为「操作」。 -我们也打算称呼它们为「操作」。 +We are going to call them "**operations**" too. #### 定义一个*路径操作装饰器* @@ -252,15 +252,14 @@ https://example.com/items/foo * 请求路径为 `/` * 使用 get 操作 -!!! info "`@decorator` Info" - `@something` 语法在 Python 中被称为「装饰器」。 - - 像一顶漂亮的装饰帽一样,将它放在一个函数的上方(我猜测这个术语的命名就是这么来的)。 +!!! !!! info "`@decorator` Info" `@something` 语法在 Python 中被称为「装饰器」。 + You put it on top of a function. 像一顶漂亮的装饰帽一样,将它放在一个函数的上方(我猜测这个术语的命名就是这么来的)。 + 装饰器接收位于其下方的函数并且用它完成一些工作。 - + 在我们的例子中,这个装饰器告诉 **FastAPI** 位于其下方的函数对应着**路径** `/` 加上 `get` **操作**。 - + 它是一个「**路径操作装饰器**」。 你也可以使用其他的操作: @@ -276,13 +275,13 @@ https://example.com/items/foo * `@app.patch()` * `@app.trace()` -!!! tip +!!! !!! tip 您可以随意使用任何一个操作(HTTP方法)。 **FastAPI** 没有强制要求操作有任何特定的含义。 - + 此处提供的信息仅作为指导,而不是要求。 - + 比如,当使用 GraphQL 时通常你所有的动作都通过 `post` 一种方法执行。 ### 步骤 4:定义**路径操作函数** @@ -311,7 +310,7 @@ https://example.com/items/foo {!../../../docs_src/first_steps/tutorial003.py!} ``` -!!! note +!!! !!! note 如果你不知道两者的区别,请查阅 [Async: *"In a hurry?"*](https://fastapi.tiangolo.com/async/#in-a-hurry){.internal-link target=_blank}。 ### 步骤 5:返回内容 @@ -324,9 +323,9 @@ https://example.com/items/foo 你还可以返回 Pydantic 模型(稍后你将了解更多)。 -还有许多其他将会自动转换为 JSON 的对象和模型(包括 ORM 对象等)。尝试下使用你最喜欢的一种,它很有可能已经被支持。 +还有许多其他将会自动转换为 JSON 的对象和模型(包括 ORM 对象等)。 尝试下使用你最喜欢的一种,它很有可能已经被支持。 -## 总结 +## Recap * 导入 `FastAPI`。 * 创建一个 `app` 实例。 diff --git a/docs/zh/docs/tutorial/handling-errors.md b/docs/zh/docs/tutorial/handling-errors.md index 9b066bc2cee65..46a31245ea790 100644 --- a/docs/zh/docs/tutorial/handling-errors.md +++ b/docs/zh/docs/tutorial/handling-errors.md @@ -4,18 +4,18 @@ 这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。 -需要向客户端返回错误提示的场景主要如下: +You could need to tell the client that: -- 客户端没有执行操作的权限 -- 客户端没有访问资源的权限 -- 客户端要访问的项目不存在 -- 等等 ... +* 客户端没有执行操作的权限 +* 客户端没有访问资源的权限 +* 客户端要访问的项目不存在 +* etc. 遇到这些情况时,通常要返回 **4XX**(400 至 499)**HTTP 状态码**。 -**4XX** 状态码与表示请求成功的 **2XX**(200 至 299) HTTP 状态码类似。 +This is similar to the 200 HTTP status codes (from 200 to 299). Those "200" status codes mean that somehow there was a "success" in the request. -只不过,**4XX** 状态码表示客户端发生的错误。 +The status codes in the 400 range mean that there was an error from the client. 大家都知道**「404 Not Found」**错误,还有调侃这个错误的笑话吧? @@ -27,7 +27,6 @@ ```Python hl_lines="1" {!../../../docs_src/handling_errors/tutorial001.py!} - ``` ### 触发 `HTTPException` @@ -44,7 +43,6 @@ ```Python hl_lines="11" {!../../../docs_src/handling_errors/tutorial001.py!} - ``` ### 响应结果 @@ -55,7 +53,6 @@ { "item": "The Foo Wrestlers" } - ``` 但如果客户端请求 `http://example.com/items/bar`(`item_id` `「bar」` 不存在时),则会接收到 HTTP 状态码 - 404(「未找到」错误)及如下 JSON 响应结果: @@ -64,21 +61,15 @@ { "detail": "Item not found" } - ``` -!!! tip "提示" - - 触发 `HTTPException` 时,可以用参数 `detail` 传递任何能转换为 JSON 的值,不仅限于 `str`。 - - 还支持传递 `dict`、`list` 等数据结构。 - - **FastAPI** 能自动处理这些数据,并将之转换为 JSON。 +!!! 触发 `HTTPException` 时,可以用参数 `detail` 传递任何能转换为 JSON 的值,不仅限于 `str`。 + 还支持传递 `dict`、`list` 等数据结构。 **FastAPI** 能自动处理这些数据,并将之转换为 JSON。 ## 添加自定义响应头 -有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些方面的安全需要。 +有些场景下要为 HTTP 错误添加自定义响应头。 例如,出于某些方面的安全需要。 一般情况下可能不会需要在代码中直接使用响应头。 @@ -86,7 +77,6 @@ ```Python hl_lines="14" {!../../../docs_src/handling_errors/tutorial002.py!} - ``` ## 安装自定义异常处理器 @@ -101,7 +91,6 @@ ```Python hl_lines="5-7 13-18 24" {!../../../docs_src/handling_errors/tutorial003.py!} - ``` 请求 `/unicorns/yolo` 时,路径操作会触发 `UnicornException`。 @@ -111,30 +100,27 @@ 接收到的错误信息清晰明了,HTTP 状态码为 `418`,JSON 内容如下: ```JSON -{"message": "Oops! yolo did something. There goes a rainbow..."} - +{"message": "Oops! yolo did something. {"message": "Oops! yolo did something. There goes a rainbow..."} ``` -!!! note "技术细节" - - `from starlette.requests import Request` 和 `from starlette.responses import JSONResponse` 也可以用于导入 `Request` 和 `JSONResponse`。 +!!! note "Technical Details" + You could also use `from starlette.requests import Request` and `from starlette.responses import JSONResponse`. - **FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应操作都可以直接从 Starlette 导入。同理,`Request` 也是如此。 + `from starlette.requests import Request` 和 `from starlette.responses import JSONResponse` 也可以用于导入 `Request` 和 `JSONResponse`。 **FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应操作都可以直接从 Starlette 导入。 同理,`Request` 也是如此。 - -## 覆盖默认异常处理器 +## Override the default exception handlers **FastAPI** 自带了一些默认异常处理器。 触发 `HTTPException` 或请求无效数据时,这些处理器返回默认的 JSON 响应结果。 -不过,也可以使用自定义处理器覆盖默认异常处理器。 +You can override these exception handlers with your own. ### 覆盖请求验证异常 请求中包含无效数据时,**FastAPI** 内部会触发 `RequestValidationError`。 -该异常也内置了默认异常处理器。 +And it also includes a default exception handler for it. 覆盖默认异常处理器时需要导入 `RequestValidationError`,并用 `@app.excption_handler(RequestValidationError)` 装饰异常处理器。 @@ -142,7 +128,6 @@ ```Python hl_lines="2 14-16" {!../../../docs_src/handling_errors/tutorial004.py!} - ``` 访问 `/items/foo`,可以看到以下内容替换了默认 JSON 错误信息: @@ -160,30 +145,26 @@ } ] } - ``` -以下是文本格式的错误信息: +you will get a text version, with: ``` 1 validation error path -> item_id value is not a valid integer (type=type_error.integer) - ``` -### `RequestValidationError` vs `ValidationError` - -!!! warning "警告" - - 如果您觉得现在还用不到以下技术细节,可以先跳过下面的内容。 +#### `RequestValidationError` vs `ValidationError` +!!! warning + These are technical details that you might skip if it's not important for you now. `RequestValidationError` 是 Pydantic 的 `ValidationError` 的子类。 **FastAPI** 调用的就是 `RequestValidationError` 类,因此,如果在 `response_model` 中使用 Pydantic 模型,且数据有错误时,在日志中就会看到这个错误。 -但客户端或用户看不到这个错误。反之,客户端接收到的是 HTTP 状态码为 `500` 的「内部服务器错误」。 +但客户端或用户看不到这个错误。 反之,客户端接收到的是 HTTP 状态码为 `500` 的「内部服务器错误」。 这是因为在*响应*或代码(不是在客户端的请求里)中出现的 Pydantic `ValidationError` 是代码的 bug。 @@ -197,15 +178,12 @@ path -> item_id ```Python hl_lines="3-4 9-11 22" {!../../../docs_src/handling_errors/tutorial004.py!} - ``` -!!! note "技术细节" - - 还可以使用 `from starlette.responses import PlainTextResponse`。 - - **FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应都可以直接从 Starlette 导入。 +!!! note "Technical Details" + You could also use `from starlette.responses import PlainTextResponse`. + 还可以使用 `from starlette.responses import PlainTextResponse`。 **FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应都可以直接从 Starlette 导入。 ### 使用 `RequestValidationError` 的请求体 @@ -215,7 +193,6 @@ path -> item_id ```Python hl_lines="14" {!../../../docs_src/handling_errors/tutorial005.py!} - ``` 现在试着发送一个无效的 `item`,例如: @@ -225,7 +202,6 @@ path -> item_id "title": "towel", "size": "XL" } - ``` 收到的响应包含 `body` 信息,并说明数据是无效的: @@ -247,10 +223,9 @@ path -> item_id "size": "XL" } } - ``` -### FastAPI `HTTPException` vs Starlette `HTTPException` +#### FastAPI `HTTPException` vs Starlette `HTTPException` **FastAPI** 也提供了自有的 `HTTPException`。 @@ -270,20 +245,14 @@ OAuth 2.0 等安全工具需要在内部调用这些响应头。 ```Python from starlette.exceptions import HTTPException as StarletteHTTPException - ``` ### 复用 **FastAPI** 异常处理器 FastAPI 支持先对异常进行某些处理,然后再使用 **FastAPI** 中处理该异常的默认异常处理器。 -从 `fastapi.exception_handlers` 中导入要复用的默认异常处理器: - ```Python hl_lines="2-5 15 21" {!../../../docs_src/handling_errors/tutorial006.py!} - ``` -虽然,本例只是输出了夸大其词的错误信息。 - -但也足以说明,可以在处理异常之后再复用默认的异常处理器。 +In this example you are just `print`ing the error with a very expressive message, but you get the idea. You can use the exception and then just re-use the default exception handlers. diff --git a/docs/zh/docs/tutorial/header-params.md b/docs/zh/docs/tutorial/header-params.md index c4b1c38ceecc8..945345f38ef25 100644 --- a/docs/zh/docs/tutorial/header-params.md +++ b/docs/zh/docs/tutorial/header-params.md @@ -6,9 +6,41 @@ 首先导入 `Header`: -```Python hl_lines="3" -{!../../../docs_src/header_params/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="3" + {!> ../../../docs_src/header_params/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="3" + {!> ../../../docs_src/header_params/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3" + {!> ../../../docs_src/header_params/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + {!../../../docs_src/header_params/tutorial001.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3" + {!../../../docs_src/header_params/tutorial002.py!} + ``` ## 声明 `Header` 参数 @@ -16,17 +48,49 @@ 第一个值是默认值,你可以传递所有的额外验证或注释参数: -```Python hl_lines="9" -{!../../../docs_src/header_params/tutorial001.py!} -``` +=== "Python 3.10+" -!!! note "技术细节" - `Header` 是 `Path`, `Query` 和 `Cookie` 的兄弟类型。它也继承自通用的 `Param` 类. + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/header_params/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/header_params/tutorial001_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial001.py!} + ``` + +!!! !!! note "技术细节" + `Header` 是 `Path`, `Query` 和 `Cookie` 的兄弟类型。 它也继承自通用的 `Param` 类. 但是请记得,当你从`fastapi`导入 `Query`, `Path`, `Header`, 或其他时,实际上导入的是返回特定类型的函数。 !!! info - 为了声明headers, 你需要使用`Header`, 因为否则参数将被解释为查询参数。 + To declare headers, you need to use `Header`, because otherwise the parameters would be interpreted as query parameters. ## 自动转换 @@ -44,17 +108,51 @@ 如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置`Header`的参数 `convert_underscores` 为 `False`: -```Python hl_lines="10" -{!../../../docs_src/header_params/tutorial002.py!} -``` +=== "Python 3.10+" -!!! warning - 在设置 `convert_underscores` 为 `False` 之前,请记住,一些HTTP代理和服务器不允许使用带有下划线的headers。 + ```Python hl_lines="10" + {!> ../../../docs_src/header_params/tutorial002_an_py310.py!} + ``` +=== "Python 3.9+" + + ```Python hl_lines="11" + !!! info + 为了声明headers, 你需要使用Header, 因为否则参数将被解释为查询参数。 + ``` +, 因为否则参数将被解释为查询参数。 + + +=== "Python 3.6+" + + ```Python hl_lines="12" + {!> ../../../docs_src/header_params/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8" + {!> ../../../docs_src/header_params/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!../../../docs_src/header_params/tutorial001.py!} + ``` + +!!! warning + Before setting `convert_underscores` to `False`, bear in mind that some HTTP proxies and servers disallow the usage of headers with underscores. ## 重复的 headers -有可能收到重复的headers。这意味着,相同的header具有多个值。 +有可能收到重复的headers。 这意味着,相同的header具有多个值。 您可以在类型声明中使用一个list来定义这些情况。 @@ -62,9 +160,53 @@ 比如, 为了声明一个 `X-Token` header 可以出现多次,你可以这样写: -```Python hl_lines="9" -{!../../../docs_src/header_params/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/header_params/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/header_params/tutorial003_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/header_params/tutorial003_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + !!! warning + 在设置 convert_underscoresFalse 之前,请记住,一些HTTP代理和服务器不允许使用带有下划线的headers。 + ``` + 为 False 之前,请记住,一些HTTP代理和服务器不允许使用带有下划线的headers。 + + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!../../../docs_src/header_params/tutorial003.py!} + ``` 如果你与*路径操作*通信时发送两个HTTP headers,就像: diff --git a/docs/zh/docs/tutorial/index.md b/docs/zh/docs/tutorial/index.md index 6180d3de399ae..e9f55adc42962 100644 --- a/docs/zh/docs/tutorial/index.md +++ b/docs/zh/docs/tutorial/index.md @@ -25,10 +25,12 @@ $ uvicorn main:app --reload INFO: Waiting for application startup. INFO: Application startup complete. ``` +INFO: Application startup complete. +```
-强烈建议你在本地编写或复制代码,对其进行编辑并运行。 +**强烈建议**你在本地编写或复制代码,对其进行编辑并运行。 在编辑器中使用 FastAPI 会真正地展现出它的优势:只需要编写很少的代码,所有的类型检查,代码补全等等。 @@ -52,7 +54,7 @@ $ pip install "fastapi[all]" ......以上安装还包括了 `uvicorn`,你可以将其用作运行代码的服务器。 -!!! note +!!! note "说明" 你也可以分开来安装。 假如你想将应用程序部署到生产环境,你可能要执行以下操作: @@ -61,12 +63,14 @@ $ pip install "fastapi[all]" pip install fastapi ``` + 并且安装`uvicorn`来作为服务器: ``` pip install "uvicorn[standard]" ``` + 然后对你想使用的每个可选依赖项也执行相同的操作。 ## 进阶用户指南 diff --git a/docs/zh/docs/tutorial/metadata.md b/docs/zh/docs/tutorial/metadata.md index 3e669bc72fa24..209920b95de28 100644 --- a/docs/zh/docs/tutorial/metadata.md +++ b/docs/zh/docs/tutorial/metadata.md @@ -1,105 +1,123 @@ -# 元数据和文档 URL - -你可以在 **FastAPI** 应用中自定义几个元数据配置。 - -## 标题、描述和版本 - -你可以设定: - -* **Title**:在 OpenAPI 和自动 API 文档用户界面中作为 API 的标题/名称使用。 -* **Description**:在 OpenAPI 和自动 API 文档用户界面中用作 API 的描述。 -* **Version**:API 版本,例如 `v2` 或者 `2.5.0`。 - * 如果你之前的应用程序版本也使用 OpenAPI 会很有用。 - -使用 `title`、`description` 和 `version` 来设置它们: - -```Python hl_lines="4-6" -{!../../../docs_src/metadata/tutorial001.py!} -``` - -通过这样设置,自动 API 文档看起来会像: - - - -## 标签元数据 - -你也可以使用参数 `openapi_tags`,为用于分组路径操作的不同标签添加额外的元数据。 - -它接受一个列表,这个列表包含每个标签对应的一个字典。 - -每个字典可以包含: - -* `name`(**必要**):一个 `str`,它与*路径操作*和 `APIRouter` 中使用的 `tags` 参数有相同的标签名。 -* `description`:一个用于简短描述标签的 `str`。它支持 Markdown 并且会在文档用户界面中显示。 -* `externalDocs`:一个描述外部文档的 `dict`: - * `description`:用于简短描述外部文档的 `str`。 - * `url`(**必要**):外部文档的 URL `str`。 - -### 创建标签元数据 - -让我们在带有标签的示例中为 `users` 和 `items` 试一下。 - -创建标签元数据并把它传递给 `openapi_tags` 参数: - -```Python hl_lines="3-16 18" -{!../../../docs_src/metadata/tutorial004.py!} -``` - -注意你可以在描述内使用 Markdown,例如「login」会显示为粗体(**login**)以及「fancy」会显示为斜体(_fancy_)。 - -!!! 提示 - 不必为你使用的所有标签都添加元数据。 - -### 使用你的标签 - -将 `tags` 参数和*路径操作*(以及 `APIRouter`)一起使用,将其分配给不同的标签: - -```Python hl_lines="21 26" -{!../../../docs_src/metadata/tutorial004.py!} -``` - -!!! 信息 - 阅读更多关于标签的信息[路径操作配置](../path-operation-configuration/#tags){.internal-link target=_blank}。 - -### 查看文档 - -如果你现在查看文档,它们会显示所有附加的元数据: - - - -### 标签顺序 - -每个标签元数据字典的顺序也定义了在文档用户界面显示的顺序。 - -例如按照字母顺序,即使 `users` 排在 `items` 之后,它也会显示在前面,因为我们将它的元数据添加为列表内的第一个字典。 - -## OpenAPI URL - -默认情况下,OpenAPI 模式服务于 `/openapi.json`。 - -但是你可以通过参数 `openapi_url` 对其进行配置。 - -例如,将其设置为服务于 `/api/v1/openapi.json`: - -```Python hl_lines="3" -{!../../../docs_src/metadata/tutorial002.py!} -``` - -如果你想完全禁用 OpenAPI 模式,可以将其设置为 `openapi_url=None`,这样也会禁用使用它的文档用户界面。 - -## 文档 URLs - -你可以配置两个文档用户界面,包括: - -* **Swagger UI**:服务于 `/docs`。 - * 可以使用参数 `docs_url` 设置它的 URL。 - * 可以通过设置 `docs_url=None` 禁用它。 -* ReDoc:服务于 `/redoc`。 - * 可以使用参数 `redoc_url` 设置它的 URL。 - * 可以通过设置 `redoc_url=None` 禁用它。 - -例如,设置 Swagger UI 服务于 `/documentation` 并禁用 ReDoc: - -```Python hl_lines="3" -{!../../../docs_src/metadata/tutorial003.py!} -``` +# 元数据和文档 URL + +你可以在 **FastAPI** 应用中自定义几个元数据配置。 + +## Metadata for API + +**Description**:在 OpenAPI 和自动 API 文档用户界面中用作 API 的描述。 +| The license information for the exposed API. It can contain several fields.
`license_info` fields + +| Parameter | Type | Description | +| ----------------------------------------------------------------------------------------------------------------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `str` | **REQUIRED** (if a `license_info` is set). The license name used for the API. | +| `identifier` | `str` | An SPDX license expression for the API. The `identifier` field is mutually exclusive of the `url` field. Available since OpenAPI 3.1.0, FastAPI 0.99.0. | +| `!!! 信息 + 阅读更多关于标签的信息路径操作配置{.internal-link target=_blank}。` | `str` | A URL to the license used for the API. MUST be in the format of a URL. | +
| +|| + +你可以设定: + +```Python hl_lines="3-16 19-32" +{!../../../docs_src/metadata/tutorial001.py!} +``` + +!!! tip + You can write Markdown in the `description` field and it will be rendered in the output. + +通过这样设置,自动 API 文档看起来会像: + + + +## License identifier + +Since OpenAPI 3.1.0 and FastAPI 0.99.0, you can also set the `license_info` with an `identifier` instead of a `url`. + +For example: + +```Python hl_lines="31" +{!../../../docs_src/metadata/tutorial001_1.py!} +``` + +## 标签元数据 + +你也可以使用参数 `openapi_tags`,为用于分组路径操作的不同标签添加额外的元数据。 + +它接受一个列表,这个列表包含每个标签对应的一个字典。 + +每个字典可以包含: + +* `name`(**必要**):一个 `str`,它与*路径操作*和 `APIRouter` 中使用的 `tags` 参数有相同的标签名。 +* `description`:一个用于简短描述标签的 `str`。 它支持 Markdown 并且会在文档用户界面中显示。 +* `externalDocs`:一个描述外部文档的 `dict`: + * `description`:用于简短描述外部文档的 `str`。 + * `url`(**必要**):外部文档的 URL `str`。 + +### 创建标签元数据 + +让我们在带有标签的示例中为 `users` 和 `items` 试一下。 + +创建标签元数据并把它传递给 `openapi_tags` 参数: + +```Python hl_lines="3-16 18" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +注意你可以在描述内使用 Markdown,例如「login」会显示为粗体(**login**)以及「fancy」会显示为斜体(_fancy_)。 + +!!! !!! 提示 + 不必为你使用的所有标签都添加元数据。 + +### 使用你的标签 + +将 `tags` 参数和*路径操作*(以及 `APIRouter`)一起使用,将其分配给不同的标签: + +```Python hl_lines="21 26" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +!!! info + Read more about tags in [Path Operation Configuration](../path-operation-configuration/#tags){.internal-link target=_blank}. + +### 查看文档 + +如果你现在查看文档,它们会显示所有附加的元数据: + + + +### 标签顺序 + +每个标签元数据字典的顺序也定义了在文档用户界面显示的顺序。 + +例如按照字母顺序,即使 `users` 排在 `items` 之后,它也会显示在前面,因为我们将它的元数据添加为列表内的第一个字典。 + +## OpenAPI URL + +默认情况下,OpenAPI 模式服务于 `/openapi.json`。 + +但是你可以通过参数 `openapi_url` 对其进行配置。 + +例如,将其设置为服务于 `/api/v1/openapi.json`: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial002.py!} +``` + +如果你想完全禁用 OpenAPI 模式,可以将其设置为 `openapi_url=None`,这样也会禁用使用它的文档用户界面。 + +## 文档 URLs + +你可以配置两个文档用户界面,包括: + +* **Swagger UI**:服务于 `/docs`。 + * 可以使用参数 `docs_url` 设置它的 URL。 + * 可以通过设置 `docs_url=None` 禁用它。 +* **Version**:API 版本,例如 `v2` 或者 `2.5.0`。 + * 可以使用参数 `redoc_url` 设置它的 URL。 + * 可以通过设置 `redoc_url=None` 禁用它。 + +例如,设置 Swagger UI 服务于 `/documentation` 并禁用 ReDoc: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial003.py!} +``` diff --git a/docs/zh/docs/tutorial/middleware.md b/docs/zh/docs/tutorial/middleware.md index c9a7e7725a120..8be591d8ee697 100644 --- a/docs/zh/docs/tutorial/middleware.md +++ b/docs/zh/docs/tutorial/middleware.md @@ -2,7 +2,7 @@ 你可以向 **FastAPI** 应用添加中间件. -"中间件"是一个函数,它在每个**请求**被特定的*路径操作*处理之前,以及在每个**响应**返回之前工作. +"中间件"是一个函数,它在每个**请求**被特定的*路径操作*处理之前,以及在每个**响应**返回之前工作. And also with every **response** before returning it. * 它接收你的应用程序的每一个**请求**. * 然后它可以对这个**请求**做一些事情或者执行任何需要的代码. @@ -11,7 +11,7 @@ * 它可以对该**响应**做些什么或者执行任何需要的代码. * 然后它返回这个 **响应**. -!!! note "技术细节" +!!! !!! note "技术细节" 如果你使用了 `yield` 关键字依赖, 依赖中的退出代码将在执行中间件*后*执行. 如果有任何后台任务(稍后记录), 它们将在执行中间件*后*运行. @@ -20,7 +20,7 @@ 要创建中间件你可以在函数的顶部使用装饰器 `@app.middleware("http")`. -中间件参数接收如下参数: +The middleware function receives: * `request`. * 一个函数 `call_next` 它将接收 `request` 作为参数. @@ -32,15 +32,15 @@ {!../../../docs_src/middleware/tutorial001.py!} ``` -!!! tip +!!! !!! tip 请记住可以 用'X-' 前缀添加专有自定义请求头. 但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 Starlette's CORS docs文档中. -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以使用 `from starlette.requests import Request`. - **FastAPI** 为了开发者方便提供了该对象. 但其实它直接来自于 Starlette. + **FastAPI** 为了开发者方便提供了该对象. 但其实它直接来自于 Starlette. But it comes directly from Starlette. ### 在 `response` 的前和后 diff --git a/docs/zh/docs/tutorial/path-operation-configuration.md b/docs/zh/docs/tutorial/path-operation-configuration.md index f79b0e692d8cd..7102406acc732 100644 --- a/docs/zh/docs/tutorial/path-operation-configuration.md +++ b/docs/zh/docs/tutorial/path-operation-configuration.md @@ -2,9 +2,8 @@ *路径操作装饰器*支持多种配置参数。 -!!! warning "警告" - - 注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。 +!!! warning + Notice that these parameters are passed directly to the *path operation decorator*, not to your *path operation function*. ## `status_code` 状态码 @@ -14,71 +13,154 @@ 如果记不住数字码的涵义,也可以用 `status` 的快捷常量: -```Python hl_lines="3 17" -{!../../../docs_src/path_operation_configuration/tutorial001.py!} -``` +=== "Python 3.10+" -状态码在响应中使用,并会被添加到 OpenAPI 概图。 + ```Python hl_lines="1 15" + 注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="3 17" + !!! check "检查" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3 17" + API 文档会把该路径操作标记为弃用: + ``` -!!! note "技术细节" +状态码在响应中使用,并会被添加到 OpenAPI 概图。 - 也可以使用 `from starlette import status` 导入状态码。 +!!! 也可以使用 `from starlette import status` 导入状态码。 - **FastAPI** 的`fastapi.status` 和 `starlette.status` 一样,只是快捷方式。实际上,`fastapi.status` 直接继承自 Starlette。 + **FastAPI** 的`fastapi.status` 和 `starlette.status` 一样,只是快捷方式。 But it comes directly from Starlette. -## `tags` 参数 +## Tags `tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签: -```Python hl_lines="17 22 27" -{!../../../docs_src/path_operation_configuration/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="15 20 25" + {!../../../docs_src/path_operation_configuration/tutorial005.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17 22 27" + 注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 22 27" + {!../../../docs_src/path_operation_configuration/tutorial002.py!} + ``` OpenAPI 概图会自动添加标签,供 API 文档接口使用: - + + +### Tags with Enums + +If you have a big application, you might end up accumulating **several tags**, and you would want to make sure you always use the **same tag** for related *path operations*. -## `summary` 和 `description` 参数 +In these cases, it could make sense to store the tags in an `Enum`. -路径装饰器还支持 `summary` 和 `description` 这两个参数: +**FastAPI** supports that the same way as with plain strings: -```Python hl_lines="20-21" -{!../../../docs_src/path_operation_configuration/tutorial003.py!} +```Python hl_lines="1 8-10 13 18" +{!../../../docs_src/path_operation_configuration/tutorial001.py!} ``` -## 文档字符串(`docstring`) +## Summary and description + +`summary` 和 `description` 参数 + +=== "Python 3.10+" + + ```Python hl_lines="18-19" + {!../../../docs_src/path_operation_configuration/tutorial003.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20-21" + !!! warning "警告" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20-21" + 路径装饰器还支持 summarydescription 这两个参数: + ``` + 和 description 这两个参数: + + +## Description from docstring 描述内容比较长且占用多行时,可以在函数的 docstring 中声明*路径操作*的描述,**FastAPI** 支持从文档字符串中读取描述内容。 文档字符串支持 Markdown,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。 -```Python hl_lines="19-27" -{!../../../docs_src/path_operation_configuration/tutorial004.py!} -``` +=== "Python 3.10+" -下图为 Markdown 文本在 API 文档中的显示效果: + ```Python hl_lines="17-25" + {!../../../docs_src/path_operation_configuration/tutorial004.py!} + ``` - +=== "Python 3.9+" + + ```Python hl_lines="19-27" + 下图为 Markdown 文本在 API 文档中的显示效果: + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-27" + 文档字符串(docstring) + ``` +) + + +It will be used in the interactive docs: + + ## 响应描述 `response_description` 参数用于定义响应的描述说明: -```Python hl_lines="21" -{!../../../docs_src/path_operation_configuration/tutorial005.py!} -``` +=== "Python 3.10+" -!!! info "说明" + ```Python hl_lines="19" + !!! info "说明" + ``` - 注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。 +=== "Python 3.9+" + + ```Python hl_lines="21" + !!! note "技术细节" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="21" + tags 参数 + ``` + 参数 + -!!! check "检查" +!!! info + Notice that `response_description` refers specifically to the response, the `description` refers to the *path operation* in general. - OpenAPI 规定每个*路径操作*都要有响应描述。 +!!! OpenAPI 规定每个*路径操作*都要有响应描述。 如果没有定义响应描述,**FastAPI** 则自动生成内容为 "Successful response" 的响应描述。 - + ## 弃用*路径操作* @@ -88,14 +170,14 @@ OpenAPI 概图会自动添加标签,供 API 文档接口使用: {!../../../docs_src/path_operation_configuration/tutorial006.py!} ``` -API 文档会把该路径操作标记为弃用: +It will be clearly marked as deprecated in the interactive docs: - + 下图显示了正常*路径操作*与弃用*路径操作* 的区别: - + -## 小结 +## Recap 通过传递参数给*路径操作装饰器* ,即可轻松地配置*路径操作*、添加元数据。 diff --git a/docs/zh/docs/tutorial/path-params-numeric-validations.md b/docs/zh/docs/tutorial/path-params-numeric-validations.md index 13512a08edf06..fc045d9b4b3a7 100644 --- a/docs/zh/docs/tutorial/path-params-numeric-validations.md +++ b/docs/zh/docs/tutorial/path-params-numeric-validations.md @@ -6,9 +6,48 @@ 首先,从 `fastapi` 导入 `Path`: -```Python hl_lines="1" -{!../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="1 3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="1 3" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3-4" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + {!../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3" + {!../../../docs_src/path_params_numeric_validations/tutorial002.py!} + ``` + +!!! info + FastAPI added support for `Annotated` (and started recommending it) in version 0.95.0. + + If you have an older version, you would get errors when trying to use `Annotated`. + + Make sure you [Upgrade the FastAPI version](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} to at least 0.95.1 before using `Annotated`. ## 声明元数据 @@ -16,107 +55,245 @@ 例如,要声明路径参数 `item_id`的 `title` 元数据值,你可以输入: -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8" + {!../../../docs_src/path_params_numeric_validations/tutorial005.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` !!! note - 路径参数总是必需的,因为它必须是路径的一部分。 + A path parameter is always required as it has to be part of the path. 所以,你应该在声明时使用 `...` 将其标记为必需参数。 - + 然而,即使你使用 `None` 声明路径参数或设置一个其他默认值也不会有任何影响,它依然会是必需参数。 ## 按需对参数排序 +!!! tip + This is probably not as important or necessary if you use `Annotated`. + 假设你想要声明一个必需的 `str` 类型查询参数 `q`。 而且你不需要为该参数声明任何其他内容,所以实际上你并不需要使用 `Query`。 -但是你仍然需要使用 `Path` 来声明路径参数 `item_id`。 +But you still need to use `Path` for the `item_id` path parameter. And you don't want to use `Annotated` for some reason. 如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错。 但是你可以对其重新排序,并将不带默认值的值(查询参数 `q`)放到最前面。 -对 **FastAPI** 来说这无关紧要。它将通过参数的名称、类型和默认值声明(`Query`、`Path` 等)来检测参数,而不在乎参数的顺序。 +对 **FastAPI** 来说这无关紧要。 它将通过参数的名称、类型和默认值声明(`Query`、`Path` 等)来检测参数,而不在乎参数的顺序。 因此,你可以将函数声明为: -```Python hl_lines="7" -{!../../../docs_src/path_params_numeric_validations/tutorial002.py!} -``` +=== "Python 3.6 non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/path_params_numeric_validations/tutorial002.py!} + ``` + +!!! note "技术细节" + 当你从 `fastapi` 导入 `Query`、`Path` 和其他同类对象时,它们实际上是函数。 + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!../../../docs_src/path_params_numeric_validations/tutorial006.py!} + ``` ## 按需对参数排序的技巧 -如果你想不使用 `Query` 声明没有默认值的查询参数 `q`,同时使用 `Path` 声明路径参数 `item_id`,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。 +!!! tip + This is probably not as important or necessary if you use `Annotated`. + +Here's a **small trick** that can be handy, but you won't need it often. + +If you want to: + +* !!! info + `Query`、`Path` 以及你后面会看到的其他类继承自一个共同的 `Param` 类(不需要直接使用它)。 +* 但是你仍然需要使用 `Path` 来声明路径参数 `item_id`。 +* have them in a different order +* not use `Annotated` + +...Python has a little special syntax for that. 传递 `*` 作为函数的第一个参数。 -Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。即使它们没有默认值。 +Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。 即使它们没有默认值。 ```Python hl_lines="7" {!../../../docs_src/path_params_numeric_validations/tutorial003.py!} ``` +### Better with `Annotated` + +Have in mind that if you use `Annotated`, as you are not using function parameter default values, you won't have this problem, and you probably won't need to use `*`. + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!../../../docs_src/path_params_numeric_validations/tutorial001.py!} + ``` + ## 数值校验:大于等于 使用 `Query` 和 `Path`(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。 像下面这样,添加 `ge=1` 后,`item_id` 将必须是一个大于(`g`reater than)或等于(`e`qual)`1` 的整数。 -```Python hl_lines="8" -{!../../../docs_src/path_params_numeric_validations/tutorial004.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!../../../docs_src/path_params_numeric_validations/tutorial004.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8" + {!> ../../../docs_src/path_params_numeric_validations/tutorial004.py!} + ``` ## 数值校验:大于和小于等于 同样的规则适用于: * `gt`:大于(`g`reater `t`han) -* `le`:小于等于(`l`ess than or `e`qual) +* `ge`:大于等于(`g`reater than or `e`qual) -```Python hl_lines="9" -{!../../../docs_src/path_params_numeric_validations/tutorial005.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + 总结 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!> ../../../docs_src/path_params_numeric_validations/tutorial005.py!} + ``` ## 数值校验:浮点数、大于和小于 数值校验同样适用于 `float` 值。 -能够声明 gt 而不仅仅是 ge 在这个前提下变得重要起来。例如,你可以要求一个值必须大于 `0`,即使它小于 `1`。 +能够声明 gt 而不仅仅是 ge 在这个前提下变得重要起来。 例如,你可以要求一个值必须大于 `0`,即使它小于 `1`。 -因此,`0.5` 将是有效值。但是 `0.0`或 `0` 不是。 +因此,`0.5` 将是有效值。 但是 `0.0`或 `0` 不是。 对于 lt 也是一样的。 -```Python hl_lines="11" -{!../../../docs_src/path_params_numeric_validations/tutorial006.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="13" + !!! note + 路径参数总是必需的,因为它必须是路径的一部分。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="12" + 如果你想不使用 Query 声明没有默认值的查询参数 q,同时使用 Path 声明路径参数 item_id,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。 + ``` + 声明没有默认值的查询参数 q,同时使用 Path 声明路径参数 item_id,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。 + + +=== "Python 3.6+ non-Annotated" -## 总结 + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11" + {!> ../../../docs_src/path_params_numeric_validations/tutorial006.py!} + ``` + +## Recap 你能够以与 [查询参数和字符串校验](query-params-str-validations.md){.internal-link target=_blank} 相同的方式使用 `Query`、`Path`(以及其他你还没见过的类)声明元数据和字符串校验。 而且你还可以声明数值校验: * `gt`:大于(`g`reater `t`han) -* `ge`:大于等于(`g`reater than or `e`qual) +* `le`:小于等于(`l`ess than or `e`qual) * `lt`:小于(`l`ess `t`han) * `le`:小于等于(`l`ess than or `e`qual) !!! info - `Query`、`Path` 以及你后面会看到的其他类继承自一个共同的 `Param` 类(不需要直接使用它)。 + `Query`, `Path`, and other classes you will see later are subclasses of a common `Param` class. 而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。 -!!! note "技术细节" - 当你从 `fastapi` 导入 `Query`、`Path` 和其他同类对象时,它们实际上是函数。 +!!! note "Technical Details" + When you import `Query`, `Path` and others from `fastapi`, they are actually functions. 当被调用时,它们返回同名类的实例。 - - 如此,你导入 `Query` 这个函数。当你调用它时,它将返回一个同样命名为 `Query` 的类的实例。 - + + 如此,你导入 `Query` 这个函数。 当你调用它时,它将返回一个同样命名为 `Query` 的类的实例。 + 因为使用了这些函数(而不是直接使用类),所以你的编辑器不会标记有关其类型的错误。 - + 这样,你可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。 diff --git a/docs/zh/docs/tutorial/path-params.md b/docs/zh/docs/tutorial/path-params.md index 1b428d6627112..a307010299d68 100644 --- a/docs/zh/docs/tutorial/path-params.md +++ b/docs/zh/docs/tutorial/path-params.md @@ -24,7 +24,7 @@ 在这个例子中,`item_id` 被声明为 `int` 类型。 -!!! check +!!! !!! check 这将为你的函数提供编辑器支持,包括错误检查、代码补全等等。 ## 数据转换 @@ -35,7 +35,7 @@ {"item_id":3} ``` -!!! check +!!! !!! check 注意函数接收(并返回)的值为 3,是一个 Python `int` 值,而不是字符串 `"3"`。 所以,**FastAPI** 通过上面的类型声明提供了对请求的自动"解析"。 @@ -63,20 +63,20 @@ 如果你提供的是 `float` 而非整数也会出现同样的错误,比如: http://127.0.0.1:8000/items/4.2 -!!! check +!!! !!! check 所以,通过同样的 Python 类型声明,**FastAPI** 提供了数据校验功能。 注意上面的错误同样清楚地指出了校验未通过的具体原因。 - + 在开发和调试与你的 API 进行交互的代码时,这非常有用。 ## 文档 当你打开浏览器访问 http://127.0.0.1:8000/docs,你将看到自动生成的交互式 API 文档: - + -!!! check +!!! !!! check 再一次,还是通过相同的 Python 类型声明,**FastAPI** 为你提供了自动生成的交互式文档(集成 Swagger UI)。 注意这里的路径参数被声明为一个整数。 @@ -87,19 +87,19 @@ 正因如此,**FastAPI** 内置了一个可选的 API 文档(使用 Redoc): - + -同样的,还有很多其他兼容的工具,包括适用于多种语言的代码生成工具。 +The same way, there are many compatible tools. 同样的,还有很多其他兼容的工具,包括适用于多种语言的代码生成工具。 ## Pydantic -所有的数据校验都由 Pydantic 在幕后完成,所以你可以从它所有的优点中受益。并且你知道它在这方面非常胜任。 +所有的数据校验都由 Pydantic 在幕后完成,所以你可以从它所有的优点中受益。 并且你知道它在这方面非常胜任。 你可以使用同样的类型声明来声明 `str`、`float`、`bool` 以及许多其他的复合数据类型。 本教程的下一章节将探讨其中的一些内容。 -## 顺序很重要 +## Order matters 在创建*路径操作*时,你会发现有些情况下路径是固定的。 @@ -108,12 +108,21 @@ 然后,你还可以使用路径 `/users/{user_id}` 来通过用户 ID 获取关于特定用户的数据。 由于*路径操作*是按顺序依次运行的,你需要确保路径 `/users/me` 声明在路径 `/users/{user_id}`之前: + ```Python hl_lines="6 11" {!../../../docs_src/path_params/tutorial003.py!} ``` 否则,`/users/{user_id}` 的路径还将与 `/users/me` 相匹配,"认为"自己正在接收一个值为 `"me"` 的 `user_id` 参数。 +Similarly, you cannot redefine a path operation: + +```Python hl_lines="6 11" +{!../../../docs_src/path_params/tutorial003b.py!} +``` + +The first one will always be used since the path matches first. + ## 预设值 如果你有一个接收路径参数的路径操作,但你希望预先设定可能的有效参数值,则可以使用标准的 Python `Enum` 类型。 @@ -130,10 +139,10 @@ {!../../../docs_src/path_params/tutorial005.py!} ``` -!!! info +!!! !!! info 枚举(或 enums)从 3.4 版本起在 Python 中可用。 -!!! tip +!!! !!! tip 如果你想知道,"AlexNet"、"ResNet" 和 "LeNet" 只是机器学习中的模型名称。 ### 声明*路径参数* @@ -146,13 +155,13 @@ ### 查看文档 -因为已经指定了*路径参数*的可用值,所以交互式文档可以恰当地展示它们: +*路径参数*的值将是一个*枚举成员*。 - + ### 使用 Python *枚举类型* -*路径参数*的值将是一个*枚举成员*。 +因为已经指定了*路径参数*的可用值,所以交互式文档可以恰当地展示它们: #### 比较*枚举成员* @@ -166,11 +175,11 @@ 你可以使用 `model_name.value` 或通常来说 `your_enum_member.value` 来获取实际的值(在这个例子中为 `str`): -```Python hl_lines="19" +```Python hl_lines="20" {!../../../docs_src/path_params/tutorial005.py!} ``` -!!! tip +!!! !!! tip 你也可以通过 `ModelName.lenet.value` 来获取值 `"lenet"`。 #### 返回*枚举成员* @@ -179,10 +188,19 @@ 在返回给客户端之前,它们将被转换为对应的值: -```Python hl_lines="18-21" +```Python hl_lines="18 21 23" {!../../../docs_src/path_params/tutorial005.py!} ``` +In your client you will get a JSON response like: + +```JSON +{ + "model_name": "alexnet", + "message": "Deep Learning FTW!" +} +``` + ## 包含路径的路径参数 假设你有一个*路径操作*,它的路径为 `/files/{file_path}`。 @@ -193,7 +211,7 @@ ### OpenAPI 支持 -OpenAPI 不支持任何方式去声明*路径参数*以在其内部包含*路径*,因为这可能会导致难以测试和定义的情况出现。 +你可以使用直接来自 Starlette 的选项来声明一个包含*路径*的*路径参数*: 不过,你仍然可以通过 Starlette 的一个内部工具在 **FastAPI** 中实现它。 @@ -201,7 +219,7 @@ OpenAPI 不支持任何方式去声明*路径参数*以在其内部包含*路径 ### 路径转换器 -你可以使用直接来自 Starlette 的选项来声明一个包含*路径*的*路径参数*: +OpenAPI 不支持任何方式去声明*路径参数*以在其内部包含*路径*,因为这可能会导致难以测试和定义的情况出现。 ``` /files/{file_path:path} @@ -215,12 +233,12 @@ OpenAPI 不支持任何方式去声明*路径参数*以在其内部包含*路径 {!../../../docs_src/path_params/tutorial004.py!} ``` -!!! tip +!!! !!! tip 你可能会需要参数包含 `/home/johndoe/myfile.txt`,以斜杠(`/`)开头。 在这种情况下,URL 将会是 `/files//home/johndoe/myfile.txt`,在`files` 和 `home` 之间有一个双斜杠(`//`)。 -## 总结 +## Recap 使用 **FastAPI**,通过简短、直观和标准的 Python 类型声明,你将获得: diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md index 070074839f634..15ef418f38a1c 100644 --- a/docs/zh/docs/tutorial/query-params-str-validations.md +++ b/docs/zh/docs/tutorial/query-params-str-validations.md @@ -4,11 +4,25 @@ 让我们以下面的应用程序为例: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial001.py!} -``` +=== "Python 3.10+" -查询参数 `q` 的类型为 `str`,默认值为 `None`,因此它是可选的。 + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial001.py!} + ``` + +!!! note + 请记住,在这种情况下 FastAPI 将不会检查列表的内容。 + +!!! note + FastAPI will know that the value of `q` is not required because of the default value `= None`. + + The `Union` in `Union[str, None]` will allow your editor to give you better support and detect errors. ## 额外的校验 @@ -16,19 +30,122 @@ ### 导入 `Query` -为此,首先从 `fastapi` 导入 `Query`: +To achieve that, first import: -```Python hl_lines="1" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +* 为此,首先从 `fastapi` 导入 `Query`: +* `Annotated` from `typing` (or from `typing_extensions` in Python below 3.9) + +=== "Python 3.10+" + + In Python 3.9 or above, `Annotated` is part of the standard library, so you can import it from `typing`. + + ```Python hl_lines="1 3" + {!../../../docs_src/query_params_str_validations/tutorial002.py!} + ``` + +=== "Python 3.6+" + + In versions of Python below Python 3.9 you import `Annotated` from `typing_extensions`. + + It will already be installed with FastAPI. + + ```Python hl_lines="3-4" + {!> ../../../docs_src/query_params_str_validations/tutorial002_an.py!} + ``` + +!!! info + FastAPI added support for `Annotated` (and started recommending it) in version 0.95.0. + + If you have an older version, you would get errors when trying to use `Annotated`. + + Make sure you [Upgrade the FastAPI version](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} to at least 0.95.1 before using `Annotated`. + +## !!! tip + 要声明类型为 `list` 的查询参数,如上例所示,你需要显式地使用 `Query`,否则该参数将被解释为请求体。 + +Remember I told you before that `Annotated` can be used to add metadata to your parameters in the [Python Types Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}? + +Now it's the time to use it with FastAPI. 🚀 + +We had this type annotation: + +=== "Python 3.10+" + + ```Python + q: str | None = None + ``` + +=== "Python 3.6+" + + ```Python + q: Union[str, None] = None + ``` + +What we will do is wrap that with `Annotated`, so it becomes: + +=== "Python 3.10+" + + ```Python + 总结 + ``` + +=== "Python 3.6+" + + ```Python + q: Annotated[Union[str, None]] = None + ``` + +查询参数 `q` 的类型为 `str`,默认值为 `None`,因此它是可选的。 + +Now let's jump to the fun stuff. 🎉 + +## 但是 `Query` 显式地将其声明为查询参数。 + +Now that we have this `Annotated` where we can put more metadata, add `Query` to it, and set the parameter `max_length` to 50: + +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!../../../docs_src/query_params_str_validations/tutorial006d.py!} + ``` + +!!! note + 具有默认值还会使该参数成为可选参数。 + +But now, having `Query(max_length=50)` inside of `Annotated`, we are telling FastAPI that we want it to extract this value from the query parameters (this would have been the default anyway 🤷) and that we want to have **additional validation** for this value (that's why we do this, to get the additional validation). 😎 + +FastAPI will now: + +* **Validate** the data making sure that the max length is 50 characters +* Show a **clear error** for the client when the data is not valid +* **Document** the parameter in the OpenAPI schema *path operation* (so it will show up in the **automatic docs UI**) -## 使用 `Query` 作为默认值 +## 你可以向 `Query` 的第一个参数传入 `None` 用作查询参数的默认值,以同样的方式你也可以传递其他默认值。 + +Previous versions of FastAPI (before 0.95.0) required you to use `Query` as the default value of your parameter, instead of putting it in `Annotated`, there's a high chance that you will see code using it around, so I'll explain it to you. + +!!! tip + For new code and whenever possible, use `Annotated` as explained above. There are multiple advantages (explained below) and no disadvantages. 🍰 现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!../../../docs_src/query_params_str_validations/tutorial002.py!} + ``` 由于我们必须用 `Query(default=None)` 替换默认值 `None`,`Query` 的第一个参数同样也是用于定义默认值。 @@ -40,13 +157,44 @@ q: Union[str, None] = Query(default=None) ...使得参数可选,等同于: +```Python +q: Union[str, None] = None +``` + +And in Python 3.10 and above: + +```Python +q: str | None = Query(default=None) +``` + +有另一种方法可以显式的声明一个值是必需的,即将默认参数的默认值设为 `...` : + ```Python q: str = None ``` -但是 `Query` 显式地将其声明为查询参数。 +声明为必需参数 + +!!! info + Have in mind that the most important part to make a parameter optional is the part: + + ```Python + = None + ``` + -然后,我们可以将更多的参数传递给 `Query`。在本例中,适用于字符串的 `max_length` 参数: + or the: + + ```Python + = Query(default=None) + ``` + + + as it will use that `None` as the default value, and that way make the parameter **not required**. + + The `Union[str, None]` part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required. + +然后,我们可以将更多的参数传递给 `Query`。 在本例中,适用于字符串的 `max_length` 参数: ```Python q: Union[str, None] = Query(default=None, max_length=50) @@ -54,21 +202,124 @@ q: Union[str, None] = Query(default=None, max_length=50) 将会校验数据,在数据无效时展示清晰的错误信息,并在 OpenAPI 模式的*路径操作*中记录该参​​数。 +### 使用 `Query` 作为默认值 + +!!! tip + 请记住,在大多数情况下,当你需要某些东西时,可以简单地省略 `default` 参数,因此你通常不必使用 `...` 或 `Required` + +Instead use the actual default value of the function parameter. Otherwise, it would be inconsistent. + +For example, this is not allowed: + +```Python +q: Annotated[str, Query(default="rick")] = "morty" +``` + +...because it's not clear if the default value should be `"rick"` or `"morty"`. + +So, you would use (preferably): + +```Python +q: Annotated[str, Query()] = "rick" +``` + +...or in older code bases you will find: + +```Python +q: str = Query(default="rick") +``` + +### Advantages of `Annotated` + +**Using `Annotated` is recommended** instead of the default value in function parameters, it is **better** for multiple reasons. 🤓 + +The **default** value of the **function parameter** is the **actual default** value, that's more intuitive with Python in general. 😌 + +You could **call** that same function in **other places** without FastAPI, and it would **work as expected**. If there's a **required** parameter (without a default value), your **editor** will let you know with an error, **Python** will also complain if you run it without passing the required parameter. + +When you don't use `Annotated` and instead use the **(old) default value style**, if you call that function without FastAPI in **other place**, you have to **remember** to pass the arguments to the function for it to work correctly, otherwise the values will be different from what you expect (e.g. `QueryInfo` or something similar instead of `str`). And your editor won't complain, and Python won't complain running that function, only when the operations inside error out. + +Because `Annotated` can have more than one metadata annotation, you could now even use the same function with other tools, like Typer. 🚀 + ## 添加更多校验 你还可以添加 `min_length` 参数: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial003_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!../../../docs_src/query_params_str_validations/tutorial003.py!} + ``` ## 添加正则表达式 你可以定义一个参数值必须匹配的正则表达式: -```Python hl_lines="11" -{!../../../docs_src/query_params_str_validations/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial004_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="12" + {!> ../../../docs_src/query_params_str_validations/tutorial004_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!../../../docs_src/query_params_str_validations/tutorial013.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11" + {!../../../docs_src/query_params_str_validations/tutorial004.py!} + ``` 这个指定的正则表达式通过以下规则检查接收到的参数值: @@ -76,24 +327,56 @@ q: Union[str, None] = Query(default=None, max_length=50) * `fixedquery`: 值精确地等于 `fixedquery`。 * `$`: 到此结束,在 `fixedquery` 之后没有更多字符。 -如果你对所有的这些**「正则表达式」**概念感到迷茫,请不要担心。对于许多人来说这都是一个困难的主题。你仍然可以在无需正则表达式的情况下做很多事情。 +如果你对所有的这些**「正则表达式」**概念感到迷茫,请不要担心。 对于许多人来说这都是一个困难的主题。 你仍然可以在无需正则表达式的情况下做很多事情。 但是,一旦你需要用到并去学习它们时,请了解你已经可以在 **FastAPI** 中直接使用它们。 +### Pydantic v1 `regex` instead of `pattern` + +Before Pydantic version 2 and before FastAPI 0.100.0, the parameter was called `regex` instead of `pattern`, but it's now deprecated. + +You could still see some code using it: + +=== "Python 3.10+ Pydantic v1" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial004_an_py310_regex.py!} + ``` + +But know that this is deprecated and it should be updated to use the new parameter `pattern`. 🤓 + ## 默认值 -你可以向 `Query` 的第一个参数传入 `None` 用作查询参数的默认值,以同样的方式你也可以传递其他默认值。 +!!! note + 请记住,不同的工具对 OpenAPI 的支持程度可能不同。 假设你想要声明查询参数 `q`,使其 `min_length` 为 `3`,并且默认值为 `fixedquery`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial005.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial005_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + {!../../../docs_src/query_params_str_validations/tutorial005.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!../../../docs_src/query_params_str_validations/tutorial008.py!} + ``` !!! note - 具有默认值还会使该参数成为可选参数。 + Having a default value of any type, including `None`, makes the parameter optional (not required). -## 声明为必需参数 +## Make it required 当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 `q` 参数成为必需参数,例如: @@ -109,54 +392,151 @@ q: Union[str, None] = None 但是现在我们正在用 `Query` 声明它,例如: -```Python -q: Union[str, None] = Query(default=None, min_length=3) -``` +=== "Annotated" + + ```Python + q: Annotated[Union[str, None], Query(min_length=3)] = None + ``` + +=== "non-Annotated" + + ```Python + q: Union[str, None] = Query(default=None, min_length=3) + ``` 因此,当你在使用 `Query` 且需要声明一个值是必需的时,只需不声明默认参数: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + {!../../../docs_src/query_params_str_validations/tutorial006c.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!../../../docs_src/query_params_str_validations/tutorial006.py!} + ``` + + + !!! tip + Notice that, even though in this case the `Query()` is used as the function parameter default value, we don't pass the `default=None` to `Query()`. + + Still, probably better to use the `Annotated` version. 😉 ### 使用省略号(`...`)声明必需参数 -有另一种方法可以显式的声明一个值是必需的,即将默认参数的默认值设为 `...` : +There's an alternative way to explicitly declare that a value is required. You can set the default to the literal value `...`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial006b.py!} -``` +=== "Python 3.9+" -!!! info + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006b_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial006b_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!../../../docs_src/query_params_str_validations/tutorial006b.py!} + ``` + +!!! !!! info 如果你之前没见过 `...` 这种用法:它是一个特殊的单独值,它是 Python 的一部分并且被称为「省略号」。 + Pydantic 和 FastAPI 使用它来显式的声明需要一个值。 这将使 **FastAPI** 知道此查询参数是必需的。 ### 使用`None`声明必需参数 -你可以声明一个参数可以接收`None`值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是`None`。 +你可以声明一个参数可以接收`None`值,但它仍然是必需的。 这将强制客户端发送一个值,即使该值是`None`。 为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...`: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial006c.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006c_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006c_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial006c_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial006c_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial006c.py!} + ``` !!! tip - Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没有设默认值的情况下使用 `Optional` 或 `Union[Something, None]` 时,它具有特殊行为,你可以在 Pydantic 文档中阅读有关必需可选字段的更多信息。 + Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about Required Optional fields. ### 使用Pydantic中的`Required`代替省略号(`...`) 如果你觉得使用 `...` 不舒服,你也可以从 Pydantic 导入并使用 `Required`: -```Python hl_lines="2 8" -{!../../../docs_src/query_params_str_validations/tutorial006d.py!} -``` +=== "Python 3.9+" -!!! tip - 请记住,在大多数情况下,当你需要某些东西时,可以简单地省略 `default` 参数,因此你通常不必使用 `...` 或 `Required` + ```Python hl_lines="4 10" + {!> ../../../docs_src/query_params_str_validations/tutorial006d_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="2 9" + {!> ../../../docs_src/query_params_str_validations/tutorial006d_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 8" + {!> ../../../docs_src/query_params_str_validations/tutorial006d.py!} + ``` + +!!! tip + Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use `...` nor `Required`. ## 查询参数列表 / 多个值 @@ -164,9 +544,53 @@ q: Union[str, None] = Query(default=None, min_length=3) 例如,要声明一个可在 URL 中出现多次的查询参数 `q`,你可以这样写: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial011.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + !!! tip + Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没有设默认值的情况下使用 OptionalUnion[Something, None] 时,它具有特殊行为,你可以在 Pydantic 文档中阅读有关必需可选字段的更多信息。 + ``` + 或 Union[Something, None] 时,它具有特殊行为,你可以在 Pydantic 文档中阅读有关必需可选字段的更多信息。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial011_py310.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial011_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!../../../docs_src/query_params_str_validations/tutorial011.py!} + ``` 然后,输入如下网址: @@ -188,19 +612,45 @@ http://localhost:8000/items/?q=foo&q=bar ``` !!! tip - 要声明类型为 `list` 的查询参数,如上例所示,你需要显式地使用 `Query`,否则该参数将被解释为请求体。 + To declare a query parameter with a type of `list`, like in the example above, you need to explicitly use `Query`, otherwise it would be interpreted as a request body. 交互式 API 文档将会相应地进行更新,以允许使用多个值: - + ### 具有默认值的查询参数列表 / 多个值 你还可以定义在没有任何给定值时的默认 `list` 值: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial012.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial012_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial012_an.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial012_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!../../../docs_src/query_params_str_validations/tutorial012.py!} + ``` 如果你访问: @@ -223,14 +673,31 @@ http://localhost:8000/items/ 你也可以直接使用 `list` 代替 `List [str]`: -```Python hl_lines="7" -{!../../../docs_src/query_params_str_validations/tutorial013.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial013_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial013_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial013.py!} + ``` !!! note - 请记住,在这种情况下 FastAPI 将不会检查列表的内容。 + Have in mind that in this case, FastAPI won't check the contents of the list. - 例如,`List[int]` 将检查(并记录到文档)列表的内容必须是整数。但是单独的 `list` 不会。 + 例如,`List[int]` 将检查(并记录到文档)列表的内容必须是整数。 但是单独的 `list` 不会。 ## 声明更多元数据 @@ -239,21 +706,85 @@ http://localhost:8000/items/ 这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。 !!! note - 请记住,不同的工具对 OpenAPI 的支持程度可能不同。 + Have in mind that different tools might have different levels of OpenAPI support. 其中一些可能不会展示所有已声明的额外信息,尽管在大多数情况下,缺少的这部分功能已经计划进行开发。 你可以添加 `title`: -```Python hl_lines="10" -{!../../../docs_src/query_params_str_validations/tutorial007.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial007_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial007_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!../../../docs_src/query_params_str_validations/tutorial007.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial007_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial007.py!} + ``` 以及 `description`: -```Python hl_lines="13" -{!../../../docs_src/query_params_str_validations/tutorial008.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="14" + {!> ../../../docs_src/query_params_str_validations/tutorial008_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/query_params_str_validations/tutorial008_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="15" + {!> ../../../docs_src/query_params_str_validations/tutorial008_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="12" + {!> ../../../docs_src/query_params_str_validations/tutorial008_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="13" + {!../../../docs_src/query_params_str_validations/tutorial001.py!} + ``` ## 别名参数 @@ -273,9 +804,41 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值: -```Python hl_lines="9" -{!../../../docs_src/query_params_str_validations/tutorial009.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial009_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9" + {!> ../../../docs_src/query_params_str_validations/tutorial009_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial009_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!> ../../../docs_src/query_params_str_validations/tutorial009_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9" + {!../../../docs_src/query_params_str_validations/tutorial009.py!} + ``` ## 弃用参数 @@ -285,15 +848,87 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 那么将参数 `deprecated=True` 传入 `Query`: -```Python hl_lines="18" -{!../../../docs_src/query_params_str_validations/tutorial010.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="19" + {!> ../../../docs_src/query_params_str_validations/tutorial010_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19" + {!> ../../../docs_src/query_params_str_validations/tutorial010_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/query_params_str_validations/tutorial010_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="17" + {!> ../../../docs_src/query_params_str_validations/tutorial010_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="18" + {!../../../docs_src/query_params_str_validations/tutorial010.py!} + ``` 文档将会像下面这样展示它: - + + +## Exclude from OpenAPI + +To exclude a query parameter from the generated OpenAPI schema (and thus, from the automatic documentation systems), set the parameter `include_in_schema` of `Query` to `False`: + +=== "Python 3.10+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + {!> ../../../docs_src/query_params_str_validations/tutorial014_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8" + {!> ../../../docs_src/query_params_str_validations/tutorial014_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!> ../../../docs_src/query_params_str_validations/tutorial014.py!} + ``` -## 总结 +## Recap 你可以为查询参数声明额外的校验和元数据。 diff --git a/docs/zh/docs/tutorial/query-params.md b/docs/zh/docs/tutorial/query-params.md index b1668a2d2523f..dead131d45f8e 100644 --- a/docs/zh/docs/tutorial/query-params.md +++ b/docs/zh/docs/tutorial/query-params.md @@ -6,7 +6,7 @@ {!../../../docs_src/query_params/tutorial001.py!} ``` -查询字符串是键值对的集合,这些键值对位于 URL 的 `?` 之后,并以 `&` 符号分隔。 +查询字符串是键值对的集合,这些键值对位于 URL 的 `? ` 之后,并以 `&` 符号分隔。 例如,在以下 url 中: @@ -42,7 +42,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 http://127.0.0.1:8000/items/ ``` -将与访问以下地址相同: +would be the same as going to: ``` http://127.0.0.1:8000/items/?skip=0&limit=10 @@ -63,22 +63,41 @@ http://127.0.0.1:8000/items/?skip=20 通过同样的方式,你可以将它们的默认值设置为 `None` 来声明可选查询参数: -```Python hl_lines="7" -{!../../../docs_src/query_params/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7" + 将与访问以下地址相同: + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!../../../docs_src/query_params/tutorial002.py!} + ``` 在这个例子中,函数参数 `q` 将是可选的,并且默认值为 `None`。 !!! check - 还要注意的是,**FastAPI** 足够聪明,能够分辨出参数 `item_id` 是路径参数而 `q` 不是,因此 `q` 是一个查询参数。 + Also notice that **FastAPI** is smart enough to notice that the path parameter `item_id` is a path parameter and `q` is not, so, it's a query parameter. ## 查询参数类型转换 你还可以声明 `bool` 类型,它们将被自动转换: -```Python hl_lines="7" -{!../../../docs_src/query_params/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7" + {!../../../docs_src/query_params/tutorial003.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + !!! check + 还要注意的是,FastAPI 足够聪明,能够分辨出参数 item_id 是路径参数而 q 不是,因此 q 是一个查询参数。 + ``` + 是路径参数而 q 不是,因此 q 是一个查询参数。 + 这个例子中,如果你访问: @@ -110,7 +129,7 @@ http://127.0.0.1:8000/items/foo?short=on http://127.0.0.1:8000/items/foo?short=yes ``` -或任何其他的变体形式(大写,首字母大写等等),你的函数接收的 `short` 参数都会是布尔值 `True`。对于值为 `False` 的情况也是一样的。 +或任何其他的变体形式(大写,首字母大写等等),你的函数接收的 `short` 参数都会是布尔值 `True`。 对于值为 `False` 的情况也是一样的。 ## 多个路径和查询参数 @@ -121,9 +140,17 @@ http://127.0.0.1:8000/items/foo?short=yes 它们将通过名称被检测到: -```Python hl_lines="6 8" -{!../../../docs_src/query_params/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="6 8" + {!> ../../../docs_src/query_params/tutorial004_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8 10" + {!../../../docs_src/query_params/tutorial004.py!} + ``` ## 必需查询参数 @@ -179,9 +206,20 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy 当然,你也可以定义一些参数为必需的,一些具有默认值,而某些则完全是可选的: -```Python hl_lines="7" -{!../../../docs_src/query_params/tutorial006.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="8" + {!../../../docs_src/query_params/tutorial006.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + !!! tip + 你还可以像在 路径参数{.internal-link target=_blank} 中那样使用 Enum。 + ``` +。 + 在这个例子中,有3个查询参数: @@ -190,4 +228,4 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy * `limit`,一个可选的 `int` 类型参数。 !!! tip - 你还可以像在 [路径参数](path-params.md#predefined-values){.internal-link target=_blank} 中那样使用 `Enum`。 + You could also use `Enum`s the same way as with [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}. diff --git a/docs/zh/docs/tutorial/request-files.md b/docs/zh/docs/tutorial/request-files.md index 03474907ee94b..cf5d05139c827 100644 --- a/docs/zh/docs/tutorial/request-files.md +++ b/docs/zh/docs/tutorial/request-files.md @@ -2,45 +2,75 @@ `File` 用于定义客户端的上传文件。 -!!! info "说明" +!!! info + To receive uploaded files, first install `python-multipart`. + E.g. 例如: `pip install python-multipart`。 + 因为上传文件以「表单数据」形式发送。 - 所以接收上传文件,要预先安装 `python-multipart`。 - - 例如: `pip install python-multipart`。 - ## 导入 `File` 从 `fastapi` 导入 `File` 和 `UploadFile`: -```Python hl_lines="1" -{!../../../docs_src/request_files/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="3" + !!! info "说明" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1" + FastAPI 支持同时上传多个文件。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + {!../../../docs_src/request_files/tutorial001.py!} + ``` ## 定义 `File` 参数 创建文件(`File`)参数的方式与 `Body` 和 `Form` 一样: -```Python hl_lines="7" -{!../../../docs_src/request_files/tutorial001.py!} -``` +=== "Python 3.9+" -!!! info "说明" + ```Python hl_lines="9" + !!! info "说明" + ``` - `File` 是直接继承自 `Form` 的类。 +=== "Python 3.6+" - 注意,从 `fastapi` 导入的 `Query`、`Path`、`File` 等项,实际上是返回特定类的函数。 + ```Python hl_lines="8" + 使用 `async` 方法时,**FastAPI** 在线程池中执行文件方法,并 `await` 操作完成。 + ``` -!!! tip "提示" +=== "Python 3.6+ non-Annotated" - 声明文件体必须使用 `File`,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!../../../docs_src/request_files/tutorial001.py!} + ``` + +!!! `File` 是直接继承自 `Form` 的类。 + + 注意,从 `fastapi` 导入的 `Query`、`Path`、`File` 等项,实际上是返回特定类的函数。 + +!!! tip + To declare File bodies, you need to use `File`, because otherwise the parameters would be interpreted as query parameters or body (JSON) parameters. 文件作为「表单数据」上传。 如果把*路径操作函数*参数的类型声明为 `bytes`,**FastAPI** 将以 `bytes` 形式读取和接收文件内容。 -这种方式把文件的所有内容都存储在内存里,适用于小型文件。 +Have in mind that this means that the whole contents will be stored in memory. This will work well for small files. 不过,很多情况下,`UploadFile` 更好用。 @@ -48,18 +78,36 @@ 定义文件参数时使用 `UploadFile`: -```Python hl_lines="12" -{!../../../docs_src/request_files/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="14" + !!! note "技术细节" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="13" + 声明文件体必须使用 `File`,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="12" + {!../../../docs_src/request_files/tutorial001.py!} + ``` `UploadFile` 与 `bytes` 相比有更多优势: +* 本节介绍了如何用 `File` 把上传文件声明为(表单数据的)输入参数。 * 使用 `spooled` 文件: * 存储在内存的文件超出最大上限时,FastAPI 会把文件存入磁盘; * 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存; * 可获取上传文件的元数据; -* 自带 file-like `async` 接口; * 暴露的 Python `SpooledTemporaryFile` 对象,可直接传递给其他预期「file-like」对象的库。 +* 自带 file-like `async` 接口; ### `UploadFile` @@ -67,14 +115,14 @@ * `filename`:上传文件名字符串(`str`),例如, `myimage.jpg`; * `content_type`:内容类型(MIME 类型 / 媒体类型)字符串(`str`),例如,`image/jpeg`; -* `file`: `SpooledTemporaryFile`file-like 对象)。其实就是 Python文件,可直接传递给其他预期 `file-like` 对象的函数或支持库。 +* `file`: `SpooledTemporaryFile`file-like 对象)。 其实就是 Python文件,可直接传递给其他预期 `file-like` 对象的函数或支持库。 -`UploadFile` 支持以下 `async` 方法,(使用内部 `SpooledTemporaryFile`)可调用相应的文件方法。 +`UploadFile` has the following `async` methods. They all call the corresponding file methods underneath (using the internal `SpooledTemporaryFile`). * `write(data)`:把 `data` (`str` 或 `bytes`)写入文件; * `read(size)`:按指定数量的字节或字符(`size` (`int`))读取文件内容; * `seek(offset)`:移动至文件 `offset` (`int`)字节处的位置; - * 例如,`await myfile.seek(0) ` 移动到文件开头; + * 例如,`await myfile.seek(0)` 移动到文件开头; * 执行 `await myfile.read()` 后,需再次读取已读取内容时,这种方法特别好用; * `close()`:关闭文件。 @@ -92,13 +140,10 @@ contents = await myfile.read() contents = myfile.file.read() ``` -!!! note "`async` 技术细节" - - 使用 `async` 方法时,**FastAPI** 在线程池中执行文件方法,并 `await` 操作完成。 - -!!! note "Starlette 技术细节" +!!! note "`async` Technical Details" When you use the `async` methods, **FastAPI** runs the file methods in a threadpool and awaits for them. - **FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要功能,使之与 **Pydantic** 及 FastAPI 的其它部件兼容。 +!!! note "Starlette Technical Details" + **FastAPI**'s `UploadFile` inherits directly from **Starlette**'s `UploadFile`, but adds some necessary parts to make it compatible with **Pydantic** and the other parts of FastAPI. ## 什么是 「表单数据」 @@ -106,17 +151,13 @@ contents = myfile.file.read() **FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。 -!!! note "技术细节" - - 不包含文件时,表单数据一般用 `application/x-www-form-urlencoded`「媒体类型」编码。 - - 但表单包含文件时,编码为 `multipart/form-data`。使用了 `File`,**FastAPI** 就知道要从请求体的正确位置获取文件。 +!!! 不包含文件时,表单数据一般用 `application/x-www-form-urlencoded`「媒体类型」编码。 + 但表单包含文件时,编码为 `multipart/form-data`。 使用了 `File`,**FastAPI** 就知道要从请求体的正确位置获取文件。 + 编码和表单字段详见 MDN Web 文档的 POST 小节。 -!!! warning "警告" - - 可在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `multipart/form-data`,不是 `application/json`。 +!!! 可在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。 因为此时请求体的编码是 `multipart/form-data`,不是 `application/json`。 这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。 @@ -124,14 +165,40 @@ contents = myfile.file.read() 您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选: +=== "Python 3.10+" + + ```Python hl_lines="9 17" + 这种方式把文件的所有内容都存储在内存里,适用于小型文件。 + ``` + === "Python 3.9+" - ```Python hl_lines="7 14" - {!> ../../../docs_src/request_files/tutorial001_02_py310.py!} + ```Python hl_lines="9 17" + !!! warning "警告" ``` === "Python 3.6+" + ```Python hl_lines="10 18" + !!! note "async 技术细节" + ``` + 技术细节" + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7 15" + {!> ../../../docs_src/request_files/tutorial001_02_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="9 17" {!> ../../../docs_src/request_files/tutorial001_02.py!} ``` @@ -140,25 +207,62 @@ contents = myfile.file.read() 您也可以将 `File()` 与 `UploadFile` 一起使用,例如,设置额外的元数据: -```Python hl_lines="13" -{!../../../docs_src/request_files/tutorial001_03.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9 15" + !!! tip "提示" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8 14" + **FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要功能,使之与 **Pydantic** 及 FastAPI 的其它部件兼容。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7 13" + 可用同一个「表单字段」发送含多个文件的「表单数据」。 + ``` ## 多文件上传 -FastAPI 支持同时上传多个文件。 +It's possible to upload several files at the same time. -可用同一个「表单字段」发送含多个文件的「表单数据」。 +They would be associated to the same "form field" sent using "form data". 上传多个文件时,要声明含 `bytes` 或 `UploadFile` 的列表(`List`): === "Python 3.9+" + ```Python hl_lines="10 15" + !!! note "Starlette 技术细节" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11 16" + UploadFile 支持以下 async 方法,(使用内部 SpooledTemporaryFile)可调用相应的文件方法。 + ``` + 支持以下 async 方法,(使用内部 SpooledTemporaryFile)可调用相应的文件方法。 + + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + ```Python hl_lines="8 13" {!> ../../../docs_src/request_files/tutorial002_py39.py!} ``` -=== "Python 3.6+" +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. ```Python hl_lines="10 15" {!> ../../../docs_src/request_files/tutorial002.py!} @@ -166,12 +270,9 @@ FastAPI 支持同时上传多个文件。 接收的也是含 `bytes` 或 `UploadFile` 的列表(`list`)。 +!!! 也可以使用 `from starlette.responses import HTMLResponse`。 -!!! note "技术细节" - - 也可以使用 `from starlette.responses import HTMLResponse`。 - - `fastapi.responses` 其实与 `starlette.responses` 相同,只是为了方便开发者调用。实际上,大多数 **FastAPI** 的响应都直接从 Starlette 调用。 + `fastapi.responses` 其实与 `starlette.responses` 相同,只是为了方便开发者调用。 实际上,大多数 **FastAPI** 的响应都直接从 Starlette 调用。 But most of the available responses come directly from Starlette. ### 带有额外元数据的多文件上传 @@ -179,16 +280,34 @@ FastAPI 支持同时上传多个文件。 === "Python 3.9+" - ```Python hl_lines="16" - {!> ../../../docs_src/request_files/tutorial003_py39.py!} + ```Python hl_lines="11 18-20" + !!! note "技术细节" ``` === "Python 3.6+" - ```Python hl_lines="18" + ```Python hl_lines="12 19-21" + {!../../../docs_src/request_files/tutorial001_03.py!} + ``` + +=== "Python 3.9+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="9 16" + {!> ../../../docs_src/request_files/tutorial003_py39.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="11 18" {!> ../../../docs_src/request_files/tutorial003.py!} ``` -## 小结 +## Recap -本节介绍了如何用 `File` 把上传文件声明为(表单数据的)输入参数。 +Use `File`, `bytes`, and `UploadFile` to declare files to be uploaded in the request, sent as form data. diff --git a/docs/zh/docs/tutorial/request-forms-and-files.md b/docs/zh/docs/tutorial/request-forms-and-files.md index 70cd70f98650c..6dc779760bfbf 100644 --- a/docs/zh/docs/tutorial/request-forms-and-files.md +++ b/docs/zh/docs/tutorial/request-forms-and-files.md @@ -2,36 +2,66 @@ FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。 -!!! info "说明" +!!! 接收上传文件或表单数据,要预先安装 <a href="https://andrew-d.github.io/python-multipart/" class="external-link" target="_blank">`python-multipart`</a>。 - 接收上传文件或表单数据,要预先安装 `python-multipart`。 - - 例如,`pip install python-multipart`。 + E.g. 例如,`pip install python-multipart`。 ## 导入 `File` 与 `Form` -```Python hl_lines="1" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="3" + !!! warning "警告" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1" + {!> ../../../docs_src/request_forms_and_files/tutorial001_an.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + {!../../../docs_src/request_forms_and_files/tutorial001.py!} + ``` ## 定义 `File` 与 `Form` 参数 创建文件和表单参数的方式与 `Body` 和 `Query` 一样: -```Python hl_lines="8" -{!../../../docs_src/request_forms_and_files/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="10-12" + 小结 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-11" + !!! info "说明" + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="8" + {!../../../docs_src/request_forms_and_files/tutorial001.py!} + ``` 文件和表单字段作为表单数据上传与接收。 声明文件可以使用 `bytes` 或 `UploadFile` 。 -!!! warning "警告" - - 可在一个*路径操作*中声明多个 `File` 与 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码为 `multipart/form-data`,不是 `application/json`。 +!!! 可在一个*路径操作*中声明多个 `File` 与 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。 因为此时请求体的编码为 `multipart/form-data`,不是 `application/json`。 这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。 -## 小结 +## Recap 在同一个请求中接收数据和文件时,应同时使用 `File` 和 `Form`。 diff --git a/docs/zh/docs/tutorial/request-forms.md b/docs/zh/docs/tutorial/request-forms.md index 6436ffbcdc26e..06300fee297d5 100644 --- a/docs/zh/docs/tutorial/request-forms.md +++ b/docs/zh/docs/tutorial/request-forms.md @@ -2,27 +2,59 @@ 接收的不是 JSON,而是表单字段时,要使用 `Form`。 -!!! info "说明" +!!! 要使用表单,需预先安装 <a href="https://andrew-d.github.io/python-multipart/" class="external-link" target="_blank">`python-multipart`</a>。 - 要使用表单,需预先安装 `python-multipart`。 - - 例如,`pip install python-multipart`。 + E.g. 例如,`pip install python-multipart`。 ## 导入 `Form` 从 `fastapi` 导入 `Form`: -```Python hl_lines="1" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="3" + !!! tip "提示" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1" + `Form` 是直接继承自 `Body` 的类。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1" + {!../../../docs_src/request_forms/tutorial001.py!} + ``` ## 定义 `Form` 参数 创建表单(`Form`)参数的方式与 `Body` 和 `Query` 一样: -```Python hl_lines="7" -{!../../../docs_src/request_forms/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="9" + !!! info "说明" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="8" + 声明表单体要显式使用 `Form` ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7" + {!../../../docs_src/request_forms/tutorial001.py!} + ``` 例如,OAuth2 规范的 "密码流" 模式规定要通过表单字段发送 `username` 和 `password`。 @@ -30,13 +62,11 @@ 使用 `Form` 可以声明与 `Body` (及 `Query`、`Path`、`Cookie`)相同的元数据和验证。 -!!! info "说明" - - `Form` 是直接继承自 `Body` 的类。 +!!! info + `Form` is a class that inherits directly from `Body`. -!!! tip "提示" - - 声明表单体要显式使用 `Form` ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 +!!! tip + To declare form bodies, you need to use `Form` explicitly, because without it the parameters would be interpreted as query parameters or body (JSON) parameters. ## 关于 "表单字段" @@ -44,20 +74,16 @@ **FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。 -!!! note "技术细节" - - 表单数据的「媒体类型」编码一般为 `application/x-www-form-urlencoded`。 - - 但包含文件的表单编码为 `multipart/form-data`。文件处理详见下节。 +!!! 表单数据的「媒体类型」编码一般为 `application/x-www-form-urlencoded`。 + 但包含文件的表单编码为 `multipart/form-data`。 You'll read about handling files in the next chapter. + 编码和表单字段详见 MDN Web 文档的 POST小节。 -!!! warning "警告" - - 可在一个*路径操作*中声明多个 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `application/x-www-form-urlencoded`,不是 `application/json`。 +!!! 可在一个*路径操作*中声明多个 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。 因为此时请求体的编码是 `application/x-www-form-urlencoded`,不是 `application/json`。 这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。 -## 小结 +## Recap 本节介绍了如何使用 `Form` 声明表单数据输入参数。 diff --git a/docs/zh/docs/tutorial/response-model.md b/docs/zh/docs/tutorial/response-model.md index ea3d0666ded6e..121d6543453e4 100644 --- a/docs/zh/docs/tutorial/response-model.md +++ b/docs/zh/docs/tutorial/response-model.md @@ -2,48 +2,135 @@ 你可以在任意的*路径操作*中使用 `response_model` 参数来声明用于响应的模型: +You can use **type annotations** the same way you would for input data in function **parameters**, you can use Pydantic models, lists, dictionaries, scalar values like integers, booleans, etc. + +=== "Python 3.10+" + + ```Python hl_lines="16 21" + {!> ../../../docs_src/response_model/tutorial001_01_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="18 23" + https://fastapi.tiangolo.com/img/tutorial/response-model/image02.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18 23" + {!> ../../../docs_src/response_model/tutorial001_01.py!} + ``` + +!!! info + 你还可以使用: + +* **Validate** the returned data. + * If the data is invalid (e.g. you are missing a field), it means that *your* app code is broken, not returning what it should, and it will return a server error instead of returning incorrect data. This way you and your clients can be certain that they will receive the data and the data shape expected. +* 在 OpenAPI 的*路径操作*中为响应添加一个 JSON Schema。 + * This will be used by the **automatic docs**. + * It will also be used by automatic client code generation tools. + +但最重要的是: + +* It will **limit and filter** the output data to what is defined in the return type. + * This is particularly important for **security**, we'll see more of that below. + +## !!! tip + `{"name", "description"}` 语法创建一个具有这两个值的 `set`。 + +There are some cases where you need or want to return some data that is not exactly what the type declares. + +For example, you could want to **return a dictionary** or a database object, but **declare it as a Pydantic model**. This way the Pydantic model would do all the data documentation, validation, etc. for the object that you returned (e.g. a dictionary or database object). + +If you added the return type annotation, tools and editors would complain with a (correct) error telling you that your function is returning a type (e.g. a dict) that is different from what you declared (e.g. a Pydantic model). + +!!! tip + 但是依然建议你使用上面提到的主意,使用多个类而不是这些参数。 + +!!! note "技术细节" + 响应模型在参数中被声明,而不是作为函数返回类型的注解,这是因为路径函数可能不会真正返回该响应模型,而是返回一个 `dict`、数据库对象或其他模型,然后再使用 `response_model` 来执行字段约束和序列化。 + * `@app.get()` * `@app.post()` * `@app.put()` * `@app.delete()` * 等等。 -```Python hl_lines="17" -{!../../../docs_src/response_model/tutorial001.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001_py39.py!} + ``` + +=== "Python 3.6+" -!!! note - 注意,`response_model`是「装饰器」方法(`get`,`post` 等)的一个参数。不像之前的所有参数和请求体,它不属于*路径操作函数*。 + ```Python hl_lines="17 22 24-27" + 并在自动生成文档系统中使用。 + ``` + +!!! !!! note + 注意,`response_model`是「装饰器」方法(`get`,`post` 等)的一个参数。 不像之前的所有参数和请求体,它不属于*路径操作函数*。 它接收的类型与你将为 Pydantic 模型属性所声明的类型相同,因此它可以是一个 Pydantic 模型,但也可以是一个由 Pydantic 模型组成的 `list`,例如 `List[Item]`。 -FastAPI 将使用此 `response_model` 来: +将输出数据转换为其声明的类型。 -* 将输出数据转换为其声明的类型。 -* 校验数据。 -* 在 OpenAPI 的*路径操作*中为响应添加一个 JSON Schema。 -* 并在自动生成文档系统中使用。 +!!! tip + If you have strict type checks in your editor, mypy, etc, you can declare the function return type as `Any`. -但最重要的是: + That way you tell the editor that you are intentionally returning anything. But FastAPI will still do the data documentation, validation, filtering, etc. with the `response_model`. -* 会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。 +### 使用 `response_model_exclude_unset` 参数 -!!! note "技术细节" - 响应模型在参数中被声明,而不是作为函数返回类型的注解,这是因为路径函数可能不会真正返回该响应模型,而是返回一个 `dict`、数据库对象或其他模型,然后再使用 `response_model` 来执行字段约束和序列化。 +FastAPI 将使用此 `response_model` 来: + +This way you can add correct type annotations to your functions even when you are returning a type different than the response model, to be used by the editor and tools like mypy. And still you can have FastAPI do the data validation, documentation, etc. using the `response_model`. + +!!! danger + 永远不要存储用户的明文密码,也不要在响应中发送密码。 ## 返回与输入相同的数据 现在我们声明一个 `UserIn` 模型,它将包含一个明文密码属性。 -```Python hl_lines="9 11" -{!../../../docs_src/response_model/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="7 9" + {!> ../../../docs_src/response_model/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11" + {!../../../docs_src/response_model/tutorial001.py!} + ``` + +!!! info + To use `EmailStr`, first install `email_validator`. + + E.g. `pip install email-validator` + or `pip install pydantic[email]`. 我们正在使用此模型声明输入数据,并使用同一模型声明输出数据: -```Python hl_lines="17-18" -{!../../../docs_src/response_model/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="16" + {!> ../../../docs_src/response_model/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18" + {!../../../docs_src/response_model/tutorial002.py!} + ``` 现在,每当浏览器使用一个密码创建用户时,API 都会在响应中返回相同的密码。 @@ -52,47 +139,208 @@ FastAPI 将使用此 `response_model` 来: 但是,如果我们在其他的*路径操作*中使用相同的模型,则可能会将用户的密码发送给每个客户端。 !!! danger - 永远不要存储用户的明文密码,也不要在响应中发送密码。 + Never store the plain password of a user or send it in a response like this, unless you know all the caveats and you know what you are doing. ## 添加输出模型 相反,我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型: -```Python hl_lines="9 11 16" -{!../../../docs_src/response_model/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="9 11 16" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11 16" + {!../../../docs_src/response_model/tutorial003.py!} + ``` 这样,即便我们的*路径操作函数*将会返回包含密码的相同输入用户: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24" + {!../../../docs_src/response_model/tutorial003.py!} + ``` ...我们已经将 `response_model` 声明为了不包含密码的 `UserOut` 模型: -```Python hl_lines="22" -{!../../../docs_src/response_model/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` 因此,**FastAPI** 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。 +### `response_model_include` 和 `response_model_exclude` + +In this case, because the two models are different, if we annotated the function return type as `UserOut`, the editor and tools would complain that we are returning an invalid type, as those are different classes. + +That's why in this example we have to declare it in the `response_model` parameter. + +...but continue reading below to see how to overcome that. + +## Return Type and Data Filtering + +Let's continue from the previous example. We wanted to **annotate the function with one type** but return something that includes **more data**. + +校验数据。 + +In the previous example, because the classes were different, we had to use the `response_model` parameter. But that also means that we don't get the support from the editor and tools checking the function return type. + +But in most of the cases where we need to do something like this, we want the model just to **filter/remove** some of the data as in this example. + +And in those cases, we can use classes and inheritance to take advantage of function **type annotations** to get better support in the editor and tools, and still get the FastAPI **data filtering**. + +=== "Python 3.10+" + + ```Python hl_lines="7-10 13-14 18" + {!> ../../../docs_src/response_model/tutorial003_01_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-13 15-16 20" + {!> ../../../docs_src/response_model/tutorial003_01.py!} + ``` + +With this, we get tooling support, from editors and mypy as this code is correct in terms of types, but we also get the data filtering from FastAPI. + +How does this work? Let's check that out. 🤓 + +### Type Annotations and Tooling + +First let's see how editors, mypy and other tools would see this. + +`BaseUser` has the base fields. Then `UserIn` inherits from `BaseUser` and adds the `password` field, so, it will include all the fields from both models. + +We annotate the function return type as `BaseUser`, but we are actually returning a `UserIn` instance. + +The editor, mypy, and other tools won't complain about this because, in typing terms, `UserIn` is a subclass of `BaseUser`, which means it's a *valid* type when what is expected is anything that is a `BaseUser`. + +### FastAPI Data Filtering + +Now, for FastAPI, it will see the return type and make sure that what you return includes **only** the fields that are declared in the type. + +FastAPI does several things internally with Pydantic to make sure that those same rules of class inheritance are not used for the returned data filtering, otherwise you could end up returning much more data than what you expected. + +This way, you can get the best of both worlds: type annotations with **tooling support** and **data filtering**. + ## 在文档中查看 当你查看自动化文档时,你可以检查输入模型和输出模型是否都具有自己的 JSON Schema: - + 并且两种模型都将在交互式 API 文档中使用: - + + +## Other Return Type Annotations + +There might be cases where you return something that is not a valid Pydantic field and you annotate it in the function, only to get the support provided by tooling (the editor, mypy, etc). + +### Return a Response Directly + +The most common case would be [returning a Response directly as explained later in the advanced docs](../advanced/response-directly.md){.internal-link target=_blank}. + +```Python hl_lines="8 10-11" +{!../../../docs_src/response_model/tutorial002.py!} +``` + +This simple case is handled automatically by FastAPI because the return type annotation is the class (or a subclass) of `Response`. + +And tools will also be happy because both `RedirectResponse` and `JSONResponse` are subclasses of `Response`, so the type annotation is correct. + +### Annotate a Response Subclass + +You can also use a subclass of `Response` in the type annotation: + +```Python hl_lines="8-9" +{!../../../docs_src/response_model/tutorial003.py!} +``` + +This will also work because `RedirectResponse` is a subclass of `Response`, and FastAPI will automatically handle this simple case. + +### Invalid Return Type Annotations + +But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object) and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that type annotation, and will fail. + +The same would happen if you had something like a union between different types where one or more of them are not valid Pydantic types, for example this would fail 💥: + +=== "Python 3.10+" + + ```Python hl_lines="8" + {!> ../../../docs_src/response_model/tutorial003_04_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/response_model/tutorial003_04.py!} + ``` + +...this fails because the type annotation is not a Pydantic type and is not just a single `Response` class or subclass, it's a union (any of the two) between a `Response` and a `dict`. + +### Disable Response Model + +Continuing from the example above, you might not want to have the default data validation, documentation, filtering, etc. that is performed by FastAPI. + +But you might want to still keep the return type annotation in the function to get the support from tools like editors and type checkers (e.g. mypy). + +!!! tip + 请注意默认值可以是任何值,而不仅是`None`。 + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/response_model/tutorial003_05_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/response_model/tutorial003_05.py!} + ``` + +This will make FastAPI skip the response model generation and that way you can have any return type annotations you need without it affecting your FastAPI application. 🤓 ## 响应模型编码参数 你的响应模型可以具有默认值,例如: -```Python hl_lines="11 13-14" -{!../../../docs_src/response_model/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="9 11-12" + {!> ../../../docs_src/response_model/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="11 13-14" + {!> ../../../docs_src/response_model/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11 13-14" + {!../../../docs_src/response_model/tutorial004.py!} + ``` * `description: Union[str, None] = None` 具有默认值 `None`。 * `tax: float = 10.5` 具有默认值 `10.5`. @@ -102,13 +350,28 @@ FastAPI 将使用此 `response_model` 来: 举个例子,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但你又不想发送充满默认值的很长的 JSON 响应。 -### 使用 `response_model_exclude_unset` 参数 +### !!! info + FastAPI 通过 Pydantic 模型的 `.dict()` 配合 该方法的 `exclude_unset` 参数 来实现此功能。 你可以设置*路径操作装饰器*的 `response_model_exclude_unset=True` 参数: -```Python hl_lines="24" -{!../../../docs_src/response_model/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24" + {!../../../docs_src/response_model/tutorial004.py!} + ``` 然后响应中将不会包含那些默认值,而是仅有实际设置的值。 @@ -122,10 +385,10 @@ FastAPI 将使用此 `response_model` 来: ``` !!! info - FastAPI 通过 Pydantic 模型的 `.dict()` 配合 该方法的 `exclude_unset` 参数 来实现此功能。 + FastAPI uses Pydantic model's `.dict()` with its `exclude_unset` parameter to achieve this. !!! info - 你还可以使用: + You can also use: * `response_model_exclude_defaults=True` * `response_model_exclude_none=True` @@ -166,11 +429,11 @@ FastAPI 将使用此 `response_model` 来: 因此,它们将包含在 JSON 响应中。 !!! tip - 请注意默认值可以是任何值,而不仅是`None`。 + Notice that the default values can be anything, not only `None`. 它们可以是一个列表(`[]`),一个值为 `10.5`的 `float`,等等。 -### `response_model_include` 和 `response_model_exclude` +### `response_model_include` and `response_model_exclude` 你还可以使用*路径操作装饰器*的 `response_model_include` 和 `response_model_exclude` 参数。 @@ -179,18 +442,26 @@ FastAPI 将使用此 `response_model` 来: 如果你只有一个 Pydantic 模型,并且想要从输出中移除一些数据,则可以使用这种快捷方法。 !!! tip - 但是依然建议你使用上面提到的主意,使用多个类而不是这些参数。 + But it is still recommended to use the ideas above, using multiple classes, instead of these parameters. 这是因为即使使用 `response_model_include` 或 `response_model_exclude` 来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。 - + 这也适用于作用类似的 `response_model_by_alias`。 -```Python hl_lines="31 37" -{!../../../docs_src/response_model/tutorial005.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="29 35" + {!> ../../../docs_src/response_model/tutorial005_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="31 37" + {!../../../docs_src/response_model/tutorial005.py!} + ``` !!! tip - `{"name", "description"}` 语法创建一个具有这两个值的 `set`。 + The syntax `{"name", "description"}` creates a `set` with those two values. 等同于 `set(["name", "description"])`。 @@ -198,11 +469,19 @@ FastAPI 将使用此 `response_model` 来: 如果你忘记使用 `set` 而是使用 `list` 或 `tuple`,FastAPI 仍会将其转换为 `set` 并且正常工作: -```Python hl_lines="31 37" -{!../../../docs_src/response_model/tutorial006.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="29 35" + {!> ../../../docs_src/response_model/tutorial006_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="31 37" + {!../../../docs_src/response_model/tutorial006.py!} + ``` -## 总结 +## Recap 使用*路径操作装饰器*的 `response_model` 参数来定义响应模型,特别是确保私有数据被过滤掉。 diff --git a/docs/zh/docs/tutorial/response-status-code.md b/docs/zh/docs/tutorial/response-status-code.md index 3578319423adc..5a265909abcff 100644 --- a/docs/zh/docs/tutorial/response-status-code.md +++ b/docs/zh/docs/tutorial/response-status-code.md @@ -12,12 +12,12 @@ {!../../../docs_src/response_status_code/tutorial001.py!} ``` -!!! note - 注意,`status_code` 是「装饰器」方法(`get`,`post` 等)的一个参数。不像之前的所有参数和请求体,它不属于*路径操作函数*。 +!!! !!! note + 注意,`status_code` 是「装饰器」方法(`get`,`post` 等)的一个参数。 不像之前的所有参数和请求体,它不属于*路径操作函数*。 `status_code` 参数接收一个表示 HTTP 状态码的数字。 -!!! info +!!! !!! info `status_code` 也能够接收一个 `IntEnum` 类型,比如 Python 的 `http.HTTPStatus`。 它将会: @@ -25,16 +25,16 @@ * 在响应中返回该状态码。 * 在 OpenAPI 模式中(以及在用户界面中)将其记录为: - + -!!! note +!!! !!! note 一些响应状态码(请参阅下一部分)表示响应没有响应体。 FastAPI 知道这一点,并将生成表明没有响应体的 OpenAPI 文档。 ## 关于 HTTP 状态码 -!!! note +!!! !!! note 如果你已经了解什么是 HTTP 状态码,请跳到下一部分。 在 HTTP 协议中,你将发送 3 位数的数字状态码作为响应的一部分。 @@ -43,18 +43,18 @@ 简而言之: -* `100` 及以上状态码用于「消息」响应。你很少直接使用它们。具有这些状态代码的响应不能带有响应体。 -* **`200`** 及以上状态码用于「成功」响应。这些是你最常使用的。 +* `100` 及以上状态码用于「消息」响应。 你很少直接使用它们。 具有这些状态代码的响应不能带有响应体。 +* **`200`** 及以上状态码用于「成功」响应。 这些是你最常使用的。 * `200` 是默认状态代码,它表示一切「正常」。 - * 另一个例子会是 `201`,「已创建」。它通常在数据库中创建了一条新记录后使用。 - * 一个特殊的例子是 `204`,「无内容」。此响应在没有内容返回给客户端时使用,因此该响应不能包含响应体。 -* **`300`** 及以上状态码用于「重定向」。具有这些状态码的响应可能有或者可能没有响应体,但 `304`「未修改」是个例外,该响应不得含有响应体。 -* **`400`** 及以上状态码用于「客户端错误」响应。这些可能是你第二常使用的类型。 + * 另一个例子会是 `201`,「已创建」。 它通常在数据库中创建了一条新记录后使用。 + * 一个特殊的例子是 `204`,「无内容」。 此响应在没有内容返回给客户端时使用,因此该响应不能包含响应体。 +* **`300`** 及以上状态码用于「重定向」。 具有这些状态码的响应可能有或者可能没有响应体,但 `304`「未修改」是个例外,该响应不得含有响应体。 +* **`400`** 及以上状态码用于「客户端错误」响应。 这些可能是你第二常使用的类型。 * 一个例子是 `404`,用于「未找到」响应。 * 对于来自客户端的一般错误,你可以只使用 `400`。 -* `500` 及以上状态码用于服务器端错误。你几乎永远不会直接使用它们。当你的应用程序代码或服务器中的某些部分出现问题时,它将自动返回这些状态代码之一。 +* `500` 及以上状态码用于服务器端错误。 你几乎永远不会直接使用它们。 当你的应用程序代码或服务器中的某些部分出现问题时,它将自动返回这些状态代码之一。 -!!! tip +!!! !!! tip 要了解有关每个状态代码以及适用场景的更多信息,请查看 MDN 关于 HTTP 状态码的文档。 ## 记住名称的捷径 @@ -77,12 +77,12 @@ 它们只是一种便捷方式,它们具有同样的数字代码,但是这样使用你就可以使用编辑器的自动补全功能来查找它们: - + -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以使用 `from starlette import status`。 - 为了给你(即开发者)提供方便,**FastAPI** 提供了与 `starlette.status` 完全相同的 `fastapi.status`。但它直接来自于 Starlette。 + 为了给你(即开发者)提供方便,**FastAPI** 提供了与 `starlette.status` 完全相同的 `fastapi.status`。 但它直接来自于 Starlette。 ## 更改默认状态码 diff --git a/docs/zh/docs/tutorial/schema-extra-example.md b/docs/zh/docs/tutorial/schema-extra-example.md index 8f5fbfe70bbb5..14abdac8624b7 100644 --- a/docs/zh/docs/tutorial/schema-extra-example.md +++ b/docs/zh/docs/tutorial/schema-extra-example.md @@ -1,58 +1,262 @@ -# 模式的额外信息 - 例子 +# Declare Request Example Data -您可以在JSON模式中定义额外的信息。 +You can declare examples of the data your app can receive. -一个常见的用例是添加一个将在文档中显示的`example`。 +Here are several ways to do it. -有几种方法可以声明额外的 JSON 模式信息。 +## Extra JSON Schema data in Pydantic models -## Pydantic `schema_extra` +You can declare `examples` for a Pydantic model that will be added to the generated JSON Schema. -您可以使用 `Config` 和 `schema_extra` 为Pydantic模型声明一个示例,如Pydantic 文档:定制 Schema 中所述: +=== "Python 3.10+ Pydantic v2" -```Python hl_lines="15-23" -{!../../../docs_src/schema_extra_example/tutorial001.py!} -``` + ```Python hl_lines="13-24" + {!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} + ``` -这些额外的信息将按原样添加到输出的JSON模式中。 +=== "Python 3.10+ Pydantic v1" + + ```Python hl_lines="13-23" + {!> ../../../docs_src/schema_extra_example/tutorial001_py310_pv1.py!} + ``` + +=== "Python 3.6+ Pydantic v2" + + ```Python hl_lines="15-26" + {!../../../docs_src/schema_extra_example/tutorial001.py!} + ``` + +=== "Python 3.6+ Pydantic v1" + + ```Python hl_lines="15-25" + {!> ../../../docs_src/schema_extra_example/tutorial001_pv1.py!} + ``` + +That extra info will be added as-is to the output **JSON Schema** for that model, and it will be used in the API docs. + +=== "Pydantic v2" + + In Pydantic version 2, you would use the attribute `model_config`, that takes a `dict` as described in Pydantic's docs: Model Config. + + You can set `"json_schema_extra"` with a `dict` containing any additonal data you would like to show up in the generated JSON Schema, including `examples`. + +=== "Pydantic v1" + + In Pydantic version 1, you would use an internal class `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization. + + You can set `schema_extra` with a `dict` containing any additonal data you would like to show up in the generated JSON Schema, including `examples`. + +!!! tip + You could use the same technique to extend the JSON Schema and add your own custom extra info. + + For example you could use it to add metadata for a frontend user interface, etc. + +!!! info + OpenAPI 3.1.0 (used since FastAPI 0.99.0) added support for `examples`, which is part of the **JSON Schema** standard. + + Before that, it only supported the keyword `example` with a single example. That is still supported by OpenAPI 3.1.0, but is deprecated and is not part of the JSON Schema standard. So you are encouraged to migrate `example` to `examples`. 🤓 + + You can read more at the end of this page. ## `Field` 的附加参数 -在 `Field`, `Path`, `Query`, `Body` 和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 `example`: +When using `Field()` with Pydantic models, you can also declare additional `examples`: -```Python hl_lines="4 10-13" -{!../../../docs_src/schema_extra_example/tutorial002.py!} -``` +=== "Python 3.10+" -!!! warning - 请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。 + ```Python hl_lines="2 8-11" + {!> ../../../docs_src/schema_extra_example/tutorial002_py310.py!} + ``` -## `Body` 额外参数 +=== "Python 3.6+" -你可以通过传递额外信息给 `Field` 同样的方式操作`Path`, `Query`, `Body`等。 + ```Python hl_lines="4 10-13" + {!../../../docs_src/schema_extra_example/tutorial002.py!} + ``` + +## 所以,虽然 `example` 不是JSON Schema的一部分,但它是OpenAPI的一部分,这将被文档UI使用。 + +When using any of: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +you can also declare a group of `examples` with additional information that will be added to **OpenAPI**. + +### 关于 `example` 和 `examples`... 比如,你可以将请求体的一个 `example` 传递给 `Body`: -```Python hl_lines="20-25" -{!../../../docs_src/schema_extra_example/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="22-29" + {!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="22-29" + !!! warning + 请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="23-30" + Pydantic schema_extra + ``` + + -## 文档 UI 中的例子 +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="18-25" + {!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="20-27" + 您可以使用 Configschema_extra 为Pydantic模型声明一个示例,如Pydantic 文档:定制 Schema 中所述: + ``` + 和 schema_extra 为Pydantic模型声明一个示例,如Pydantic 文档:定制 Schema 中所述: + + +### 模式的额外信息 - 例子 使用上面的任何方法,它在 `/docs` 中看起来都是这样的: - + + +### `Body` 额外参数 + +You can of course also pass multiple `examples`: + +=== "Python 3.10+" + + ```Python hl_lines="23-38" + {!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="23-38" + {!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24-39" + 同样的方法,你可以添加你自己的额外信息,这些信息将被添加到每个模型的JSON模式中,例如定制前端用户界面,等等。 + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="19-34" + 其他信息 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="21-36" + {!../../../docs_src/schema_extra_example/tutorial003.py!} + ``` + +### 文档 UI 中的例子 + +你可以通过传递额外信息给 `Field` 同样的方式操作`Path`, `Query`, `Body`等。 + + ## 技术细节 -关于 `example` 和 `examples`... +!!! tip + If you are already using **FastAPI** version **0.99.0 or above**, you can probably **skip** these details. + + They are more relevant for older versions, before OpenAPI 3.1.0 was available. + + You can consider this a brief OpenAPI and JSON Schema **history lesson**. 🤓 + +!!! warning + These are very technical details about the standards **JSON Schema** and **OpenAPI**. + + If the ideas above already work for you, that might be enough, and you probably don't need these details, feel free to skip them. + +Before OpenAPI 3.1.0, OpenAPI used an older and modified version of **JSON Schema**. + +在 `Field`, `Path`, `Query`, `Body` 和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 `example`: + +OpenAPI also added `example` and `examples` fields to other parts of the specification: + +* 所以 OpenAPI为了相似的目的定义了自己的 `example` (使用 `example`, 而不是 `examples`), 这也是文档 UI 所使用的 (使用 Swagger UI). + * `Path()` + * `Query()` + * `Header()` + * `Cookie()` +* 您可以在JSON模式中定义额外的信息。 + * `Body()` + * `File()` + * `Form()` + +### OpenAPI's `examples` field + +The shape of this field `examples` from OpenAPI is a `dict` with **multiple examples**, each with extra information that will be added to **OpenAPI** too. + +The keys of the `dict` identify each example, and each value is another `dict`. + +一个常见的用例是添加一个将在文档中显示的`example`。 + +* `summary`: Short description for the example. +* `description`: A long description that can contain Markdown text. +* `value`: This is the actual example shown, e.g. a `dict`. +* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`. + +这些额外的信息将按原样添加到输出的JSON模式中。 + +### 有几种方法可以声明额外的 JSON 模式信息。 JSON Schema在最新的一个版本中定义了一个字段 `examples` ,但是 OpenAPI 基于之前的一个旧版JSON Schema,并没有 `examples`. -所以 OpenAPI为了相似的目的定义了自己的 `example` (使用 `example`, 而不是 `examples`), 这也是文档 UI 所使用的 (使用 Swagger UI). +And then the new OpenAPI 3.1.0 was based on the latest version (JSON Schema 2020-12) that included this new field `examples`. + +And now this new `examples` field takes precedence over the old single (and custom) `example` field, that is now deprecated. + +This new `examples` field in JSON Schema is **just a `list`** of examples, not a dict with extra metadata as in the other places in OpenAPI (described above). + +!!! info + Even after OpenAPI 3.1.0 was released with this new simpler integration with JSON Schema, for a while, Swagger UI, the tool that provides the automatic docs, didn't support OpenAPI 3.1.0 (it does since version 5.0.0 🎉). + + Because of that, versions of FastAPI previous to 0.99.0 still used versions of OpenAPI lower than 3.1.0. + +### Pydantic and FastAPI `examples` + +When you add `examples` inside of a Pydantic model, using `schema_extra` or `Field(examples=["something"])` that example is added to the **JSON Schema** for that Pydantic model. + +And that **JSON Schema** of the Pydantic model is included in the **OpenAPI** of your API, and then it's used in the docs UI. + +In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1.0) when you used `example` or `examples` with any of the other utilities (`Query()`, `Body()`, etc.) those examples were not added to the JSON Schema that describes that data (not even to OpenAPI's own version of JSON Schema), they were added directly to the *path operation* declaration in OpenAPI (outside the parts of OpenAPI that use JSON Schema). + +But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above, everything is more consistent and the examples are included in JSON Schema. -所以,虽然 `example` 不是JSON Schema的一部分,但它是OpenAPI的一部分,这将被文档UI使用。 +### Summary -## 其他信息 +I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅 -同样的方法,你可以添加你自己的额外信息,这些信息将被添加到每个模型的JSON模式中,例如定制前端用户界面,等等。 +In short, **upgrade to FastAPI 0.99.0 or above**, and things are much **simpler, consistent, and intuitive**, and you don't have to know all these historic details. 😎 diff --git a/docs/zh/docs/tutorial/security/first-steps.md b/docs/zh/docs/tutorial/security/first-steps.md index 86c3320ce1e9e..32827f42aac1b 100644 --- a/docs/zh/docs/tutorial/security/first-steps.md +++ b/docs/zh/docs/tutorial/security/first-steps.md @@ -8,11 +8,11 @@ 固然,**FastAPI** 支持 **OAuth2** 身份验证。 -但为了节省开发者的时间,不要只为了查找很少的内容,不得不阅读冗长的规范文档。 +But let's save you the time of reading the full long specification just to find those little pieces of information you need. 我们建议使用 **FastAPI** 的安全工具。 -## 概览 +## How it looks 首先,看看下面的代码是怎么运行的,然后再回过头来了解其背后的原理。 @@ -20,170 +20,345 @@ 把下面的示例代码复制到 `main.py`: -```Python -{!../../../docs_src/security/tutorial001.py!} -``` - -## 运行 - -!!! info "说明" - - 先安装 `python-multipart`。 - - 安装命令: `pip install python-multipart`。 - - 这是因为 **OAuth2** 使用**表单数据**发送 `username` 与 `password`。 - -用下面的命令运行该示例: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -## 查看文档 - -打开 API 文档: http://127.0.0.1:8000/docs。 - -界面如下图所示: - - - -!!! check "Authorize 按钮!" - - 页面右上角出现了一个「**Authorize**」按钮。 - - *路径操作*的右上角也出现了一个可以点击的小锁图标。 - -点击 **Authorize** 按钮,弹出授权表单,输入 `username` 与 `password` 及其它可选字段: - - - -!!! note "笔记" - - 目前,在表单中输入内容不会有任何反应,后文会介绍相关内容。 - -虽然此文档不是给前端最终用户使用的,但这个自动工具非常实用,可在文档中与所有 API 交互。 - -前端团队(可能就是开发者本人)可以使用本工具。 +=== "Python 3.9+" -第三方应用与系统也可以调用本工具。 + ```Python + !!! tip "提示" + ``` -开发者也可以用它来调试、检查、测试应用。 +=== "Python 3.6+" -## 密码流 + ```Python + 令牌只是用于验证用户的字符串 + ``` -现在,我们回过头来介绍这段代码的原理。 +=== "Python 3.6+ non-Annotated" -`Password` **流**是 OAuth2 定义的,用于处理安全与身份验证的方式(**流**)。 + !!! tip + Prefer to use the `Annotated` version if possible. -OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户身份。 + ```Python + {!../../../docs_src/security/tutorial001.py!} + ``` -但在本例中,**FastAPI** 应用会处理 API 与身份验证。 -下面,我们来看一下简化的运行流程: - -- 用户在前端输入 `username` 与`password`,并点击**回车** -- (用户浏览器中运行的)前端把 `username` 与`password` 发送至 API 中指定的 URL(使用 `tokenUrl="token"` 声明) -- API 检查 `username` 与`password`,并用令牌(`Token`) 响应(暂未实现此功能): - - 令牌只是用于验证用户的字符串 - - 一般来说,令牌会在一段时间后过期 - - 过时后,用户要再次登录 - - 这样一来,就算令牌被人窃取,风险也较低。因为它与永久密钥不同,**在绝大多数情况下**不会长期有效 -- 前端临时将令牌存储在某个位置 -- 用户点击前端,前往前端应用的其它部件 -- 前端需要从 API 中提取更多数据: - - 为指定的端点(Endpoint)进行身份验证 - - 因此,用 API 验证身份时,要发送值为 `Bearer` + 令牌的请求头 `Authorization` - - 假如令牌为 `foobar`,`Authorization` 请求头就是: `Bearer foobar` - -## **FastAPI** 的 `OAuth2PasswordBearer` - -**FastAPI** 提供了不同抽象级别的安全工具。 - -本例使用 **OAuth2** 的 **Password** 流以及 **Bearer** 令牌(`Token`)。为此要使用 `OAuth2PasswordBearer` 类。 - -!!! info "说明" - - `Bearer` 令牌不是唯一的选择。 - - 但它是最适合这个用例的方案。 - - 甚至可以说,它是适用于绝大多数用例的最佳方案,除非您是 OAuth2 的专家,知道为什么其它方案更合适。 - - 本例中,**FastAPI** 还提供了构建工具。 - -创建 `OAuth2PasswordBearer` 的类实例时,要传递 `tokenUrl` 参数。该参数包含客户端(用户浏览器中运行的前端) 的 URL,用于发送 `username` 与 `password`,并获取令牌。 - -```Python hl_lines="6" -{!../../../docs_src/security/tutorial001.py!} -``` - -!!! tip "提示" - - 在此,`tokenUrl="token"` 指向的是暂未创建的相对 URL `token`。这个相对 URL 相当于 `./token`。 - - 因为使用的是相对 URL,如果 API 位于 `https://example.com/`,则指向 `https://example.com/token`。但如果 API 位于 `https://example.com/api/v1/`,它指向的就是`https://example.com/api/v1/token`。 - - 使用相对 URL 非常重要,可以确保应用在遇到[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}这样的高级用例时,也能正常运行。 - -该参数不会创建端点或*路径操作*,但会声明客户端用来获取令牌的 URL `/token` 。此信息用于 OpenAPI 及 API 文档。 - -接下来,学习如何创建实际的路径操作。 - -!!! info "说明" - - 严苛的 **Pythonista** 可能不喜欢用 `tokenUrl` 这种命名风格代替 `token_url`。 - - 这种命名方式是因为要使用与 OpenAPI 规范中相同的名字。以便在深入校验安全方案时,能通过复制粘贴查找更多相关信息。 +## 运行 -`oauth2_scheme` 变量是 `OAuth2PasswordBearer` 的实例,也是**可调用项**。 +!!! 打开 API 文档: http://127.0.0.1:8000/docs。

-以如下方式调用: +
先安装 <a href="https://andrew-d.github.io/python-multipart/" class="external-link" target="_blank">`python-multipart`</a>。 安装命令: `pip install python-multipart`。
 
-```Python
-oauth2_scheme(some, parameters)
-```
+这是因为 **OAuth2** 使用**表单数据**发送 `username` 与 `password`。
+
-因此,`Depends` 可以调用 `oauth2_scheme` 变量。 +

+ 用下面的命令运行该示例: +

-### 使用 +
-接下来,使用 `Depends` 把 `oauth2_scheme` 传入依赖项。 +```console +$ uvicorn main:app --reload -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -该依赖项使用字符串(`str`)接收*路径操作函数*的参数 `token` 。 - -**FastAPI** 使用依赖项在 OpenAPI 概图(及 API 文档)中定义**安全方案**。 - -!!! info "技术细节" +
- **FastAPI** 使用(在依赖项中声明的)类 `OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,这是因为它继承自 `fastapi.security.oauth2.OAuth2`,而该类又是继承自`fastapi.security.base.SecurityBase`。 +

+ Check it +

+ +

+ Go to the interactive docs at: http://127.0.0.1:8000/docs. +

+ +

+ You will see something like this: +

+ +

+ +

+ +

+ !!! check "Authorize button!" + You already have a shiny new "Authorize" button. +

+ +
And your *path operation* has a little lock in the top-right corner that you can click.
+
+ +

+ 点击 Authorize 按钮,弹出授权表单,输入 usernamepassword 及其它可选字段: +

+ +

+ +

+ +

+ !!! note + It doesn't matter what you type in the form, it won't work yet. But we'll get there. +

+ +

+ 虽然此文档不是给前端最终用户使用的,但这个自动工具非常实用,可在文档中与所有 API 交互。 +

+ +

+ 前端团队(可能就是开发者本人)可以使用本工具。 +

+ +

+ 第三方应用与系统也可以调用本工具。 +

+ +

+ 开发者也可以用它来调试、检查、测试应用。 +

+ +

+ 密码流 +

+ +

+ Now let's go back a bit and understand what is all that. +

+ +

+ Password 是 OAuth2 定义的,用于处理安全与身份验证的方式()。 +

+ +

+ OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户身份。 +

+ +

+ 但在本例中,FastAPI 应用会处理 API 与身份验证。 +

+ +

+ So, let's review it from that simplified point of view: +

+ + + +

+ FastAPIOAuth2PasswordBearer +

+ +

+ FastAPI 提供了不同抽象级别的安全工具。 +

+ +

+ 本例使用 OAuth2Password 流以及 Bearer 令牌(Token)。 为此要使用 OAuth2PasswordBearer 类。 +

+ +

+ !!! `Bearer` 令牌不是唯一的选择。 +

+ +
但它是最适合这个用例的方案。
+
+甚至可以说,它是适用于绝大多数用例的最佳方案,除非您是 OAuth2 的专家,知道为什么其它方案更合适。
+
+本例中,**FastAPI** 还提供了构建工具。
+
+ +

+ 创建 OAuth2PasswordBearer 的类实例时,要传递 tokenUrl 参数。 该参数包含客户端(用户浏览器中运行的前端) 的 URL,用于发送 usernamepassword,并获取令牌。 +

+ +

+ === "Python 3.9+" + +

    !!! check "Authorize 按钮!"
+
+

+ +

+ === "Python 3.6+" + +

    !!! info "说明"
+
+

+ +

+ === "Python 3.6+ non-Annotated" +

+ +
!!! tip
+    Prefer to use the `Annotated` version if possible.
+
+ +
    {!../../../docs_src/security/tutorial001.py!}
+
+ +

+ !!! 在此,`tokenUrl="token"` 指向的是暂未创建的相对 URL `token`。 这个相对 URL 相当于 `./token`。 +

+ +
因为使用的是相对 URL,如果 API 位于 `https://example.com/`,则指向 `https://example.com/token`。 但如果 API 位于 `https://example.com/api/v1/`,它指向的就是`https://example.com/api/v1/token`。
+
+使用相对 URL 非常重要,可以确保应用在遇到[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}这样的高级用例时,也能正常运行。
+
+ +

+ 该参数不会创建端点或路径操作,但会声明客户端用来获取令牌的 URL /token 。 此信息用于 OpenAPI 及 API 文档。 +

+ +

+ 接下来,学习如何创建实际的路径操作。 +

+ +

+ !!! 严苛的 **Pythonista** 可能不喜欢用 `tokenUrl` 这种命名风格代替 `token_url`。 +

+ +
这种命名方式是因为要使用与 OpenAPI 规范中相同的名字。 以便在深入校验安全方案时,能通过复制粘贴查找更多相关信息。
+
+ +

+ oauth2_scheme 变量是 OAuth2PasswordBearer 的实例,也是可调用项。 +

+ +

+ It could be called as: +

+ +
oauth2_scheme(some, parameters)
+
+ +

+ 因此,Depends 可以调用 oauth2_scheme 变量。 +

+ +

+ 使用 +

+ +

+ 接下来,使用 Dependsoauth2_scheme 传入依赖项。 +

- 所有与 OpenAPI(及 API 文档)集成的安全工具都继承自 `SecurityBase`, 这就是为什么 **FastAPI** 能把它们集成至 OpenAPI 的原因。 +

+ === "Python 3.9+" + +

    !!! info "说明"
+
+

-## 实现的操作 +

+ === "Python 3.6+" + +

    !!! info "说明"
+
+

-FastAPI 校验请求中的 `Authorization` 请求头,核对请求头的值是不是由 `Bearer ` + 令牌组成, 并返回令牌字符串(`str`)。 +

+ === "Python 3.6+ non-Annotated" +

-如果没有找到 `Authorization` 请求头,或请求头的值不是 `Bearer ` + 令牌。FastAPI 直接返回 401 错误状态码(`UNAUTHORIZED`)。 +
!!! tip
+    Prefer to use the `Annotated` version if possible.
+
+ +
    {!../../../docs_src/security/tutorial001.py!}
+
+ +

+ 该依赖项使用字符串(str)接收路径操作函数的参数 token 。 +

+ +

+ FastAPI 使用依赖项在 OpenAPI 概图(及 API 文档)中定义安全方案。 +

+ +

+ !!! info "Technical Details" + FastAPI will know that it can use the class OAuth2PasswordBearer (declared in a dependency) to define the security scheme in OpenAPI because it inherits from fastapi.security.oauth2.OAuth2, which in turn inherits from fastapi.security.base.SecurityBase. +

-开发者不需要检查错误信息,查看令牌是否存在,只要该函数能够执行,函数中就会包含令牌字符串。 +
所有与 OpenAPI(及 API 文档)集成的安全工具都继承自 `SecurityBase`, 这就是为什么 **FastAPI** 能把它们集成至 OpenAPI 的原因。
+
-正如下图所示,API 文档已经包含了这项功能: +

+ What it does +

- +

+ FastAPI 校验请求中的 Authorization 请求头,核对请求头的值是不是由 Bearer + 令牌组成, 并返回令牌字符串(str)。 +

-目前,暂时还没有实现验证令牌是否有效的功能,不过后文很快就会介绍的。 +

+ 如果没有找到 Authorization 请求头,或请求头的值不是 Bearer + 令牌。 FastAPI 直接返回 401 错误状态码(UNAUTHORIZED)。 +

-## 小结 +

+ You don't even have to check if the token exists to return an error. You can be sure that if your function is executed, it will have a str in that token. +

-看到了吧,只要多写三四行代码,就可以添加基础的安全表单。 +

+ You can try it already in the interactive docs: +

+ +

+ +

+ +

+ We are not verifying the validity of the token yet, but that's a start already. +

+ +

+ Recap +

+ +

+ 看到了吧,只要多写三四行代码,就可以添加基础的安全表单。 +

diff --git a/docs/zh/docs/tutorial/security/get-current-user.md b/docs/zh/docs/tutorial/security/get-current-user.md index 477baec3afbf4..c503a34059ba5 100644 --- a/docs/zh/docs/tutorial/security/get-current-user.md +++ b/docs/zh/docs/tutorial/security/get-current-user.md @@ -2,24 +2,73 @@ 在上一章节中,(基于依赖项注入系统的)安全系统向*路径操作函数*提供了一个 `str` 类型的 `token`: -```Python hl_lines="10" -{!../../../docs_src/security/tutorial001.py!} -``` +=== "Python 3.9+" + + ```Python hl_lines="12" + 这些内容在下一章节。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11" + !!! tip + 你可能还记得请求体也是使用 Pydantic 模型来声明的。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="10" + {!../../../docs_src/security/tutorial002.py!} + ``` 但这还不是很实用。 让我们来使它返回当前用户给我们。 - ## 创建一个用户模型 首先,让我们来创建一个用户 Pydantic 模型。 与使用 Pydantic 声明请求体的方式相同,我们可以在其他任何地方使用它: -```Python hl_lines="5 12-16" -{!../../../docs_src/security/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="5 12-16" + {!> ../../../docs_src/security/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="5 12-16" + {!> ../../../docs_src/security/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="5 13-17" + {!> ../../../docs_src/security/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3 10-14" + {!../../../docs_src/security/tutorial002.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="5 12-16" + {!../../../docs_src/security/tutorial001.py!} + ``` ## 创建一个 `get_current_user` 依赖项 @@ -31,41 +80,139 @@ 与我们之前直接在路径操作中所做的相同,我们新的依赖项 `get_current_user` 将从子依赖项 `oauth2_scheme` 中接收一个 `str` 类型的 `token`: -```Python hl_lines="25" -{!../../../docs_src/security/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="25" + {!> ../../../docs_src/security/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="25" + {!> ../../../docs_src/security/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="26" + {!> ../../../docs_src/security/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="23" + {!../../../docs_src/security/tutorial002.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="25" + {!../../../docs_src/security/tutorial002.py!} + ``` ## 获取用户 `get_current_user` 将使用我们创建的(伪)工具函数,该函数接收 `str` 类型的令牌并返回我们的 Pydantic `User` 模型: -```Python hl_lines="19-22 26-27" -{!../../../docs_src/security/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="19-22 26-27" + {!> ../../../docs_src/security/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19-22 26-27" + {!> ../../../docs_src/security/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20-23 27-28" + {!> ../../../docs_src/security/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="17-20 24-25" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="19-22 26-27" + {!> ../../../docs_src/security/tutorial002.py!} + ``` ## 注入当前用户 因此现在我们可以在*路径操作*中使用 `get_current_user` 作为 `Depends` 了: -```Python hl_lines="31" -{!../../../docs_src/security/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="31" + {!> ../../../docs_src/security/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="31" + {!> ../../../docs_src/security/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="32" + !!! check + 这种依赖系统的设计方式使我们可以拥有不同的依赖项(不同的「可依赖类型」),并且它们都返回一个 User 模型。 + ``` + 模型。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="29" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="31" + {!> ../../../docs_src/security/tutorial002.py!} + ``` 注意我们将 `current_user` 的类型声明为 Pydantic 模型 `User`。 这将帮助我们在函数内部使用所有的代码补全和类型检查。 !!! tip - 你可能还记得请求体也是使用 Pydantic 模型来声明的。 + You might remember that request bodies are also declared with Pydantic models. 在这里 **FastAPI** 不会搞混,因为你正在使用的是 `Depends`。 !!! check - 这种依赖系统的设计方式使我们可以拥有不同的依赖项(不同的「可依赖类型」),并且它们都返回一个 `User` 模型。 + The way this dependency system is designed allows us to have different dependencies (different "dependables") that all return a `User` model. 我们并未被局限于只能有一个返回该类型数据的依赖项。 - ## 其他模型 现在你可以直接在*路径操作函数*中获取当前用户,并使用 `Depends` 在**依赖注入**级别处理安全性机制。 @@ -74,24 +221,23 @@ 但是你并未被限制只能使用某些特定的数据模型,类或类型。 -你想要在模型中使用 `id` 和 `email` 而不使用任何的 `username`?当然可以。你可以同样地使用这些工具。 +你想要在模型中使用 `id` 和 `email` 而不使用任何的 `username`? 当然可以。 你可以同样地使用这些工具。 -你只想要一个 `str`?或者仅仅一个 `dict`?还是直接一个数据库模型类的实例?它们的工作方式都是一样的。 +你只想要一个 `str`? 或者仅仅一个 `dict`? 还是直接一个数据库模型类的实例? 它们的工作方式都是一样的。 -实际上你没有用户登录到你的应用程序,而是只拥有访问令牌的机器人,程序或其他系统?再一次,它们的工作方式也是一样的。 - -尽管去使用你的应用程序所需要的任何模型,任何类,任何数据库。**FastAPI** 通过依赖项注入系统都帮你搞定。 +实际上你没有用户登录到你的应用程序,而是只拥有访问令牌的机器人,程序或其他系统? 再一次,它们的工作方式也是一样的。 +尽管去使用你的应用程序所需要的任何模型,任何类,任何数据库。 **FastAPI** 通过依赖项注入系统都帮你搞定。 ## 代码体积 -这个示例似乎看起来很冗长。考虑到我们在同一文件中混合了安全性,数据模型工具函数和路径操作等代码。 +这个示例似乎看起来很冗长。 考虑到我们在同一文件中混合了安全性,数据模型工具函数和路径操作等代码。 但关键的是。 安全性和依赖项注入内容只需要编写一次。 -你可以根据需要使其变得很复杂。而且只需要在一个地方写一次。但仍然具备所有的灵活性。 +你可以根据需要使其变得很复杂。 而且只需要在一个地方写一次。 但仍然具备所有的灵活性。 但是,你可以有无数个使用同一安全系统的端点(*路径操作*)。 @@ -99,11 +245,43 @@ 所有的这无数个*路径操作*甚至可以小到只需 3 行代码: -```Python hl_lines="30-32" -{!../../../docs_src/security/tutorial002.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="30-32" + 总结 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="30-32" + {!> ../../../docs_src/security/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="31-33" + {!> ../../../docs_src/security/tutorial002_an.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="28-30" + {!> ../../../docs_src/security/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="30-32" + {!../../../docs_src/security/tutorial002.py!} + ``` -## 总结 +## Recap 现在你可以直接在*路径操作函数*中获取当前用户。 @@ -111,4 +289,4 @@ 我们只需要再为用户/客户端添加一个真正发送 `username` 和 `password` 的*路径操作*。 -这些内容在下一章节。 +That comes next. diff --git a/docs/zh/docs/tutorial/security/index.md b/docs/zh/docs/tutorial/security/index.md index 0595f5f636129..b4b26eb0e49a9 100644 --- a/docs/zh/docs/tutorial/security/index.md +++ b/docs/zh/docs/tutorial/security/index.md @@ -10,7 +10,7 @@ 但首先,让我们来看一些小的概念。 -## 没有时间? +## In a hurry? 如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到下一章。 @@ -32,7 +32,7 @@ OAuth2是一个规范,它定义了几种处理身份认证和授权的方法 OAuth2 没有指定如何加密通信,它期望你为应用程序使用 HTTPS 进行通信。 -!!! tip +!!! !!! tip 在有关**部署**的章节中,你将了解如何使用 Traefik 和 Let's Encrypt 免费设置 HTTPS。 @@ -44,11 +44,11 @@ OpenID Connect 是另一个基于 **OAuth2** 的规范。 例如,Google 登录使用 OpenID Connect(底层使用OAuth2)。 -但是 Facebook 登录不支持 OpenID Connect。它具有自己的 OAuth2 风格。 +但是 Facebook 登录不支持 OpenID Connect。 它具有自己的 OAuth2 风格。 ### OpenID(非「OpenID Connect」) -还有一个「OpenID」规范。它试图解决与 **OpenID Connect** 相同的问题,但它不是基于 OAuth2。 +还有一个「OpenID」规范。 它试图解决与 **OpenID Connect** 相同的问题,但它不是基于 OAuth2。 因此,它是一个完整的附加系统。 @@ -70,14 +70,14 @@ OpenAPI 定义了以下安全方案: * `apiKey`:一个特定于应用程序的密钥,可以来自: * 查询参数。 - * 请求头。 + * A header. * cookie。 * `http`:标准的 HTTP 身份认证系统,包括: - * `bearer`: 一个值为 `Bearer` 加令牌字符串的 `Authorization` 请求头。这是从 OAuth2 继承的。 + * `bearer`: 一个值为 `Bearer` 加令牌字符串的 `Authorization` 请求头。 这是从 OAuth2 继承的。 * HTTP Basic 认证方式。 * HTTP Digest,等等。 * `oauth2`:所有的 OAuth2 处理安全性的方式(称为「流程」)。 - *以下几种流程适合构建 OAuth 2.0 身份认证的提供者(例如 Google,Facebook,Twitter,GitHub 等): + * *以下几种流程适合构建 OAuth 2.0 身份认证的提供者(例如 Google,Facebook,Twitter,GitHub 等): * `implicit` * `clientCredentials` * `authorizationCode` * `implicit` * `clientCredentials` * `authorizationCode` @@ -87,7 +87,7 @@ OpenAPI 定义了以下安全方案: * 此自动发现机制是 OpenID Connect 规范中定义的内容。 -!!! tip +!!! !!! tip 集成其他身份认证/授权提供者(例如Google,Facebook,Twitter,GitHub等)也是可能的,而且较为容易。 最复杂的问题是创建一个像这样的身份认证/授权提供程序,但是 **FastAPI** 为你提供了轻松完成任务的工具,同时为你解决了重活。 diff --git a/docs/zh/docs/tutorial/security/oauth2-jwt.md b/docs/zh/docs/tutorial/security/oauth2-jwt.md index 054198545ef8e..7e126a75d93d3 100644 --- a/docs/zh/docs/tutorial/security/oauth2-jwt.md +++ b/docs/zh/docs/tutorial/security/oauth2-jwt.md @@ -4,25 +4,25 @@ 本章的示例代码真正实现了在应用的数据库中保存哈希密码等功能。 -接下来,我们紧接上一章,继续完善安全机制。 +We are going to start from where we left in the previous chapter and increment it. ## JWT 简介 JWT 即**JSON 网络令牌**(JSON Web Tokens)。 -JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。JWT 的内容如下所示: +JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。 It looks like this: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` -JWT 字符串没有加密,任何人都能用它恢复原始信息。 +It is not encrypted, so, anyone could recover the information from the contents. -但 JWT 使用了签名机制。接受令牌时,可以用签名校验令牌。 +But it's signed. So, when you receive a token that you emitted, you can verify that you actually emitted it. -使用 JWT 创建有效期为一周的令牌。第二天,用户持令牌再次访问时,仍为登录状态。 +使用 JWT 创建有效期为一周的令牌。 第二天,用户持令牌再次访问时,仍为登录状态。 -令牌于一周后过期,届时,用户身份验证就会失败。只有再次登录,才能获得新的令牌。如果用户(或第三方)篡改令牌的过期时间,因为签名不匹配会导致身份验证失败。 +After a week, the token will be expired and the user will not be authorized and will have to sign in again to get a new token. 如果用户(或第三方)篡改令牌的过期时间,因为签名不匹配会导致身份验证失败。 如需深入了解 JWT 令牌,了解它的工作方式,请参阅 https://jwt.io。 @@ -44,15 +44,13 @@ $ pip install python-jose[cryptography] 本教程推荐的后端是:pyca/cryptography。 -!!! tip "提示" - - 本教程以前使用 PyJWT。 +!!! 本教程以前使用 <a href="https://pyjwt.readthedocs.io/" class="external-link" target="_blank">PyJWT</a>。 但后来换成了 Python-jose,因为 Python-jose 支持 PyJWT 的所有功能,还支持与其它工具集成时可能会用到的一些其它功能。 ## 密码哈希 -**哈希**是指把特定内容(本例中为密码)转换为乱码形式的字节序列(其实就是字符串)。 +"Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish. 每次传入完全相同的内容时(比如,完全相同的密码),返回的都是完全相同的乱码。 @@ -60,7 +58,7 @@ $ pip install python-jose[cryptography] ### 为什么使用密码哈希 -原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。 +If your database is stolen, the thief won't have your users' plaintext passwords, only the hashes. 这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大)。 @@ -70,7 +68,7 @@ Passlib 是处理密码哈希的 Python 包。 它支持很多安全哈希算法及配套工具。 -本教程推荐的算法是 **Bcrypt**。 +The recommended algorithm is "Bcrypt". 因此,请先安装附带 Bcrypt 的 PassLib: @@ -84,41 +82,68 @@ $ pip install passlib[bcrypt] -!!! tip "提示" - - `passlib` 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。 - - 例如,把 Django 应用的数据共享给 FastAPI 应用的数据库。或利用同一个数据库,可以逐步把应用从 Django 迁移到 FastAPI。 +!!! `passlib` 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。 + 例如,把 Django 应用的数据共享给 FastAPI 应用的数据库。 或利用同一个数据库,可以逐步把应用从 Django 迁移到 FastAPI。 + 并且,用户可以同时从 Django 应用或 FastAPI 应用登录。 ## 密码哈希与校验 从 `passlib` 导入所需工具。 -创建用于密码哈希和身份校验的 PassLib **上下文**。 +Create a PassLib "context". This is what will be used to hash and verify passwords. -!!! tip "提示" - - PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验的已弃用旧算法等。 +!!! PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验的已弃用旧算法等。 例如,用它读取和校验其它系统(如 Django)生成的密码,但要使用其它算法,如 Bcrypt,生成新的哈希密码。 - + 同时,这些功能都是兼容的。 接下来,创建三个工具函数,其中一个函数用于哈希用户的密码。 -第一个函数用于校验接收的密码是否匹配存储的哈希值。 +And another utility to verify if a received password matches the hash stored. 第三个函数用于身份验证,并返回用户。 -```Python hl_lines="7 48 55-56 59-60 69-75" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.10+" -!!! note "笔记" + ```Python hl_lines="7 48 55-56 59-60 69-75" + !!! tip "提示" + ``` - 查看新的(伪)数据库 `fake_users_db`,就能看到哈希后的密码:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`。 +=== "Python 3.9+" + + ```Python hl_lines="7 48 55-56 59-60 69-75" + 接下来,我们紧接上一章,继续完善安全机制。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="7 49 56-57 60-61 70-76" + 而且,FastAPI 还提供了一些工具,在不影响灵活、稳定和安全的前提下,尽可能地简化安全机制。 + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="6 47 54-55 58-59 68-74" + 原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="7 48 55-56 59-60 69-75" + {!../../../docs_src/security/tutorial004.py!} + ``` + +!!! note + If you check the new (fake) database `fake_users_db`, you will see how the hashed password looks like now: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. ## 处理 JWT 令牌 @@ -148,9 +173,41 @@ $ openssl rand -hex 32 创建生成新的访问令牌的工具函数。 -```Python hl_lines="6 12-14 28-30 78-86" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="6 12-14 28-30 78-86" + 注意,代码中没有明文密码**`secret`**,只保存了它的哈希值。 + ``` + +=== "Python 3.9+" + + ```Python hl_lines="6 12-14 28-30 78-86" + FastAPI 还支持以相对简单的方式,使用 OAuth2 等安全、标准的协议。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="6 13-15 29-31 79-87" + 注意,请求中 `Authorization` 响应头的值以 `Bearer` 开头。 + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="5 11-13 27-29 77-85" + 第一个函数用于校验接收的密码是否匹配存储的哈希值。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="6 12-14 28-30 78-86" + {!../../../docs_src/security/tutorial004.py!} + ``` ## 更新依赖项 @@ -160,9 +217,41 @@ $ openssl rand -hex 32 如果令牌无效,则直接返回 HTTP 错误。 -```Python hl_lines="89-106" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="89-106" + !!! tip "提示" + ``` + +=== "Python 3.9+" + + ```Python hl_lines="89-106" + 本教程推荐的算法是 Bcrypt。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="90-107" + 开发者可以灵活选择最适合项目的安全机制。 + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="88-105" + 创建用于密码哈希和身份校验的 PassLib 上下文。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="89-106" + {!../../../docs_src/security/tutorial004.py!} + ``` ## 更新 `/token` *路径操作* @@ -170,9 +259,43 @@ $ openssl rand -hex 32 创建并返回真正的 JWT 访问令牌。 -```Python hl_lines="115-128" -{!../../../docs_src/security/tutorial004.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="117-132" + !!! tip "提示" + ``` + +=== "Python 3.9+" + + ```Python hl_lines="117-132" + !!! check "检查" + ``` + +=== "Python 3.6+" + + ```Python hl_lines="118-133" + OAuth2 支持scopes(作用域)。 + ``` +(作用域)。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="114-127" + 查看新的(伪)数据库 `fake_users_db`,就能看到哈希后的密码:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="115-128" + {!../../../docs_src/security/tutorial004.py!} + ``` ### JWT `sub` 的技术细节 @@ -182,17 +305,17 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 除了识别用户与许可用户在 API 上直接执行操作之外,JWT 还可能用于其它事情。 -例如,识别**汽车**或**博客**。 +For example, you could identify a "car" or a "blog post". -接着,为实体添加权限,比如**驾驶**(汽车)或**编辑**(博客)。 +Then you could add permissions about that entity, like "drive" (for the car) or "edit" (for the blog). -然后,把 JWT 令牌交给用户(或机器人),他们就可以执行驾驶汽车,或编辑博客等操作。无需注册账户,只要有 API 生成的 JWT 令牌就可以。 +JWT 字符串没有加密,任何人都能用它恢复原始信息。 同理,JWT 可以用于更复杂的场景。 在这些情况下,多个实体的 ID 可能是相同的,以 ID `foo` 为例,用户的 ID 是 `foo`,车的 ID 是 `foo`,博客的 ID 也是 `foo`。 -为了避免 ID 冲突,在给用户创建 JWT 令牌时,可以为 `sub` 键的值加上前缀,例如 `username:`。因此,在本例中,`sub` 的值可以是:`username:johndoe`。 +为了避免 ID 冲突,在给用户创建 JWT 令牌时,可以为 `sub` 键的值加上前缀,例如 `username:`。 因此,在本例中,`sub` 的值可以是:`username:johndoe`。 注意,划重点,`sub` 键在整个应用中应该只有一个唯一的标识符,而且应该是字符串。 @@ -202,7 +325,7 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 可以看到如下用户界面: - + 用与上一章同样的方式实现应用授权。 @@ -210,11 +333,10 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 用户名: `johndoe` 密码: `secret` -!!! check "检查" +!!! check + Notice that nowhere in the code is the plaintext password "`secret`", we only have the hashed version. - 注意,代码中没有明文密码**`secret`**,只保存了它的哈希值。 - - + 调用 `/users/me/` 端点,收到下面的响应: @@ -227,44 +349,43 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 } ``` - + 打开浏览器的开发者工具,查看数据是怎么发送的,而且数据里只包含了令牌,只有验证用户的第一个请求才发送密码,并获取访问令牌,但之后不会再发送密码: - - -!!! note "笔记" + - 注意,请求中 `Authorization` 响应头的值以 `Bearer` 开头。 +!!! note + Notice the header `Authorization`, with a value that starts with `Bearer`. ## `scopes` 高级用法 -OAuth2 支持**`scopes`**(作用域)。 +OAuth2 has the notion of "scopes". -**`scopes`**为 JWT 令牌添加指定权限。 +You can use them to add a specific set of permissions to a JWT token. 让持有令牌的用户或第三方在指定限制条件下与 API 交互。 **高级用户指南**中将介绍如何使用 `scopes`,及如何把 `scopes` 集成至 **FastAPI**。 -## 小结 +## Recap 至此,您可以使用 OAuth2 和 JWT 等标准配置安全的 **FastAPI** 应用。 几乎在所有框架中,处理安全问题很快都会变得非常复杂。 -有些包为了简化安全流,不得不在数据模型、数据库和功能上做出妥协。而有些过于简化的软件包其实存在了安全隐患。 +有些包为了简化安全流,不得不在数据模型、数据库和功能上做出妥协。 而有些过于简化的软件包其实存在了安全隐患。 --- **FastAPI** 不向任何数据库、数据模型或工具做妥协。 -开发者可以灵活选择最适合项目的安全机制。 +It gives you all the flexibility to choose the ones that fit your project the best. 还可以直接使用 `passlib` 和 `python-jose` 等维护良好、使用广泛的包,这是因为 **FastAPI** 不需要任何复杂机制,就能集成外部的包。 -而且,**FastAPI** 还提供了一些工具,在不影响灵活、稳定和安全的前提下,尽可能地简化安全机制。 +But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness, or security. -**FastAPI** 还支持以相对简单的方式,使用 OAuth2 等安全、标准的协议。 +And you can use and implement secure, standard protocols, like OAuth2 in a relatively simple way. -**高级用户指南**中详细介绍了 OAuth2**`scopes`**的内容,遵循同样的标准,实现更精密的权限系统。OAuth2 的作用域是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制,让用户授权第三方应用与 API 交互。 +**高级用户指南**中详细介绍了 OAuth2**`scopes`**的内容,遵循同样的标准,实现更精密的权限系统。 OAuth2 的作用域是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制,让用户授权第三方应用与 API 交互。 diff --git a/docs/zh/docs/tutorial/security/simple-oauth2.md b/docs/zh/docs/tutorial/security/simple-oauth2.md index 276f3d63b69f9..7b782c9e1bd33 100644 --- a/docs/zh/docs/tutorial/security/simple-oauth2.md +++ b/docs/zh/docs/tutorial/security/simple-oauth2.md @@ -8,7 +8,7 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户端/用户必须将 `username` 和 `password` 字段作为表单数据发送。 -而且规范明确了字段必须这样命名。因此 `user-name` 或 `email` 是行不通的。 +而且规范明确了字段必须这样命名。 因此 `user-name` 或 `email` 是行不通的。 不过不用担心,你可以在前端按照你的想法将它展示给最终用户。 @@ -24,7 +24,7 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 这个表单字段的名称为 `scope`(单数形式),但实际上它是一个由空格分隔的「作用域」组成的长字符串。 -每个「作用域」只是一个字符串(中间没有空格)。 +Each "scope" is just a string (without spaces). 它们通常用于声明特定的安全权限,例如: @@ -33,12 +33,12 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 * Google 使用了 `https://www.googleapis.com/auth/drive` 。 !!! info - 在 OAuth2 中「作用域」只是一个声明所需特定权限的字符串。 + In OAuth2 a "scope" is just a string that declares a specific permission required. 它有没有 `:` 这样的其他字符或者是不是 URL 都没有关系。 - + 这些细节是具体的实现。 - + 对 OAuth2 来说它们就只是字符串而已。 ## 获取 `username` 和 `password` 的代码 @@ -49,9 +49,45 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 首先,导入 `OAuth2PasswordRequestForm`,然后在 `token` 的*路径操作*中通过 `Depends` 将其作为依赖项使用。 -```Python hl_lines="4 76" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="4 78" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 78" + !!! tip + 在下一章中,你将看到一个真实的安全实现,使用了哈希密码和 JWT 令牌。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 79" + !!! info + OAuth2PasswordRequestForm 并不像 OAuth2PasswordBearer 一样是 FastAPI 的一个特殊的类。 + ``` + 并不像 OAuth2PasswordBearer 一样是 FastAPI 的一个特殊的类。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="2 74" + 如果没有这个用户,我们将返回一个错误消息,提示「用户名或密码错误」。 + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="4 76" + {!../../../docs_src/security/tutorial003.py!} + ``` `OAuth2PasswordRequestForm` 是一个类依赖项,声明了如下的请求表单: @@ -61,7 +97,7 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 * 一个可选的 `grant_type`. !!! tip - OAuth2 规范实际上*要求* `grant_type` 字段使用一个固定的值 `password`,但是 `OAuth2PasswordRequestForm` 没有作强制约束。 + The OAuth2 spec actually *requires* a field `grant_type` with a fixed value of `password`, but `OAuth2PasswordRequestForm` doesn't enforce it. 如果你需要强制要求这一点,请使用 `OAuth2PasswordRequestFormStrict` 而不是 `OAuth2PasswordRequestForm`。 @@ -69,30 +105,66 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 * 一个可选的 `client_secret`(我们的示例不需要它)。 !!! info - `OAuth2PasswordRequestForm` 并不像 `OAuth2PasswordBearer` 一样是 FastAPI 的一个特殊的类。 - - `OAuth2PasswordBearer` 使得 **FastAPI** 明白它是一个安全方案。所以它得以通过这种方式添加到 OpenAPI 中。 + The `OAuth2PasswordRequestForm` is not a special class for **FastAPI** as is `OAuth2PasswordBearer`. + `OAuth2PasswordBearer` 使得 **FastAPI** 明白它是一个安全方案。 所以它得以通过这种方式添加到 OpenAPI 中。 + 但 `OAuth2PasswordRequestForm` 只是一个你可以自己编写的类依赖项,或者你也可以直接声明 `Form` 参数。 - + 但是由于这是一种常见的使用场景,因此 FastAPI 出于简便直接提供了它。 ### 使用表单数据 !!! tip - 类依赖项 `OAuth2PasswordRequestForm` 的实例不会有用空格分隔的长字符串属性 `scope`,而是具有一个 `scopes` 属性,该属性将包含实际被发送的每个作用域字符串组成的列表。 + The instance of the dependency class `OAuth2PasswordRequestForm` won't have an attribute `scope` with the long string separated by spaces, instead, it will have a `scopes` attribute with the actual list of strings for each scope sent. 在此示例中我们没有使用 `scopes`,但如果你需要的话可以使用该功能。 现在,使用表单字段中的 `username` 从(伪)数据库中获取用户数据。 -如果没有这个用户,我们将返回一个错误消息,提示「用户名或密码错误」。 +如果密码不匹配,我们将返回同一个错误。 对于这个错误,我们使用 `HTTPException` 异常: -```Python hl_lines="3 77-79" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="3 79-81" + !!! info + 我们在此处返回的值为 Bearer 的额外响应头 WWW-Authenticate 也是规范的一部分。 + ``` + 的额外响应头 WWW-Authenticate 也是规范的一部分。 + + +=== "Python 3.9+" + + ```Python hl_lines="3 79-81" + https://fastapi.tiangolo.com/img/tutorial/security/image05.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3 80-82" + !!! info + 在 OAuth2 中「作用域」只是一个声明所需特定权限的字符串。 + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="1 75-77" + {!../../../docs_src/security/tutorial003.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="3 77-79" + {!../../../docs_src/security/tutorial003.py!} + ``` ### 校验密码 @@ -102,7 +174,7 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 永远不要保存明文密码,因此,我们将使用(伪)哈希密码系统。 -如果密码不匹配,我们将返回同一个错误。 +If the passwords don't match, we return the same error. #### 哈希密码 @@ -118,9 +190,44 @@ OAuth2 规定在使用(我们打算用的)「password 流程」时,客户 因此,小偷将无法尝试在另一个系统中使用这些相同的密码(由于许多用户在任何地方都使用相同的密码,因此这很危险)。 -```Python hl_lines="80-83" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="82-85" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="82-85" + https://fastapi.tiangolo.com/img/tutorial/security/image04.png + ``` + +=== "Python 3.6+" + + ```Python hl_lines="83-86" + !!! tip + OAuth2 规范实际上要求 grant_type 字段使用一个固定的值 password,但是 OAuth2PasswordRequestForm 没有作强制约束。 + ``` + 字段使用一个固定的值 password,但是 OAuth2PasswordRequestForm 没有作强制约束。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="78-81" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="80-83" + {!../../../docs_src/security/tutorial003.py!} + ``` #### 关于 `**user_dict` @@ -139,34 +246,69 @@ UserInDB( ``` !!! info - 有关 `user_dict` 的更完整说明,请参阅[**额外的模型**文档](../extra-models.md#about-user_indict){.internal-link target=_blank}。 + For a more complete explanation of `**user_dict` check back in [the documentation for **Extra Models**](../extra-models.md#about-user_indict){.internal-link target=_blank}. ## 返回令牌 `token` 端点的响应必须是一个 JSON 对象。 -它应该有一个 `token_type`。在我们的例子中,由于我们使用的是「Bearer」令牌,因此令牌类型应为「`bearer`」。 +它应该有一个 `token_type`。 在我们的例子中,由于我们使用的是「Bearer」令牌,因此令牌类型应为「`bearer`」。 并且还应该有一个 `access_token` 字段,它是一个包含我们的访问令牌的字符串。 对于这个简单的示例,我们将极其不安全地返回相同的 `username` 作为令牌。 !!! tip - 在下一章中,你将看到一个真实的安全实现,使用了哈希密码和 JWT 令牌。 + In the next chapter, you will see a real secure implementation, with password hashing and JWT tokens. 但现在,让我们仅关注我们需要的特定细节。 -```Python hl_lines="85" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="87" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="87" + {!> ../../../docs_src/security/tutorial003_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="88" + !!! info + 有关 user_dict 的更完整说明,请参阅额外的模型文档{.internal-link target=_blank}。 + ``` + 的更完整说明,请参阅[**额外的模型**文档](../extra-models.md#about-user_indict){.internal-link target=_blank}。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="83" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="85" + {!../../../docs_src/security/tutorial003.py!} + ``` !!! tip - 根据规范,你应该像本示例一样,返回一个带有 `access_token` 和 `token_type` 的 JSON。 + By the spec, you should return a JSON with an `access_token` and a `token_type`, the same as in this example. 这是你必须在代码中自行完成的工作,并且要确保使用了这些 JSON 字段。 - + 这几乎是唯一的你需要自己记住并正确地执行以符合规范的事情。 - + 其余的,**FastAPI** 都会为你处理。 ## 更新依赖项 @@ -181,26 +323,64 @@ UserInDB( 因此,在我们的端点中,只有当用户存在,身份认证通过且处于启用状态时,我们才能获得该用户: -```Python hl_lines="58-67 69-72 90" -{!../../../docs_src/security/tutorial003.py!} -``` +=== "Python 3.10+" + + ```Python hl_lines="58-66 69-74 94" + {!> ../../../docs_src/security/tutorial003_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="58-66 69-74 94" + 每个「作用域」只是一个字符串(中间没有空格)。 + ``` + +=== "Python 3.6+" + + ```Python hl_lines="59-67 70-75 95" + !!! tip + 类依赖项 OAuth2PasswordRequestForm 的实例不会有用空格分隔的长字符串属性 scope,而是具有一个 scopes 属性,该属性将包含实际被发送的每个作用域字符串组成的列表。 + ``` + 的实例不会有用空格分隔的长字符串属性 scope,而是具有一个 scopes 属性,该属性将包含实际被发送的每个作用域字符串组成的列表。 + + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="56-64 67-70 88" + {!> ../../../docs_src/security/tutorial003_py310.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="58-66 69-72 90" + !!! tip + 根据规范,你应该像本示例一样,返回一个带有 access_tokentoken_type 的 JSON。 + ``` + 和 token_type 的 JSON。 + !!! info - 我们在此处返回的值为 `Bearer` 的额外响应头 `WWW-Authenticate` 也是规范的一部分。 + The additional header `WWW-Authenticate` with value `Bearer` we are returning here is also part of the spec. 任何的 401「未认证」HTTP(错误)状态码都应该返回 `WWW-Authenticate` 响应头。 - + 对于 bearer 令牌(我们的例子),该响应头的值应为 `Bearer`。 - + 实际上你可以忽略这个额外的响应头,不会有什么问题。 - + 但此处提供了它以符合规范。 - + 而且,(现在或将来)可能会有工具期望得到并使用它,然后对你或你的用户有用处。 - + 这就是遵循标准的好处... -## 实际效果 +## See it in action 打开交互式文档:http://127.0.0.1:8000/docs。 @@ -214,11 +394,11 @@ UserInDB( 密码:`secret` - + 在系统中进行身份认证后,你将看到: - + ### 获取本人的用户数据 @@ -236,7 +416,7 @@ UserInDB( } ``` - + 如果你点击锁定图标并注销,然后再次尝试同一操作,则会得到 HTTP 401 错误: @@ -264,7 +444,7 @@ UserInDB( } ``` -## 总结 +## Recap 现在你掌握了为你的 API 实现一个基于 `username` 和 `password` 的完整安全系统的工具。 diff --git a/docs/zh/docs/tutorial/sql-databases.md b/docs/zh/docs/tutorial/sql-databases.md index 482588f94d7ec..53de41a7bd3c2 100644 --- a/docs/zh/docs/tutorial/sql-databases.md +++ b/docs/zh/docs/tutorial/sql-databases.md @@ -1,5 +1,12 @@ # SQL (关系型) 数据库 +!!! info + These docs are about to be updated. 🎉 + + The current version assumes Pydantic v1, and SQLAlchemy versions less than 2.0. + + The new docs will include Pydantic v2 and will use SQLModel (which is also based on SQLAlchemy) once it is updated to use Pydantic v2 as well. + **FastAPI**不需要你使用SQL(关系型)数据库。 但是您可以使用任何您想要的关系型数据库。 @@ -14,17 +21,19 @@ * Oracle * Microsoft SQL Server,等等其它数据库 -在此示例中,我们将使用**SQLite**,因为它使用单个文件并且 在Python中具有集成支持。因此,您可以复制此示例并按原样来运行它。 +在此示例中,我们将使用**SQLite**,因为它使用单个文件并且 在Python中具有集成支持。 因此,您可以复制此示例并按原样来运行它。 稍后,对于您的产品级别的应用程序,您可能会要使用像**PostgreSQL**这样的数据库服务器。 -!!! tip +!!! !!! tip 这儿有一个**FastAPI**和**PostgreSQL**的官方项目生成器,全部基于**Docker**,包括前端和更多工具:https://github.com/tiangolo/full-stack-fastapi-postgresql -!!! note - 请注意,大部分代码是`SQLAlchemy`的标准代码,您可以用于任何框架。FastAPI特定的代码和往常一样少。 +!!! !!! note + 请注意,大部分代码是`SQLAlchemy`的标准代码,您可以用于任何框架。 -## ORMs(对象关系映射) + FastAPI特定的代码和往常一样少。 + +## ORMs **FastAPI**可与任何数据库在任何样式的库中一起与 数据库进行通信。 @@ -36,19 +45,19 @@ ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间 例如,一个类`Pet`可以表示一个 SQL 表`pets`。 -该类的每个*实例对象都代表数据库中的一行数据。* +And each *instance* object of that class represents a row in the database. -又例如,一个对象`orion_cat`(`Pet`的一个实例)可以有一个属性`orion_cat.type`, 对标数据库中的`type`列。并且该属性的值可以是其它,例如`"cat"`。 +又例如,一个对象`orion_cat`(`Pet`的一个实例)可以有一个属性`orion_cat.type`, 对标数据库中的`type`列。 并且该属性的值可以是其它,例如`"cat"`。 这些 ORM 还具有在表或实体之间建立关系的工具(比如创建多表关系)。 -这样,您还可以拥有一个属性`orion_cat.owner`,它包含该宠物所有者的数据,这些数据取自另外一个表。 - 因此,`orion_cat.owner.name`可能是该宠物主人的姓名(来自表`owners`中的列`name`)。 +这样,您还可以拥有一个属性`orion_cat.owner`,它包含该宠物所有者的数据,这些数据取自另外一个表。 + 它可能有一个像`"Arquilian"`(一种业务逻辑)。 -当您尝试从您的宠物对象访问它时,ORM 将完成所有工作以从相应的表*所有者那里再获取信息。* +当您尝试从您的宠物对象访问它时,ORM 将完成所有工作以从相应的表*所有者那里再获取信息。

常见的 ORM 例如:Django-ORM(Django 框架的一部分)、SQLAlchemy ORM(SQLAlchemy 的一部分,独立于框架)和 Peewee(独立于框架)等。 @@ -56,7 +65,7 @@ ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间 以类似的方式,您也可以使用任何其他 ORM。 -!!! tip +!!! !!! tip 在文档中也有一篇使用 Peewee 的等效的文章。 ## 文件结构 @@ -65,6 +74,7 @@ ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间 ``` . +. └── sql_app ├── __init__.py ├── crud.py @@ -78,9 +88,24 @@ ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间 现在让我们看看每个文件/模块的作用。 +## Install `SQLAlchemy` + +!!! tip + SQLAlchemy 模型`User`包含一个`hashed_password`,它应该是一个包含散列的安全密码。 + +
+ +```console +$ pip install sqlalchemy + +---> 100% +``` + +
+ ## 创建 SQLAlchemy 部件 -让我们涉及到文件`sql_app/database.py`。 +该文件将位于文件中的同一目录中`sql_app.db`。 ### 导入 SQLAlchemy 部件 @@ -96,7 +121,7 @@ ORM 具有在代码和数据库表(“*关系型”)中的**对象**之间 在这个例子中,我们正在“连接”到一个 SQLite 数据库(用 SQLite 数据库打开一个文件)。 -该文件将位于文件中的同一目录中`sql_app.db`。 +让我们涉及到文件`sql_app/database.py`。 这就是为什么最后一部分是`./sql_app.db`. @@ -108,7 +133,7 @@ SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" ...并根据您的数据库数据和相关凭据(也适用于 MySQL、MariaDB 或任何其他)对其进行调整。 -!!! tip +!!! !!! tip 如果您想使用不同的数据库,这是就是您必须修改的地方。 @@ -124,27 +149,27 @@ SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" #### 注意 -参数: +The argument: ```Python connect_args={"check_same_thread": False} ``` -...仅用于`SQLite`,在其他数据库不需要它。 +...仅用于`SQLite`,在其他数据库不需要它。 It's not needed for other databases. -!!! info "技术细节" +!!! !!! info "技术细节" 默认情况下,SQLite 只允许一个线程与其通信,假设有多个线程的话,也只将处理一个独立的请求。 - + 这是为了防止意外地为不同的事物(不同的请求)共享相同的连接。 - + 但是在 FastAPI 中,普遍使用def函数,多个线程可以为同一个请求与数据库交互,所以我们需要使用`connect_args={"check_same_thread": False}`来让SQLite允许这样。 - + 此外,我们将确保每个请求都在依赖项中获得自己的数据库连接会话,因此不需要该默认机制。 ### 创建一个`SessionLocal`类 -每个实例`SessionLocal`都会是一个数据库会话。当然该类本身还不是数据库会话。 +每个实例`SessionLocal`都会是一个数据库会话。 当然该类本身还不是数据库会话。 但是一旦我们创建了一个`SessionLocal`类的实例,这个实例将是实际的数据库会话。 @@ -168,7 +193,7 @@ connect_args={"check_same_thread": False} {!../../../docs_src/sql_databases/sql_app/database.py!} ``` -## 创建数据库模型 +## 创建 Pydantic 模型 现在让我们看看文件`sql_app/models.py`。 @@ -176,8 +201,7 @@ connect_args={"check_same_thread": False} 我们将使用我们之前创建的`Base`类来创建 SQLAlchemy 模型。 -!!! tip - SQLAlchemy 使用的“**模型**”这个术语 来指代与数据库交互的这些类和实例。 +!!! 使用您的数据创建一个 SQLAlchemy 模型*实例。

而 Pydantic 也使用“模型”这个术语 来指代不同的东西,即数据验证、转换以及文档类和实例。 @@ -223,17 +247,16 @@ connect_args={"check_same_thread": False} 当您访问`my_user.items`时,SQLAlchemy 实际上会从`items`表中的获取一批记录并在此处填充进去。 -同样,当访问 Item中的属性`owner`时,它将包含表中的`User`SQLAlchemy 模型`users`。使用`owner_id`属性/列及其外键来了解要从`users`表中获取哪条记录。 +同样,当访问 Item中的属性`owner`时,它将包含表中的`User`SQLAlchemy 模型`users`。 使用`owner_id`属性/列及其外键来了解要从`users`表中获取哪条记录。 -## 创建 Pydantic 模型 +## 创建数据库模型 现在让我们查看一下文件`sql_app/schemas.py`。 -!!! tip - 为了避免 SQLAlchemy*模型*和 Pydantic*模型*之间的混淆,我们将有`models.py`(SQLAlchemy 模型的文件)和`schemas.py`( Pydantic 模型的文件)。 +!!! 现在创建当从 API 返回数据时、将在读取数据时使用的Pydantic*模型(schemas)。

这些 Pydantic 模型或多或少地定义了一个“schema”(一个有效的数据形状)。 - + 因此,这将帮助我们在使用两者时避免混淆。 ### 创建初始 Pydantic*模型*/模式 @@ -282,7 +305,8 @@ name: str ### 创建用于读取/返回的Pydantic*模型/模式* -现在创建当从 API 返回数据时、将在读取数据时使用的Pydantic*模型(schemas)。* +!!! tip + 请注意,读取用户(从 API 返回)时将使用不包括`password`的`User` Pydantic*模型*。 例如,在创建一个项目之前,我们不知道分配给它的 ID 是什么,但是在读取它时(从 API 返回时)我们已经知道它的 ID。 @@ -309,7 +333,7 @@ name: str ``` !!! tip - 请注意,读取用户(从 API 返回)时将使用不包括`password`的`User` Pydantic*模型*。 + Notice that the `User`, the Pydantic *model* that will be used when reading a user (returning it from the API) doesn't include the `password`. ### 使用 Pydantic 的`orm_mode` @@ -337,13 +361,13 @@ name: str {!> ../../../docs_src/sql_databases/sql_app/schemas.py!} ``` -!!! tip +!!! !!! tip 请注意,它使用`=`分配一个值,例如: `orm_mode = True` - + 它不使用之前的`:`来类型声明。 - + 这是设置配置值,而不是声明类型。 Pydantic`orm_mode`将告诉 Pydantic*模型*读取数据,即它不是一个`dict`,而是一个 ORM 模型(或任何其他具有属性的任意对象)。 @@ -392,7 +416,7 @@ current_user.items **CRUD**分别为:**增加**、**查询**、**更改**和**删除**,即增删改查。 -...虽然在这个例子中我们只是新增和查询。 +...although in this example we are only creating and reading. ### 读取数据 @@ -404,14 +428,13 @@ current_user.items * 通过 ID 和电子邮件查询单个用户。 * 查询多个用户。 -* 查询多个项目。 +* Read multiple items. ```Python hl_lines="1 3 6-7 10-11 14-15 27-28" {!../../../docs_src/sql_databases/sql_app/crud.py!} ``` -!!! tip - 通过创建仅专用于与数据库交互(获取用户或项目)的函数,独立于*路径操作函数*,您可以更轻松地在多个部分中重用它们,并为它们添加单元测试。 +!!! 该类的每个*实例对象都代表数据库中的一行数据。

### 创建数据 @@ -419,9 +442,10 @@ current_user.items 它的步骤是: -* 使用您的数据创建一个 SQLAlchemy 模型*实例。* +* !!! tip + 为了避免 SQLAlchemy*模型*和 Pydantic*模型*之间的混淆,我们将有`models.py`(SQLAlchemy 模型的文件)和`schemas.py`( Pydantic 模型的文件)。 * 使用`add`来将该实例对象添加到您的数据库。 -* 使用`commit`来对数据库的事务提交(以便保存它们)。 +* `commit` the changes to the database (so that they are saved). * 使用`refresh`来刷新您的数据库实例(以便它包含来自数据库的任何新数据,例如生成的 ID)。 ```Python hl_lines="18-24 31-36" @@ -429,32 +453,32 @@ current_user.items ``` !!! tip - SQLAlchemy 模型`User`包含一个`hashed_password`,它应该是一个包含散列的安全密码。 + The SQLAlchemy model for `User` contains a `hashed_password` that should contain a secure hashed version of the password. 但由于 API 客户端提供的是原始密码,因此您需要将其提取并在应用程序中生成散列密码。 - + 然后将hashed_password参数与要保存的值一起传递。 -!!! warning +!!! !!! warning 此示例不安全,密码未经过哈希处理。 在现实生活中的应用程序中,您需要对密码进行哈希处理,并且永远不要以明文形式保存它们。 - + 有关更多详细信息,请返回教程中的安全部分。 - + 在这里,我们只关注数据库的工具和机制。 -!!! tip +!!! !!! tip 这里不是将每个关键字参数传递给Item并从Pydantic模型中读取每个参数,而是先生成一个字典,其中包含Pydantic模型的数据: `item.dict()` - + 然后我们将dict的键值对 作为关键字参数传递给 SQLAlchemy `Item`: - + `Item(**item.dict())` - + 然后我们传递 Pydantic模型未提供的额外关键字参数`owner_id`: - + `Item(**item.dict(), owner_id=user_id)` ## 主**FastAPI**应用程序 @@ -485,7 +509,7 @@ current_user.items “迁移”是每当您更改 SQLAlchemy 模型的结构、添加新属性等以在数据库中复制这些更改、添加新列、新表等时所需的一组步骤。 -您可以在[Project Generation - Template](https://fastapi.tiangolo.com/zh/project-generation/)的模板中找到一个 FastAPI 项目中的 Alembic 示例。具体在[`alembic`代码目录中](https://github.com/tiangolo/full-stack-fastapi-postgresql/tree/master/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/alembic/)。 +您可以在[Project Generation - Template](https://fastapi.tiangolo.com/zh/project-generation/)的模板中找到一个 FastAPI 项目中的 Alembic 示例。 具体在[`alembic`代码目录中](https://github.com/tiangolo/full-stack-fastapi-postgresql/tree/master/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/alembic/)。 ### 创建依赖项 @@ -511,14 +535,14 @@ current_user.items {!> ../../../docs_src/sql_databases/sql_app/main.py!} ``` -!!! info +!!! !!! info 我们将`SessionLocal()`请求的创建和处理放在一个`try`块中。 然后我们在finally块中关闭它。 - - 通过这种方式,我们确保数据库会话在请求后始终关闭。即使在处理请求时出现异常。 - - 但是您不能从退出代码中引发另一个异常(在yield之后)。可以查阅 [Dependencies with yield and HTTPException](https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-httpexception) + + 通过这种方式,我们确保数据库会话在请求后始终关闭。 即使在处理请求时出现异常。 + + 但是您不能从退出代码中引发另一个异常(在yield之后)。 可以查阅 [Dependencies with yield and HTTPException](https://fastapi.tiangolo.com/zh/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-httpexception) *然后,当在路径操作函数*中使用依赖项时,我们使用`Session`,直接从 SQLAlchemy 导入的类型声明它。 @@ -536,10 +560,10 @@ current_user.items {!> ../../../docs_src/sql_databases/sql_app/main.py!} ``` -!!! info "技术细节" +!!! !!! info "技术细节" 参数`db`实际上是 type `SessionLocal`,但是这个类(用 创建`sessionmaker()`)是 SQLAlchemy 的“代理” `Session`,所以,编辑器并不真正知道提供了哪些方法。 - 但是通过将类型声明为Session,编辑器现在可以知道可用的方法(.add()、.query()、.commit()等)并且可以提供更好的支持(比如完成)。类型声明不影响实际对象。 + 但是通过将类型声明为Session,编辑器现在可以知道可用的方法(.add()、.query()、.commit()等)并且可以提供更好的支持(比如完成)。 类型声明不影响实际对象。 ### 创建您的**FastAPI** *路径操作* @@ -563,12 +587,12 @@ current_user.items 这样,我们就可以直接从*路径操作函数*内部调用`crud.get_user`并使用该会话,来进行对数据库操作。 -!!! tip +!!! !!! tip 请注意,您返回的值是 SQLAlchemy 模型或 SQLAlchemy 模型列表。 但是由于所有路径操作的response_model都使用 Pydantic模型/使用orm_mode模式,因此您的 Pydantic 模型中声明的数据将从它们中提取并返回给客户端,并进行所有正常的过滤和验证。 -!!! tip +!!! !!! tip 另请注意,`response_models`应当是标准 Python 类型,例如`List[schemas.Item]`. 但是由于它的内容/参数List是一个 使用orm_mode模式的Pydantic模型,所以数据将被正常检索并返回给客户端,所以没有问题。 @@ -600,10 +624,10 @@ def read_user(user_id: int, db: Session = Depends(get_db)): ... ``` -!!! info +!!! !!! info 如果您需要异步连接到关系数据库,请参阅[Async SQL (Relational) Databases](https://fastapi.tiangolo.com/zh/advanced/async-sql-databases/) -!!! note "Very Technical Details" +!!! !!! note "Very Technical Details" 如果您很好奇并且拥有深厚的技术知识,您可以在[Async](https://fastapi.tiangolo.com/zh/async/#very-technical-details)文档中查看有关如何处理 `async def`于`def`差别的技术细节。 ## 迁移 @@ -618,7 +642,7 @@ def read_user(user_id: int, db: Session = Depends(get_db)): ## 审查所有文件 -最后回顾整个案例,您应该有一个名为的目录`my_super_project`,其中包含一个名为`sql_app`。 + 最后回顾整个案例,您应该有一个名为的目录`my_super_project`,其中包含一个名为`sql_app`。 `sql_app`中应该有以下文件: @@ -676,13 +700,13 @@ def read_user(user_id: int, db: Session = Depends(get_db)): {!> ../../../docs_src/sql_databases/sql_app/main.py!} ``` -## 执行项目 +## Check it 您可以复制这些代码并按原样使用它。 -!!! info +!!! !!! info - 事实上,这里的代码只是大多数测试代码的一部分。 + 事实上,这里的代码只是大多数测试代码的一部分。 As most of the code in these docs. 你可以用 Uvicorn 运行它: @@ -697,74 +721,138 @@ $ uvicorn sql_app.main:app --reload -打开浏览器进入 http://127.0.0.1:8000/docs。 - -您将能够与您的**FastAPI**应用程序交互,从真实数据库中读取数据: - - - -## 直接与数据库交互 - -如果您想独立于 FastAPI 直接浏览 SQLite 数据库(文件)以调试其内容、添加表、列、记录、修改数据等,您可以使用[SQLite 的 DB Browser](https://sqlitebrowser.org/) - -它看起来像这样: - - - -您还可以使用[SQLite Viewer](https://inloop.github.io/sqlite-viewer/)或[ExtendsClass](https://extendsclass.com/sqlite-browser.html)等在线 SQLite 浏览器。 - -## 中间件替代数据库会话 - -如果你不能使用依赖项`yield`——例如,如果你没有使用**Python 3.7**并且不能安装上面提到的**Python 3.6**的“backports” ——你可以在类似的“中间件”中设置会话方法。 - -“中间件”基本功能是一个为每个请求执行的函数在请求之前进行执行相应的代码,以及在请求执行之后执行相应的代码。 - -### 创建中间件 - -我们将添加中间件(只是一个函数)将为每个请求创建一个新的 SQLAlchemy`SessionLocal`,将其添加到请求中,然后在请求完成后关闭它。 - -=== "Python 3.9+" - - ```Python hl_lines="12-20" - {!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!} - ``` - -=== "Python 3.6+" - - ```Python hl_lines="14-22" - {!> ../../../docs_src/sql_databases/sql_app/alt_main.py!} - ``` - -!!! info - 我们将`SessionLocal()`请求的创建和处理放在一个`try`块中。 - - 然后我们在finally块中关闭它。 - - 通过这种方式,我们确保数据库会话在请求后始终关闭,即使在处理请求时出现异常也会关闭。 - -### 关于`request.state` - -`request.state`是每个`Request`对象的属性。它用于存储附加到请求本身的任意对象,例如本例中的数据库会话。您可以在[Starlette 的关于`Request`state](https://www.starlette.io/requests/#other-state)的文档中了解更多信息。 - -对于这种情况下,它帮助我们确保在所有请求中使用单个数据库会话,然后关闭(在中间件中)。 - -### 使用`yield`依赖项与使用中间件的区别 - -在此处添加**中间件**与`yield`的依赖项的作用效果类似,但也有一些区别: - -* 中间件需要更多的代码并且更复杂一些。 -* 中间件必须是一个`async`函数。 - * 如果其中有代码必须“等待”网络,它可能会在那里“阻止”您的应用程序并稍微降低性能。 - * 尽管这里的`SQLAlchemy`工作方式可能不是很成问题。 - * 但是,如果您向等待大量I/O的中间件添加更多代码,则可能会出现问题。 -* *每个*请求都会运行一个中间件。 - * 将为每个请求创建一个连接。 - * 即使处理该请求的*路径操作*不需要数据库。 - -!!! tip - `tyield`当依赖项 足以满足用例时,使用`tyield`依赖项方法会更好。 - -!!! info - `yield`的依赖项是最近刚加入**FastAPI**中的。 - - 所以本教程的先前版本只有带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。 +打开浏览器进入 http://127.0.0.1:8000/docs。

+ +

+ 您将能够与您的FastAPI应用程序交互,从真实数据库中读取数据: +

+ +

+ +

+ +

+ 直接与数据库交互 +

+ +

+ 如果您想独立于 FastAPI 直接浏览 SQLite 数据库(文件)以调试其内容、添加表、列、记录、修改数据等,您可以使用SQLite 的 DB Browser +

+ +

+ 它看起来像这样: +

+ +

+ +

+ +

+ 您还可以使用SQLite ViewerExtendsClass等在线 SQLite 浏览器。 +

+ +

+ 中间件替代数据库会话 +

+ +

+ 如果你不能使用依赖项yield——例如,如果你没有使用Python 3.7并且不能安装上面提到的Python 3.6的“backports” ——你可以在类似的“中间件”中设置会话方法。 +

+ +

+ “中间件”基本功能是一个为每个请求执行的函数在请求之前进行执行相应的代码,以及在请求执行之后执行相应的代码。 +

+ +

+ 创建中间件 +

+ +

+ 我们将添加中间件(只是一个函数)将为每个请求创建一个新的 SQLAlchemySessionLocal,将其添加到请求中,然后在请求完成后关闭它。 +

+ +

+ === "Python 3.9+" + +

    {!> ../../../docs_src/sql_databases/sql_app_py39/alt_main.py!}
+
+

+ +

+ === "Python 3.6+" + +

    {!> ../../../docs_src/sql_databases/sql_app/alt_main.py!}
+
+

+ +

+ !!! !!! info + 我们将SessionLocal()请求的创建和处理放在一个try块中。 +

+ +
然后我们在finally块中关闭它。
+
+通过这种方式,我们确保数据库会话在请求后始终关闭,即使在处理请求时出现异常也会关闭。 Even if there was an exception while processing the request.
+
+ +

+ 关于request.state +

+ +

+ request.state是每个Request对象的属性。 它用于存储附加到请求本身的任意对象,例如本例中的数据库会话。 您可以在Starlette 的关于Requeststate的文档中了解更多信息。 +

+ +

+ 对于这种情况下,它帮助我们确保在所有请求中使用单个数据库会话,然后关闭(在中间件中)。 +

+ +

+ 使用yield依赖项与使用中间件的区别 +

+ +

+ !!! info + yield的依赖项是最近刚加入FastAPI中的。 +

+ + + +

+ !!! !!! tip + tyield当依赖项 足以满足用例时,使用tyield依赖项方法会更好。 +

+ +

+ !!! 在此处添加中间件yield的依赖项的作用效果类似,但也有一些区别: +

+ +
所以本教程的先前版本只有带有中间件的示例,并且可能有多个应用程序使用中间件进行数据库会话管理。
+
diff --git a/docs/zh/docs/tutorial/static-files.md b/docs/zh/docs/tutorial/static-files.md index e7c5c3f0a16b0..48c361d914e67 100644 --- a/docs/zh/docs/tutorial/static-files.md +++ b/docs/zh/docs/tutorial/static-files.md @@ -11,24 +11,24 @@ {!../../../docs_src/static_files/tutorial001.py!} ``` -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以用 `from starlette.staticfiles import StaticFiles`。 - **FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。 + **FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。 但它确实来自Starlette。 ### 什么是"挂载"(Mounting) "挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。 -这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。 +这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。 OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。 你可以在**高级用户指南**中了解更多。 ## 细节 -这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。 +这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。 因此,任何以`"/static"`开头的路径都会被它处理。 - `directory="static"` 指向包含你的静态文件的目录名字。 +`directory="static"` 指向包含你的静态文件的目录名字。 `name="static"` 提供了一个能被**FastAPI**内部使用的名字。 diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md index 41f01f8d84a41..b1e467f73482c 100644 --- a/docs/zh/docs/tutorial/testing.md +++ b/docs/zh/docs/tutorial/testing.md @@ -8,10 +8,10 @@ ## 使用 `TestClient` -!!! 信息 +!!! !!! 信息 要使用 `TestClient`,先要安装 `httpx`. - 例:`pip install httpx`. + E.g. 例:`pip install httpx`. 导入 `TestClient`. @@ -27,19 +27,19 @@ {!../../../docs_src/app_testing/tutorial001.py!} ``` -!!! 提示 +!!! !!! 提示 注意测试函数是普通的 `def`,不是 `async def`。 还有client的调用也是普通的调用,不是用 `await`。 - + 这让你可以直接使用 `pytest` 而不会遇到麻烦。 -!!! note "技术细节" +!!! !!! note "技术细节" 你也可以用 `from starlette.testclient import TestClient`。 - **FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。 + **FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。 但它直接来自Starlette。 -!!! 提示 +!!! !!! 提示 除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。 ## 分离测试 @@ -54,6 +54,7 @@ ``` . +. ├── app │   ├── __init__.py │   └── main.py @@ -68,10 +69,11 @@ ### 测试文件 -然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): +然后你会有一个包含测试的文件 `test_main.py` 。 app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): ``` hl_lines="5" . +. ├── app │   ├── __init__.py │   ├── main.py @@ -96,6 +98,7 @@ ``` . +. ├── app │   ├── __init__.py │   ├── main.py @@ -130,7 +133,7 @@ === "Python 3.10+ non-Annotated" - !!! tip + !!! !!! tip Prefer to use the `Annotated` version if possible. ```Python @@ -139,7 +142,7 @@ === "Python 3.6+ non-Annotated" - !!! tip + !!! !!! tip Prefer to use the `Annotated` version if possible. ```Python @@ -158,7 +161,7 @@ 接着只需在测试中同样操作。 -示例: +E.g.: * 传一个*路径* 或*查询* 参数,添加到URL上。 * 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。 @@ -168,7 +171,7 @@ 关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 HTTPX 文档. -!!! 信息 +!!! !!! 信息 注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。 如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。