mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-06 20:31:56 -05:00
Update pages that cause docs building error
This commit is contained in:
@@ -1,44 +1,57 @@
|
||||
# OPENAPI 中的其他响应
|
||||
# OpenAPI 中的附加响应 { #additional-responses-in-openapi }
|
||||
|
||||
您可以声明附加响应,包括附加状态代码、媒体类型、描述等。
|
||||
/// warning | 警告
|
||||
|
||||
这些额外的响应将包含在OpenAPI模式中,因此它们也将出现在API文档中。
|
||||
这是一个相对高级的话题。
|
||||
|
||||
但是对于那些额外的响应,你必须确保你直接返回一个像 `JSONResponse` 一样的 `Response` ,并包含你的状态代码和内容。
|
||||
|
||||
## `model`附加响应
|
||||
您可以向路径操作装饰器传递参数 `responses` 。
|
||||
|
||||
它接收一个 `dict`,键是每个响应的状态代码(如`200`),值是包含每个响应信息的其他 `dict`。
|
||||
|
||||
每个响应字典都可以有一个关键模型,其中包含一个 `Pydantic` 模型,就像 `response_model` 一样。
|
||||
|
||||
**FastAPI**将采用该模型,生成其`JSON Schema`并将其包含在`OpenAPI`中的正确位置。
|
||||
|
||||
例如,要声明另一个具有状态码 `404` 和`Pydantic`模型 `Message` 的响应,可以写:
|
||||
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
|
||||
|
||||
/// note
|
||||
|
||||
请记住,您必须直接返回 `JSONResponse` 。
|
||||
如果你刚开始使用 **FastAPI**,可能暂时用不到。
|
||||
|
||||
///
|
||||
|
||||
/// info
|
||||
你可以声明附加响应,包括额外的状态码、媒体类型、描述等。
|
||||
|
||||
`model` 密钥不是OpenAPI的一部分。
|
||||
**FastAPI**将从那里获取`Pydantic`模型,生成` JSON Schema` ,并将其放在正确的位置。
|
||||
- 正确的位置是:
|
||||
- 在键 `content` 中,其具有另一个`JSON`对象( `dict` )作为值,该`JSON`对象包含:
|
||||
- 媒体类型的密钥,例如 `application/json` ,它包含另一个`JSON`对象作为值,该对象包含:
|
||||
- 一个键` schema` ,它的值是来自模型的`JSON Schema`,正确的位置在这里。
|
||||
- **FastAPI**在这里添加了对OpenAPI中另一个地方的全局JSON模式的引用,而不是直接包含它。这样,其他应用程序和客户端可以直接使用这些JSON模式,提供更好的代码生成工具等。
|
||||
这些附加响应会被包含在 OpenAPI 模式中,因此它们也会出现在 API 文档中。
|
||||
|
||||
但是对于这些附加响应,你必须确保直接返回一个 `Response`(例如 `JSONResponse`),并携带你的状态码和内容。
|
||||
|
||||
## 带有 `model` 的附加响应 { #additional-response-with-model }
|
||||
|
||||
你可以向你的*路径操作装饰器*传入参数 `responses`。
|
||||
|
||||
它接收一个 `dict`:键是每个响应的状态码(例如 `200`),值是包含该响应信息的另一个 `dict`。
|
||||
|
||||
这些响应的每个 `dict` 都可以有一个键 `model`,包含一个 Pydantic 模型,就像 `response_model` 一样。
|
||||
|
||||
**FastAPI** 会获取该模型,生成它的 JSON Schema,并将其放在 OpenAPI 中的正确位置。
|
||||
|
||||
例如,要声明另一个状态码为 `404` 且具有 Pydantic 模型 `Message` 的响应,你可以这样写:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *}
|
||||
|
||||
/// note | 注意
|
||||
|
||||
记住你需要直接返回 `JSONResponse`。
|
||||
|
||||
///
|
||||
|
||||
**在OpenAPI中为该路径操作生成的响应将是:**
|
||||
/// info | 信息
|
||||
|
||||
```json hl_lines="3-12"
|
||||
`model` 键不是 OpenAPI 的一部分。
|
||||
|
||||
**FastAPI** 会从这里获取 Pydantic 模型,生成 JSON Schema,并把它放到正确的位置。
|
||||
|
||||
正确的位置是:
|
||||
|
||||
* 在键 `content` 中,它的值是另一个 JSON 对象(`dict`),该对象包含:
|
||||
* 一个媒体类型作为键,例如 `application/json`,它的值是另一个 JSON 对象,该对象包含:
|
||||
* 一个键 `schema`,它的值是来自该模型的 JSON Schema,这里就是正确的位置。
|
||||
* **FastAPI** 会在这里添加一个引用,指向你 OpenAPI 中另一个位置的全局 JSON Schemas,而不是直接内联。这样,其他应用和客户端可以直接使用这些 JSON Schemas,提供更好的代码生成工具等。
|
||||
|
||||
///
|
||||
|
||||
为该*路径操作*在 OpenAPI 中生成的响应将是:
|
||||
|
||||
```JSON hl_lines="3-12"
|
||||
{
|
||||
"responses": {
|
||||
"404": {
|
||||
@@ -73,10 +86,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
**模式被引用到OpenAPI模式中的另一个位置:**
|
||||
```json hl_lines="4-16"
|
||||
|
||||
这些模式在 OpenAPI 模式中被引用到另一个位置:
|
||||
|
||||
```JSON hl_lines="4-16"
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
@@ -153,48 +167,54 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 主响应的其他媒体类型
|
||||
|
||||
您可以使用相同的 `responses` 参数为相同的主响应添加不同的媒体类型。
|
||||
## 主响应的其他媒体类型 { #additional-media-types-for-the-main-response }
|
||||
|
||||
例如,您可以添加一个额外的媒体类型` image/png` ,声明您的路径操作可以返回JSON对象(媒体类型 `application/json` )或PNG图像:
|
||||
你可以使用同一个 `responses` 参数为同一个主响应添加不同的媒体类型。
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
|
||||
例如,你可以添加一个额外的媒体类型 `image/png`,声明你的*路径操作*可以返回 JSON 对象(媒体类型为 `application/json`)或 PNG 图片:
|
||||
|
||||
/// note
|
||||
{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *}
|
||||
|
||||
- 请注意,您必须直接使用 `FileResponse` 返回图像。
|
||||
/// note | 注意
|
||||
|
||||
请注意,你必须直接使用 `FileResponse` 返回图片。
|
||||
|
||||
///
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
- 除非在 `responses` 参数中明确指定不同的媒体类型,否则**FastAPI**将假定响应与主响应类具有相同的媒体类型(默认为` application/json` )。
|
||||
- 但是如果您指定了一个自定义响应类,并将 `None `作为其媒体类型,**FastAPI**将使用 `application/json` 作为具有关联模型的任何其他响应。
|
||||
除非你在 `responses` 参数中明确指定不同的媒体类型,否则 FastAPI 会假设响应与主响应类具有相同的媒体类型(默认是 `application/json`)。
|
||||
|
||||
但是如果你指定了一个媒体类型为 `None` 的自定义响应类,FastAPI 会对任何具有关联模型的附加响应使用 `application/json`。
|
||||
|
||||
///
|
||||
|
||||
## 组合信息
|
||||
您还可以联合接收来自多个位置的响应信息,包括 `response_model `、 `status_code` 和 `responses `参数。
|
||||
## 组合信息 { #combining-information }
|
||||
|
||||
您可以使用默认的状态码 `200` (或者您需要的自定义状态码)声明一个 `response_model `,然后直接在OpenAPI模式中在 `responses` 中声明相同响应的其他信息。
|
||||
你也可以把来自多个位置的响应信息组合在一起,包括 `response_model`、`status_code` 和 `responses` 参数。
|
||||
|
||||
**FastAPI**将保留来自 `responses` 的附加信息,并将其与模型中的JSON Schema结合起来。
|
||||
你可以声明一个 `response_model`,使用默认状态码 `200`(或根据需要使用自定义状态码),然后在 `responses` 中直接在 OpenAPI 模式里为同一个响应声明附加信息。
|
||||
|
||||
例如,您可以使用状态码 `404` 声明响应,该响应使用`Pydantic`模型并具有自定义的` description` 。
|
||||
**FastAPI** 会保留来自 `responses` 的附加信息,并把它与你的模型生成的 JSON Schema 合并。
|
||||
|
||||
以及一个状态码为 `200` 的响应,它使用您的 `response_model` ,但包含自定义的 `example` :
|
||||
例如,你可以声明一个状态码为 `404` 的响应,它使用一个 Pydantic 模型并带有自定义的 `description`。
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
|
||||
以及一个状态码为 `200` 的响应,它使用你的 `response_model`,但包含自定义的 `example`:
|
||||
|
||||
所有这些都将被合并并包含在您的OpenAPI中,并在API文档中显示:
|
||||
{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *}
|
||||
|
||||
## 联合预定义响应和自定义响应
|
||||
所有这些都会被合并并包含到你的 OpenAPI 中,并显示在 API 文档里:
|
||||
|
||||
<img src="/img/tutorial/additional-responses/image01.png">
|
||||
|
||||
## 组合预定义响应和自定义响应 { #combine-predefined-responses-and-custom-ones }
|
||||
|
||||
你可能希望有一些适用于许多*路径操作*的预定义响应,但同时又想把它们与每个*路径操作*所需的自定义响应组合在一起。
|
||||
|
||||
在这些情况下,你可以使用 Python 的“解包”`dict` 的技巧 `**dict_to_unpack`:
|
||||
|
||||
您可能希望有一些应用于许多路径操作的预定义响应,但是你想将不同的路径和自定义的相应组合在一块。
|
||||
对于这些情况,你可以使用Python的技术,将 `dict` 与 `**dict_to_unpack` 解包:
|
||||
```Python
|
||||
old_dict = {
|
||||
"old key": "old value",
|
||||
@@ -203,19 +223,25 @@ old_dict = {
|
||||
new_dict = {**old_dict, "new key": "new value"}
|
||||
```
|
||||
|
||||
这里, new_dict 将包含来自 old_dict 的所有键值对加上新的键值对:
|
||||
```python
|
||||
这里,`new_dict` 将包含来自 `old_dict` 的所有键值对,再加上新的键值对:
|
||||
|
||||
```Python
|
||||
{
|
||||
"old key": "old value",
|
||||
"second old key": "second old value",
|
||||
"new key": "new value",
|
||||
}
|
||||
```
|
||||
您可以使用该技术在路径操作中重用一些预定义的响应,并将它们与其他自定义响应相结合。
|
||||
**例如:**
|
||||
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
|
||||
## 有关OpenAPI响应的更多信息
|
||||
|
||||
要了解您可以在响应中包含哪些内容,您可以查看OpenAPI规范中的以下部分:
|
||||
+ [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responsesObject),它包括 Response Object 。
|
||||
+ [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responseObject),您可以直接在 `responses` 参数中的每个响应中包含任何内容。包括 `description` 、 `headers` 、 `content` (其中是声明不同的媒体类型和JSON Schemas)和 `links` 。
|
||||
你可以使用该技巧在*路径操作*中重用一些预定义响应,并把它们与额外的自定义响应组合在一起。
|
||||
|
||||
例如:
|
||||
|
||||
{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *}
|
||||
|
||||
## 关于 OpenAPI 响应的更多信息 { #more-information-about-openapi-responses }
|
||||
|
||||
要查看响应中究竟可以包含什么,你可以查看 OpenAPI 规范中的以下部分:
|
||||
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object" class="external-link" target="_blank">OpenAPI Responses 对象</a>,它包含 `Response Object`。
|
||||
* <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object" class="external-link" target="_blank">OpenAPI Response 对象</a>,你可以把这里的任何内容直接包含到 `responses` 参数中的每个响应里。包括 `description`、`headers`、`content`(在这里声明不同的媒体类型和 JSON Schemas),以及 `links`。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 额外的状态码
|
||||
# 额外的状态码 { #additional-status-codes }
|
||||
|
||||
**FastAPI** 默认使用 `JSONResponse` 返回一个响应,将你的 *路径操作* 中的返回内容放到该 `JSONResponse` 中。
|
||||
|
||||
**FastAPI** 会自动使用默认的状态码或者使用你在 *路径操作* 中设置的状态码。
|
||||
|
||||
## 额外的状态码
|
||||
## 额外的状态码 { #additional-status-codes_1 }
|
||||
|
||||
如果你想要返回主要状态码之外的状态码,你可以通过直接返回一个 `Response` 来实现,比如 `JSONResponse`,然后直接设置额外的状态码。
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
|
||||
但是你也希望它能够接受新的条目。并且当这些条目不存在时,会自动创建并返回 201 「创建」的 HTTP 状态码。
|
||||
|
||||
要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。
|
||||
要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为你要的值。
|
||||
|
||||
{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *}
|
||||
{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *}
|
||||
|
||||
/// warning | 警告
|
||||
/// warning
|
||||
|
||||
当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。
|
||||
|
||||
FastAPI 不会用模型等对该响应进行序列化。
|
||||
它不会用模型等进行序列化。
|
||||
|
||||
确保其中有你想要的数据,且返回的值为合法的 JSON(如果你使用 `JSONResponse` 的话)。
|
||||
|
||||
@@ -34,7 +34,7 @@ FastAPI 不会用模型等对该响应进行序列化。
|
||||
|
||||
///
|
||||
|
||||
## OpenAPI 和 API 文档
|
||||
## OpenAPI 和 API 文档 { #openapi-and-api-docs }
|
||||
|
||||
如果你直接返回额外的状态码和响应,它们不会包含在 OpenAPI 方案(API 文档)中,因为 FastAPI 没办法预先知道你要返回什么。
|
||||
|
||||
|
||||
@@ -1,65 +1,163 @@
|
||||
# 高级依赖项
|
||||
# 高级依赖项 { #advanced-dependencies }
|
||||
|
||||
## 参数化的依赖项
|
||||
## 参数化的依赖项 { #parameterized-dependencies }
|
||||
|
||||
我们之前看到的所有依赖项都是写死的函数或类。
|
||||
目前我们看到的依赖项都是固定的函数或类。
|
||||
|
||||
但也可以为依赖项设置参数,避免声明多个不同的函数或类。
|
||||
但有时你可能希望为依赖项设置参数,而不必声明许多不同的函数或类。
|
||||
|
||||
假设要创建校验查询参数 `q` 是否包含固定内容的依赖项。
|
||||
假设我们要有一个依赖项,用来检查查询参数 `q` 是否包含某个固定内容。
|
||||
|
||||
但此处要把待检验的固定内容定义为参数。
|
||||
但我们希望能够把这个固定内容参数化。
|
||||
|
||||
## **可调用**实例
|
||||
## “可调用”的实例 { #a-callable-instance }
|
||||
|
||||
Python 可以把类实例变为**可调用项**。
|
||||
在 Python 中,可以让某个类的实例变成“可调用对象”(callable)。
|
||||
|
||||
这里说的不是类本身(类本就是可调用项),而是类实例。
|
||||
这里指的是类的实例(类本身已经是可调用的),而不是类本身。
|
||||
|
||||
为此,需要声明 `__call__` 方法:
|
||||
为此,声明一个 `__call__` 方法:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011.py hl[10] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *}
|
||||
|
||||
本例中,**FastAPI** 使用 `__call__` 检查附加参数及子依赖项,稍后,还要调用它向*路径操作函数*传递值。
|
||||
在这种情况下,**FastAPI** 会使用这个 `__call__` 来检查附加参数和子依赖,并且稍后会调用它,把返回值传递给你的*路径操作函数*中的参数。
|
||||
|
||||
## 参数化实例
|
||||
## 参数化实例 { #parameterize-the-instance }
|
||||
|
||||
接下来,使用 `__init__` 声明用于**参数化**依赖项的实例参数:
|
||||
现在,我们可以用 `__init__` 声明实例的参数,用来“参数化”这个依赖项:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011.py hl[7] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *}
|
||||
|
||||
本例中,**FastAPI** 不使用 `__init__`,我们要直接在代码中使用。
|
||||
在本例中,**FastAPI** 不会接触或关心 `__init__`,我们会在自己的代码中直接使用它。
|
||||
|
||||
## 创建实例
|
||||
## 创建实例 { #create-an-instance }
|
||||
|
||||
使用以下代码创建类实例:
|
||||
我们可以这样创建该类的实例:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011.py hl[16] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *}
|
||||
|
||||
这样就可以**参数化**依赖项,它包含 `checker.fixed_content` 的属性 - `"bar"`。
|
||||
这样就把依赖项“参数化”了,现在它内部带有属性 `checker.fixed_content` 的值 `"bar"`。
|
||||
|
||||
## 把实例作为依赖项
|
||||
## 把实例作为依赖项 { #use-the-instance-as-a-dependency }
|
||||
|
||||
然后,不要再在 `Depends(checker)` 中使用 `Depends(FixedContentQueryChecker)`, 而是要使用 `checker`,因为依赖项是类实例 - `checker`,不是类。
|
||||
然后,我们可以在 `Depends(checker)` 中使用这个 `checker`,而不是 `Depends(FixedContentQueryChecker)`,因为依赖项是实例 `checker`,不是类本身。
|
||||
|
||||
处理依赖项时,**FastAPI** 以如下方式调用 `checker`:
|
||||
解析依赖项时,**FastAPI** 会像这样调用 `checker`:
|
||||
|
||||
```Python
|
||||
checker(q="somequery")
|
||||
```
|
||||
|
||||
……并用*路径操作函数*的参数 `fixed_content_included` 返回依赖项的值:
|
||||
...并将其返回值作为依赖项的值,传给我们的*路径操作函数*中的参数 `fixed_content_included`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial011.py hl[20] *}
|
||||
{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
本章示例有些刻意,也看不出有什么用处。
|
||||
这些看起来可能有些牵强,目前它的用处也许还不太明显。
|
||||
|
||||
这个简例只是为了说明高级依赖项的运作机制。
|
||||
这些示例刻意保持简单,但展示了整体的工作方式。
|
||||
|
||||
在有关安全的章节中,工具函数将以这种方式实现。
|
||||
在安全相关的章节里,有一些工具函数就是以相同的方式实现的。
|
||||
|
||||
只要能理解本章内容,就能理解安全工具背后的运行机制。
|
||||
如果你理解了这里的内容,你就已经知道那些安全工具在底层是如何工作的。
|
||||
|
||||
///
|
||||
|
||||
## 带 `yield` 的依赖项、`HTTPException`、`except` 与后台任务 { #dependencies-with-yield-httpexception-except-and-background-tasks }
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
你很可能不需要了解这些技术细节。
|
||||
|
||||
这些细节主要在你的 FastAPI 应用版本低于 0.121.0 且你正遇到带 `yield` 的依赖项问题时才有用。
|
||||
|
||||
///
|
||||
|
||||
带 `yield` 的依赖项随着时间演进以覆盖不同用例并修复一些问题,下面是变更摘要。
|
||||
|
||||
### 带 `yield` 的依赖项与 `scope` { #dependencies-with-yield-and-scope }
|
||||
|
||||
在 0.121.0 版本中,FastAPI 为带 `yield` 的依赖项新增了 `Depends(scope="function")` 的支持。
|
||||
|
||||
使用 `Depends(scope="function")` 时,`yield` 之后的退出代码会在*路径操作函数*结束后、响应发送给客户端之前立即执行。
|
||||
|
||||
而当使用默认的 `Depends(scope="request")` 时,`yield` 之后的退出代码会在响应发送之后执行。
|
||||
|
||||
你可以在文档 [带 `yield` 的依赖项 - 提前退出与 `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 中了解更多。
|
||||
|
||||
### 带 `yield` 的依赖项与 `StreamingResponse`(技术细节) { #dependencies-with-yield-and-streamingresponse-technical-details }
|
||||
|
||||
在 FastAPI 0.118.0 之前,如果你使用带 `yield` 的依赖项,它会在*路径操作函数*返回后、发送响应之前运行 `yield` 之后的退出代码。
|
||||
|
||||
这样做的目的是避免在等待响应通过网络传输期间不必要地占用资源。
|
||||
|
||||
这也意味着,如果你返回的是 `StreamingResponse`,那么该带 `yield` 的依赖项的退出代码会在开始发送响应前就已经执行完毕。
|
||||
|
||||
例如,如果你在带 `yield` 的依赖项中持有一个数据库会话,那么 `StreamingResponse` 在流式发送数据时将无法使用该会话,因为会话已经在 `yield` 之后的退出代码里被关闭了。
|
||||
|
||||
在 0.118.0 中,这一行为被回退为:让 `yield` 之后的退出代码在响应发送之后再执行。
|
||||
|
||||
/// info | 信息
|
||||
|
||||
如你在下文所见,这与 0.106.0 之前的行为非常相似,但对若干边界情况做了改进和修复。
|
||||
|
||||
///
|
||||
|
||||
#### 需要提前执行退出代码的用例 { #use-cases-with-early-exit-code }
|
||||
|
||||
在某些特定条件下,旧的行为(在发送响应之前执行带 `yield` 依赖项的退出代码)会更有利。
|
||||
|
||||
例如,设想你在带 `yield` 的依赖项中仅用数据库会话来校验用户,而在*路径操作函数*中并不会再次使用该会话;同时,响应需要很长时间才能发送完,比如一个缓慢发送数据的 `StreamingResponse`,且它出于某种原因并不使用数据库。
|
||||
|
||||
这种情况下,会一直持有数据库会话直到响应发送完毕;但如果并不再使用它,就没有必要一直占用。
|
||||
|
||||
代码可能如下:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py *}
|
||||
|
||||
退出代码(自动关闭 `Session`)位于:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *}
|
||||
|
||||
...会在响应把慢速数据发送完之后才运行:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *}
|
||||
|
||||
但由于 `generate_stream()` 并不使用数据库会话,因此在发送响应期间保持会话打开并非必要。
|
||||
|
||||
如果你使用的是 SQLModel(或 SQLAlchemy)并碰到这种特定用例,你可以在不再需要时显式关闭会话:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *}
|
||||
|
||||
这样会话会释放数据库连接,让其他请求可以使用。
|
||||
|
||||
如果你还有其他需要在 `yield` 依赖项中提前退出的用例,请创建一个 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub 讨论问题</a>,说明你的具体用例以及为何提前关闭会对你有帮助。
|
||||
|
||||
如果确有有力的用例需要提前关闭,我会考虑新增一种选择性启用提前关闭的方式。
|
||||
|
||||
### 带 `yield` 的依赖项与 `except`(技术细节) { #dependencies-with-yield-and-except-technical-details }
|
||||
|
||||
在 FastAPI 0.110.0 之前,如果你在带 `yield` 的依赖项中用 `except` 捕获了一个异常,并且没有再次抛出它,那么该异常会被自动抛出/转发给任意异常处理器或内部服务器错误处理器。
|
||||
|
||||
在 0.110.0 中对此作出了变更,以修复将异常转发为未处理(内部服务器错误)时造成的内存消耗问题,并使其与常规 Python 代码的行为保持一致。
|
||||
|
||||
### 后台任务与带 `yield` 的依赖项(技术细节) { #background-tasks-and-dependencies-with-yield-technical-details }
|
||||
|
||||
在 FastAPI 0.106.0 之前,`yield` 之后抛出异常是不可行的,因为带 `yield` 的依赖项中的退出代码会在响应发送之后才执行,此时[异常处理器](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}已经运行完毕。
|
||||
|
||||
之所以这样设计,主要是为了允许在后台任务中继续使用依赖项通过 `yield`“产出”的对象,因为退出代码会在后台任务完成之后才执行。
|
||||
|
||||
在 FastAPI 0.106.0 中,这一行为被修改,目的是避免在等待响应通过网络传输时一直占用资源。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
另外,后台任务通常是一段独立的逻辑,应该单独处理,并使用它自己的资源(例如它自己的数据库连接)。
|
||||
|
||||
因此,这样做你的代码通常会更清晰。
|
||||
|
||||
///
|
||||
|
||||
如果你过去依赖于旧行为,现在应在后台任务内部自行创建所需资源,并且只在内部使用不依赖于带 `yield` 依赖项资源的数据。
|
||||
|
||||
例如,不要复用相同的数据库会话,而是在后台任务内部创建一个新的会话,并用这个新会话从数据库获取对象。然后,不是把数据库对象本身作为参数传给后台任务函数,而是传递该对象的 ID,并在后台任务函数内部再次获取该对象。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 异步测试
|
||||
# 异步测试 { #async-tests }
|
||||
|
||||
您已经了解了如何使用 `TestClient` 测试 **FastAPI** 应用程序。但是到目前为止,您只了解了如何编写同步测试,而没有使用 `async` 异步函数。
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
让我们看看如何才能实现这一点。
|
||||
|
||||
## pytest.mark.anyio
|
||||
## pytest.mark.anyio { #pytest-mark-anyio }
|
||||
|
||||
如果我们想在测试中调用异步函数,那么我们的测试函数必须是异步的。 AnyIO 为此提供了一个简洁的插件,它允许我们指定一些测试函数要异步调用。
|
||||
|
||||
## HTTPX
|
||||
## HTTPX { #httpx }
|
||||
|
||||
即使您的 **FastAPI** 应用程序使用普通的 `def` 函数而不是 `async def` ,它本质上仍是一个 `async` 异步应用程序。
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
`TestClient` 是基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> 的。幸运的是,我们可以直接使用它来测试API。
|
||||
|
||||
## 示例
|
||||
## 示例 { #example }
|
||||
|
||||
举个简单的例子,让我们来看一个[更大的应用](../tutorial/bigger-applications.md){.internal-link target=_blank}和[测试](../tutorial/testing.md){.internal-link target=_blank}中描述的类似文件结构:
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
|
||||
文件 `main.py` 将包含:
|
||||
|
||||
{* ../../docs_src/async_tests/main.py *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/main.py *}
|
||||
|
||||
文件 `test_main.py` 将包含针对 `main.py` 的测试,现在它可能看起来如下:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py *}
|
||||
|
||||
## 运行测试
|
||||
## 运行测试 { #run-it }
|
||||
|
||||
您可以通过以下方式照常运行测试:
|
||||
|
||||
@@ -52,13 +52,13 @@ $ pytest
|
||||
|
||||
</div>
|
||||
|
||||
## 详细说明
|
||||
## 详细说明 { #in-detail }
|
||||
|
||||
这个标记 `@pytest.mark.anyio` 会告诉 pytest 该测试函数应该被异步调用:
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[7] *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
请注意,测试函数现在用的是 `async def`,而不是像以前使用 `TestClient` 时那样只是 `def` 。
|
||||
|
||||
@@ -66,7 +66,7 @@ $ pytest
|
||||
|
||||
我们现在可以使用应用程序创建一个 `AsyncClient` ,并使用 `await` 向其发送异步请求。
|
||||
|
||||
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
|
||||
{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *}
|
||||
|
||||
这相当于:
|
||||
|
||||
@@ -76,24 +76,24 @@ response = client.get('/')
|
||||
|
||||
我们曾经通过它向 `TestClient` 发出请求。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
请注意,我们正在将 async/await 与新的 `AsyncClient` 一起使用——请求是异步的。
|
||||
|
||||
///
|
||||
|
||||
/// warning
|
||||
/// warning | 警告
|
||||
|
||||
如果您的应用程序依赖于生命周期事件, `AsyncClient` 将不会触发这些事件。为了确保它们被触发,请使用 <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a> 中的 `LifespanManager` 。
|
||||
|
||||
///
|
||||
|
||||
## 其他异步函数调用
|
||||
## 其他异步函数调用 { #other-asynchronous-function-calls }
|
||||
|
||||
由于测试函数现在是异步的,因此除了在测试中向 FastAPI 应用程序发送请求之外,您现在还可以调用(和使用 `await` 等待)其他 `async` 异步函数,就和您在代码中的其他任何地方调用它们的方法一样。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
如果您在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB 的 MotorClient</a> 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如通过 `'@app.on_event("startup")` 回调函数进行初始化。
|
||||
如果您在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB 的 MotorClient</a> 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如在 `@app.on_event("startup")` 回调中初始化。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,30 +1,131 @@
|
||||
# 使用代理
|
||||
# 使用代理 { #behind-a-proxy }
|
||||
|
||||
有些情况下,您可能要使用 Traefik 或 Nginx 等**代理**服务器,并添加应用不能识别的附加路径前缀配置。
|
||||
在很多情况下,你会在 FastAPI 应用前面使用像 Traefik 或 Nginx 这样的**代理**。
|
||||
|
||||
此时,要使用 `root_path` 配置应用。
|
||||
这些代理可以处理 HTTPS 证书等事项。
|
||||
|
||||
`root_path` 是 ASGI 规范提供的机制,FastAPI 就是基于此规范开发的(通过 Starlette)。
|
||||
## 代理转发的请求头 { #proxy-forwarded-headers }
|
||||
|
||||
在你的应用前面的**代理**通常会在把请求转发给你的**服务器**之前,临时设置一些请求头,让服务器知道该请求是由代理**转发**的,并告知原始(公网)URL,包括域名、是否使用 HTTPS 等。
|
||||
|
||||
**服务器**程序(例如通过 **FastAPI CLI** 运行的 **Uvicorn**)能够解析这些请求头,然后把这些信息传递给你的应用。
|
||||
|
||||
但出于安全考虑,由于服务器并不知道自己处在受信任的代理之后,它默认不会解析这些请求头。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
这些代理相关的请求头包括:
|
||||
|
||||
- <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For" class="external-link" target="_blank">X-Forwarded-For</a>
|
||||
- <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto" class="external-link" target="_blank">X-Forwarded-Proto</a>
|
||||
- <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host" class="external-link" target="_blank">X-Forwarded-Host</a>
|
||||
|
||||
///
|
||||
|
||||
### 启用代理转发的请求头 { #enable-proxy-forwarded-headers }
|
||||
|
||||
你可以用 *CLI 选项* `--forwarded-allow-ips` 启动 FastAPI CLI,并传入应该被信任、允许读取这些转发请求头的 IP 地址列表。
|
||||
|
||||
如果设置为 `--forwarded-allow-ips="*"`,就会信任所有来源 IP。
|
||||
|
||||
如果你的**服务器**位于受信任的**代理**之后,并且只有代理会与它通信,这将使其接受该**代理**的任何 IP。
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run --forwarded-allow-ips="*"
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### 使用 HTTPS 的重定向 { #redirects-with-https }
|
||||
|
||||
例如,假设你定义了一个*路径操作* `/items/`:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *}
|
||||
|
||||
如果客户端尝试访问 `/items`,默认会被重定向到 `/items/`。
|
||||
|
||||
但在设置 *CLI 选项* `--forwarded-allow-ips` 之前,它可能会重定向到 `http://localhost:8000/items/`。
|
||||
|
||||
而你的应用可能托管在 `https://mysuperapp.com`,重定向应当是 `https://mysuperapp.com/items/`。
|
||||
|
||||
通过设置 `--proxy-headers`,FastAPI 现在就可以重定向到正确的位置。😎
|
||||
|
||||
```
|
||||
https://mysuperapp.com/items/
|
||||
```
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
如果你想了解更多关于 HTTPS 的内容,查看指南:[关于 HTTPS](../deployment/https.md){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
### 代理转发请求头如何工作 { #how-proxy-forwarded-headers-work }
|
||||
|
||||
下面是一个可视化图示,展示了**代理**如何在客户端与**应用服务器**之间添加转发请求头:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Proxy as Proxy/Load Balancer
|
||||
participant Server as FastAPI Server
|
||||
|
||||
Client->>Proxy: HTTPS Request<br/>Host: mysuperapp.com<br/>Path: /items
|
||||
|
||||
Note over Proxy: Proxy adds forwarded headers
|
||||
|
||||
Proxy->>Server: HTTP Request<br/>X-Forwarded-For: [client IP]<br/>X-Forwarded-Proto: https<br/>X-Forwarded-Host: mysuperapp.com<br/>Path: /items
|
||||
|
||||
Note over Server: Server interprets headers<br/>(if --forwarded-allow-ips is set)
|
||||
|
||||
Server->>Proxy: HTTP Response<br/>with correct HTTPS URLs
|
||||
|
||||
Proxy->>Client: HTTPS Response
|
||||
```
|
||||
|
||||
**代理**会拦截原始客户端请求,并在将请求传递给**应用服务器**之前,添加特殊的*转发*请求头(`X-Forwarded-*`)。
|
||||
|
||||
这些请求头保留了原始请求中否则会丢失的信息:
|
||||
|
||||
- X-Forwarded-For:原始客户端的 IP 地址
|
||||
- X-Forwarded-Proto:原始协议(`https`)
|
||||
- X-Forwarded-Host:原始主机(`mysuperapp.com`)
|
||||
|
||||
当 **FastAPI CLI** 配置了 `--forwarded-allow-ips` 后,它会信任并使用这些请求头,例如用于在重定向中生成正确的 URL。
|
||||
|
||||
## 移除路径前缀的代理 { #proxy-with-a-stripped-path-prefix }
|
||||
|
||||
你可能会有一个代理,为你的应用添加一个路径前缀。
|
||||
|
||||
在这些情况下,你可以使用 `root_path` 来配置你的应用。
|
||||
|
||||
`root_path` 是 ASGI 规范(FastAPI 基于该规范,通过 Starlette 构建)提供的机制。
|
||||
|
||||
`root_path` 用于处理这些特定情况。
|
||||
|
||||
在挂载子应用时,也可以在内部使用。
|
||||
在挂载子应用时,它也会在内部使用。
|
||||
|
||||
## 移除路径前缀的代理
|
||||
“移除路径前缀的代理”在这里的意思是:你可以在代码中声明一个路径 `/app`,然后在顶层添加一层(代理),把你的 **FastAPI** 应用放在类似 `/api/v1` 的路径下。
|
||||
|
||||
本例中,移除路径前缀的代理是指在代码中声明路径 `/app`,然后在应用顶层添加代理,把 **FastAPI** 应用放在 `/api/v1` 路径下。
|
||||
在这种情况下,原始路径 `/app` 实际上会在 `/api/v1/app` 提供服务。
|
||||
|
||||
本例的原始路径 `/app` 实际上是在 `/api/v1/app` 提供服务。
|
||||
即使你的所有代码都假设只有 `/app`。
|
||||
|
||||
哪怕所有代码都假设只有 `/app`。
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *}
|
||||
|
||||
代理只在把请求传送给 Uvicorn 之前才会**移除路径前缀**,让应用以为它是在 `/app` 提供服务,因此不必在代码中加入前缀 `/api/v1`。
|
||||
代理会在将请求传递给应用服务器(可能是通过 FastAPI CLI 运行的 Uvicorn)之前,实时**“移除”**这个**路径前缀**,让你的应用认为它是在 `/app` 被服务,这样你就不需要更新所有代码去包含 `/api/v1` 前缀。
|
||||
|
||||
但之后,在(前端)打开 API 文档时,代理会要求在 `/openapi.json`,而不是 `/api/v1/openapi.json` 中提取 OpenAPI 概图。
|
||||
到这里,一切都会像往常一样工作。
|
||||
|
||||
因此, (运行在浏览器中的)前端会尝试访问 `/openapi.json`,但没有办法获取 OpenAPI 概图。
|
||||
但是,当你打开集成的文档界面(前端)时,它会期望在 `/openapi.json` 获取 OpenAPI 模式,而不是在 `/api/v1/openapi.json`。
|
||||
|
||||
这是因为应用使用了以 `/api/v1` 为路径前缀的代理,前端要从 `/api/v1/openapi.json` 中提取 OpenAPI 概图。
|
||||
因此,(在浏览器中运行的)前端会尝试访问 `/openapi.json`,但无法获取 OpenAPI 模式。
|
||||
|
||||
因为我们的应用使用了路径前缀为 `/api/v1` 的代理,前端需要从 `/api/v1/openapi.json` 获取 OpenAPI 模式。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
@@ -39,15 +140,15 @@ proxy --> server
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
IP `0.0.0.0` 常用于指程序监听本机或服务器上的所有有效 IP。
|
||||
IP `0.0.0.0` 通常表示程序监听该机器/服务器上的所有可用 IP。
|
||||
|
||||
///
|
||||
|
||||
API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`(使用代理时的 URL)。例如:
|
||||
文档界面还需要 OpenAPI 模式声明该 API 的 `server` 位于 `/api/v1`(代理后面)。例如:
|
||||
|
||||
```JSON hl_lines="4-8"
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
// More stuff here
|
||||
"servers": [
|
||||
{
|
||||
@@ -60,53 +161,53 @@ API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`(使用
|
||||
}
|
||||
```
|
||||
|
||||
本例中的 `Proxy` 是 **Traefik**,`server` 是运行 FastAPI 应用的 **Uvicorn**。
|
||||
在此示例中,“Proxy” 可以是 **Traefik** 之类的。服务器可以是用 **Uvicorn** 的 **FastAPI CLI** 运行你的 FastAPI 应用。
|
||||
|
||||
### 提供 `root_path`
|
||||
### 提供 `root_path` { #providing-the-root-path }
|
||||
|
||||
为此,要以如下方式使用命令行选项 `--root-path`:
|
||||
为此,你可以像下面这样使用命令行选项 `--root-path`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --root-path /api/v1
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Hypercorn 也支持 `--root-path `选项。
|
||||
如果你使用 Hypercorn,它也有 `--root-path` 选项。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
ASGI 规范定义的 `root_path` 就是为了这种用例。
|
||||
ASGI 规范为这种用例定义了 `root_path`。
|
||||
|
||||
并且 `--root-path` 命令行选项支持 `root_path`。
|
||||
命令行选项 `--root-path` 会提供该 `root_path`。
|
||||
|
||||
///
|
||||
|
||||
### 查看当前的 `root_path`
|
||||
### 查看当前的 `root_path` { #checking-the-current-root-path }
|
||||
|
||||
获取应用为每个请求使用的当前 `root_path`,这是 `scope` 字典的内容(也是 ASGI 规范的内容)。
|
||||
你可以获取应用在每个请求中使用的当前 `root_path`,它是 `scope` 字典的一部分(ASGI 规范的一部分)。
|
||||
|
||||
我们在这里的信息里包含 `roo_path` 只是为了演示。
|
||||
这里我们把它包含在响应消息中仅用于演示。
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *}
|
||||
|
||||
然后,用以下命令启动 Uvicorn:
|
||||
然后,如果你这样启动 Uvicorn:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --root-path /api/v1
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
返回的响应如下:
|
||||
响应类似于:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -115,19 +216,19 @@ $ uvicorn main:app --root-path /api/v1
|
||||
}
|
||||
```
|
||||
|
||||
### 在 FastAPI 应用里设置 `root_path`
|
||||
### 在 FastAPI 应用中设置 `root_path` { #setting-the-root-path-in-the-fastapi-app }
|
||||
|
||||
还有一种方案,如果不能提供 `--root-path` 或等效的命令行选项,则在创建 FastAPI 应用时要设置 `root_path` 参数。
|
||||
或者,如果你无法提供类似 `--root-path` 的命令行选项,你可以在创建 FastAPI 应用时设置参数 `root_path`:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *}
|
||||
|
||||
传递 `root_path` 给 `FastAPI` 与传递 `--root-path` 命令行选项给 Uvicorn 或 Hypercorn 一样。
|
||||
把 `root_path` 传给 `FastAPI` 等同于把命令行选项 `--root-path` 传给 Uvicorn 或 Hypercorn。
|
||||
|
||||
### 关于 `root_path`
|
||||
### 关于 `root_path` { #about-root-path }
|
||||
|
||||
注意,服务器(Uvicorn)只是把 `root_path` 传递给应用。
|
||||
请注意,服务器(Uvicorn)不会用这个 `root_path` 做别的事情,只会把它传给应用。
|
||||
|
||||
在浏览器中输入 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app 时能看到标准响应:</a>
|
||||
但是,如果你用浏览器打开 <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>,你会看到正常的响应:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -136,25 +237,25 @@ $ uvicorn main:app --root-path /api/v1
|
||||
}
|
||||
```
|
||||
|
||||
它不要求访问 `http://127.0.0.1:800/api/v1/app`。
|
||||
因此,它不会期望被访问于 `http://127.0.0.1:8000/api/v1/app`。
|
||||
|
||||
Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶部添加 `/api/v1` 前缀是代理要做的事情。
|
||||
Uvicorn 会期望代理以 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶部额外添加 `/api/v1` 前缀是代理的职责。
|
||||
|
||||
## 关于移除路径前缀的代理
|
||||
## 关于移除路径前缀的代理 { #about-proxies-with-a-stripped-path-prefix }
|
||||
|
||||
注意,移除路径前缀的代理只是配置代理的方式之一。
|
||||
请记住,移除路径前缀只是配置代理的一种方式。
|
||||
|
||||
大部分情况下,代理默认都不会移除路径前缀。
|
||||
在很多情况下,默认是代理不会移除路径前缀。
|
||||
|
||||
(未移除路径前缀时)代理监听 `https://myawesomeapp.com` 等对象,如果浏览器跳转到 `https://myawesomeapp.com/api/v1/app`,且服务器(例如 Uvicorn)监听 `http://127.0.0.1:8000` 代理(未移除路径前缀) 会在同样的路径:`http://127.0.0.1:8000/api/v1/app` 访问 Uvicorn。
|
||||
在这种情况下(没有移除路径前缀),代理会监听类似 `https://myawesomeapp.com`,当浏览器访问 `https://myawesomeapp.com/api/v1/app` 且你的服务器(例如 Uvicorn)监听 `http://127.0.0.1:8000` 时,代理(未移除路径前缀)会以相同路径访问 Uvicorn:`http://127.0.0.1:8000/api/v1/app`。
|
||||
|
||||
## 本地测试 Traefik
|
||||
## 使用 Traefik 进行本地测试 { #testing-locally-with-traefik }
|
||||
|
||||
您可以轻易地在本地使用 <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a> 运行移除路径前缀的试验。
|
||||
你可以很容易地使用 <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a> 在本地运行一个移除路径前缀的实验。
|
||||
|
||||
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">下载 Traefik</a>,这是一个二进制文件,需要解压文件,并在 Terminal 中直接运行。
|
||||
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">下载 Traefik</a>,它是一个单独的二进制文件,你可以解压压缩包并直接在终端中运行。
|
||||
|
||||
然后创建包含如下内容的 `traefik.toml` 文件:
|
||||
然后创建一个 `traefik.toml` 文件,内容如下:
|
||||
|
||||
```TOML hl_lines="3"
|
||||
[entryPoints]
|
||||
@@ -166,15 +267,15 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶
|
||||
filename = "routes.toml"
|
||||
```
|
||||
|
||||
这个文件把 Traefik 监听端口设置为 `9999`,并设置要使用另一个文件 `routes.toml`。
|
||||
这告诉 Traefik 监听端口 9999,并使用另一个文件 `routes.toml`。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
使用端口 9999 代替标准的 HTTP 端口 80,这样就不必使用管理员权限运行(`sudo`)。
|
||||
我们使用 9999 端口而不是标准 HTTP 端口 80,这样你就不需要用管理员(`sudo`)权限运行。
|
||||
|
||||
///
|
||||
|
||||
接下来,创建 `routes.toml`:
|
||||
现在创建另一个文件 `routes.toml`:
|
||||
|
||||
```TOML hl_lines="5 12 20"
|
||||
[http]
|
||||
@@ -201,9 +302,9 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶
|
||||
|
||||
这个文件配置 Traefik 使用路径前缀 `/api/v1`。
|
||||
|
||||
然后,它把请求重定位到运行在 `http://127.0.0.1:8000` 上的 Uvicorn。
|
||||
随后 Traefik 会把请求转发到运行在 `http://127.0.0.1:8000` 的 Uvicorn。
|
||||
|
||||
现在,启动 Traefik:
|
||||
现在启动 Traefik:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -215,21 +316,21 @@ INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
|
||||
|
||||
</div>
|
||||
|
||||
接下来,使用 Uvicorn 启动应用,并使用 `--root-path` 选项:
|
||||
然后使用 `--root-path` 选项启动你的应用:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --root-path /api/v1
|
||||
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### 查看响应
|
||||
### 查看响应 { #check-the-responses }
|
||||
|
||||
访问含 Uvicorn 端口的 URL:<a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app,就能看到标准响应:</a>
|
||||
现在,如果你访问 Uvicorn 端口对应的 URL:<a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a>,你会看到正常响应:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -240,13 +341,13 @@ $ uvicorn main:app --root-path /api/v1
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
注意,就算访问 `http://127.0.0.1:8000/app`,也显示从选项 `--root-path` 中提取的 `/api/v1`,这是 `root_path` 的值。
|
||||
注意,尽管你是通过 `http://127.0.0.1:8000/app` 访问,它仍显示 `root_path` 为 `/api/v1`,该值来自 `--root-path` 选项。
|
||||
|
||||
///
|
||||
|
||||
打开含 Traefik 端口的 URL,包含路径前缀:<a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app。</a>
|
||||
现在打开包含路径前缀、使用 Traefik 端口的 URL:<a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app</a>。
|
||||
|
||||
得到同样的响应:
|
||||
我们得到相同的响应:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -255,57 +356,57 @@ $ uvicorn main:app --root-path /api/v1
|
||||
}
|
||||
```
|
||||
|
||||
但这一次 URL 包含了代理提供的路径前缀:`/api/v1`。
|
||||
但这次 URL 中带有代理提供的前缀路径:`/api/v1`。
|
||||
|
||||
当然,这是通过代理访问应用的方式,因此,路径前缀 `/app/v1` 版本才是**正确**的。
|
||||
当然,这里的想法是每个人都通过代理访问应用,因此带有路径前缀 `/api/v1` 的版本才是“正确”的。
|
||||
|
||||
而不带路径前缀的版本(`http://127.0.0.1:8000/app`),则由 Uvicorn 直接提供,专供*代理*(Traefik)访问。
|
||||
而不带路径前缀的版本(`http://127.0.0.1:8000/app`)由 Uvicorn 直接提供,仅供_代理_(Traefik)访问。
|
||||
|
||||
这演示了代理(Traefik)如何使用路径前缀,以及服务器(Uvicorn)如何使用选项 `--root-path` 中的 `root_path`。
|
||||
这说明了代理(Traefik)如何使用路径前缀,以及服务器(Uvicorn)如何使用 `--root-path` 选项提供的 `root_path`。
|
||||
|
||||
### 查看文档
|
||||
### 查看文档界面 { #check-the-docs-ui }
|
||||
|
||||
但这才是有趣的地方 ✨
|
||||
有趣的部分来了。✨
|
||||
|
||||
访问应用的**官方**方式是通过含路径前缀的代理。因此,不出所料,如果没有在 URL 中添加路径前缀,直接访问通过 Uvicorn 运行的 API 文档,不能正常访问,因为需要通过代理才能访问。
|
||||
访问应用的“官方”方式应该是通过我们定义的带有路径前缀的代理。因此,正如预期的那样,如果你尝试不带路径前缀、直接由 Uvicorn 提供的文档界面,它将无法工作,因为它期望通过代理访问。
|
||||
|
||||
输入 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs 查看 API 文档:</a>
|
||||
你可以在 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> 查看:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image01.png">
|
||||
|
||||
但输入**官方**链接 `/api/v1/docs`,并使用端口 `9999` 访问 API 文档,就能正常运行了!🎉
|
||||
但如果我们在“官方”URL(代理端口为 `9999`)的 `/api/v1/docs` 访问文档界面,它就能正常工作!🎉
|
||||
|
||||
输入 <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs 查看文档:</a>
|
||||
你可以在 <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> 查看:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image02.png">
|
||||
|
||||
一切正常。 ✔️
|
||||
完全符合我们的预期。✔️
|
||||
|
||||
这是因为 FastAPI 在 OpenAPI 里使用 `root_path` 提供的 URL 创建默认 `server`。
|
||||
这是因为 FastAPI 使用该 `root_path` 在 OpenAPI 中创建默认的 `server`,其 URL 来自 `root_path`。
|
||||
|
||||
## 附加的服务器
|
||||
## 附加的服务器 { #additional-servers }
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
此用例较难,可以跳过。
|
||||
这是一个更高级的用例,可以跳过。
|
||||
|
||||
///
|
||||
|
||||
默认情况下,**FastAPI** 使用 `root_path` 的链接在 OpenAPI 概图中创建 `server`。
|
||||
默认情况下,**FastAPI** 会在 OpenAPI 模式中使用 `root_path` 的 URL 创建一个 `server`。
|
||||
|
||||
但也可以使用其它备选 `servers`,例如,需要同一个 API 文档与 staging 和生产环境交互。
|
||||
但你也可以提供其他备选的 `servers`,例如你希望让“同一个”文档界面同时与预发布环境和生产环境交互。
|
||||
|
||||
如果传递自定义 `servers` 列表,并有 `root_path`( 因为 API 使用了代理),**FastAPI** 会在列表开头使用这个 `root_path` 插入**服务器**。
|
||||
如果你传入了自定义的 `servers` 列表,并且存在 `root_path`(因为你的 API 位于代理后面),**FastAPI** 会在列表开头插入一个使用该 `root_path` 的“server”。
|
||||
|
||||
例如:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
|
||||
{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *}
|
||||
|
||||
这段代码生产如下 OpenAPI 概图:
|
||||
会生成如下的 OpenAPI 模式:
|
||||
|
||||
```JSON hl_lines="5-7"
|
||||
{
|
||||
"openapi": "3.0.2",
|
||||
"openapi": "3.1.0",
|
||||
// More stuff here
|
||||
"servers": [
|
||||
{
|
||||
@@ -328,30 +429,38 @@ $ uvicorn main:app --root-path /api/v1
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
注意,自动生成服务器时,`url` 的值 `/api/v1` 提取自 `roog_path`。
|
||||
注意这个自动生成的服务器,`url` 的值为 `/api/v1`,取自 `root_path`。
|
||||
|
||||
///
|
||||
|
||||
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs 的 API 文档所示如下:</a>
|
||||
在 <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs</a> 的文档界面中,它看起来是这样的:
|
||||
|
||||
<img src="/img/tutorial/behind-a-proxy/image03.png">
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
API 文档与所选的服务器进行交互。
|
||||
文档界面会与你所选择的服务器交互。
|
||||
|
||||
///
|
||||
|
||||
### 从 `root_path` 禁用自动服务器
|
||||
/// note | 技术细节
|
||||
|
||||
如果不想让 **FastAPI** 包含使用 `root_path` 的自动服务器,则要使用参数 `root_path_in_servers=False`:
|
||||
OpenAPI 规范中的 `servers` 属性是可选的。
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
|
||||
如果你没有指定 `servers` 参数,并且 `root_path` 等于 `/`,则默认情况下,生成的 OpenAPI 模式中会完全省略 `servers` 属性,这等价于只有一个 `url` 值为 `/` 的服务器。
|
||||
|
||||
这样,就不会在 OpenAPI 概图中包含服务器了。
|
||||
///
|
||||
|
||||
## 挂载子应用
|
||||
### 从 `root_path` 禁用自动服务器 { #disable-automatic-server-from-root-path }
|
||||
|
||||
如需挂载子应用(详见 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}),也要通过 `root_path` 使用代理,这与正常应用一样,别无二致。
|
||||
如果你不希望 **FastAPI** 包含一个使用 `root_path` 的自动服务器,可以使用参数 `root_path_in_servers=False`:
|
||||
|
||||
FastAPI 在内部使用 `root_path`,因此子应用也可以正常运行。✨
|
||||
{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *}
|
||||
|
||||
这样它就不会被包含到 OpenAPI 模式中。
|
||||
|
||||
## 挂载子应用 { #mounting-a-sub-application }
|
||||
|
||||
如果你需要在使用带有 `root_path` 的代理时挂载一个子应用(参见 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}),你可以像预期的那样正常操作。
|
||||
|
||||
FastAPI 会在内部智能地使用 `root_path`,因此它可以直接正常工作。✨
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
# 自定义响应 - HTML,流,文件和其他
|
||||
# 自定义响应 - HTML、流、文件等 { #custom-response-html-stream-file-others }
|
||||
|
||||
**FastAPI** 默认会使用 `JSONResponse` 返回响应。
|
||||
|
||||
你可以通过直接返回 `Response` 来重载它,参见 [直接返回响应](response-directly.md){.internal-link target=_blank}。
|
||||
|
||||
但如果你直接返回 `Response`,返回数据不会自动转换,也不会自动生成文档(例如,在 HTTP 头 `Content-Type` 中包含特定的「媒体类型」作为生成的 OpenAPI 的一部分)。
|
||||
但如果你直接返回一个 `Response`(或其任意子类,比如 `JSONResponse`),返回数据不会自动转换(即使你声明了 `response_model`),也不会自动生成文档(例如,在生成的 OpenAPI 中,HTTP 头 `Content-Type` 里的特定「媒体类型」不会被包含)。
|
||||
|
||||
你还可以在 *路径操作装饰器* 中声明你想用的 `Response`。
|
||||
你还可以在 *路径操作装饰器* 中通过 `response_class` 参数声明要使用的 `Response`(例如任意 `Response` 子类)。
|
||||
|
||||
你从 *路径操作函数* 中返回的内容将被放在该 `Response` 中。
|
||||
|
||||
并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或者 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。
|
||||
并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。
|
||||
|
||||
/// note | 说明
|
||||
/// note | 注意
|
||||
|
||||
如果你使用不带有任何媒体类型的响应类,FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。
|
||||
如果你使用不带有任何媒体类型的响应类,FastAPI 会认为你的响应没有任何内容,所以不会在生成的 OpenAPI 文档中记录响应格式。
|
||||
|
||||
///
|
||||
|
||||
## 使用 `ORJSONResponse`
|
||||
## 使用 `ORJSONResponse` { #use-orjsonresponse }
|
||||
|
||||
例如,如果你需要压榨性能,你可以安装并使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 并将响应设置为 `ORJSONResponse`。
|
||||
|
||||
导入你想要使用的 `Response` 类(子类)然后在 *路径操作装饰器* 中声明它。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
|
||||
对于较大的响应,直接返回一个 `Response` 会比返回一个字典快得多。
|
||||
|
||||
/// info | 提示
|
||||
这是因为默认情况下,FastAPI 会检查其中的每一项并确保它可以被序列化为 JSON,使用教程中解释的相同 [JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank}。这正是它允许你返回「任意对象」的原因,例如数据库模型。
|
||||
|
||||
但如果你确定你返回的内容是「可以用 JSON 序列化」的,你可以将它直接传给响应类,从而避免在传给响应类之前先通过 `jsonable_encoder` 带来的额外开销。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *}
|
||||
|
||||
/// info | 信息
|
||||
|
||||
参数 `response_class` 也会用来定义响应的「媒体类型」。
|
||||
|
||||
@@ -36,22 +42,22 @@
|
||||
|
||||
///
|
||||
|
||||
/// tip | 小贴士
|
||||
/// tip | 提示
|
||||
|
||||
`ORJSONResponse` 目前只在 FastAPI 中可用,而在 Starlette 中不可用。
|
||||
|
||||
///
|
||||
|
||||
## HTML 响应
|
||||
## HTML 响应 { #html-response }
|
||||
|
||||
使用 `HTMLResponse` 来从 **FastAPI** 中直接返回一个 HTML 响应。
|
||||
|
||||
* 导入 `HTMLResponse`。
|
||||
* 将 `HTMLResponse` 作为你的 *路径操作* 的 `response_class` 参数传入。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
|
||||
{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *}
|
||||
|
||||
/// info | 提示
|
||||
/// info | 信息
|
||||
|
||||
参数 `response_class` 也会用来定义响应的「媒体类型」。
|
||||
|
||||
@@ -61,13 +67,13 @@
|
||||
|
||||
///
|
||||
|
||||
### 返回一个 `Response`
|
||||
### 返回一个 `Response` { #return-a-response }
|
||||
|
||||
正如你在 [直接返回响应](response-directly.md){.internal-link target=_blank} 中了解到的,你也可以通过直接返回响应在 *路径操作* 中直接重载响应。
|
||||
|
||||
和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
|
||||
{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *}
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
@@ -75,33 +81,33 @@
|
||||
|
||||
///
|
||||
|
||||
/// info | 提示
|
||||
/// info | 信息
|
||||
|
||||
当然,实际的 `Content-Type` 头,状态码等等,将来自于你返回的 `Response` 对象。
|
||||
当然,实际的 `Content-Type` 头、状态码等等,将来自于你返回的 `Response` 对象。
|
||||
|
||||
///
|
||||
|
||||
### OpenAPI 中的文档和重载 `Response`
|
||||
### 在 OpenAPI 中文档化并重载 `Response` { #document-in-openapi-and-override-response }
|
||||
|
||||
如果你想要在函数内重载响应,但是同时在 OpenAPI 中文档化「媒体类型」,你可以使用 `response_class` 参数并返回一个 `Response` 对象。
|
||||
|
||||
接着 `response_class` 参数只会被用来文档化 OpenAPI 的 *路径操作*,你的 `Response` 用来返回响应。
|
||||
|
||||
### 直接返回 `HTMLResponse`
|
||||
#### 直接返回 `HTMLResponse` { #return-an-htmlresponse-directly }
|
||||
|
||||
比如像这样:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial004.py hl[7,23,21] *}
|
||||
{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *}
|
||||
|
||||
在这个例子中,函数 `generate_html_response()` 已经生成并返回 `Response` 对象而不是在 `str` 中返回 HTML。
|
||||
|
||||
通过返回函数 `generate_html_response()` 的调用结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response` 对象,
|
||||
通过返回函数 `generate_html_response()` 的调用结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response` 对象。
|
||||
|
||||
但如果你在 `response_class` 中也传入了 `HTMLResponse`,**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML。
|
||||
但如果你在 `response_class` 中也传入了 `HTMLResponse`,**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML:
|
||||
|
||||
<img src="/img/tutorial/custom-response/image01.png">
|
||||
|
||||
## 可用响应
|
||||
## 可用响应 { #available-responses }
|
||||
|
||||
这里有一些可用的响应。
|
||||
|
||||
@@ -115,7 +121,7 @@
|
||||
|
||||
///
|
||||
|
||||
### `Response`
|
||||
### `Response` { #response }
|
||||
|
||||
其他全部的响应都继承自主类 `Response`。
|
||||
|
||||
@@ -128,77 +134,115 @@
|
||||
* `headers` - 一个由字符串组成的 `dict`。
|
||||
* `media_type` - 一个给出媒体类型的 `str`,比如 `"text/html"`。
|
||||
|
||||
FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。
|
||||
FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 `media_type` 的 Content-Type 头,并为文本类型附加一个字符集。
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
|
||||
### `HTMLResponse`
|
||||
### `HTMLResponse` { #htmlresponse }
|
||||
|
||||
如上文所述,接受文本或字节并返回 HTML 响应。
|
||||
|
||||
### `PlainTextResponse`
|
||||
### `PlainTextResponse` { #plaintextresponse }
|
||||
|
||||
接受文本或字节并返回纯文本响应。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
|
||||
{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *}
|
||||
|
||||
### `JSONResponse`
|
||||
### `JSONResponse` { #jsonresponse }
|
||||
|
||||
接受数据并返回一个 `application/json` 编码的响应。
|
||||
|
||||
如上文所述,这是 **FastAPI** 中使用的默认响应。
|
||||
|
||||
### `ORJSONResponse`
|
||||
### `ORJSONResponse` { #orjsonresponse }
|
||||
|
||||
如上文所述,`ORJSONResponse` 是一个使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 的快速的可选 JSON 响应。
|
||||
|
||||
/// info | 信息
|
||||
|
||||
### `UJSONResponse`
|
||||
这需要先安装 `orjson`,例如使用 `pip install orjson`。
|
||||
|
||||
///
|
||||
|
||||
### `UJSONResponse` { #ujsonresponse }
|
||||
|
||||
`UJSONResponse` 是一个使用 <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a> 的可选 JSON 响应。
|
||||
|
||||
/// info | 信息
|
||||
|
||||
这需要先安装 `ujson`,例如使用 `pip install ujson`。
|
||||
|
||||
///
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
在处理某些边缘情况时,`ujson` 不如 Python 的内置实现那么谨慎。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
|
||||
{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *}
|
||||
|
||||
/// tip | 小贴士
|
||||
/// tip | 提示
|
||||
|
||||
`ORJSONResponse` 可能是一个更快的选择。
|
||||
|
||||
///
|
||||
|
||||
### `RedirectResponse`
|
||||
### `RedirectResponse` { #redirectresponse }
|
||||
|
||||
返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)。
|
||||
返回 HTTP 重定向。默认情况下使用 307 状态码(临时重定向)。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
|
||||
你可以直接返回一个 `RedirectResponse`:
|
||||
|
||||
### `StreamingResponse`
|
||||
{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *}
|
||||
|
||||
---
|
||||
|
||||
或者你可以把它用于 `response_class` 参数:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *}
|
||||
|
||||
如果你这么做,那么你可以在 *路径操作* 函数中直接返回 URL。
|
||||
|
||||
在这种情况下,将使用 `RedirectResponse` 的默认 `status_code`,即 `307`。
|
||||
|
||||
---
|
||||
|
||||
你也可以将 `status_code` 参数和 `response_class` 参数结合使用:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *}
|
||||
|
||||
### `StreamingResponse` { #streamingresponse }
|
||||
|
||||
采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
|
||||
{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *}
|
||||
|
||||
#### 对类似文件的对象使用 `StreamingResponse`
|
||||
#### 对类似文件的对象使用 `StreamingResponse` { #using-streamingresponse-with-file-like-objects }
|
||||
|
||||
如果您有类似文件的对象(例如,由 `open()` 返回的对象),则可以在 `StreamingResponse` 中将其返回。
|
||||
如果您有一个<a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">类文件</a>对象(例如由 `open()` 返回的对象),你可以创建一个生成器函数来迭代该类文件对象。
|
||||
|
||||
包括许多与云存储,视频处理等交互的库。
|
||||
这样,你就不必先把它全部读入内存,可以将该生成器函数传给 `StreamingResponse` 并返回它。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
|
||||
这也包括许多与云存储、视频处理等交互的库。
|
||||
|
||||
/// tip | 小贴士
|
||||
{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *}
|
||||
|
||||
1. 这是生成器函数。之所以是「生成器函数」,是因为它内部包含 `yield` 语句。
|
||||
2. 通过使用 `with` 代码块,我们可以确保在生成器函数完成后关闭类文件对象。因此,在它完成发送响应之后会被关闭。
|
||||
3. 这个 `yield from` 告诉函数去迭代名为 `file_like` 的那个对象。然后,对于每个被迭代出来的部分,都把该部分作为来自这个生成器函数(`iterfile`)的值再 `yield` 出去。
|
||||
|
||||
因此,它是一个把「生成」工作内部转交给其他东西的生成器函数。
|
||||
|
||||
通过这种方式,我们可以把它放在 `with` 代码块中,从而确保类文件对象在结束后被关闭。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
注意在这里,因为我们使用的是不支持 `async` 和 `await` 的标准 `open()`,我们使用普通的 `def` 声明了路径操作。
|
||||
|
||||
///
|
||||
|
||||
### `FileResponse`
|
||||
### `FileResponse` { #fileresponse }
|
||||
|
||||
异步传输文件作为响应。
|
||||
|
||||
@@ -209,10 +253,60 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它
|
||||
* `media_type` - 给出媒体类型的字符串。如果未设置,则文件名或路径将用于推断媒体类型。
|
||||
* `filename` - 如果给出,它将包含在响应的 `Content-Disposition` 中。
|
||||
|
||||
文件响应将包含适当的 `Content-Length`,`Last-Modified` 和 `ETag` 的响应头。
|
||||
文件响应将包含适当的 `Content-Length`、`Last-Modified` 和 `ETag` 响应头。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
|
||||
{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *}
|
||||
|
||||
## 额外文档
|
||||
你也可以使用 `response_class` 参数:
|
||||
|
||||
您还可以使用 `response` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。
|
||||
{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *}
|
||||
|
||||
在这种情况下,你可以在 *路径操作* 函数中直接返回文件路径。
|
||||
|
||||
## 自定义响应类 { #custom-response-class }
|
||||
|
||||
你可以创建你自己的自定义响应类,继承自 `Response` 并使用它。
|
||||
|
||||
例如,假设你想使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>,但要使用内置 `ORJSONResponse` 类没有启用的一些自定义设置。
|
||||
|
||||
假设你想让它返回带缩进、格式化的 JSON,因此你想使用 orjson 选项 `orjson.OPT_INDENT_2`。
|
||||
|
||||
你可以创建一个 `CustomORJSONResponse`。你需要做的主要事情是实现一个返回 `bytes` 的 `Response.render(content)` 方法:
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *}
|
||||
|
||||
现在,不再是返回:
|
||||
|
||||
```json
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
…这个响应将返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Hello World"
|
||||
}
|
||||
```
|
||||
|
||||
当然,你很可能会找到比格式化 JSON 更好的方式来利用这一点。😉
|
||||
|
||||
## 默认响应类 { #default-response-class }
|
||||
|
||||
在创建 **FastAPI** 类实例或 `APIRouter` 时,你可以指定默认要使用的响应类。
|
||||
|
||||
用于定义它的参数是 `default_response_class`。
|
||||
|
||||
在下面的示例中,**FastAPI** 会在所有 *路径操作* 中默认使用 `ORJSONResponse`,而不是 `JSONResponse`。
|
||||
|
||||
{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
你仍然可以像之前一样在 *路径操作* 中重载 `response_class`。
|
||||
|
||||
///
|
||||
|
||||
## 额外文档 { #additional-documentation }
|
||||
|
||||
你还可以使用 `responses` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。
|
||||
|
||||
@@ -1,97 +1,87 @@
|
||||
# 使用数据类
|
||||
# 使用数据类 { #using-dataclasses }
|
||||
|
||||
FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 模型声明请求与响应。
|
||||
FastAPI 基于 **Pydantic** 构建,我已经向你展示过如何使用 Pydantic 模型声明请求与响应。
|
||||
|
||||
但 FastAPI 还可以使用数据类(<a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>):
|
||||
但 FastAPI 也支持以相同方式使用 <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *}
|
||||
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
|
||||
|
||||
这还是借助于 **Pydantic** 及其<a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">内置的 `dataclasses`</a>。
|
||||
这仍然得益于 **Pydantic**,因为它对 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses` 的内置支持</a>。
|
||||
|
||||
因此,即便上述代码没有显式使用 Pydantic,FastAPI 仍会使用 Pydantic 把标准数据类转换为 Pydantic 数据类(`dataclasses`)。
|
||||
因此,即便上面的代码没有显式使用 Pydantic,FastAPI 也会使用 Pydantic 将那些标准数据类转换为 Pydantic 风格的 dataclasses。
|
||||
|
||||
并且,它仍然支持以下功能:
|
||||
|
||||
* 数据验证
|
||||
* 数据序列化
|
||||
* 数据存档等
|
||||
* 数据文档等
|
||||
|
||||
数据类的和运作方式与 Pydantic 模型相同。实际上,它的底层使用的也是 Pydantic。
|
||||
这与使用 Pydantic 模型时的工作方式相同。而且底层实际上也是借助 Pydantic 实现的。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
注意,数据类不支持 Pydantic 模型的所有功能。
|
||||
请注意,数据类不能完成 Pydantic 模型能做的所有事情。
|
||||
|
||||
因此,开发时仍需要使用 Pydantic 模型。
|
||||
因此,你可能仍然需要使用 Pydantic 模型。
|
||||
|
||||
但如果数据类很多,这一技巧能给 FastAPI 开发 Web API 增添不少助力。🤓
|
||||
但如果你已有一堆数据类,这个技巧可以让它们很好地为使用 FastAPI 的 Web API 所用。🤓
|
||||
|
||||
///
|
||||
|
||||
## `response_model` 使用数据类
|
||||
## 在 `response_model` 中使用数据类 { #dataclasses-in-response-model }
|
||||
|
||||
在 `response_model` 参数中使用 `dataclasses`:
|
||||
你也可以在 `response_model` 参数中使用 `dataclasses`:
|
||||
|
||||
{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *}
|
||||
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *}
|
||||
|
||||
本例把数据类自动转换为 Pydantic 数据类。
|
||||
该数据类会被自动转换为 Pydantic 的数据类。
|
||||
|
||||
API 文档中也会显示相关概图:
|
||||
这样,它的模式会显示在 API 文档界面中:
|
||||
|
||||
<img src="/img/tutorial/dataclasses/image01.png">
|
||||
|
||||
## 在嵌套数据结构中使用数据类
|
||||
## 在嵌套数据结构中使用数据类 { #dataclasses-in-nested-data-structures }
|
||||
|
||||
您还可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。
|
||||
你也可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。
|
||||
|
||||
还有一些情况也可以使用 Pydantic 的 `dataclasses`。例如,在 API 文档中显示错误。
|
||||
在某些情况下,你可能仍然需要使用 Pydantic 的 `dataclasses` 版本。例如,如果自动生成的 API 文档出现错误。
|
||||
|
||||
本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`:
|
||||
在这种情况下,你可以直接把标准的 `dataclasses` 替换为 `pydantic.dataclasses`,它是一个可直接替换的实现:
|
||||
|
||||
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
|
||||
{!../../docs_src/dataclasses_/tutorial003.py!}
|
||||
```
|
||||
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *}
|
||||
|
||||
1. 本例依然要从标准的 `dataclasses` 中导入 `field`;
|
||||
1. 我们仍然从标准库的 `dataclasses` 导入 `field`。
|
||||
2. `pydantic.dataclasses` 是 `dataclasses` 的可直接替换版本。
|
||||
3. `Author` 数据类包含一个由 `Item` 数据类组成的列表。
|
||||
4. `Author` 数据类被用作 `response_model` 参数。
|
||||
5. 你可以将其它标准类型注解与数据类一起用作请求体。
|
||||
|
||||
在本例中,它是一个 `Item` 数据类列表。
|
||||
6. 这里我们返回一个字典,里面的 `items` 是一个数据类列表。
|
||||
|
||||
FastAPI 仍然能够将数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON。
|
||||
7. 这里的 `response_model` 使用了 “`Author` 数据类列表” 的类型注解。
|
||||
|
||||
同样,你可以将 `dataclasses` 与标准类型注解组合使用。
|
||||
8. 注意,这个 *路径操作函数* 使用的是常规的 `def` 而不是 `async def`。
|
||||
|
||||
一如既往,在 FastAPI 中你可以按需组合 `def` 和 `async def`。
|
||||
|
||||
如果需要回顾何时用哪一个,请查看关于 [`async` 和 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 的文档中的 _“急不可待?”_ 一节。
|
||||
9. 这个 *路径操作函数* 返回的不是数据类(当然也可以返回数据类),而是包含内部数据的字典列表。
|
||||
|
||||
FastAPI 会使用(包含数据类的)`response_model` 参数来转换响应。
|
||||
|
||||
2. 使用 `pydantic.dataclasses` 直接替换 `dataclasses`;
|
||||
你可以将 `dataclasses` 与其它类型注解以多种不同方式组合,来构建复杂的数据结构。
|
||||
|
||||
3. `Author` 数据类包含 `Item` 数据类列表;
|
||||
更多细节请参考上面代码中的内联注释提示。
|
||||
|
||||
4. `Author` 数据类用于 `response_model` 参数;
|
||||
## 深入学习 { #learn-more }
|
||||
|
||||
5. 其它带有数据类的标准类型注解也可以作为请求体;
|
||||
你还可以把 `dataclasses` 与其它 Pydantic 模型组合、从它们继承、把它们包含到你自己的模型中等。
|
||||
|
||||
本例使用的是 `Item` 数据类列表;
|
||||
想了解更多,请查看 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">Pydantic 关于 dataclasses 的文档</a>。
|
||||
|
||||
6. 这行代码返回的是包含 `items` 的字典,`items` 是数据类列表;
|
||||
## 版本 { #version }
|
||||
|
||||
FastAPI 仍能把数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON;
|
||||
|
||||
7. 这行代码中,`response_model` 的类型注解是 `Author` 数据类列表;
|
||||
|
||||
再一次,可以把 `dataclasses` 与标准类型注解一起使用;
|
||||
|
||||
8. 注意,*路径操作函数*使用的是普通函数,不是异步函数;
|
||||
|
||||
与往常一样,在 FastAPI 中,可以按需组合普通函数与异步函数;
|
||||
|
||||
如果不清楚何时使用异步函数或普通函数,请参阅**急不可待?**一节中对 <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank" class="internal-link">`async` 与 `await`</a> 的说明;
|
||||
|
||||
9. *路径操作函数*返回的不是数据类(虽然它可以返回数据类),而是返回内含数据的字典列表;
|
||||
|
||||
FastAPI 使用(包含数据类的) `response_model` 参数转换响应。
|
||||
|
||||
把 `dataclasses` 与其它类型注解组合在一起,可以组成不同形式的复杂数据结构。
|
||||
|
||||
更多内容详见上述代码内的注释。
|
||||
|
||||
## 深入学习
|
||||
|
||||
您还可以把 `dataclasses` 与其它 Pydantic 模型组合在一起,继承合并的模型,把它们包含在您自己的模型里。
|
||||
|
||||
详见 <a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/" class="external-link" target="_blank">Pydantic 官档 - 数据类</a>。
|
||||
|
||||
## 版本
|
||||
|
||||
本章内容自 FastAPI `0.67.0` 版起生效。🔖
|
||||
自 FastAPI 版本 `0.67.0` 起可用。🔖
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# 生命周期事件
|
||||
# 生命周期事件 { #lifespan-events }
|
||||
|
||||
你可以定义在应用**启动**前执行的逻辑(代码)。这意味着在应用**开始接收请求**之前,这些代码只会被执行**一次**。
|
||||
|
||||
同样地,你可以定义在应用**关闭**时应执行的逻辑。在这种情况下,这段代码将在**处理可能的多次请求后**执行**一次**。
|
||||
|
||||
因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**("生命周期"这个词很重要😉)。
|
||||
因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**(“生命周期”这个词很重要😉)。
|
||||
|
||||
这对于设置你需要在整个应用中使用的**资源**非常有用,这些资源在请求之间**共享**,你可能需要在之后进行**释放**。例如,数据库连接池,或加载一个共享的机器学习模型。
|
||||
|
||||
## 用例
|
||||
## 用例 { #use-case }
|
||||
|
||||
让我们从一个示例用例开始,看看如何解决它。
|
||||
让我们从一个示例**用例**开始,看看如何用它来解决问题。
|
||||
|
||||
假设你有几个**机器学习的模型**,你想要用它们来处理请求。
|
||||
假设你有几个**机器学习的模型**,你想要用它们来处理请求。🤖
|
||||
|
||||
相同的模型在请求之间是共享的,因此并非每个请求或每个用户各自拥有一个模型。
|
||||
|
||||
@@ -20,19 +20,17 @@
|
||||
|
||||
你可以在模块/文件的顶部加载它,但这也意味着即使你只是在运行一个简单的自动化测试,它也会**加载模型**,这样测试将**变慢**,因为它必须在能够独立运行代码的其他部分之前等待模型加载完成。
|
||||
|
||||
这就是我们要解决的问题——在处理请求前加载模型,但只是在应用开始接收请求前,而不是代码执行时。
|
||||
这就是我们要解决的问题——在处理请求前加载模型,但只是在应用开始接收请求前,而不是在代码被加载时。
|
||||
|
||||
## 生命周期 lifespan
|
||||
## Lifespan { #lifespan }
|
||||
|
||||
你可以使用`FastAPI()`应用的`lifespan`参数和一个上下文管理器(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。
|
||||
你可以使用 `FastAPI` 应用的 `lifespan` 参数和一个“上下文管理器”(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。
|
||||
|
||||
让我们从一个例子开始,然后详细介绍。
|
||||
|
||||
我们使用`yield`创建了一个异步函数`lifespan()`像这样:
|
||||
我们使用 `yield` 创建了一个异步函数 `lifespan()` 像这样:
|
||||
|
||||
```Python hl_lines="16 19"
|
||||
{!../../docs_src/events/tutorial003.py!}
|
||||
```
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}
|
||||
|
||||
在这里,我们在 `yield` 之前将(虚拟的)模型函数放入机器学习模型的字典中,以此模拟加载模型的耗时**启动**操作。这段代码将在应用程序**开始处理请求之前**执行,即**启动**期间。
|
||||
|
||||
@@ -40,35 +38,31 @@
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
**关闭**事件只会在你停止应用时触发。
|
||||
**关闭**事件会在你**停止**应用时发生。
|
||||
|
||||
可能你需要启动一个新版本,或者你只是你厌倦了运行它。 🤷
|
||||
可能你需要启动一个新版本,或者你只是厌倦了运行它。 🤷
|
||||
|
||||
///
|
||||
|
||||
## 生命周期函数
|
||||
### 生命周期函数 { #lifespan-function }
|
||||
|
||||
首先要注意的是,我们定义了一个带有 `yield` 的异步函数。这与带有 `yield` 的依赖项非常相似。
|
||||
|
||||
```Python hl_lines="14-19"
|
||||
{!../../docs_src/events/tutorial003.py!}
|
||||
```
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}
|
||||
|
||||
这个函数在 `yield`之前的部分,会在应用启动前执行。
|
||||
这个函数在 `yield` 之前的部分,会在应用启动前执行。
|
||||
|
||||
剩下的部分在 `yield` 之后,会在应用完成后执行。
|
||||
|
||||
## 异步上下文管理器
|
||||
### 异步上下文管理器 { #async-context-manager }
|
||||
|
||||
如你所见,这个函数有一个装饰器 `@asynccontextmanager` 。
|
||||
如你所见,这个函数有一个装饰器 `@asynccontextmanager`。
|
||||
|
||||
它将函数转化为所谓的“**异步上下文管理器**”。
|
||||
|
||||
```Python hl_lines="1 13"
|
||||
{!../../docs_src/events/tutorial003.py!}
|
||||
```
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}
|
||||
|
||||
在 Python 中, **上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。
|
||||
在 Python 中,**上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。
|
||||
|
||||
```Python
|
||||
with open("file.txt") as file:
|
||||
@@ -82,21 +76,19 @@ async with lifespan(app):
|
||||
await do_stuff()
|
||||
```
|
||||
|
||||
你可以像上面一样创建了一个上下文管理器或者异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。
|
||||
你可以像上面一样创建一个上下文管理器或者异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。
|
||||
|
||||
但在我们上面的例子里,我们并不是直接使用,而是传递给 FastAPI 来供其使用。
|
||||
|
||||
`FastAPI()` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的上下文管理器 `lifespan` 传给它。
|
||||
`FastAPI` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的异步上下文管理器 `lifespan` 传给它。
|
||||
|
||||
```Python hl_lines="22"
|
||||
{!../../docs_src/events/tutorial003.py!}
|
||||
```
|
||||
{* ../../docs_src/events/tutorial003_py39.py hl[22] *}
|
||||
|
||||
## 替代事件(弃用)
|
||||
## 替代事件(弃用) { #alternative-events-deprecated }
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
配置**启动**和**关闭**事件的推荐方法是使用 `FastAPI()` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`)事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。
|
||||
配置**启动**和**关闭**的推荐方法是使用 `FastAPI` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`)事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。
|
||||
|
||||
你可以跳过这一部分。
|
||||
|
||||
@@ -104,70 +96,70 @@ async with lifespan(app):
|
||||
|
||||
有一种替代方法可以定义在**启动**和**关闭**期间执行的逻辑。
|
||||
|
||||
**FastAPI** 支持定义在应用启动前,或应用关闭时执行的事件处理器(函数)。
|
||||
你可以定义在应用启动前或应用关闭时需要执行的事件处理器(函数)。
|
||||
|
||||
事件函数既可以声明为异步函数(`async def`),也可以声明为普通函数(`def`)。
|
||||
|
||||
### `startup` 事件
|
||||
### `startup` 事件 { #startup-event }
|
||||
|
||||
使用 `startup` 事件声明 `app` 启动前运行的函数:
|
||||
使用事件 `"startup"` 声明一个在应用启动前运行的函数:
|
||||
|
||||
{* ../../docs_src/events/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/events/tutorial001_py39.py hl[8] *}
|
||||
|
||||
本例中,`startup` 事件处理器函数为项目数据库(只是**字典**)提供了一些初始值。
|
||||
本例中,`startup` 事件处理器函数为项目“数据库”(只是一个 `dict`)提供了一些初始值。
|
||||
|
||||
**FastAPI** 支持多个事件处理器函数。
|
||||
|
||||
只有所有 `startup` 事件处理器运行完毕,**FastAPI** 应用才开始接收请求。
|
||||
|
||||
### `shutdown` 事件
|
||||
### `shutdown` 事件 { #shutdown-event }
|
||||
|
||||
使用 `shutdown` 事件声明 `app` 关闭时运行的函数:
|
||||
使用事件 `"shutdown"` 声明一个在应用关闭时运行的函数:
|
||||
|
||||
{* ../../docs_src/events/tutorial002.py hl[6] *}
|
||||
{* ../../docs_src/events/tutorial002_py39.py hl[6] *}
|
||||
|
||||
此处,`shutdown` 事件处理器函数在 `log.txt` 中写入一行文本 `Application shutdown`。
|
||||
此处,`shutdown` 事件处理器函数会向文件 `log.txt` 写入一行文本 `"Application shutdown"`。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
`open()` 函数中,`mode="a"` 指的是**追加**。因此这行文本会添加在文件已有内容之后,不会覆盖之前的内容。
|
||||
在 `open()` 函数中,`mode="a"` 指的是“追加”。因此这行文本会添加在文件已有内容之后,不会覆盖之前的内容。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
注意,本例使用 Python `open()` 标准函数与文件交互。
|
||||
注意,本例使用 Python 标准的 `open()` 函数与文件交互。
|
||||
|
||||
这个函数执行 I/O(输入/输出)操作,需要等待内容写进磁盘。
|
||||
这个函数执行 I/O(输入/输出)操作,需要“等待”内容写进磁盘。
|
||||
|
||||
但 `open()` 函数不支持使用 `async` 与 `await`。
|
||||
但 `open()` 不使用 `async` 和 `await`。
|
||||
|
||||
因此,声明事件处理函数要使用 `def`,不能使用 `asnyc def`。
|
||||
因此,声明事件处理函数要使用 `def`,而不是 `async def`。
|
||||
|
||||
///
|
||||
|
||||
### `startup` 和 `shutdown` 一起使用
|
||||
### `startup` 和 `shutdown` 一起使用 { #startup-and-shutdown-together }
|
||||
|
||||
启动和关闭的逻辑很可能是连接在一起的,你可能希望启动某个东西然后结束它,获取一个资源然后释放它等等。
|
||||
|
||||
在不共享逻辑或变量的不同函数中处理这些逻辑比较困难,因为你需要在全局变量中存储值或使用类似的方式。
|
||||
|
||||
因此,推荐使用 `lifespan` 。
|
||||
因此,推荐使用上面所述的 `lifespan`。
|
||||
|
||||
## 技术细节
|
||||
## 技术细节 { #technical-details }
|
||||
|
||||
只是为好奇者提供的技术细节。🤓
|
||||
|
||||
在底层,这部分是<a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">生命周期协议</a>的一部分,参见 ASGI 技术规范,定义了称为启动(`startup`)和关闭(`shutdown`)的事件。
|
||||
在底层,这部分是 ASGI 技术规范中的 <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Lifespan 协议</a>的一部分,定义了称为 `startup` 和 `shutdown` 的事件。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
有关事件处理器的详情,请参阅 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette 官档 - 事件</a>。
|
||||
你可以在 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette 的 Lifespan 文档</a> 中阅读更多关于 `lifespan` 处理器的内容。
|
||||
|
||||
包括如何处理生命周期状态,这可以用于程序的其他部分。
|
||||
包括如何处理生命周期状态,以便在代码的其他部分使用。
|
||||
|
||||
///
|
||||
|
||||
## 子应用
|
||||
## 子应用 { #sub-applications }
|
||||
|
||||
🚨 **FastAPI** 只会触发主应用中的生命周期事件,不包括[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}中的。
|
||||
🚨 请注意,这些生命周期事件(startup 和 shutdown)只会在主应用上执行,不会在[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}上执行。
|
||||
|
||||
@@ -1,237 +1,208 @@
|
||||
# 生成客户端
|
||||
# 生成 SDK { #generating-sdks }
|
||||
|
||||
因为 **FastAPI** 是基于OpenAPI规范的,自然您可以使用许多相匹配的工具,包括自动生成API文档 (由 Swagger UI 提供)。
|
||||
因为 **FastAPI** 基于 **OpenAPI** 规范,它的 API 可以用许多工具都能理解的标准格式来描述。
|
||||
|
||||
一个不太明显而又特别的优势是,你可以为你的API针对不同的**编程语言**来**生成客户端**(有时候被叫做 <abbr title="Software Development Kits">**SDKs**</abbr> )。
|
||||
这让你可以轻松生成最新的**文档**、多语言的客户端库(<abbr title="Software Development Kits - 软件开发工具包">**SDKs**</abbr>),以及与代码保持同步的**测试**或**自动化工作流**。
|
||||
|
||||
## OpenAPI 客户端生成
|
||||
本指南将带你为 FastAPI 后端生成一个 **TypeScript SDK**。
|
||||
|
||||
有许多工具可以从**OpenAPI**生成客户端。
|
||||
## 开源 SDK 生成器 { #open-source-sdk-generators }
|
||||
|
||||
一个常见的工具是 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>。
|
||||
一个功能多样的选择是 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>,它支持**多种编程语言**,可以根据你的 OpenAPI 规范生成 SDK。
|
||||
|
||||
如果您正在开发**前端**,一个非常有趣的替代方案是 <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>。
|
||||
对于 **TypeScript 客户端**,<a href="https://heyapi.dev/" class="external-link" target="_blank">Hey API</a> 是为 TypeScript 生态打造的专用方案,提供优化的使用体验。
|
||||
|
||||
## 生成一个 TypeScript 前端客户端
|
||||
你还可以在 <a href="https://openapi.tools/#sdk" class="external-link" target="_blank">OpenAPI.Tools</a> 上发现更多 SDK 生成器。
|
||||
|
||||
让我们从一个简单的 FastAPI 应用开始:
|
||||
/// tip | 提示
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
|
||||
|
||||
请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item` 和 `ResponseMessage`。
|
||||
|
||||
### API 文档
|
||||
|
||||
如果您访问API文档,您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image01.png">
|
||||
|
||||
您可以看到这些模式,因为它们是用程序中的模型声明的。
|
||||
|
||||
那些信息可以在应用的 **OpenAPI模式** 被找到,然后显示在API文档中(通过Swagger UI)。
|
||||
|
||||
OpenAPI中所包含的模型里有相同的信息可以用于 **生成客户端代码**。
|
||||
|
||||
### 生成一个TypeScript 客户端
|
||||
|
||||
现在我们有了带有模型的应用,我们可以为前端生成客户端代码。
|
||||
|
||||
#### 安装 `openapi-ts`
|
||||
|
||||
您可以使用以下工具在前端代码中安装 `openapi-ts`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ npm install @hey-api/openapi-ts --save-dev
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
#### 生成客户端代码
|
||||
|
||||
要生成客户端代码,您可以使用现在将要安装的命令行应用程序 `openapi-ts`。
|
||||
|
||||
因为它安装在本地项目中,所以您可能无法直接使用此命令,但您可以将其放在 `package.json` 文件中。
|
||||
|
||||
它可能看起来是这样的:
|
||||
|
||||
```JSON hl_lines="7"
|
||||
{
|
||||
"name": "frontend-app",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios"
|
||||
},
|
||||
"author": "",
|
||||
"license": "",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "^0.27.38",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这里添加 NPM `generate-client` 脚本后,您可以使用以下命令运行它:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ npm run generate-client
|
||||
|
||||
frontend-app@1.0.0 generate-client /home/user/code/frontend-app
|
||||
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
此命令将在 `./src/client` 中生成代码,并将在其内部使用 `axios`(前端HTTP库)。
|
||||
|
||||
### 尝试客户端代码
|
||||
|
||||
现在您可以导入并使用客户端代码,它可能看起来像这样,请注意,您可以为这些方法使用自动补全:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image02.png">
|
||||
|
||||
您还将自动补全要发送的数据:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image03.png">
|
||||
|
||||
/// tip
|
||||
|
||||
请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。
|
||||
FastAPI 会自动生成 **OpenAPI 3.1** 规范,因此你使用的任何工具都必须支持该版本。
|
||||
|
||||
///
|
||||
|
||||
如果发送的数据字段不符,你也会看到编辑器的错误提示:
|
||||
## 来自 FastAPI 赞助商的 SDK 生成器 { #sdk-generators-from-fastapi-sponsors }
|
||||
|
||||
本节介绍的是由赞助 FastAPI 的公司提供的、具备**风险投资背景**或**公司支持**的方案。这些产品在高质量生成的 SDK 之上,提供了**更多特性**和**集成**。
|
||||
|
||||
通过 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这些公司帮助确保框架及其**生态**保持健康并且**可持续**。
|
||||
|
||||
他们的赞助也体现了对 FastAPI **社区**(也就是你)的高度承诺,不仅关注提供**优秀的服务**,也支持一个**健壮且繁荣的框架**——FastAPI。🙇
|
||||
|
||||
例如,你可以尝试:
|
||||
|
||||
* <a href="https://speakeasy.com/editor?utm_source=fastapi+repo&utm_medium=github+sponsorship" class="external-link" target="_blank">Speakeasy</a>
|
||||
* <a href="https://www.stainless.com/?utm_source=fastapi&utm_medium=referral" class="external-link" target="_blank">Stainless</a>
|
||||
* <a href="https://developers.liblab.com/tutorials/sdk-for-fastapi?utm_source=fastapi" class="external-link" target="_blank">liblab</a>
|
||||
|
||||
其中一些方案也可能是开源的或提供免费层级,你可以不花钱就先试用。其他商业 SDK 生成器也可在网上找到。🤓
|
||||
|
||||
## 创建一个 TypeScript SDK { #create-a-typescript-sdk }
|
||||
|
||||
先从一个简单的 FastAPI 应用开始:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
|
||||
|
||||
请注意,这些*路径操作*使用 `Item` 和 `ResponseMessage` 模型来定义它们的请求载荷和响应载荷。
|
||||
|
||||
### API 文档 { #api-docs }
|
||||
|
||||
访问 `/docs` 时,你会看到有用于请求发送和响应接收数据的**模式**:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image01.png">
|
||||
|
||||
之所以能看到这些模式,是因为它们在应用中用模型声明了。
|
||||
|
||||
这些信息会包含在应用的 **OpenAPI 模式** 中,并显示在 API 文档里。
|
||||
|
||||
OpenAPI 中包含的这些模型信息就是用于**生成客户端代码**的基础。
|
||||
|
||||
### Hey API { #hey-api }
|
||||
|
||||
当我们有了带模型的 FastAPI 应用后,可以使用 Hey API 来生成 TypeScript 客户端。最快的方式是通过 npx:
|
||||
|
||||
```sh
|
||||
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client
|
||||
```
|
||||
|
||||
这会在 `./src/client` 生成一个 TypeScript SDK。
|
||||
|
||||
你可以在其官网了解如何<a href="https://heyapi.dev/openapi-ts/get-started" class="external-link" target="_blank">安装 `@hey-api/openapi-ts`</a>,以及阅读<a href="https://heyapi.dev/openapi-ts/output" class="external-link" target="_blank">生成结果</a>的说明。
|
||||
|
||||
### 使用 SDK { #using-the-sdk }
|
||||
|
||||
现在你可以导入并使用客户端代码了。它可能是这样,并且你会发现方法有自动补全:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image02.png">
|
||||
|
||||
要发送的载荷也会有自动补全:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image03.png">
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
请注意 `name` 和 `price` 的自动补全,它们是在 FastAPI 应用中的 `Item` 模型里定义的。
|
||||
|
||||
///
|
||||
|
||||
你发送的数据如果不符合要求,会在编辑器中显示内联错误:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image04.png">
|
||||
|
||||
响应(response)对象也拥有自动补全:
|
||||
响应对象同样有自动补全:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image05.png">
|
||||
|
||||
## 带有标签的 FastAPI 应用
|
||||
## 带有标签的 FastAPI 应用 { #fastapi-app-with-tags }
|
||||
|
||||
在许多情况下,你的FastAPI应用程序会更复杂,你可能会使用标签来分隔不同组的*路径操作(path operations)*。
|
||||
很多情况下,你的 FastAPI 应用会更大,你可能会用标签来划分不同组的*路径操作*。
|
||||
|
||||
例如,您可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔:
|
||||
例如,你可以有一个 **items** 相关的部分和另一个 **users** 相关的部分,它们可以用标签来分隔:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
|
||||
|
||||
### 生成带有标签的 TypeScript 客户端
|
||||
### 生成带标签的 TypeScript 客户端 { #generate-a-typescript-client-with-tags }
|
||||
|
||||
如果您使用标签为FastAPI应用生成客户端,它通常也会根据标签分割客户端代码。
|
||||
如果你为使用了标签的 FastAPI 应用生成客户端,通常也会根据标签来拆分客户端代码。
|
||||
|
||||
通过这种方式,您将能够为客户端代码进行正确地排序和分组:
|
||||
这样你就可以在客户端代码中把内容正确地组织和分组:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image06.png">
|
||||
|
||||
在这个案例中,您有:
|
||||
在这个例子中,你会有:
|
||||
|
||||
* `ItemsService`
|
||||
* `UsersService`
|
||||
|
||||
### 客户端方法名称
|
||||
### 客户端方法名 { #client-method-names }
|
||||
|
||||
现在生成的方法名像 `createItemItemsPost` 看起来不太简洁:
|
||||
现在,像 `createItemItemsPost` 这样的生成方法名看起来不太简洁:
|
||||
|
||||
```TypeScript
|
||||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
|
||||
```
|
||||
|
||||
...这是因为客户端生成器为每个 *路径操作* 使用OpenAPI的内部 **操作 ID(operation ID)**。
|
||||
……这是因为客户端生成器会把每个*路径操作*的 OpenAPI 内部**操作 ID(operation ID)**用作方法名的一部分。
|
||||
|
||||
OpenAPI要求每个操作 ID 在所有 *路径操作* 中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP方法/操作**来生成此操作ID,因为这样可以确保这些操作 ID 是唯一的。
|
||||
OpenAPI 要求每个操作 ID 在所有*路径操作*中都是唯一的,因此 FastAPI 会使用**函数名**、**路径**和**HTTP 方法/操作**来生成操作 ID,以确保其唯一性。
|
||||
|
||||
但接下来我会告诉你如何改进。 🤓
|
||||
接下来我会告诉你如何改进。🤓
|
||||
|
||||
## 自定义操作ID和更好的方法名
|
||||
## 自定义操作 ID 与更好的方法名 { #custom-operation-ids-and-better-method-names }
|
||||
|
||||
您可以**修改**这些操作ID的**生成**方式,以使其更简洁,并在客户端中具有**更简洁的方法名称**。
|
||||
你可以**修改**这些操作 ID 的**生成**方式,使之更简单,从而在客户端中得到**更简洁的方法名**。
|
||||
|
||||
在这种情况下,您必须确保每个操作ID在其他方面是**唯一**的。
|
||||
在这种情况下,你需要用其他方式确保每个操作 ID 依然是**唯一**的。
|
||||
|
||||
例如,您可以确保每个*路径操作*都有一个标签,然后根据**标签**和*路径操作***名称**(函数名)来生成操作ID。
|
||||
例如,你可以确保每个*路径操作*都有一个标签,然后基于**标签**和*路径操作***名称**(函数名)来生成操作 ID。
|
||||
|
||||
### 自定义生成唯一ID函数
|
||||
### 自定义唯一 ID 生成函数 { #custom-generate-unique-id-function }
|
||||
|
||||
FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID**,也用于任何所需自定义模型的名称,用于请求或响应。
|
||||
FastAPI 为每个*路径操作*使用一个**唯一 ID**,它既用于**操作 ID**,也用于请求或响应里任何需要的自定义模型名称。
|
||||
|
||||
你可以自定义该函数。它接受一个 `APIRoute` 对象作为输入,并输出一个字符串。
|
||||
你可以自定义这个函数。它接收一个 `APIRoute` 并返回一个字符串。
|
||||
|
||||
例如,以下是一个示例,它使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。
|
||||
例如,这里使用第一个标签(你很可能只有一个标签)和*路径操作*名称(函数名)。
|
||||
|
||||
然后,你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**:
|
||||
然后你可以把这个自定义函数通过 `generate_unique_id_function` 参数传给 **FastAPI**:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
|
||||
|
||||
### 使用自定义操作ID生成TypeScript客户端
|
||||
### 使用自定义操作 ID 生成 TypeScript 客户端 { #generate-a-typescript-client-with-custom-operation-ids }
|
||||
|
||||
现在,如果你再次生成客户端,你会发现它具有改善的方法名称:
|
||||
现在再次生成客户端,你会看到方法名已经改进:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image07.png">
|
||||
|
||||
正如你所见,现在方法名称中只包含标签和函数名,不再包含URL路径和HTTP操作的信息。
|
||||
如你所见,方法名现在由标签和函数名组成,不再包含 URL 路径和 HTTP 操作的信息。
|
||||
|
||||
### 预处理用于客户端生成器的OpenAPI规范
|
||||
### 为客户端生成器预处理 OpenAPI 规范 { #preprocess-the-openapi-specification-for-the-client-generator }
|
||||
|
||||
生成的代码仍然存在一些**重复的信息**。
|
||||
生成的代码中仍有一些**重复信息**。
|
||||
|
||||
我们已经知道该方法与 **items** 相关,因为它在 `ItemsService` 中(从标签中获取),但方法名中仍然有标签名作为前缀。😕
|
||||
我们已经知道这个方法与 **items** 有关,因为它位于 `ItemsService`(来自标签),但方法名里仍然带有标签名前缀。😕
|
||||
|
||||
一般情况下对于OpenAPI,我们可能仍然希望保留它,因为这将确保操作ID是**唯一的**。
|
||||
通常我们仍然希望在 OpenAPI 中保留它,以确保操作 ID 的**唯一性**。
|
||||
|
||||
但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI 操作ID,以使方法名称更加美观和**简洁**。
|
||||
但对于生成的客户端,我们可以在生成之前**修改** OpenAPI 的操作 ID,只是为了让方法名更美观、更**简洁**。
|
||||
|
||||
我们可以将 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**:
|
||||
我们可以把 OpenAPI JSON 下载到 `openapi.json` 文件中,然后用如下脚本**移除这个标签前缀**:
|
||||
|
||||
{* ../../docs_src/generate_clients/tutorial004.py *}
|
||||
{* ../../docs_src/generate_clients/tutorial004_py39.py *}
|
||||
|
||||
通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。
|
||||
//// tab | Node.js
|
||||
|
||||
### 使用预处理的OpenAPI生成TypeScript客户端
|
||||
|
||||
现在,由于最终结果保存在文件openapi.json中,你可以修改 package.json 文件以使用此本地文件,例如:
|
||||
|
||||
```JSON hl_lines="7"
|
||||
{
|
||||
"name": "frontend-app",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
|
||||
},
|
||||
"author": "",
|
||||
"license": "",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "^0.27.38",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
```Javascript
|
||||
{!> ../../docs_src/generate_clients/tutorial004.js!}
|
||||
```
|
||||
|
||||
生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能:
|
||||
////
|
||||
|
||||
这样,操作 ID 会从 `items-get_items` 之类的名字重命名为 `get_items`,从而让客户端生成器生成更简洁的方法名。
|
||||
|
||||
### 使用预处理后的 OpenAPI 生成 TypeScript 客户端 { #generate-a-typescript-client-with-the-preprocessed-openapi }
|
||||
|
||||
因为最终结果现在保存在 `openapi.json` 中,你需要更新输入位置:
|
||||
|
||||
```sh
|
||||
npx @hey-api/openapi-ts -i ./openapi.json -o src/client
|
||||
```
|
||||
|
||||
生成新客户端后,你将拥有**简洁的方法名**,并具备**自动补全**、**内联错误**等功能:
|
||||
|
||||
<img src="/img/tutorial/generate-clients/image08.png">
|
||||
|
||||
## 优点
|
||||
## 优点 { #benefits }
|
||||
|
||||
当使用自动生成的客户端时,你将获得以下的自动补全功能:
|
||||
使用自动生成的客户端时,你会获得以下内容的**自动补全**:
|
||||
|
||||
* 方法。
|
||||
* 请求体中的数据、查询参数等。
|
||||
* 响应数据。
|
||||
* 方法
|
||||
* 请求体中的数据、查询参数等
|
||||
* 响应数据
|
||||
|
||||
你还将获得针对所有内容的错误提示。
|
||||
你还会为所有内容获得**内联错误**。
|
||||
|
||||
每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*将作为方法可用,旧的方法将被删除,并且其他任何更改将反映在生成的代码中。 🤓
|
||||
每当你更新后端代码并**重新生成**前端时,新的*路径操作*会作为方法可用,旧的方法会被移除,其他任何更改都会反映到生成的代码中。🤓
|
||||
|
||||
这也意味着如果有任何更改,它将自动**反映**在客户端代码中。如果你**构建**客户端,在使用的数据上存在**不匹配**时,它将报错。
|
||||
这也意味着如果有任何变更,它会自动**反映**到客户端代码中。而当你**构建**客户端时,如果所用数据存在任何**不匹配**,它会直接报错。
|
||||
|
||||
因此,你将在开发周期的早期**检测到许多错误**,而不必等待错误在生产环境中向最终用户展示,然后尝试调试问题所在。 ✨
|
||||
因此,你可以在开发周期的早期就**发现许多错误**,而不必等到错误在生产环境中暴露给最终用户后再去调试问题所在。✨
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 高级中间件
|
||||
# 高级中间件 { #advanced-middleware }
|
||||
|
||||
用户指南介绍了如何为应用添加[自定义中间件](../tutorial/middleware.md){.internal-link target=_blank} 。
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
本章学习如何使用其它中间件。
|
||||
|
||||
## 添加 ASGI 中间件
|
||||
## 添加 ASGI 中间件 { #adding-asgi-middlewares }
|
||||
|
||||
因为 **FastAPI** 基于 Starlette,且执行 <abbr title="Asynchronous Server Gateway Interface,异步服务器网关界面">ASGI</abbr> 规范,所以可以使用任意 ASGI 中间件。
|
||||
因为 **FastAPI** 基于 Starlette,且执行 <abbr title="Asynchronous Server Gateway Interface - 异步服务器网关接口">ASGI</abbr> 规范,所以可以使用任意 ASGI 中间件。
|
||||
|
||||
中间件不必是专为 FastAPI 或 Starlette 定制的,只要遵循 ASGI 规范即可。
|
||||
|
||||
@@ -39,11 +39,11 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
|
||||
|
||||
`app.add_middleware()` 的第一个参数是中间件的类,其它参数则是要传递给中间件的参数。
|
||||
|
||||
## 集成中间件
|
||||
## 集成中间件 { #integrated-middlewares }
|
||||
|
||||
**FastAPI** 为常见用例提供了一些中间件,下面介绍怎么使用这些中间件。
|
||||
|
||||
/// note | 技术细节
|
||||
/// note | 注意
|
||||
|
||||
以下几个示例中也可以使用 `from starlette.middleware.something import SomethingMiddleware`。
|
||||
|
||||
@@ -51,45 +51,47 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow")
|
||||
|
||||
///
|
||||
|
||||
## `HTTPSRedirectMiddleware`
|
||||
## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware }
|
||||
|
||||
强制所有传入请求必须是 `https` 或 `wss`。
|
||||
|
||||
任何传向 `http` 或 `ws` 的请求都会被重定向至安全方案。
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
|
||||
{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *}
|
||||
|
||||
## `TrustedHostMiddleware`
|
||||
## `TrustedHostMiddleware` { #trustedhostmiddleware }
|
||||
|
||||
强制所有传入请求都必须正确设置 `Host` 请求头,以防 HTTP 主机头攻击。
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
|
||||
{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *}
|
||||
|
||||
支持以下参数:
|
||||
|
||||
* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名,或使用 `allowed_hosts=["*"]` 允许任意主机名,或省略中间件。
|
||||
* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名。若要允许任意主机名,可使用 `allowed_hosts=["*"]` 或省略此中间件。
|
||||
* `www_redirect` - 若设置为 `True`,对允许主机的非 www 版本的请求将被重定向到其 www 版本。默认为 `True`。
|
||||
|
||||
如果传入的请求没有通过验证,则发送 `400` 响应。
|
||||
|
||||
## `GZipMiddleware`
|
||||
## `GZipMiddleware` { #gzipmiddleware }
|
||||
|
||||
处理 `Accept-Encoding` 请求头中包含 `gzip` 请求的 GZip 响应。
|
||||
处理 `Accept-Encoding` 请求头中包含 `"gzip"` 请求的 GZip 响应。
|
||||
|
||||
中间件会处理标准响应与流响应。
|
||||
|
||||
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
|
||||
{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *}
|
||||
|
||||
支持以下参数:
|
||||
|
||||
* `minimum_size` - 小于最小字节的响应不使用 GZip。 默认值是 `500`。
|
||||
* `minimum_size` - 小于该最小字节数的响应不使用 GZip。默认值是 `500`。
|
||||
* `compresslevel` - GZip 压缩使用的级别,为 1 到 9 的整数。默认为 `9`。值越低压缩越快但文件更大,值越高压缩越慢但文件更小。
|
||||
|
||||
## 其它中间件
|
||||
## 其它中间件 { #other-middlewares }
|
||||
|
||||
除了上述中间件外,FastAPI 还支持其它ASGI 中间件。
|
||||
|
||||
例如:
|
||||
|
||||
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn 的 `ProxyHeadersMiddleware`</a>
|
||||
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn 的 `ProxyHeadersMiddleware`</a>
|
||||
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
|
||||
|
||||
其它可用中间件详见 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette 官档 - 中间件</a> 及 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome 列表</a>。
|
||||
其它可用中间件详见 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette 官档 - 中间件</a> 及 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome 列表</a>。
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# OpenAPI 回调
|
||||
# OpenAPI 回调 { #openapi-callbacks }
|
||||
|
||||
您可以创建触发外部 API 请求的*路径操作* API,这个外部 API 可以是别人创建的,也可以是由您自己创建的。
|
||||
您可以创建一个包含*路径操作*的 API,它会触发对别人创建的*外部 API*的请求(很可能就是那个会“使用”您 API 的同一个开发者)。
|
||||
|
||||
API 应用调用外部 API 时的流程叫做**回调**。因为外部开发者编写的软件发送请求至您的 API,然后您的 API 要进行回调,并把请求发送至外部 API。
|
||||
当您的 API 应用调用*外部 API*时,这个过程被称为“回调”。因为外部开发者编写的软件会先向您的 API 发送请求,然后您的 API 再进行*回调*,向*外部 API*发送请求(很可能也是该开发者创建的)。
|
||||
|
||||
此时,我们需要存档外部 API 的*信息*,比如应该有哪些*路径操作*,返回什么样的请求体,应该返回哪种响应等。
|
||||
此时,我们需要存档外部 API 的*信息*,比如应该有哪些*路径操作*,请求体应该是什么,应该返回什么响应等。
|
||||
|
||||
## 使用回调的应用
|
||||
## 使用回调的应用 { #an-app-with-callbacks }
|
||||
|
||||
示例如下。
|
||||
|
||||
@@ -14,16 +14,16 @@ API 应用调用外部 API 时的流程叫做**回调**。因为外部开发者
|
||||
|
||||
发票包括 `id`、`title`(可选)、`customer`、`total` 等属性。
|
||||
|
||||
API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建一条发票记录。
|
||||
API 的用户(外部开发者)要在您的 API 内使用 POST 请求创建一条发票记录。
|
||||
|
||||
(假设)您的 API 将:
|
||||
|
||||
* 把发票发送至外部开发者的消费者
|
||||
* 归集现金
|
||||
* 把通知发送至 API 的用户(外部开发者)
|
||||
* 通过(从您的 API)发送 POST 请求至外部 API (即**回调**)来完成
|
||||
* 通过(从您的 API)发送 POST 请求至外部 API(即**回调**)来完成
|
||||
|
||||
## 常规 **FastAPI** 应用
|
||||
## 常规 **FastAPI** 应用 { #the-normal-fastapi-app }
|
||||
|
||||
添加回调前,首先看下常规 API 应用是什么样子。
|
||||
|
||||
@@ -31,17 +31,17 @@ API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建
|
||||
|
||||
这部分代码很常规,您对绝大多数代码应该都比较熟悉了:
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[10:14,37:54] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
`callback_url` 查询参数使用 Pydantic 的 <a href="https://pydantic-docs.helpmanual.io/usage/types/#urls" class="external-link" target="_blank">URL</a> 类型。
|
||||
`callback_url` 查询参数使用 Pydantic 的 <a href="https://docs.pydantic.dev/latest/api/networks/" class="external-link" target="_blank">Url</a> 类型。
|
||||
|
||||
///
|
||||
|
||||
此处唯一比较新的内容是*路径操作装饰器*中的 `callbacks=invoices_callback_router.routes` 参数,下文介绍。
|
||||
|
||||
## 存档回调
|
||||
## 存档回调 { #documenting-the-callback }
|
||||
|
||||
实际的回调代码高度依赖于您自己的 API 应用。
|
||||
|
||||
@@ -51,14 +51,14 @@ API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建
|
||||
|
||||
```Python
|
||||
callback_url = "https://example.com/api/v1/invoices/events/"
|
||||
requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
```
|
||||
|
||||
但回调最重要的部分可能是,根据 API 要发送给回调请求体的数据等内容,确保您的 API 用户(外部开发者)正确地实现*外部 API*。
|
||||
|
||||
因此,我们下一步要做的就是添加代码,为从 API 接收回调的*外部 API*存档。
|
||||
|
||||
这部分文档在 `/docs` 下的 Swagger API 文档中显示,并且会告诉外部开发者如何构建*外部 API*。
|
||||
这部分文档在 `/docs` 下的 Swagger UI 中显示,并且会告诉外部开发者如何构建*外部 API*。
|
||||
|
||||
本例没有实现回调本身(只是一行代码),只有文档部分。
|
||||
|
||||
@@ -66,17 +66,17 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
|
||||
实际的回调只是 HTTP 请求。
|
||||
|
||||
实现回调时,要使用 <a href="https://www.encode.io/httpx/" class="external-link" target="_blank">HTTPX</a> 或 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>。
|
||||
实现回调时,要使用 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> 或 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>。
|
||||
|
||||
///
|
||||
|
||||
## 编写回调文档代码
|
||||
## 编写回调文档代码 { #write-the-callback-documentation-code }
|
||||
|
||||
应用不执行这部分代码,只是用它来*记录 外部 API* 。
|
||||
|
||||
但,您已经知道用 **FastAPI** 创建自动 API 文档有多简单了。
|
||||
|
||||
我们要使用与存档*外部 API* 相同的知识……通过创建外部 API 要实现的*路径操作*(您的 API 要调用的)。
|
||||
我们要使用与存档*外部 API* 相同的知识...通过创建外部 API 要实现的*路径操作*(您的 API 要调用的)。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -86,13 +86,13 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
|
||||
///
|
||||
|
||||
### 创建回调的 `APIRouter`
|
||||
### 创建回调的 `APIRouter` { #create-a-callback-apirouter }
|
||||
|
||||
首先,新建包含一些用于回调的 `APIRouter`。
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[5,26] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *}
|
||||
|
||||
### 创建回调*路径操作*
|
||||
### 创建回调*路径操作* { #create-the-callback-path-operation }
|
||||
|
||||
创建回调*路径操作*也使用之前创建的 `APIRouter`。
|
||||
|
||||
@@ -101,16 +101,16 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
|
||||
* 声明要接收的请求体,例如,`body: InvoiceEvent`
|
||||
* 还要声明要返回的响应,例如,`response_model=InvoiceEventReceived`
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[17:19,22:23,29:33] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *}
|
||||
|
||||
回调*路径操作*与常规*路径操作*有两点主要区别:
|
||||
|
||||
* 它不需要任何实际的代码,因为应用不会调用这段代码。它只是用于存档*外部 API*。因此,函数的内容只需要 `pass` 就可以了
|
||||
* *路径*可以包含 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>(详见下文),可以使用带参数的变量,以及发送至您的 API 的原始请求的部分
|
||||
* *路径*可以包含 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>(详见下文),可以使用带参数的变量,以及发送至您的 API 的原始请求的部分
|
||||
|
||||
### 回调路径表达式
|
||||
### 回调路径表达式 { #the-callback-path-expression }
|
||||
|
||||
回调*路径*支持包含发送给您的 API 的原始请求的部分的 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>。
|
||||
回调*路径*支持包含发送给您的 API 的原始请求的部分的 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>。
|
||||
|
||||
本例中是**字符串**:
|
||||
|
||||
@@ -159,17 +159,17 @@ JSON 请求体包含如下内容:
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
注意,回调 URL包含 `callback_url` (`https://www.external.org/events`)中的查询参数,还有 JSON 请求体内部的发票 ID(`2expen51ve`)。
|
||||
注意,回调 URL 包含 `callback_url`(`https://www.external.org/events`)中的查询参数,还有 JSON 请求体内部的发票 ID(`2expen51ve`)。
|
||||
|
||||
///
|
||||
|
||||
### 添加回调路由
|
||||
### 添加回调路由 { #add-the-callback-router }
|
||||
|
||||
至此,在上文创建的回调路由里就包含了*回调路径操作*(外部开发者要在外部 API 中实现)。
|
||||
|
||||
现在使用 API *路径操作装饰器*的参数 `callbacks`,从回调路由传递属性 `.routes`(实际上只是路由/路径操作的**列表**):
|
||||
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[36] *}
|
||||
{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -177,9 +177,9 @@ JSON 请求体包含如下内容:
|
||||
|
||||
///
|
||||
|
||||
### 查看文档
|
||||
### 查看文档 { #check-the-docs }
|
||||
|
||||
现在,使用 Uvicorn 启动应用,打开 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs。</a>
|
||||
现在,使用 Uvicorn 启动应用,打开 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
|
||||
|
||||
就能看到文档的*路径操作*已经包含了**回调**的内容以及*外部 API*:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# OpenAPI 网络钩子
|
||||
# OpenAPI 网络钩子 { #openapi-webhooks }
|
||||
|
||||
有些情况下,您可能想告诉您的 API **用户**,您的应用程序可以携带一些数据调用*他们的*应用程序(给它们发送请求),通常是为了**通知**某种**事件**。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
这通常被称为**网络钩子**(Webhook)。
|
||||
|
||||
## 使用网络钩子的步骤
|
||||
## 使用网络钩子的步骤 { #webhooks-steps }
|
||||
|
||||
通常的过程是**您**在代码中**定义**要发送的消息,即**请求的主体**。
|
||||
|
||||
@@ -16,27 +16,27 @@
|
||||
|
||||
所有关于注册网络钩子的 URL 的**逻辑**以及发送这些请求的实际代码都由您决定。您可以在**自己的代码**中以任何想要的方式来编写它。
|
||||
|
||||
## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子
|
||||
## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子 { #documenting-webhooks-with-fastapi-and-openapi }
|
||||
|
||||
使用 **FastAPI**,您可以利用 OpenAPI 来自定义这些网络钩子的名称、您的应用可以发送的 HTTP 操作类型(例如 `POST`、`PUT` 等)以及您的应用将发送的**请求体**。
|
||||
|
||||
这能让您的用户更轻松地**实现他们的 API** 来接收您的**网络钩子**请求,他们甚至可能能够自动生成一些自己的 API 代码。
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
网络钩子在 OpenAPI 3.1.0 及以上版本中可用,FastAPI `0.99.0` 及以上版本支持。
|
||||
|
||||
///
|
||||
|
||||
## 带有网络钩子的应用程序
|
||||
## 带有网络钩子的应用程序 { #an-app-with-webhooks }
|
||||
|
||||
当您创建一个 **FastAPI** 应用程序时,有一个 `webhooks` 属性可以用来定义网络钩子,方式与您定义*路径操作*的时候相同,例如使用 `@app.webhooks.post()` 。
|
||||
|
||||
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
|
||||
{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *}
|
||||
|
||||
您定义的网络钩子将被包含在 `OpenAPI` 的架构中,并出现在自动生成的**文档 UI** 中。
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
`app.webhooks` 对象实际上只是一个 `APIRouter` ,与您在使用多个文件来构建应用程序时所使用的类型相同。
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
这是因为我们预计**您的用户**会以其他方式(例如通过网页仪表板)来定义他们希望接收网络钩子的请求的实际 **URL 路径**。
|
||||
|
||||
### 查看文档
|
||||
### 查看文档 { #check-the-docs }
|
||||
|
||||
现在您可以启动您的应用程序并访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
# 路径操作的高级配置
|
||||
# 路径操作的高级配置 { #path-operation-advanced-configuration }
|
||||
|
||||
## OpenAPI 的 operationId
|
||||
## OpenAPI 的 operationId { #openapi-operationid }
|
||||
|
||||
/// warning
|
||||
|
||||
如果你并非 OpenAPI 的「专家」,你可能不需要这部分内容。
|
||||
如果你并非 OpenAPI 的“专家”,你可能不需要这部分内容。
|
||||
|
||||
///
|
||||
|
||||
你可以在路径操作中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`。
|
||||
你可以在 *路径操作* 中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`。
|
||||
|
||||
务必确保每个操作路径的 `operation_id` 都是唯一的。
|
||||
务必确保每个操作的 `operation_id` 都是唯一的。
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *}
|
||||
|
||||
### 使用 *路径操作函数* 的函数名作为 operationId
|
||||
### 使用 *路径操作函数* 的函数名作为 operationId { #using-the-path-operation-function-name-as-the-operationid }
|
||||
|
||||
如果你想用你的 API 的函数名作为 `operationId` 的名字,你可以遍历一遍 API 的函数名,然后使用他们的 `APIRoute.name` 重写每个 *路径操作* 的 `operation_id`。
|
||||
如果你想用 API 的函数名作为 `operationId`,你可以遍历所有路径操作,并使用它们的 `APIRoute.name` 重写每个 *路径操作* 的 `operation_id`。
|
||||
|
||||
你应该在添加了所有 *路径操作* 之后执行此操作。
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12,13,14,15,16,17,18,19,20,21,24] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *}
|
||||
|
||||
/// tip
|
||||
|
||||
@@ -36,19 +36,137 @@
|
||||
|
||||
///
|
||||
|
||||
## 从 OpenAPI 中排除
|
||||
## 从 OpenAPI 中排除 { #exclude-from-openapi }
|
||||
|
||||
使用参数 `include_in_schema` 并将其设置为 `False` ,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了)。
|
||||
使用参数 `include_in_schema` 并将其设置为 `False`,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了):
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *}
|
||||
|
||||
## docstring 的高级描述
|
||||
## 来自 docstring 的高级描述 { #advanced-description-from-docstring }
|
||||
|
||||
你可以限制 *路径操作函数* 的 `docstring` 中用于 OpenAPI 的行数。
|
||||
|
||||
添加一个 `\f` (一个「换页」的转义字符)可以使 **FastAPI** 在那一位置截断用于 OpenAPI 的输出。
|
||||
添加一个 `\f`(一个“换页”的转义字符)可以使 **FastAPI** 在那一位置截断用于 OpenAPI 的输出。
|
||||
|
||||
剩余部分不会出现在文档中,但是其他工具(比如 Sphinx)可以使用剩余部分。
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *}
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19,20,21,22,23,24,25,26,27,28,29] *}
|
||||
## 附加响应 { #additional-responses }
|
||||
|
||||
你可能已经见过如何为一个 *路径操作* 声明 `response_model` 和 `status_code`。
|
||||
|
||||
这定义了该 *路径操作* 主响应的元数据。
|
||||
|
||||
你也可以为它声明带有各自模型、状态码等的附加响应。
|
||||
|
||||
文档中有一个完整章节,你可以阅读这里的[OpenAPI 中的附加响应](additional-responses.md){.internal-link target=_blank}。
|
||||
|
||||
## OpenAPI Extra { #openapi-extra }
|
||||
|
||||
当你在应用中声明一个 *路径操作* 时,**FastAPI** 会自动生成与该 *路径操作* 相关的元数据,以包含到 OpenAPI 方案中。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
在 OpenAPI 规范中,这被称为 <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object" class="external-link" target="_blank">Operation 对象</a>。
|
||||
|
||||
///
|
||||
|
||||
它包含关于该 *路径操作* 的所有信息,并用于生成自动文档。
|
||||
|
||||
它包括 `tags`、`parameters`、`requestBody`、`responses` 等。
|
||||
|
||||
这个特定于 *路径操作* 的 OpenAPI 方案通常由 **FastAPI** 自动生成,但你也可以扩展它。
|
||||
|
||||
/// tip
|
||||
|
||||
这是一个较低层级的扩展点。
|
||||
|
||||
如果你只需要声明附加响应,更方便的方式是使用[OpenAPI 中的附加响应](additional-responses.md){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
你可以使用参数 `openapi_extra` 扩展某个 *路径操作* 的 OpenAPI 方案。
|
||||
|
||||
### OpenAPI 扩展 { #openapi-extensions }
|
||||
|
||||
例如,这个 `openapi_extra` 可用于声明 [OpenAPI 扩展](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions):
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *}
|
||||
|
||||
当你打开自动 API 文档时,你的扩展会显示在该 *路径操作* 的底部。
|
||||
|
||||
<img src="/img/tutorial/path-operation-advanced-configuration/image01.png">
|
||||
|
||||
如果你查看最终生成的 OpenAPI(在你的 API 的 `/openapi.json`),你也会看到你的扩展作为该 *路径操作* 的一部分:
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义 OpenAPI 路径操作方案 { #custom-openapi-path-operation-schema }
|
||||
|
||||
`openapi_extra` 中的字典会与该 *路径操作* 自动生成的 OpenAPI 方案进行深度合并。
|
||||
|
||||
因此,你可以在自动生成的方案上添加额外数据。
|
||||
|
||||
例如,你可以决定用自己的代码读取并验证请求,而不使用 FastAPI 与 Pydantic 的自动功能,但你仍然希望在 OpenAPI 方案中定义该请求。
|
||||
|
||||
你可以用 `openapi_extra` 来做到:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
|
||||
|
||||
在这个示例中,我们没有声明任何 Pydantic 模型。事实上,请求体甚至没有被 <abbr title="从某种纯文本格式(如字节)转换为 Python 对象">解析</abbr> 为 JSON,而是直接以 `bytes` 读取,并由函数 `magic_data_reader()` 以某种方式负责解析。
|
||||
|
||||
尽管如此,我们仍然可以声明请求体的预期方案。
|
||||
|
||||
### 自定义 OpenAPI 内容类型 { #custom-openapi-content-type }
|
||||
|
||||
使用同样的技巧,你可以用一个 Pydantic 模型来定义 JSON Schema,然后把它包含到该 *路径操作* 的自定义 OpenAPI 方案部分中。
|
||||
|
||||
即使请求中的数据类型不是 JSON,你也可以这样做。
|
||||
|
||||
例如,在这个应用中我们不使用 FastAPI 集成的从 Pydantic 模型提取 JSON Schema 的功能,也不使用对 JSON 的自动校验。实际上,我们将请求的内容类型声明为 YAML,而不是 JSON:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *}
|
||||
|
||||
尽管我们没有使用默认的集成功能,我们仍然使用 Pydantic 模型手动生成我们想以 YAML 接收的数据的 JSON Schema。
|
||||
|
||||
然后我们直接使用请求并将请求体提取为 `bytes`。这意味着 FastAPI 甚至不会尝试将请求负载解析为 JSON。
|
||||
|
||||
接着在我们的代码中,我们直接解析该 YAML 内容,然后再次使用同一个 Pydantic 模型来验证该 YAML 内容:
|
||||
|
||||
{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *}
|
||||
|
||||
/// tip
|
||||
|
||||
这里我们复用了同一个 Pydantic 模型。
|
||||
|
||||
但同样地,我们也可以用其他方式对其进行验证。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 响应 - 更改状态码
|
||||
# 响应 - 更改状态码 { #response-change-status-code }
|
||||
|
||||
你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。
|
||||
|
||||
但在某些情况下,你需要返回一个不同于默认值的状态码。
|
||||
|
||||
## 使用场景
|
||||
## 使用场景 { #use-case }
|
||||
|
||||
例如,假设你想默认返回一个HTTP状态码为“OK”`200`。
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
|
||||
对于这些情况,你可以使用一个`Response`参数。
|
||||
|
||||
## 使用 `Response` 参数
|
||||
## 使用 `Response` 参数 { #use-a-response-parameter }
|
||||
|
||||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。
|
||||
|
||||
然后你可以在这个*临时*响应对象中设置`status_code`。
|
||||
|
||||
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
|
||||
{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *}
|
||||
|
||||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
|
||||
|
||||
**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
|
||||
**FastAPI**将使用这个*临时*响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
|
||||
|
||||
你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 响应Cookies
|
||||
# 响应Cookies { #response-cookies }
|
||||
|
||||
## 使用 `Response` 参数
|
||||
## 使用 `Response` 参数 { #use-a-response-parameter }
|
||||
|
||||
你可以在 *路径函数* 中定义一个类型为 `Response`的参数,这样你就可以在这个临时响应对象中设置cookie了。
|
||||
你可以在 *路径操作函数* 中定义一个类型为 `Response` 的参数,这样你就可以在这个临时响应对象中设置cookie了。
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
|
||||
{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *}
|
||||
|
||||
而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`,数据库model等。
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
|
||||
**FastAPI** 会使用这个 *临时* 响应对象去装在这些cookies信息 (同样还有headers和状态码等信息), 最终会将这些信息和通过`response_model`转化过的数据合并到最终的响应里。
|
||||
|
||||
你也可以在depend中定义`Response`参数,并设置cookie和header。
|
||||
你也可以在依赖中定义`Response`参数,并设置cookie和header。
|
||||
|
||||
## 直接响应 `Response`
|
||||
## 直接响应 `Response` { #return-a-response-directly }
|
||||
|
||||
你还可以在直接响应`Response`时直接创建cookies。
|
||||
|
||||
你可以参考[Return a Response Directly](response-directly.md){.internal-link target=_blank}来创建response
|
||||
你可以参考[直接返回 Response](response-directly.md){.internal-link target=_blank}来创建response
|
||||
|
||||
然后设置Cookies,并返回:
|
||||
|
||||
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
|
||||
{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *}
|
||||
|
||||
/// tip
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
///
|
||||
|
||||
### 更多信息
|
||||
### 更多信息 { #more-info }
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 直接返回响应
|
||||
# 直接返回响应 { #return-a-response-directly }
|
||||
|
||||
当你创建一个 **FastAPI** *路径操作* 时,你可以正常返回以下任意一种数据:`dict`,`list`,Pydantic 模型,数据库模型等等。
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
直接返回响应可能会有用处,比如返回自定义的响应头和 cookies。
|
||||
|
||||
## 返回 `Response`
|
||||
## 返回 `Response` { #return-a-response }
|
||||
|
||||
事实上,你可以返回任意 `Response` 或者任意 `Response` 的子类。
|
||||
|
||||
/// tip | 小贴士
|
||||
/// tip | 提示
|
||||
|
||||
`JSONResponse` 本身是一个 `Response` 的子类。
|
||||
|
||||
@@ -26,18 +26,17 @@
|
||||
|
||||
这种特性给你极大的可扩展性。你可以返回任何数据类型,重写任何数据声明或者校验,等等。
|
||||
|
||||
## 在 `Response` 中使用 `jsonable_encoder`
|
||||
## 在 `Response` 中使用 `jsonable_encoder` { #using-the-jsonable-encoder-in-a-response }
|
||||
|
||||
由于 **FastAPI** 并未对你返回的 `Response` 做任何改变,你必须确保你已经准备好响应内容。
|
||||
|
||||
例如,如果不首先将 Pydantic 模型转换为 `dict`,并将所有数据类型(如 `datetime`、`UUID` 等)转换为兼容 JSON 的类型,则不能将其放入JSONResponse中。
|
||||
例如,如果不首先将 Pydantic 模型转换为 `dict`,并将所有数据类型(如 `datetime`、`UUID` 等)转换为兼容 JSON 的类型,则不能将其放入 `JSONResponse` 中。
|
||||
|
||||
对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据。
|
||||
对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *}
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial001.py hl[4,6,20,21] *}
|
||||
|
||||
/// note | 技术细节
|
||||
/// note | 注意
|
||||
|
||||
你也可以使用 `from starlette.responses import JSONResponse`。
|
||||
|
||||
@@ -45,22 +44,22 @@
|
||||
|
||||
///
|
||||
|
||||
## 返回自定义 `Response`
|
||||
## 返回自定义 `Response` { #returning-a-custom-response }
|
||||
|
||||
上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而**FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict`等等。
|
||||
上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而 **FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict` 等等。
|
||||
|
||||
现在,让我们看看你如何才能返回一个自定义的响应。
|
||||
|
||||
假设你想要返回一个 <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> 响应。
|
||||
|
||||
你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回。
|
||||
你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回:
|
||||
|
||||
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
|
||||
{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *}
|
||||
|
||||
## 说明
|
||||
## 说明 { #notes }
|
||||
|
||||
当你直接返回 `Response` 时,它的数据既没有校验,又不会进行转换(序列化),也不会自动生成文档。
|
||||
|
||||
但是你仍可以参考 [OpenApI 中的额外响应](additional-responses.md){.internal-link target=_blank} 给响应编写文档。
|
||||
但是你仍可以参考 [OpenAPI 中的额外响应](additional-responses.md){.internal-link target=_blank} 给响应编写文档。
|
||||
|
||||
在后续的章节中你可以了解到如何使用/声明这些自定义的 `Response` 的同时还保留自动化的数据转换和文档等。
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
# 响应头
|
||||
# 响应头 { #response-headers }
|
||||
|
||||
## 使用 `Response` 参数
|
||||
## 使用 `Response` 参数 { #use-a-response-parameter }
|
||||
|
||||
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。
|
||||
你可以在你的*路径操作函数*中声明一个 `Response` 类型的参数(就像你可以为 cookies 做的那样)。
|
||||
|
||||
然后你可以在这个*临时*响应对象中设置头部。
|
||||
{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
|
||||
|
||||
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
|
||||
{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *}
|
||||
|
||||
**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
|
||||
然后你可以像平常一样返回任何你需要的对象(例如一个 `dict` 或者一个数据库模型)。
|
||||
|
||||
你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。
|
||||
如果你声明了一个 `response_model`,它仍然会被用来过滤和转换你返回的对象。
|
||||
|
||||
## 直接返回 `Response`
|
||||
**FastAPI** 将使用这个临时响应来提取头部(也包括 cookies 和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何 `response_model` 过滤。
|
||||
|
||||
你也可以在直接返回`Response`时添加头部。
|
||||
你也可以在依赖项中声明 `Response` 参数,并在其中设置头部(和 cookies)。
|
||||
|
||||
## 直接返回 `Response` { #return-a-response-directly }
|
||||
|
||||
你也可以在直接返回 `Response` 时添加头部。
|
||||
|
||||
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递:
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
|
||||
|
||||
{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *}
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。
|
||||
你也可以使用 `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`中提供了它。
|
||||
由于 `Response` 经常用于设置头部和 cookies,**FastAPI** 还在 `fastapi.Response` 中提供了它。
|
||||
|
||||
///
|
||||
|
||||
## 自定义头部
|
||||
## 自定义头部 { #custom-headers }
|
||||
|
||||
请注意,可以使用'X-'前缀添加自定义专有头部。
|
||||
请注意,可以通过<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">使用 `X-` 前缀</a>添加自定义专有头部。
|
||||
|
||||
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette的CORS文档</a>中记录的`expose_headers`参数。
|
||||
但是,如果你有自定义头部,并希望浏览器中的客户端能够看到它们,你需要将它们添加到你的 CORS 配置中(在 [CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank} 中阅读更多),使用在 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette 的 CORS 文档</a>中记录的 `expose_headers` 参数。
|
||||
|
||||
@@ -1,274 +1,274 @@
|
||||
# OAuth2 作用域
|
||||
# OAuth2 作用域 { #oauth2-scopes }
|
||||
|
||||
**FastAPI** 无缝集成 OAuth2 作用域(`Scopes`),可以直接使用。
|
||||
你可以在 **FastAPI** 中直接使用 OAuth2 作用域(Scopes),它们已无缝集成。
|
||||
|
||||
作用域是更精密的权限系统,遵循 OAuth2 标准,与 OpenAPI 应用(和 API 自动文档)集成。
|
||||
这样你就可以按照 OAuth2 标准,构建更精细的权限系统,并将其集成进你的 OpenAPI 应用(以及 API 文档)中。
|
||||
|
||||
OAuth2 也是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制。这些身份验证应用在用户登录应用时使用 OAuth2 提供指定权限。
|
||||
带作用域的 OAuth2 是很多大型身份验证提供商使用的机制,例如 Facebook、Google、GitHub、Microsoft、X (Twitter) 等。它们用它来为用户和应用授予特定权限。
|
||||
|
||||
脸书、谷歌、GitHub、微软、推特就是 OAuth2 作用域登录。
|
||||
每次你“使用” Facebook、Google、GitHub、Microsoft、X (Twitter) “登录”时,该应用就在使用带作用域的 OAuth2。
|
||||
|
||||
本章介绍如何在 **FastAPI** 应用中使用 OAuth2 作用域管理验证与授权。
|
||||
本节将介绍如何在你的 **FastAPI** 应用中,使用相同的带作用域的 OAuth2 管理认证与授权。
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
本章内容较难,刚接触 FastAPI 的新手可以跳过。
|
||||
本节内容相对进阶,如果你刚开始,可以先跳过。
|
||||
|
||||
OAuth2 作用域不是必需的,没有它,您也可以处理身份验证与授权。
|
||||
你并不一定需要 OAuth2 作用域,你也可以用你自己的方式处理认证与授权。
|
||||
|
||||
但 OAuth2 作用域与 API(通过 OpenAPI)及 API 文档集成地更好。
|
||||
但带作用域的 OAuth2 能很好地集成进你的 API(通过 OpenAPI)和 API 文档。
|
||||
|
||||
不管怎么说,**FastAPI** 支持在代码中使用作用域或其它安全/授权需求项。
|
||||
不过,无论如何,你都可以在代码中按需强制这些作用域,或任何其它安全/授权需求。
|
||||
|
||||
很多情况下,OAuth2 作用域就像一把牛刀。
|
||||
很多情况下,带作用域的 OAuth2 可能有点“大材小用”。
|
||||
|
||||
但如果您确定要使用作用域,或对它有兴趣,请继续阅读。
|
||||
但如果你确实需要它,或者只是好奇,请继续阅读。
|
||||
|
||||
///
|
||||
|
||||
## OAuth2 作用域与 OpenAPI
|
||||
## OAuth2 作用域与 OpenAPI { #oauth2-scopes-and-openapi }
|
||||
|
||||
OAuth2 规范的**作用域**是由空格分割的字符串组成的列表。
|
||||
OAuth2 规范将“作用域”定义为由空格分隔的字符串列表。
|
||||
|
||||
这些字符串支持任何格式,但不能包含空格。
|
||||
这些字符串的内容可以是任意格式,但不应包含空格。
|
||||
|
||||
作用域表示的是**权限**。
|
||||
这些作用域表示“权限”。
|
||||
|
||||
OpenAPI 中(例如 API 文档)可以定义**安全方案**。
|
||||
在 OpenAPI(例如 API 文档)中,你可以定义“安全方案”(security schemes)。
|
||||
|
||||
这些安全方案在使用 OAuth2 时,还可以声明和使用作用域。
|
||||
当这些安全方案使用 OAuth2 时,你还可以声明并使用作用域。
|
||||
|
||||
**作用域**只是(不带空格的)字符串。
|
||||
每个“作用域”只是一个(不带空格的)字符串。
|
||||
|
||||
常用于声明特定安全权限,例如:
|
||||
它们通常用于声明特定的安全权限,例如:
|
||||
|
||||
* 常见用例为,`users:read` 或 `users:write`
|
||||
* 脸书和 Instagram 使用 `instagram_basic`
|
||||
* 谷歌使用 `https://www.googleapis.com/auth/drive`
|
||||
* 常见示例:`users:read` 或 `users:write`
|
||||
* Facebook / Instagram 使用 `instagram_basic`
|
||||
* Google 使用 `https://www.googleapis.com/auth/drive`
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
OAuth2 中,**作用域**只是声明特定权限的字符串。
|
||||
在 OAuth2 中,“作用域”只是一个声明所需特定权限的字符串。
|
||||
|
||||
是否使用冒号 `:` 等符号,或是不是 URL 并不重要。
|
||||
是否包含像 `:` 这样的字符,或者是不是一个 URL,并不重要。
|
||||
|
||||
这些细节只是特定的实现方式。
|
||||
这些细节取决于具体实现。
|
||||
|
||||
对 OAuth2 来说,它们都只是字符串而已。
|
||||
对 OAuth2 而言,它们都只是字符串。
|
||||
|
||||
///
|
||||
|
||||
## 全局纵览
|
||||
## 全局纵览 { #global-view }
|
||||
|
||||
首先,快速浏览一下以下代码与**用户指南**中 [OAuth2 实现密码哈希与 Bearer JWT 令牌验证](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}一章中代码的区别。以下代码使用 OAuth2 作用域:
|
||||
首先,让我们快速看看与**用户指南**中 [OAuth2 实现密码(含哈希)、Bearer + JWT 令牌](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 示例相比有哪些变化。现在开始使用 OAuth2 作用域:
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:124,128:134,139,153] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *}
|
||||
|
||||
下面,我们逐步说明修改的代码内容。
|
||||
下面我们逐步回顾这些更改。
|
||||
|
||||
## OAuth2 安全方案
|
||||
## OAuth2 安全方案 { #oauth2-security-scheme }
|
||||
|
||||
第一个修改的地方是,使用两个作用域 `me` 和 `items ` 声明 OAuth2 安全方案。
|
||||
第一个变化是:我们在声明 OAuth2 安全方案时,添加了两个可用的作用域 `me` 和 `items`。
|
||||
|
||||
`scopes` 参数接收**字典**,键是作用域、值是作用域的描述:
|
||||
参数 `scopes` 接收一个 `dict`,以作用域为键、描述为值:
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[62:65] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *}
|
||||
|
||||
因为声明了作用域,所以登录或授权时会在 API 文档中显示。
|
||||
因为我们现在声明了这些作用域,所以当你登录/授权时,它们会显示在 API 文档里。
|
||||
|
||||
此处,选择给予访问权限的作用域: `me` 和 `items`。
|
||||
你可以选择要授予访问权限的作用域:`me` 和 `items`。
|
||||
|
||||
这也是使用脸书、谷歌、GitHub 登录时的授权机制。
|
||||
这与使用 Facebook、Google、GitHub 等登录时授予权限的机制相同:
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
## JWT 令牌作用域
|
||||
## 带作用域的 JWT 令牌 { #jwt-token-with-scopes }
|
||||
|
||||
现在,修改令牌*路径操作*,返回请求的作用域。
|
||||
现在,修改令牌的*路径操作*以返回请求的作用域。
|
||||
|
||||
此处仍然使用 `OAuth2PasswordRequestForm`。它包含类型为**字符串列表**的 `scopes` 属性,且`scopes` 属性中包含要在请求里接收的每个作用域。
|
||||
我们仍然使用 `OAuth2PasswordRequestForm`。它包含 `scopes` 属性,其值是 `list[str]`,包含请求中接收到的每个作用域。
|
||||
|
||||
这样,返回的 JWT 令牌中就包含了作用域。
|
||||
我们把这些作用域作为 JWT 令牌的一部分返回。
|
||||
|
||||
/// danger | 危险
|
||||
|
||||
为了简明起见,本例把接收的作用域直接添加到了令牌里。
|
||||
为简单起见,此处我们只是把接收到的作用域直接添加到了令牌中。
|
||||
|
||||
但在您的应用中,为了安全,应该只把作用域添加到确实需要作用域的用户,或预定义的用户。
|
||||
但在你的应用里,为了安全起见,你应该只添加该用户实际能够拥有的作用域,或你预先定义的作用域。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[153] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *}
|
||||
|
||||
## 在*路径操作*与依赖项中声明作用域
|
||||
## 在*路径操作*与依赖项中声明作用域 { #declare-scopes-in-path-operations-and-dependencies }
|
||||
|
||||
接下来,为*路径操作* `/users/me/items/` 声明作用域 `items`。
|
||||
现在我们声明,路径操作 `/users/me/items/` 需要作用域 `items`。
|
||||
|
||||
为此,要从 `fastapi` 中导入并使用 `Security` 。
|
||||
为此,从 `fastapi` 导入并使用 `Security`。
|
||||
|
||||
`Security` 声明依赖项的方式和 `Depends` 一样,但 `Security` 还能接收作用域(字符串)列表类型的参数 `scopes`。
|
||||
你可以用 `Security` 来声明依赖(就像 `Depends` 一样),但 `Security` 还接收一个 `scopes` 参数,其值是作用域(字符串)列表。
|
||||
|
||||
此处使用与 `Depends` 相同的方式,把依赖项函数 `get_current_active_user` 传递给 `Security`。
|
||||
在这里,我们把依赖函数 `get_current_active_user` 传给 `Security`(就像用 `Depends` 一样)。
|
||||
|
||||
同时,还传递了作用域**列表**,本例中只传递了一个作用域:`items`(此处支持传递更多作用域)。
|
||||
同时还传入一个作用域 `list`,此处仅包含一个作用域:`items`(也可以包含更多)。
|
||||
|
||||
依赖项函数 `get_current_active_user` 还能声明子依赖项,不仅可以使用 `Depends`,也可以使用 `Security`。声明子依赖项函数(`get_current_user`)及更多作用域。
|
||||
依赖函数 `get_current_active_user` 也可以声明子依赖,不仅可以用 `Depends`,也可以用 `Security`。它声明了自己的子依赖函数(`get_current_user`),并添加了更多的作用域需求。
|
||||
|
||||
本例要求使用作用域 `me`(还可以使用更多作用域)。
|
||||
在这个例子里,它需要作用域 `me`(也可以需要多个作用域)。
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
不必在不同位置添加不同的作用域。
|
||||
|
||||
本例使用的这种方式只是为了展示 **FastAPI** 如何处理在不同层级声明的作用域。
|
||||
这里这样做,是为了演示 **FastAPI** 如何处理在不同层级声明的作用域。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[4,139,166] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *}
|
||||
|
||||
/// info | 技术细节
|
||||
|
||||
`Security` 实际上是 `Depends` 的子类,而且只比 `Depends` 多一个参数。
|
||||
`Security` 实际上是 `Depends` 的子类,它只多了一个我们稍后会看到的参数。
|
||||
|
||||
但使用 `Security` 代替 `Depends`,**FastAPI** 可以声明安全作用域,并在内部使用这些作用域,同时,使用 OpenAPI 存档 API。
|
||||
但当你使用 `Security` 而不是 `Depends` 时,**FastAPI** 会知道它可以声明安全作用域,在内部使用它们,并用 OpenAPI 文档化 API。
|
||||
|
||||
但实际上,从 `fastapi` 导入的 `Query`、`Path`、`Depends`、`Security` 等对象,只是返回特殊类的函数。
|
||||
另外,从 `fastapi` 导入的 `Query`、`Path`、`Depends`、`Security` 等,实际上都是返回特殊类的函数。
|
||||
|
||||
///
|
||||
|
||||
## 使用 `SecurityScopes`
|
||||
## 使用 `SecurityScopes` { #use-securityscopes }
|
||||
|
||||
修改依赖项 `get_current_user`。
|
||||
现在更新依赖项 `get_current_user`。
|
||||
|
||||
这是上面的依赖项使用的依赖项。
|
||||
上面那些依赖会用到它。
|
||||
|
||||
这里使用的也是之前创建的 OAuth2 方案,并把它声明为依赖项:`oauth2_scheme`。
|
||||
这里我们使用之前创建的同一个 OAuth2 方案,并把它声明为依赖:`oauth2_scheme`。
|
||||
|
||||
该依赖项函数本身不需要作用域,因此,可以使用 `Depends` 和 `oauth2_scheme`。不需要指定安全作用域时,不必使用 `Security`。
|
||||
因为这个依赖函数本身没有任何作用域需求,所以我们可以用 `Depends(oauth2_scheme)`,当不需要指定安全作用域时,不必使用 `Security`。
|
||||
|
||||
此处还声明了从 `fastapi.security` 导入的 `SecurityScopes` 类型的特殊参数。
|
||||
我们还声明了一个从 `fastapi.security` 导入的特殊参数 `SecurityScopes` 类型。
|
||||
|
||||
`SecuriScopes` 类与 `Request` 类似(`Request` 用于直接提取请求对象)。
|
||||
这个 `SecurityScopes` 类类似于 `Request`(`Request` 用来直接获取请求对象)。
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[8,105] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *}
|
||||
|
||||
## 使用 `scopes`
|
||||
## 使用 `scopes` { #use-the-scopes }
|
||||
|
||||
参数 `security_scopes` 的类型是 `SecurityScopes`。
|
||||
|
||||
它的属性 `scopes` 是作用域列表,所有依赖项都把它作为子依赖项。也就是说所有**依赖**……这听起来有些绕,后文会有解释。
|
||||
它会有一个 `scopes` 属性,包含一个列表,里面是它自身以及所有把它作为子依赖的依赖项所需要的所有作用域。也就是说,所有“依赖者”……这可能有点绕,下面会再次解释。
|
||||
|
||||
(类 `SecurityScopes` 的)`security_scopes` 对象还提供了单字符串类型的属性 `scope_str`,该属性是(要在本例中使用的)用空格分割的作用域。
|
||||
`security_scopes` 对象(类型为 `SecurityScopes`)还提供了一个 `scope_str` 属性,它是一个用空格分隔这些作用域的单个字符串(我们将会用到它)。
|
||||
|
||||
此处还创建了后续代码中要复用(`raise`)的 `HTTPException` 。
|
||||
我们创建一个 `HTTPException`,后面可以在多个位置复用(`raise`)它。
|
||||
|
||||
该异常包含了作用域所需的(如有),以空格分割的字符串(使用 `scope_str`)。该字符串要放到包含作用域的 `WWW-Authenticate` 请求头中(这也是规范的要求)。
|
||||
在这个异常中,我们包含所需的作用域(如果有的话),以空格分隔的字符串(使用 `scope_str`)。我们把这个包含作用域的字符串放在 `WWW-Authenticate` 响应头中(这是规范要求的一部分)。
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[105,107:115] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *}
|
||||
|
||||
## 校验 `username` 与数据形状
|
||||
## 校验 `username` 与数据形状 { #verify-the-username-and-data-shape }
|
||||
|
||||
我们可以校验是否获取了 `username`,并抽取作用域。
|
||||
我们校验是否获取到了 `username`,并提取作用域。
|
||||
|
||||
然后,使用 Pydantic 模型校验数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或使用 Pydantic 模型验证数据时出错,就会触发之前创建的 `HTTPException` 异常。
|
||||
然后使用 Pydantic 模型验证这些数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或用 Pydantic 验证数据时出错,就抛出我们之前创建的 `HTTPException`。
|
||||
|
||||
对此,要使用新的属性 `scopes` 更新 Pydantic 模型 `TokenData`。
|
||||
为此,我们给 Pydantic 模型 `TokenData` 添加了一个新属性 `scopes`。
|
||||
|
||||
使用 Pydantic 验证数据可以确保数据中含有由作用域组成的**字符串列表**,以及 `username` 字符串等内容。
|
||||
通过用 Pydantic 验证数据,我们可以确保确实得到了例如一个由作用域组成的 `list[str]`,以及一个 `str` 类型的 `username`。
|
||||
|
||||
反之,如果使用**字典**或其它数据结构,就有可能在后面某些位置破坏应用,形成安全隐患。
|
||||
而不是,例如得到一个 `dict` 或其它什么,这可能会在后续某个时刻破坏应用,形成安全风险。
|
||||
|
||||
还可以使用用户名验证用户,如果没有用户,也会触发之前创建的异常。
|
||||
我们还验证是否存在该用户名的用户,如果没有,就抛出前面创建的同一个异常。
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[46,116:127] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *}
|
||||
|
||||
## 校验 `scopes`
|
||||
## 校验 `scopes` { #verify-the-scopes }
|
||||
|
||||
接下来,校验所有依赖项和依赖要素(包括*路径操作*)所需的作用域。这些作用域包含在令牌的 `scopes` 里,如果不在其中就会触发 `HTTPException` 异常。
|
||||
现在我们要验证,这个依赖以及所有依赖者(包括*路径操作*)所需的所有作用域,是否都包含在接收到的令牌里的作用域中,否则就抛出 `HTTPException`。
|
||||
|
||||
为此,要使用包含所有作用域**字符串列表**的 `security_scopes.scopes`, 。
|
||||
为此,我们使用 `security_scopes.scopes`,它包含一个由这些作用域组成的 `list[str]`。
|
||||
|
||||
{* ../../docs_src/security/tutorial005.py hl[128:134] *}
|
||||
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *}
|
||||
|
||||
## 依赖项树与作用域
|
||||
## 依赖树与作用域 { #dependency-tree-and-scopes }
|
||||
|
||||
再次查看这个依赖项树与作用域。
|
||||
再次回顾这个依赖树与作用域。
|
||||
|
||||
`get_current_active_user` 依赖项包含子依赖项 `get_current_user`,并在 `get_current_active_user`中声明了作用域 `"me"` 包含所需作用域列表 ,在 `security_scopes.scopes` 中传递给 `get_current_user`。
|
||||
由于 `get_current_active_user` 依赖把 `get_current_user` 作为子依赖,因此在 `get_current_active_user` 中声明的作用域 `"me"` 会被包含在传给 `get_current_user` 的 `security_scopes.scopes` 所需作用域列表中。
|
||||
|
||||
*路径操作*自身也声明了作用域,`"items"`,这也是 `security_scopes.scopes` 列表传递给 `get_current_user` 的。
|
||||
*路径操作*本身也声明了一个作用域 `"items"`,它也会包含在传给 `get_current_user` 的 `security_scopes.scopes` 列表中。
|
||||
|
||||
依赖项与作用域的层级架构如下:
|
||||
依赖与作用域的层级结构如下:
|
||||
|
||||
* *路径操作* `read_own_items` 包含:
|
||||
* 依赖项所需的作用域 `["items"]`:
|
||||
* `get_current_active_user`:
|
||||
* 依赖项函数 `get_current_active_user` 包含:
|
||||
* 所需的作用域 `"me"` 包含依赖项:
|
||||
* `get_current_user`:
|
||||
* 依赖项函数 `get_current_user` 包含:
|
||||
* 没有作用域需求其自身
|
||||
* 依赖项使用 `oauth2_scheme`
|
||||
* `security_scopes` 参数的类型是 `SecurityScopes`:
|
||||
* `security_scopes` 参数的属性 `scopes` 是包含上述声明的所有作用域的**列表**,因此:
|
||||
* `security_scopes.scopes` 包含用于*路径操作*的 `["me", "items"]`
|
||||
* `security_scopes.scopes` 包含*路径操作* `read_users_me` 的 `["me"]`,因为它在依赖项里被声明
|
||||
* `security_scopes.scopes` 包含用于*路径操作* `read_system_status` 的 `[]`(空列表),并且它的依赖项 `get_current_user` 也没有声明任何 `scope`
|
||||
* 带有依赖的必需作用域 `["items"]`:
|
||||
* `get_current_active_user`:
|
||||
* 依赖函数 `get_current_active_user` 包含:
|
||||
* 带有依赖的必需作用域 `["me"]`:
|
||||
* `get_current_user`:
|
||||
* 依赖函数 `get_current_user` 包含:
|
||||
* 自身不需要任何作用域。
|
||||
* 一个使用 `oauth2_scheme` 的依赖。
|
||||
* 一个类型为 `SecurityScopes` 的 `security_scopes` 参数:
|
||||
* 该 `security_scopes` 参数有一个 `scopes` 属性,它是一个包含上面所有已声明作用域的 `list`,因此:
|
||||
* 对于*路径操作* `read_own_items`,`security_scopes.scopes` 将包含 `["me", "items"]`。
|
||||
* 对于*路径操作* `read_users_me`,`security_scopes.scopes` 将包含 `["me"]`,因为它在依赖 `get_current_active_user` 中被声明。
|
||||
* 对于*路径操作* `read_system_status`,`security_scopes.scopes` 将包含 `[]`(空列表),因为它既没有声明任何带 `scopes` 的 `Security`,其依赖 `get_current_user` 也没有声明任何 `scopes`。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
此处重要且**神奇**的事情是,`get_current_user` 检查每个*路径操作*时可以使用不同的 `scopes` 列表。
|
||||
这里重要且“神奇”的地方是,`get_current_user` 在检查每个*路径操作*时会得到不同的 `scopes` 列表。
|
||||
|
||||
所有这些都依赖于在每个*路径操作*和指定*路径操作*的依赖树中的每个依赖项。
|
||||
这一切都取决于为该特定*路径操作*在其自身以及依赖树中的每个依赖里声明的 `scopes`。
|
||||
|
||||
///
|
||||
|
||||
## `SecurityScopes` 的更多细节
|
||||
## 关于 `SecurityScopes` 的更多细节 { #more-details-about-securityscopes }
|
||||
|
||||
您可以任何位置或多个位置使用 `SecurityScopes`,不一定非得在**根**依赖项中使用。
|
||||
你可以在任意位置、多个位置使用 `SecurityScopes`,不一定非得在“根”依赖里。
|
||||
|
||||
它总是在当前 `Security` 依赖项中和所有依赖因子对于**特定** *路径操作*和**特定**依赖树中安全作用域
|
||||
它总会包含当前 `Security` 依赖中以及所有依赖者在“该特定”*路径操作*和“该特定”依赖树里声明的安全作用域。
|
||||
|
||||
因为 `SecurityScopes` 包含所有由依赖项声明的作用域,可以在核心依赖函数中用它验证所需作用域的令牌,然后再在不同的*路径操作*中声明不同作用域需求。
|
||||
因为 `SecurityScopes` 会包含依赖者声明的所有作用域,你可以在一个核心依赖函数里用它验证令牌是否具有所需作用域,然后在不同的*路径操作*里声明不同的作用域需求。
|
||||
|
||||
它们会为每个*路径操作*进行单独检查。
|
||||
它们会针对每个*路径操作*分别检查。
|
||||
|
||||
## 查看文档
|
||||
## 查看文档 { #check-it }
|
||||
|
||||
打开 API 文档,进行身份验证,并指定要授权的作用域。
|
||||
打开 API 文档,你可以进行身份验证,并指定要授权的作用域。
|
||||
|
||||
<img src="/img/tutorial/security/image11.png">
|
||||
|
||||
没有选择任何作用域,也可以进行**身份验证**,但访问 `/uses/me` 或 `/users/me/items` 时,会显示没有足够的权限。但仍可以访问 `/status/`。
|
||||
如果你不选择任何作用域,你依然会“通过认证”,但当你访问 `/users/me/` 或 `/users/me/items/` 时,会收到一个错误,提示你没有足够的权限。你仍然可以访问 `/status/`。
|
||||
|
||||
如果选择了作用域 `me`,但没有选择作用域 `items`,则可以访问 `/users/me/`,但不能访问 `/users/me/items`。
|
||||
如果你选择了作用域 `me`,但没有选择作用域 `items`,你可以访问 `/users/me/`,但不能访问 `/users/me/items/`。
|
||||
|
||||
这就是通过用户提供的令牌使用第三方应用访问这些*路径操作*时会发生的情况,具体怎样取决于用户授予第三方应用的权限。
|
||||
当第三方应用使用用户提供的令牌访问这些*路径操作*时,也会发生同样的情况,取决于用户授予该应用了多少权限。
|
||||
|
||||
## 关于第三方集成
|
||||
## 关于第三方集成 { #about-third-party-integrations }
|
||||
|
||||
本例使用 OAuth2 **密码**流。
|
||||
在这个示例中我们使用的是 OAuth2 的“password”流。
|
||||
|
||||
这种方式适用于登录我们自己的应用,最好使用我们自己的前端。
|
||||
当我们登录自己的应用(很可能还有我们自己的前端)时,这是合适的。
|
||||
|
||||
因为我们能控制自己的前端应用,可以信任它接收 `username` 与 `password`。
|
||||
因为我们可以信任它来接收 `username` 和 `password`,毕竟我们掌控它。
|
||||
|
||||
但如果构建的是连接其它应用的 OAuth2 应用,比如具有与脸书、谷歌、GitHub 相同功能的第三方身份验证应用。那您就应该使用其它安全流。
|
||||
但如果你在构建一个 OAuth2 应用,让其它应用来连接(也就是说,你在构建等同于 Facebook、Google、GitHub 等的身份验证提供商),你应该使用其它的流。
|
||||
|
||||
最常用的是隐式流。
|
||||
最常见的是隐式流(implicit flow)。
|
||||
|
||||
最安全的是代码流,但实现起来更复杂,而且需要更多步骤。因为它更复杂,很多第三方身份验证应用最终建议使用隐式流。
|
||||
最安全的是代码流(authorization code flow),但实现更复杂,需要更多步骤。也因为更复杂,很多提供商最终会建议使用隐式流。
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
每个身份验证应用都会采用不同方式会命名流,以便融合入自己的品牌。
|
||||
每个身份验证提供商常常会用不同的方式给它们的流命名,以融入自己的品牌。
|
||||
|
||||
但归根结底,它们使用的都是 OAuth2 标准。
|
||||
但归根结底,它们实现的都是同一个 OAuth2 标准。
|
||||
|
||||
///
|
||||
|
||||
**FastAPI** 的 `fastapi.security.oauth2` 里包含了所有 OAuth2 身份验证流工具。
|
||||
**FastAPI** 在 `fastapi.security.oauth2` 中为所有这些 OAuth2 身份验证流提供了工具。
|
||||
|
||||
## 装饰器 `dependencies` 中的 `Security`
|
||||
## 装饰器 `dependencies` 中的 `Security` { #security-in-decorator-dependencies }
|
||||
|
||||
同样,您可以在装饰器的 `dependencies` 参数中定义 `Depends` 列表,(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank})),也可以把 `scopes` 与 `Security` 一起使用。
|
||||
就像你可以在装饰器的 `dependencies` 参数中定义 `Depends` 的 `list`(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}),你也可以在那儿配合 `Security` 使用 `scopes`。
|
||||
|
||||
@@ -1,190 +1,94 @@
|
||||
# 设置和环境变量
|
||||
# 设置和环境变量 { #settings-and-environment-variables }
|
||||
|
||||
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。
|
||||
在许多情况下,你的应用可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等。
|
||||
|
||||
这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。
|
||||
这些设置中的大多数是可变的(可能会改变),例如数据库 URL。并且很多可能是敏感的,比如密钥。
|
||||
|
||||
因此,通常会将它们提供为由应用程序读取的环境变量。
|
||||
|
||||
## 环境变量
|
||||
/// tip | 提示
|
||||
|
||||
/// tip
|
||||
|
||||
如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。
|
||||
要理解环境变量,你可以阅读[环境变量](../environment-variables.md){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。
|
||||
## 类型和验证 { #types-and-validation }
|
||||
|
||||
您可以在 shell 中创建和使用环境变量,而无需使用 Python:
|
||||
这些环境变量只能处理文本字符串,因为它们在 Python 之外,并且必须与其他程序及系统的其余部分兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。
|
||||
|
||||
//// tab | Linux、macOS、Windows Bash
|
||||
这意味着,在 Python 中从环境变量读取的任何值都是 `str` 类型,任何到不同类型的转换或任何验证都必须在代码中完成。
|
||||
|
||||
## Pydantic 的 `Settings` { #pydantic-settings }
|
||||
|
||||
幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的这些设置:<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>。
|
||||
|
||||
### 安装 `pydantic-settings` { #install-pydantic-settings }
|
||||
|
||||
首先,确保你创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装 `pydantic-settings` 包:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 您可以创建一个名为 MY_NAME 的环境变量
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// 然后您可以与其他程序一起使用它,例如
|
||||
$ echo "Hello $MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
$ pip install pydantic-settings
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
|
||||
//// tab | Windows PowerShell
|
||||
当你用以下方式安装 `all` 扩展时,它也会被一并安装:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 创建一个名为 MY_NAME 的环境变量
|
||||
$ $Env:MY_NAME = "Wade Wilson"
|
||||
|
||||
// 与其他程序一起使用它,例如
|
||||
$ echo "Hello $Env:MY_NAME"
|
||||
|
||||
Hello Wade Wilson
|
||||
$ pip install "fastapi[all]"
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
////
|
||||
### 创建 `Settings` 对象 { #create-the-settings-object }
|
||||
|
||||
### 在 Python 中读取环境变量
|
||||
从 Pydantic 导入 `BaseSettings` 并创建一个子类,这与创建 Pydantic 模型非常相似。
|
||||
|
||||
您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。
|
||||
与 Pydantic 模型一样,用类型注解声明类属性,也可以指定默认值。
|
||||
|
||||
例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容:
|
||||
你可以使用与 Pydantic 模型相同的验证功能和工具,例如不同的数据类型,以及使用 `Field()` 进行附加验证。
|
||||
|
||||
```Python hl_lines="3"
|
||||
import os
|
||||
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *}
|
||||
|
||||
name = os.getenv("MY_NAME", "World")
|
||||
print(f"Hello {name} from Python")
|
||||
```
|
||||
/// tip | 提示
|
||||
|
||||
/// tip
|
||||
|
||||
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的第二个参数是要返回的默认值。
|
||||
|
||||
如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。
|
||||
如果你想要一个可以快速复制粘贴的示例,请不要使用这个示例,使用下面最后一个示例。
|
||||
|
||||
///
|
||||
|
||||
然后,您可以调用该 Python 程序:
|
||||
当你创建该 `Settings` 类的实例(此处是 `settings` 对象)时,Pydantic 会以不区分大小写的方式读取环境变量,因此,大写变量 `APP_NAME` 仍会用于属性 `app_name`。
|
||||
|
||||
接着它会转换并验证数据。因此,当你使用该 `settings` 对象时,你将获得你声明的类型的数据(例如 `items_per_user` 将是 `int`)。
|
||||
|
||||
### 使用 `settings` { #use-the-settings }
|
||||
|
||||
然后你可以在应用中使用新的 `settings` 对象:
|
||||
|
||||
{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *}
|
||||
|
||||
### 运行服务器 { #run-the-server }
|
||||
|
||||
接下来,运行服务器,并把配置作为环境变量传入,例如你可以设置 `ADMIN_EMAIL` 和 `APP_NAME`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 这里我们还没有设置环境变量
|
||||
$ python main.py
|
||||
|
||||
// 因为我们没有设置环境变量,所以我们得到默认值
|
||||
|
||||
Hello World from Python
|
||||
|
||||
// 但是如果我们先创建一个环境变量
|
||||
$ export MY_NAME="Wade Wilson"
|
||||
|
||||
// 然后再次调用程序
|
||||
$ python main.py
|
||||
|
||||
// 现在它可以读取环境变量
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。
|
||||
|
||||
|
||||
|
||||
您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。
|
||||
|
||||
要做到这一点,在程序本身之前的同一行创建它:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// 在此程序调用行中创建一个名为 MY_NAME 的环境变量
|
||||
$ MY_NAME="Wade Wilson" python main.py
|
||||
|
||||
// 现在它可以读取环境变量
|
||||
|
||||
Hello Wade Wilson from Python
|
||||
|
||||
// 之后环境变量不再存在
|
||||
$ python main.py
|
||||
|
||||
Hello World from Python
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip
|
||||
|
||||
您可以在 <a href="https://12factor.net/config" class="external-link" target="_blank">Twelve-Factor App: Config</a> 中阅读更多相关信息。
|
||||
|
||||
///
|
||||
|
||||
### 类型和验证
|
||||
|
||||
这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。
|
||||
|
||||
这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。
|
||||
|
||||
## Pydantic 的 `Settings`
|
||||
|
||||
幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>。
|
||||
|
||||
### 创建 `Settings` 对象
|
||||
|
||||
从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。
|
||||
|
||||
与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。
|
||||
|
||||
您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。
|
||||
|
||||
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *}
|
||||
|
||||
/// tip
|
||||
|
||||
如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。
|
||||
|
||||
///
|
||||
|
||||
然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。
|
||||
|
||||
然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。
|
||||
|
||||
### 使用 `settings`
|
||||
|
||||
然后,您可以在应用程序中使用新的 `settings` 对象:
|
||||
|
||||
{* ../../docs_src/settings/tutorial001.py hl[18:20] *}
|
||||
|
||||
### 运行服务器
|
||||
|
||||
接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app
|
||||
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。
|
||||
要为单个命令设置多个环境变量,只需用空格分隔它们,并把它们都放在命令前面。
|
||||
|
||||
///
|
||||
|
||||
@@ -192,118 +96,118 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app
|
||||
|
||||
`app_name` 将为 `"ChimichangApp"`。
|
||||
|
||||
而 `items_per_user` 将保持其默认值为 `50`。
|
||||
而 `items_per_user` 会保持默认值 `50`。
|
||||
|
||||
## 在另一个模块中设置
|
||||
## 在另一个模块中放置设置 { #settings-in-another-module }
|
||||
|
||||
您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。
|
||||
你可以把这些设置放在另一个模块文件中,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。
|
||||
|
||||
例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容:
|
||||
例如,可以有一个 `config.py` 文件:
|
||||
|
||||
{* ../../docs_src/settings/app01/config.py *}
|
||||
{* ../../docs_src/settings/app01_py39/config.py *}
|
||||
|
||||
然后在一个名为 `main.py` 的文件中使用它:
|
||||
然后在 `main.py` 文件中使用它:
|
||||
|
||||
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
|
||||
{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。
|
||||
你还需要一个 `__init__.py` 文件,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。
|
||||
|
||||
///
|
||||
|
||||
## 在依赖项中使用设置
|
||||
## 在依赖项中提供设置 { #settings-in-a-dependency }
|
||||
|
||||
在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。
|
||||
在某些情况下,从依赖项中提供设置可能更有用,而不是在所有地方都使用一个全局的 `settings` 对象。
|
||||
|
||||
这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。
|
||||
这在测试期间尤其有用,因为可以很容易地用你自己的自定义设置覆盖依赖项。
|
||||
|
||||
### 配置文件
|
||||
### 配置文件 { #the-config-file }
|
||||
|
||||
根据前面的示例,您的 `config.py` 文件可能如下所示:
|
||||
延续上一个示例,你的 `config.py` 文件可能如下所示:
|
||||
|
||||
{* ../../docs_src/settings/app02/config.py hl[10] *}
|
||||
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *}
|
||||
|
||||
请注意,现在我们不创建默认实例 `settings = Settings()`。
|
||||
注意,现在我们不再创建默认实例 `settings = Settings()`。
|
||||
|
||||
### 主应用程序文件
|
||||
### 主应用文件 { #the-main-app-file }
|
||||
|
||||
现在我们创建一个依赖项,返回一个新的 `config.Settings()`。
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
我们稍后会讨论 `@lru_cache`。
|
||||
|
||||
目前,您可以将 `get_settings()` 视为普通函数。
|
||||
目前你可以把 `get_settings()` 当作普通函数。
|
||||
|
||||
///
|
||||
|
||||
然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。
|
||||
然后我们可以在“路径操作函数”中将其作为依赖项引入,并在需要的任何地方使用它。
|
||||
|
||||
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
|
||||
|
||||
### 设置和测试
|
||||
### 设置与测试 { #settings-and-testing }
|
||||
|
||||
然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象:
|
||||
接着,在测试期间,通过为 `get_settings` 创建依赖项覆盖,就可以很容易地提供一个不同的设置对象:
|
||||
|
||||
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *}
|
||||
{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *}
|
||||
|
||||
在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。
|
||||
|
||||
然后,我们可以测试它是否被使用。
|
||||
然后我们可以测试它是否被使用。
|
||||
|
||||
## 从 `.env` 文件中读取设置
|
||||
## 读取 `.env` 文件 { #reading-a-env-file }
|
||||
|
||||
如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。
|
||||
如果你有许多设置可能经常变化,或在不同环境中不同,那么把它们放进一个文件中,然后像环境变量一样从中读取,可能非常有用。
|
||||
|
||||
这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。
|
||||
这种做法非常常见:这些环境变量通常放在名为 `.env` 的文件中,该文件被称为 “dotenv”。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。
|
||||
以点(`.`)开头的文件在类 Unix 系统(如 Linux 和 macOS)中是隐藏文件。
|
||||
|
||||
但是,dotenv 文件实际上不一定要具有确切的文件名。
|
||||
但 dotenv 文件并不一定必须是这个确切的文件名。
|
||||
|
||||
///
|
||||
|
||||
Pydantic 支持使用外部库从这些类型的文件中读取。您可以在<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic 设置: Dotenv (.env) 支持</a>中阅读更多相关信息。
|
||||
Pydantic 支持使用一个外部库来从这类文件中读取。你可以在 <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a> 中阅读更多信息。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
要使其工作,您需要执行 `pip install python-dotenv`。
|
||||
要使其工作,你需要执行 `pip install python-dotenv`。
|
||||
|
||||
///
|
||||
|
||||
### `.env` 文件
|
||||
### `.env` 文件 { #the-env-file }
|
||||
|
||||
您可以使用以下内容创建一个名为 `.env` 的文件:
|
||||
你可以有一个 `.env` 文件,内容如下:
|
||||
|
||||
```bash
|
||||
ADMIN_EMAIL="deadpool@example.com"
|
||||
APP_NAME="ChimichangApp"
|
||||
```
|
||||
|
||||
### 从 `.env` 文件中读取设置
|
||||
### 从 `.env` 中读取设置 { #read-settings-from-env }
|
||||
|
||||
然后,您可以使用以下方式更新您的 `config.py`:
|
||||
然后更新 `config.py`:
|
||||
|
||||
{* ../../docs_src/settings/app03/config.py hl[9:10] *}
|
||||
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *}
|
||||
|
||||
在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。
|
||||
/// tip | 提示
|
||||
|
||||
/// tip
|
||||
|
||||
`Config` 类仅用于 Pydantic 配置。您可以在<a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Pydantic Model Config</a>中阅读更多相关信息。
|
||||
`model_config` 属性仅用于 Pydantic 配置。你可以在 <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Concepts: Configuration</a> 中阅读更多信息。
|
||||
|
||||
///
|
||||
|
||||
### 使用 `lru_cache` 仅创建一次 `Settings`
|
||||
这里我们在你的 Pydantic `Settings` 类中定义配置项 `env_file`,并将其设置为我们想要使用的 dotenv 文件名。
|
||||
|
||||
从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。
|
||||
### 使用 `lru_cache` 仅创建一次 `Settings` { #creating-the-settings-only-once-with-lru-cache }
|
||||
|
||||
但是,每次执行以下操作:
|
||||
从磁盘读取文件通常是一个代价较高(缓慢)的操作,所以你可能希望只在第一次读取,然后复用同一个设置对象,而不是为每个请求都重新读取。
|
||||
|
||||
但是,每次我们执行:
|
||||
|
||||
```Python
|
||||
Settings()
|
||||
@@ -311,35 +215,36 @@ Settings()
|
||||
|
||||
都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。
|
||||
|
||||
如果依赖项函数只是这样的:
|
||||
如果依赖项函数是这样的:
|
||||
|
||||
```Python
|
||||
def get_settings():
|
||||
return Settings()
|
||||
```
|
||||
|
||||
我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️
|
||||
我们就会为每个请求创建该对象,并为每个请求读取 `.env` 文件。 ⚠️
|
||||
|
||||
但是,由于我们在顶部使用了 `@lru_cache` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️
|
||||
但由于我们在顶部使用了 `@lru_cache` 装饰器,`Settings` 对象只会在第一次调用时创建一次。 ✔️
|
||||
|
||||
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *}
|
||||
|
||||
然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。
|
||||
接着,对于后续请求中依赖项里对 `get_settings()` 的任何调用,它不会再次执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是会一遍又一遍地返回第一次调用时返回的那个相同对象。
|
||||
|
||||
#### `lru_cache` 技术细节
|
||||
#### `lru_cache` 技术细节 { #lru-cache-technical-details }
|
||||
|
||||
`@lru_cache` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。
|
||||
`@lru_cache` 会修改它所装饰的函数,使其返回第一次返回的相同值,而不是每次都重新计算并执行函数代码。
|
||||
|
||||
因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。
|
||||
因此,下面的函数会针对每个参数组合执行一次。然后,当以完全相同的参数组合调用该函数时,将重复使用该参数组合先前返回的值。
|
||||
|
||||
例如,如果你有一个函数:
|
||||
|
||||
例如,如果您有一个函数:
|
||||
```Python
|
||||
@lru_cache
|
||||
def say_hi(name: str, salutation: str = "Ms."):
|
||||
return f"Hello {salutation} {name}"
|
||||
```
|
||||
|
||||
您的程序可以像这样执行:
|
||||
你的程序可能会像这样执行:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -382,16 +287,16 @@ participant execute as Execute function
|
||||
end
|
||||
```
|
||||
|
||||
对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。
|
||||
在我们的依赖项 `get_settings()` 的情况下,该函数甚至不接受任何参数,因此它始终返回相同的值。
|
||||
|
||||
这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。
|
||||
这样,它的行为几乎就像是一个全局变量。但由于它使用了依赖项函数,我们可以在测试时很容易地覆盖它。
|
||||
|
||||
`@lru_cache` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在<a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python 文档中了解有关 `@lru_cache` 的更多信息</a>。
|
||||
`@lru_cache` 是 `functools` 的一部分,它属于 Python 标准库。你可以在 <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python 文档中关于 `@lru_cache` 的章节</a>阅读更多信息。
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。
|
||||
你可以使用 Pydantic Settings 来处理应用的设置或配置,享受 Pydantic 模型的全部能力。
|
||||
|
||||
* 通过使用依赖项,您可以简化测试。
|
||||
* 您可以使用 `.env` 文件。
|
||||
* 使用 `@lru_cache` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。
|
||||
- 通过使用依赖项,你可以简化测试。
|
||||
- 你可以与它一起使用 `.env` 文件。
|
||||
- 使用 `@lru_cache` 可以避免为每个请求反复读取 dotenv 文件,同时允许你在测试时进行覆盖。
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
# 子应用 - 挂载
|
||||
# 子应用 - 挂载 { #sub-applications-mounts }
|
||||
|
||||
如果需要两个独立的 FastAPI 应用,拥有各自独立的 OpenAPI 与文档,则需设置一个主应用,并**挂载**一个(或多个)子应用。
|
||||
|
||||
## 挂载 **FastAPI** 应用
|
||||
## 挂载 **FastAPI** 应用 { #mounting-a-fastapi-application }
|
||||
|
||||
**挂载**是指在特定路径中添加完全**独立**的应用,然后在该路径下使用*路径操作*声明的子应用处理所有事务。
|
||||
|
||||
### 顶层应用
|
||||
### 顶层应用 { #top-level-application }
|
||||
|
||||
首先,创建主(顶层)**FastAPI** 应用及其*路径操作*:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *}
|
||||
|
||||
### 子应用
|
||||
### 子应用 { #sub-application }
|
||||
|
||||
接下来,创建子应用及其*路径操作*。
|
||||
|
||||
子应用只是另一个标准 FastAPI 应用,但这个应用是被**挂载**的应用:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *}
|
||||
|
||||
### 挂载子应用
|
||||
### 挂载子应用 { #mount-the-sub-application }
|
||||
|
||||
在顶层应用 `app` 中,挂载子应用 `subapi`。
|
||||
|
||||
本例的子应用挂载在 `/subapi` 路径下:
|
||||
|
||||
{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *}
|
||||
{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *}
|
||||
|
||||
### 查看文档
|
||||
### 查看自动 API 文档 { #check-the-automatic-api-docs }
|
||||
|
||||
如果主文件是 `main.py`,则用以下 `uvicorn` 命令运行主应用:
|
||||
现在,使用你的文件运行 `fastapi` 命令:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
查看文档 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs。</a>
|
||||
然后在 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> 打开文档。
|
||||
|
||||
下图显示的是主应用 API 文档,只包括其自有的*路径操作*。
|
||||
|
||||
<img src="/img/tutorial/sub-applications/image01.png">
|
||||
|
||||
然后查看子应用文档 <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs。</a>
|
||||
然后查看子应用文档 <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs</a>。
|
||||
|
||||
下图显示的是子应用的 API 文档,也是只包括其自有的*路径操作*,所有这些路径操作都在 `/subapi` 子路径前缀下。
|
||||
|
||||
@@ -56,7 +56,7 @@ $ uvicorn main:app --reload
|
||||
|
||||
两个用户界面都可以正常运行,因为浏览器能够与每个指定的应用或子应用会话。
|
||||
|
||||
### 技术细节:`root_path`
|
||||
### 技术细节:`root_path` { #technical-details-root-path }
|
||||
|
||||
以上述方式挂载子应用时,FastAPI 使用 ASGI 规范中的 `root_path` 机制处理挂载子应用路径之间的通信。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 模板
|
||||
# 模板 { #templates }
|
||||
|
||||
**FastAPI** 支持多种模板引擎。
|
||||
|
||||
@@ -6,9 +6,9 @@ Flask 等工具使用的 Jinja2 是最用的模板引擎。
|
||||
|
||||
在 Starlette 的支持下,**FastAPI** 应用可以直接使用工具轻易地配置 Jinja2。
|
||||
|
||||
## 安装依赖项
|
||||
## 安装依赖项 { #install-dependencies }
|
||||
|
||||
安装 `jinja2`:
|
||||
确保你创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},激活它,并安装 `jinja2`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -20,23 +20,23 @@ $ pip install jinja2
|
||||
|
||||
</div>
|
||||
|
||||
## 使用 `Jinja2Templates`
|
||||
## 使用 `Jinja2Templates` { #using-jinja2templates }
|
||||
|
||||
* 导入 `Jinja2Templates`
|
||||
* 创建可复用的 `templates` 对象
|
||||
* 在返回模板的*路径操作*中声明 `Request` 参数
|
||||
* 使用 `templates` 渲染并返回 `TemplateResponse`, 传递模板的名称、request对象以及一个包含多个键值对(用于Jinja2模板)的"context"字典,
|
||||
* 使用 `templates` 渲染并返回 `TemplateResponse`,传递模板的名称、request 对象以及一个包含多个键值对(用于 Jinja2 模板)的 "context" 字典。
|
||||
|
||||
{* ../../docs_src/templates/tutorial001.py hl[4,11,15:16] *}
|
||||
{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *}
|
||||
|
||||
/// note | 笔记
|
||||
/// note
|
||||
|
||||
在FastAPI 0.108.0,Starlette 0.29.0之前,`name`是第一个参数。
|
||||
并且,在此之前,`request`对象是作为context的一部分以键值对的形式传递的。
|
||||
在 FastAPI 0.108.0,Starlette 0.29.0 之前,`name` 是第一个参数。
|
||||
并且,在此之前,`request` 对象是作为 context 的一部分以键值对的形式传递的。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 提示
|
||||
/// tip
|
||||
|
||||
通过声明 `response_class=HTMLResponse`,API 文档就能识别响应的对象是 HTML。
|
||||
|
||||
@@ -46,11 +46,11 @@ $ pip install jinja2
|
||||
|
||||
您还可以使用 `from starlette.templating import Jinja2Templates`。
|
||||
|
||||
**FastAPI** 的 `fastapi.templating` 只是为开发者提供的快捷方式。实际上,绝大多数可用响应都直接继承自 Starlette。 `Request` 与 `StaticFiles` 也一样。
|
||||
**FastAPI** 的 `fastapi.templating` 只是为开发者提供的快捷方式。实际上,绝大多数可用响应都直接继承自 Starlette。`Request` 与 `StaticFiles` 也一样。
|
||||
|
||||
///
|
||||
|
||||
## 编写模板
|
||||
## 编写模板 { #writing-templates }
|
||||
|
||||
编写模板 `templates/item.html`,代码如下:
|
||||
|
||||
@@ -58,7 +58,7 @@ $ pip install jinja2
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
### 模板上下文
|
||||
### 模板上下文值 { #template-context-values }
|
||||
|
||||
在包含如下语句的html中:
|
||||
|
||||
@@ -70,21 +70,21 @@ Item ID: {{ id }}
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...这将显示你从"context"字典传递的 `id`:
|
||||
...这将显示你从 "context" 字典传递的 `id`:
|
||||
|
||||
```Python
|
||||
{"id": id}
|
||||
```
|
||||
|
||||
例如。当ID为 `42`时, 会渲染成:
|
||||
例如。当 ID 为 `42` 时, 会渲染成:
|
||||
|
||||
```html
|
||||
Item ID: 42
|
||||
```
|
||||
|
||||
### 模板 `url_for` 参数
|
||||
### 模板 `url_for` 参数 { #template-url-for-arguments }
|
||||
|
||||
你还可以在模板内使用 `url_for()`,其参数与*路径操作函数*的参数相同.
|
||||
你还可以在模板内使用 `url_for()`,其参数与*路径操作函数*的参数相同。
|
||||
|
||||
所以,该部分:
|
||||
|
||||
@@ -96,30 +96,30 @@ Item ID: 42
|
||||
|
||||
{% endraw %}
|
||||
|
||||
...将生成一个与处理*路径操作函数* `read_item(id=id)`的URL相同的链接
|
||||
...将生成一个与处理*路径操作函数* `read_item(id=id)`的 URL 相同的链接
|
||||
|
||||
例如。当ID为 `42`时, 会渲染成:
|
||||
例如。当 ID 为 `42` 时, 会渲染成:
|
||||
|
||||
```html
|
||||
<a href="/items/42">
|
||||
```
|
||||
|
||||
## 模板与静态文件
|
||||
## 模板与静态文件 { #templates-and-static-files }
|
||||
|
||||
你还可以在模板内部将 `url_for()`用于静态文件,例如你挂载的 `name="static"`的 `StaticFiles`。
|
||||
你还可以在模板内部将 `url_for()` 用于静态文件,例如你挂载的 `name="static"` 的 `StaticFiles`。
|
||||
|
||||
```jinja hl_lines="4"
|
||||
{!../../docs_src/templates/templates/item.html!}
|
||||
```
|
||||
|
||||
本例中,它将链接到 `static/styles.css`中的CSS文件:
|
||||
本例中,它将链接到 `static/styles.css` 中的 CSS 文件:
|
||||
|
||||
```CSS hl_lines="4"
|
||||
{!../../docs_src/templates/static/styles.css!}
|
||||
```
|
||||
|
||||
因为使用了 `StaticFiles`, **FastAPI** 应用会自动提供位于 URL `/static/styles.css`的 CSS 文件。
|
||||
因为使用了 `StaticFiles`,**FastAPI** 应用会自动提供位于 URL `/static/styles.css` 的 CSS 文件。
|
||||
|
||||
## 更多说明
|
||||
## 更多说明 { #more-details }
|
||||
|
||||
包括测试模板等更多详情,请参阅 <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">Starlette 官方文档 - 模板</a>。
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# 测试事件:启动 - 关闭
|
||||
# 测试事件:lifespan 和 startup - shutdown { #testing-events-lifespan-and-startup-shutdown }
|
||||
|
||||
使用 `TestClient` 和 `with` 语句,在测试中运行事件处理器(`startup` 与 `shutdown`)。
|
||||
当你需要在测试中运行 `lifespan` 时,可以将 `TestClient` 与 `with` 语句一起使用:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
|
||||
{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *}
|
||||
|
||||
你可以在[官方 Starlette 文档站点的“在测试中运行 lifespan”](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)阅读更多细节。
|
||||
|
||||
对于已弃用的 `startup` 和 `shutdown` 事件,可以按如下方式使用 `TestClient`:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# 测试 WebSockets
|
||||
# 测试 WebSockets { #testing-websockets }
|
||||
|
||||
测试 WebSockets 也使用 `TestClient`。
|
||||
你可以使用同一个 `TestClient` 来测试 WebSockets。
|
||||
|
||||
为此,要在 `with` 语句中使用 `TestClient` 连接 WebSocket。
|
||||
为此,在 `with` 语句中使用 `TestClient` 连接到 WebSocket:
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
|
||||
{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *}
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
更多细节详见 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">Starlette 官档 - 测试 WebSockets</a>。
|
||||
更多细节请查看 Starlette 的文档:<a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">测试 WebSockets</a>。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 直接使用请求
|
||||
# 直接使用 Request { #using-the-request-directly }
|
||||
|
||||
至此,我们已经使用多种类型声明了请求的各种组件。
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
|
||||
但有时,我们也需要直接访问 `Request` 对象。
|
||||
|
||||
## `Request` 对象的细节
|
||||
## `Request` 对象的细节 { #details-about-the-request-object }
|
||||
|
||||
实际上,**FastAPI** 的底层是 **Starlette**,**FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> 对象。
|
||||
|
||||
但直接从 `Request` 对象提取数据时(例如,读取请求体),**FastAPI** 不会验证、转换和存档数据(为 API 文档使用 OpenAPI)。
|
||||
但直接从 `Request` 对象提取数据时(例如,读取请求体),这些数据不会被 **FastAPI** 验证、转换或文档化(使用 OpenAPI,为自动的 API 用户界面)。
|
||||
|
||||
不过,仍可以验证、转换与注释(使用 Pydantic 模型的请求体等)其它正常声明的参数。
|
||||
|
||||
但在某些特定情况下,还是需要提取 `Request` 对象。
|
||||
|
||||
## 直接使用 `Request` 对象
|
||||
## 直接使用 `Request` 对象 { #use-the-request-object-directly }
|
||||
|
||||
假设要在*路径操作函数*中获取客户端 IP 地址和主机。
|
||||
|
||||
此时,需要直接访问请求。
|
||||
|
||||
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
|
||||
{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *}
|
||||
|
||||
把*路径操作函数*的参数类型声明为 `Request`,**FastAPI** 就能把 `Request` 传递到参数里。
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
///
|
||||
|
||||
## `Request` 文档
|
||||
## `Request` 文档 { #request-documentation }
|
||||
|
||||
更多细节详见 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">Starlette 官档 - `Request` 对象</a>。
|
||||
|
||||
@@ -51,6 +51,6 @@
|
||||
|
||||
您也可以使用 `from starlette.requests import Request`。
|
||||
|
||||
**FastAPI** 的 `from fastapi import Request` 只是为开发者提供的快捷方式,但其实它直接继承自 Starlette。
|
||||
**FastAPI** 直接提供它只是为了方便开发者,但它直接来自 Starlette。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# WebSockets
|
||||
# WebSockets { #websockets }
|
||||
|
||||
您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。
|
||||
您可以在 **FastAPI** 中使用 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" class="external-link" target="_blank">WebSockets</a>。
|
||||
|
||||
## 安装 `WebSockets`
|
||||
## 安装 `websockets` { #install-websockets }
|
||||
|
||||
首先,您需要安装 `WebSockets`:
|
||||
请确保您创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}、激活它,并安装 `websockets`(一个让使用“WebSocket”协议更容易的 Python 库):
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install websockets
|
||||
@@ -12,11 +14,13 @@ $ pip install websockets
|
||||
---> 100%
|
||||
```
|
||||
|
||||
## WebSockets 客户端
|
||||
</div>
|
||||
|
||||
### 在生产环境中
|
||||
## WebSockets 客户端 { #websockets-client }
|
||||
|
||||
在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。
|
||||
### 在生产环境中 { #in-production }
|
||||
|
||||
在您的生产系统中,您可能使用现代框架(如 React、Vue.js 或 Angular)创建了一个前端。
|
||||
|
||||
要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。
|
||||
|
||||
@@ -26,7 +30,7 @@ $ pip install websockets
|
||||
|
||||
---
|
||||
|
||||
但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。
|
||||
但是,在本示例中,我们将使用一个非常简单的 HTML 文档,其中包含一些 JavaScript,全部放在一个长字符串中。
|
||||
|
||||
当然,这并不是最优的做法,您不应该在生产环境中使用它。
|
||||
|
||||
@@ -34,13 +38,13 @@ $ pip install websockets
|
||||
|
||||
但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *}
|
||||
|
||||
## 创建 `websocket`
|
||||
## 创建 `websocket` { #create-a-websocket }
|
||||
|
||||
在您的 **FastAPI** 应用程序中,创建一个 `websocket`:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *}
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
@@ -50,24 +54,28 @@ $ pip install websockets
|
||||
|
||||
///
|
||||
|
||||
## 等待消息并发送消息
|
||||
## 等待消息并发送消息 { #await-for-messages-and-send-messages }
|
||||
|
||||
在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。
|
||||
|
||||
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
|
||||
{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *}
|
||||
|
||||
您可以接收和发送二进制、文本和 JSON 数据。
|
||||
|
||||
## 尝试一下
|
||||
## 尝试一下 { #try-it }
|
||||
|
||||
如果您的文件名为 `main.py`,请使用以下命令运行应用程序:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
|
||||
|
||||
您将看到一个简单的页面,如下所示:
|
||||
@@ -86,11 +94,9 @@ $ uvicorn main:app --reload
|
||||
|
||||
<img src="/img/tutorial/websockets/image04.png">
|
||||
|
||||
所有这些消息都将使用同一个 WebSocket 连
|
||||
所有这些消息都将使用同一个 WebSocket 连接。
|
||||
|
||||
接。
|
||||
|
||||
## 使用 `Depends` 和其他依赖项
|
||||
## 使用 `Depends` 和其他依赖项 { #using-depends-and-others }
|
||||
|
||||
在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容:
|
||||
|
||||
@@ -101,7 +107,7 @@ $ uvicorn main:app --reload
|
||||
* `Path`
|
||||
* `Query`
|
||||
|
||||
它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同:
|
||||
它们的工作方式与其他 FastAPI 端点/*路径操作* 相同:
|
||||
|
||||
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
|
||||
|
||||
@@ -113,16 +119,20 @@ $ uvicorn main:app --reload
|
||||
|
||||
///
|
||||
|
||||
### 尝试带有依赖项的 WebSockets
|
||||
### 尝试带有依赖项的 WebSockets { #try-the-websockets-with-dependencies }
|
||||
|
||||
如果您的文件名为 `main.py`,请使用以下命令运行应用程序:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ fastapi dev main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
|
||||
|
||||
在页面中,您可以设置:
|
||||
@@ -140,7 +150,7 @@ $ uvicorn main:app --reload
|
||||
|
||||
<img src="/img/tutorial/websockets/image05.png">
|
||||
|
||||
## 处理断开连接和多个客户端
|
||||
## 处理断开连接和多个客户端 { #handling-disconnections-and-multiple-clients }
|
||||
|
||||
当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。
|
||||
|
||||
@@ -164,13 +174,13 @@ Client #1596980209979 left the chat
|
||||
|
||||
但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。
|
||||
|
||||
如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。
|
||||
如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 <a href="https://github.com/encode/broadcaster" class="external-link" target="_blank">encode/broadcaster</a>。
|
||||
|
||||
///
|
||||
|
||||
## 更多信息
|
||||
## 更多信息 { #more-info }
|
||||
|
||||
要了解更多选项,请查看 Starlette 的文档:
|
||||
|
||||
* [WebSocket 类](https://www.starlette.dev/websockets/)
|
||||
* [基于类的 WebSocket 处理](https://www.starlette.dev/endpoints/#websocketendpoint)。
|
||||
* <a href="https://www.starlette.dev/websockets/" class="external-link" target="_blank">`WebSocket` 类</a>。
|
||||
* <a href="https://www.starlette.dev/endpoints/#websocketendpoint" class="external-link" target="_blank">基于类的 WebSocket 处理</a>。
|
||||
|
||||
@@ -1,32 +1,48 @@
|
||||
# 包含 WSGI - Flask,Django,其它
|
||||
# 包含 WSGI - Flask,Django,其它 { #including-wsgi-flask-django-others }
|
||||
|
||||
您可以挂载多个 WSGI 应用,正如您在 [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。
|
||||
您可以挂载 WSGI 应用,正如您在 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}、[在代理之后](behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。
|
||||
|
||||
为此, 您可以使用 `WSGIMiddleware` 来包装你的 WSGI 应用,如:Flask,Django,等等。
|
||||
|
||||
## 使用 `WSGIMiddleware`
|
||||
## 使用 `WSGIMiddleware` { #using-wsgimiddleware }
|
||||
|
||||
您需要导入 `WSGIMiddleware`。
|
||||
/// info | 信息
|
||||
|
||||
需要安装 `a2wsgi`,例如使用 `pip install a2wsgi`。
|
||||
|
||||
///
|
||||
|
||||
您需要从 `a2wsgi` 导入 `WSGIMiddleware`。
|
||||
|
||||
然后使用该中间件包装 WSGI 应用(例如 Flask)。
|
||||
|
||||
之后将其挂载到某一个路径下。
|
||||
|
||||
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *}
|
||||
{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *}
|
||||
|
||||
## 检查
|
||||
/// note | 注意
|
||||
|
||||
之前推荐使用 `fastapi.middleware.wsgi` 中的 `WSGIMiddleware`,但它现在已被弃用。
|
||||
|
||||
建议改用 `a2wsgi` 包,使用方式保持不变。
|
||||
|
||||
只要确保已安装 `a2wsgi` 包,并且从 `a2wsgi` 正确导入 `WSGIMiddleware` 即可。
|
||||
|
||||
///
|
||||
|
||||
## 检查 { #check-it }
|
||||
|
||||
现在,所有定义在 `/v1/` 路径下的请求将会被 Flask 应用处理。
|
||||
|
||||
其余的请求则会被 **FastAPI** 处理。
|
||||
|
||||
如果您使用 Uvicorn 运行应用实例并且访问 <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>,您将会看到由 Flask 返回的响应:
|
||||
如果你运行它并访问 <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>,你将会看到由 Flask 返回的响应:
|
||||
|
||||
```txt
|
||||
Hello, World from Flask!
|
||||
```
|
||||
|
||||
并且如果您访问 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>,您将会看到由 FastAPI 返回的响应:
|
||||
如果你访问 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>,你将会看到由 FastAPI 返回的响应:
|
||||
|
||||
```JSON
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 配置 Swagger UI
|
||||
# 配置 Swagger UI { #configure-swagger-ui }
|
||||
|
||||
你可以配置一些额外的 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI 参数</a>.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因为这是 Swagger UI 需要的。
|
||||
|
||||
## 不使用语法高亮
|
||||
## 禁用语法高亮 { #disable-syntax-highlighting }
|
||||
|
||||
比如,你可以禁用 Swagger UI 中的语法高亮。
|
||||
|
||||
@@ -18,41 +18,41 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因
|
||||
|
||||
但是你可以通过设置 `syntaxHighlight` 为 `False` 来禁用 Swagger UI 中的语法高亮:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *}
|
||||
|
||||
...在此之后,Swagger UI 将不会高亮代码:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image03.png">
|
||||
|
||||
## 改变主题
|
||||
## 改变主题 { #change-the-theme }
|
||||
|
||||
同样地,你也可以通过设置键 `"syntaxHighlight.theme"` 来设置语法高亮主题(注意中间有一个点):
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *}
|
||||
|
||||
这个配置会改变语法高亮主题:
|
||||
|
||||
<img src="/img/tutorial/extending-openapi/image04.png">
|
||||
|
||||
## 改变默认 Swagger UI 参数
|
||||
## 改变默认 Swagger UI 参数 { #change-default-swagger-ui-parameters }
|
||||
|
||||
FastAPI 包含了一些默认配置参数,适用于大多数用例。
|
||||
|
||||
其包括这些默认配置参数:
|
||||
|
||||
{* ../../fastapi/openapi/docs.py ln[7:23] *}
|
||||
{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *}
|
||||
|
||||
你可以通过在 `swagger_ui_parameters` 中设置不同的值来覆盖它们。
|
||||
|
||||
比如,如果要禁用 `deepLinking`,你可以像这样传递设置到 `swagger_ui_parameters` 中:
|
||||
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
|
||||
{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *}
|
||||
|
||||
## 其他 Swagger UI 参数
|
||||
## 其他 Swagger UI 参数 { #other-swagger-ui-parameters }
|
||||
|
||||
查看其他 Swagger UI 参数,请阅读 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">docs for Swagger UI parameters</a>。
|
||||
查看所有其他可用的配置,请阅读 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI 参数文档</a>。
|
||||
|
||||
## JavaScript-only 配置
|
||||
## JavaScript-only 配置 { #javascript-only-settings }
|
||||
|
||||
Swagger UI 同样允许使用 **JavaScript-only** 配置对象(例如,JavaScript 函数)。
|
||||
|
||||
@@ -67,4 +67,4 @@ presets: [
|
||||
|
||||
这些是 **JavaScript** 对象,而不是字符串,所以你不能直接从 Python 代码中传递它们。
|
||||
|
||||
如果你需要像这样使用 JavaScript-only 配置,你可以使用上述方法之一。覆盖所有 Swagger UI *path operation* 并手动编写任何你需要的 JavaScript。
|
||||
如果你需要像这样使用 JavaScript-only 配置,你可以使用上述方法之一。覆盖所有 Swagger UI *路径操作* 并手动编写任何你需要的 JavaScript。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 后台任务
|
||||
# 后台任务 { #background-tasks }
|
||||
|
||||
你可以定义在返回响应后运行的后台任务。
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
* 处理数据:
|
||||
* 例如,假设您收到的文件必须经过一个缓慢的过程,您可以返回一个"Accepted"(HTTP 202)响应并在后台处理它。
|
||||
|
||||
## 使用 `BackgroundTasks`
|
||||
## 使用 `BackgroundTasks` { #using-backgroundtasks }
|
||||
|
||||
首先导入 `BackgroundTasks` 并在 *路径操作函数* 中使用类型声明 `BackgroundTasks` 定义一个参数:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[1, 13] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *}
|
||||
|
||||
**FastAPI** 会创建一个 `BackgroundTasks` 类型的对象并作为该参数传入。
|
||||
|
||||
## 创建一个任务函数
|
||||
## 创建一个任务函数 { #create-a-task-function }
|
||||
|
||||
创建要作为后台任务运行的函数。
|
||||
|
||||
@@ -31,13 +31,13 @@
|
||||
|
||||
由于写操作不使用 `async` 和 `await`,我们用普通的 `def` 定义函数:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *}
|
||||
|
||||
## 添加后台任务
|
||||
## 添加后台任务 { #add-the-background-task }
|
||||
|
||||
在你的 *路径操作函数* 里,用 `.add_task()` 方法将任务函数传到 *后台任务* 对象中:
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
|
||||
{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *}
|
||||
|
||||
`.add_task()` 接收以下参数:
|
||||
|
||||
@@ -45,53 +45,13 @@
|
||||
* 应按顺序传递给任务函数的任意参数序列(`email`)。
|
||||
* 应传递给任务函数的任意关键字参数(`message="some notification"`)。
|
||||
|
||||
## 依赖注入
|
||||
## 依赖注入 { #dependency-injection }
|
||||
|
||||
使用 `BackgroundTasks` 也适用于依赖注入系统,你可以在多个级别声明 `BackgroundTasks` 类型的参数:在 *路径操作函数* 里,在依赖中(可依赖),在子依赖中,等等。
|
||||
|
||||
**FastAPI** 知道在每种情况下该做什么以及如何复用同一对象,因此所有后台任务被合并在一起并且随后在后台运行:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13, 15, 22, 25] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002_an_py39.py hl[13, 15, 22, 25] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002_an.py hl[14, 16, 23, 26] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ 没Annotated
|
||||
|
||||
/// tip
|
||||
|
||||
尽可能选择使用 `Annotated` 的版本。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11, 13, 20, 23] *}
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ 没Annotated
|
||||
|
||||
/// tip
|
||||
|
||||
尽可能选择使用 `Annotated` 的版本。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/background_tasks/tutorial002.py hl[13, 15, 22, 25] *}
|
||||
|
||||
////
|
||||
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *}
|
||||
|
||||
该示例中,信息会在响应发出 *之后* 被写到 `log.txt` 文件。
|
||||
|
||||
@@ -99,7 +59,7 @@
|
||||
|
||||
然后另一个在 *路径操作函数* 生成的后台任务会使用路径参数 `email` 写入一条信息。
|
||||
|
||||
## 技术细节
|
||||
## 技术细节 { #technical-details }
|
||||
|
||||
`BackgroundTasks` 类直接来自 <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>。
|
||||
|
||||
@@ -111,7 +71,7 @@
|
||||
|
||||
更多细节查看 <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">Starlette's official docs for Background Tasks</a>.
|
||||
|
||||
## 告诫
|
||||
## 告诫 { #caveat }
|
||||
|
||||
如果您需要执行繁重的后台计算,并且不一定需要由同一进程运行(例如,您不需要共享内存、变量等),那么使用其他更大的工具(如 <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>)可能更好。
|
||||
|
||||
@@ -119,6 +79,6 @@
|
||||
|
||||
但是,如果您需要从同一个**FastAPI**应用程序访问变量和对象,或者您需要执行小型后台任务(如发送电子邮件通知),您只需使用 `BackgroundTasks` 即可。
|
||||
|
||||
## 回顾
|
||||
## 回顾 { #recap }
|
||||
|
||||
导入并使用 `BackgroundTasks` 通过 *路径操作函数* 中的参数和依赖项来添加后台任务。
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# 更大的应用 - 多个文件
|
||||
# 更大的应用 - 多个文件 { #bigger-applications-multiple-files }
|
||||
|
||||
如果你正在开发一个应用程序或 Web API,很少会将所有的内容都放在一个文件中。
|
||||
|
||||
**FastAPI** 提供了一个方便的工具,可以在保持所有灵活性的同时构建你的应用程序。
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
如果你来自 Flask,那这将相当于 Flask 的 Blueprints。
|
||||
|
||||
///
|
||||
|
||||
## 一个文件结构示例
|
||||
## 一个文件结构示例 { #an-example-file-structure }
|
||||
|
||||
假设你的文件结构如下:
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
│ └── admin.py
|
||||
```
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
上面有几个 `__init__.py` 文件:每个目录或子目录中都有一个。
|
||||
|
||||
@@ -52,11 +52,11 @@ from app.routers import items
|
||||
* 还有一个子目录 `app/internal/` 包含另一个 `__init__.py` 文件,因此它是又一个「Python 子包」:`app.internal`。
|
||||
* `app/internal/admin.py` 是另一个子模块:`app.internal.admin`。
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
|
||||
带有注释的同一文件结构:
|
||||
|
||||
```
|
||||
```bash
|
||||
.
|
||||
├── app # 「app」是一个 Python 包
|
||||
│ ├── __init__.py # 这个文件使「app」成为一个 Python 包
|
||||
@@ -71,7 +71,7 @@ from app.routers import items
|
||||
│ └── admin.py # 「admin」子模块,例如 import app.internal.admin
|
||||
```
|
||||
|
||||
## `APIRouter`
|
||||
## `APIRouter` { #apirouter }
|
||||
|
||||
假设专门用于处理用户逻辑的文件是位于 `/app/routers/users.py` 的子模块。
|
||||
|
||||
@@ -81,23 +81,19 @@ from app.routers import items
|
||||
|
||||
你可以使用 `APIRouter` 为该模块创建*路径操作*。
|
||||
|
||||
### 导入 `APIRouter`
|
||||
### 导入 `APIRouter` { #import-apirouter }
|
||||
|
||||
你可以导入它并通过与 `FastAPI` 类相同的方式创建一个「实例」:
|
||||
|
||||
```Python hl_lines="1 3" title="app/routers/users.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
|
||||
|
||||
### 使用 `APIRouter` 的*路径操作*
|
||||
### 使用 `APIRouter` 的*路径操作* { #path-operations-with-apirouter }
|
||||
|
||||
然后你可以使用它来声明*路径操作*。
|
||||
|
||||
使用方式与 `FastAPI` 类相同:
|
||||
|
||||
```Python hl_lines="6 11 16" title="app/routers/users.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/users.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
|
||||
|
||||
你可以将 `APIRouter` 视为一个「迷你 `FastAPI`」类。
|
||||
|
||||
@@ -105,7 +101,7 @@ from app.routers import items
|
||||
|
||||
所有相同的 `parameters`、`responses`、`dependencies`、`tags` 等等。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
在此示例中,该变量被命名为 `router`,但你可以根据你的想法自由命名。
|
||||
|
||||
@@ -113,7 +109,7 @@ from app.routers import items
|
||||
|
||||
我们将在主 `FastAPI` 应用中包含该 `APIRouter`,但首先,让我们来看看依赖项和另一个 `APIRouter`。
|
||||
|
||||
## 依赖项
|
||||
## 依赖项 { #dependencies }
|
||||
|
||||
我们了解到我们将需要一些在应用程序的好几个地方所使用的依赖项。
|
||||
|
||||
@@ -121,11 +117,9 @@ from app.routers import items
|
||||
|
||||
现在我们将使用一个简单的依赖项来读取一个自定义的 `X-Token` 请求首部:
|
||||
|
||||
```Python hl_lines="1 4-6" title="app/dependencies.py"
|
||||
{!../../docs_src/bigger_applications/app/dependencies.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
我们正在使用虚构的请求首部来简化此示例。
|
||||
|
||||
@@ -133,7 +127,7 @@ from app.routers import items
|
||||
|
||||
///
|
||||
|
||||
## 其他使用 `APIRouter` 的模块
|
||||
## 其他使用 `APIRouter` 的模块 { #another-module-with-apirouter }
|
||||
|
||||
假设你在位于 `app/routers/items.py` 的模块中还有专门用于处理应用程序中「项目」的端点。
|
||||
|
||||
@@ -155,9 +149,7 @@ from app.routers import items
|
||||
|
||||
因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。
|
||||
|
||||
```Python hl_lines="5-10 16 21" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
|
||||
|
||||
由于每个*路径操作*的路径都必须以 `/` 开头,例如:
|
||||
|
||||
@@ -175,7 +167,7 @@ async def read_item(item_id: str):
|
||||
|
||||
我们可以添加一个 `dependencies` 列表,这些依赖项将被添加到路由器中的所有*路径操作*中,并将针对向它们发起的每个请求执行/解决。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
请注意,和[*路径操作装饰器*中的依赖项](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}很类似,没有值会被传递给你的*路径操作函数*。
|
||||
|
||||
@@ -196,19 +188,19 @@ 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
|
||||
/// tip | 提示
|
||||
|
||||
在 `APIRouter`中具有 `dependencies` 可以用来,例如,对一整组的*路径操作*要求身份认证。即使这些依赖项并没有分别添加到每个路径操作中。
|
||||
|
||||
///
|
||||
|
||||
/// check
|
||||
/// check | 检查
|
||||
|
||||
`prefix`、`tags`、`responses` 以及 `dependencies` 参数只是(和其他很多情况一样)**FastAPI** 的一个用于帮助你避免代码重复的功能。
|
||||
|
||||
///
|
||||
|
||||
### 导入依赖项
|
||||
### 导入依赖项 { #import-the-dependencies }
|
||||
|
||||
这些代码位于 `app.routers.items` 模块,`app/routers/items.py` 文件中。
|
||||
|
||||
@@ -216,13 +208,11 @@ async def read_item(item_id: str):
|
||||
|
||||
因此,我们通过 `..` 对依赖项使用了相对导入:
|
||||
|
||||
```Python hl_lines="3" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
|
||||
|
||||
#### 相对导入如何工作
|
||||
#### 相对导入如何工作 { #how-relative-imports-work }
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
如果你完全了解导入的工作原理,请从下面的下一部分继续。
|
||||
|
||||
@@ -244,7 +234,7 @@ from .dependencies import get_token_header
|
||||
|
||||
请记住我们的程序/文件结构是怎样的:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
<img src="/img/tutorial/bigger-applications/package.drawio.svg">
|
||||
|
||||
---
|
||||
|
||||
@@ -276,24 +266,22 @@ from ...dependencies import get_token_header
|
||||
* 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始...
|
||||
* 跳转到其父包(`app/` 目录)...
|
||||
* 然后跳转到该包的父包(该父包并不存在,`app` 已经是最顶层的包 😱)...
|
||||
* 在该父包中,找到 `dependencies` 模块(位于 `app/` 更上一级目录中的 `dependencies.py` 文件)...
|
||||
* 在该父包中,找到 `dependencies` 模块(位于 `app/dependencies.py` 的文件)...
|
||||
* 然后从中导入函数 `get_token_header`。
|
||||
|
||||
这将引用 `app/` 的往上一级,带有其自己的 `__init __.py` 等文件的某个包。但是我们并没有这个包。因此,这将在我们的示例中引发错误。🚨
|
||||
|
||||
但是现在你知道了它的工作原理,因此无论它们多么复杂,你都可以在自己的应用程序中使用相对导入。🤓
|
||||
|
||||
### 添加一些自定义的 `tags`、`responses` 和 `dependencies`
|
||||
### 添加一些自定义的 `tags`、`responses` 和 `dependencies` { #add-some-custom-tags-responses-and-dependencies }
|
||||
|
||||
我们不打算在每个*路径操作*中添加前缀 `/items` 或 `tags =["items"]`,因为我们将它们添加到了 `APIRouter` 中。
|
||||
|
||||
但是我们仍然可以添加*更多*将会应用于特定的*路径操作*的 `tags`,以及一些特定于该*路径操作*的额外 `responses`:
|
||||
|
||||
```Python hl_lines="30-31" title="app/routers/items.py"
|
||||
{!../../docs_src/bigger_applications/app/routers/items.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
最后的这个路径操作将包含标签的组合:`["items","custom"]`。
|
||||
|
||||
@@ -301,7 +289,7 @@ from ...dependencies import get_token_header
|
||||
|
||||
///
|
||||
|
||||
## `FastAPI` 主体
|
||||
## `FastAPI` 主体 { #the-main-fastapi }
|
||||
|
||||
现在,让我们来看看位于 `app/main.py` 的模块。
|
||||
|
||||
@@ -311,27 +299,23 @@ from ...dependencies import get_token_header
|
||||
|
||||
并且由于你的大部分逻辑现在都存在于其自己的特定模块中,因此主文件的内容将非常简单。
|
||||
|
||||
### 导入 `FastAPI`
|
||||
### 导入 `FastAPI` { #import-fastapi }
|
||||
|
||||
你可以像平常一样导入并创建一个 `FastAPI` 类。
|
||||
|
||||
我们甚至可以声明[全局依赖项](dependencies/global-dependencies.md){.internal-link target=_blank},它会和每个 `APIRouter` 的依赖项组合在一起:
|
||||
|
||||
```Python hl_lines="1 3 7" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
|
||||
|
||||
### 导入 `APIRouter`
|
||||
### 导入 `APIRouter` { #import-the-apirouter }
|
||||
|
||||
现在,我们导入具有 `APIRouter` 的其他子模块:
|
||||
|
||||
```Python hl_lines="5" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
|
||||
|
||||
由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 ` .` 通过「相对导入」来导入它们。
|
||||
|
||||
### 导入是如何工作的
|
||||
### 导入是如何工作的 { #how-the-importing-works }
|
||||
|
||||
这段代码:
|
||||
|
||||
@@ -355,7 +339,7 @@ from .routers import items, users
|
||||
from app.routers import items, users
|
||||
```
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
第一个版本是「相对导入」:
|
||||
|
||||
@@ -373,7 +357,7 @@ from app.routers import items, users
|
||||
|
||||
///
|
||||
|
||||
### 避免名称冲突
|
||||
### 避免名称冲突 { #avoid-name-collisions }
|
||||
|
||||
我们将直接导入 `items` 子模块,而不是仅导入其 `router` 变量。
|
||||
|
||||
@@ -390,19 +374,15 @@ from .routers.users import router
|
||||
|
||||
因此,为了能够在同一个文件中使用它们,我们直接导入子模块:
|
||||
|
||||
```Python hl_lines="5" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
|
||||
|
||||
### 包含 `users` 和 `items` 的 `APIRouter`
|
||||
### 包含 `users` 和 `items` 的 `APIRouter` { #include-the-apirouters-for-users-and-items }
|
||||
|
||||
现在,让我们来包含来自 `users` 和 `items` 子模块的 `router`。
|
||||
|
||||
```Python hl_lines="10-11" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
`users.router` 包含了 `app/routers/users.py` 文件中的 `APIRouter`。
|
||||
|
||||
@@ -422,7 +402,7 @@ from .routers.users import router
|
||||
|
||||
///
|
||||
|
||||
/// check
|
||||
/// check | 检查
|
||||
|
||||
包含路由器时,你不必担心性能问题。
|
||||
|
||||
@@ -432,7 +412,7 @@ from .routers.users import router
|
||||
|
||||
///
|
||||
|
||||
### 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter`
|
||||
### 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies }
|
||||
|
||||
现在,假设你的组织为你提供了 `app/internal/admin.py` 文件。
|
||||
|
||||
@@ -440,17 +420,13 @@ from .routers.users import router
|
||||
|
||||
对于此示例,它将非常简单。但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等:
|
||||
|
||||
```Python hl_lines="3" title="app/internal/admin.py"
|
||||
{!../../docs_src/bigger_applications/app/internal/admin.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
|
||||
|
||||
但是我们仍然希望在包含 `APIRouter` 时设置一个自定义的 `prefix`,以便其所有*路径操作*以 `/admin` 开头,我们希望使用本项目已经有的 `dependencies` 保护它,并且我们希望它包含自定义的 `tags` 和 `responses`。
|
||||
|
||||
我们可以通过将这些参数传递给 `app.include_router()` 来完成所有的声明,而不必修改原始的 `APIRouter`:
|
||||
|
||||
```Python hl_lines="14-17" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
|
||||
|
||||
这样,原始的 `APIRouter` 将保持不变,因此我们仍然可以与组织中的其他项目共享相同的 `app/internal/admin.py` 文件。
|
||||
|
||||
@@ -465,15 +441,13 @@ from .routers.users import router
|
||||
|
||||
因此,举例来说,其他项目能够以不同的身份认证方法使用相同的 `APIRouter`。
|
||||
|
||||
### 包含一个*路径操作*
|
||||
### 包含一个*路径操作* { #include-a-path-operation }
|
||||
|
||||
我们还可以直接将*路径操作*添加到 `FastAPI` 应用中。
|
||||
|
||||
这里我们这样做了...只是为了表明我们可以做到🤷:
|
||||
|
||||
```Python hl_lines="21-23" title="app/main.py"
|
||||
{!../../docs_src/bigger_applications/app/main.py!}
|
||||
```
|
||||
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
|
||||
|
||||
它将与通过 `app.include_router()` 添加的所有其他*路径操作*一起正常运行。
|
||||
|
||||
@@ -491,14 +465,14 @@ from .routers.users import router
|
||||
|
||||
///
|
||||
|
||||
## 查看自动化的 API 文档
|
||||
## 查看自动化的 API 文档 { #check-the-automatic-api-docs }
|
||||
|
||||
现在,使用 `app.main` 模块和 `app` 变量运行 `uvicorn`:
|
||||
现在,运行你的应用:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn app.main:app --reload
|
||||
$ fastapi dev app/main.py
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
@@ -509,9 +483,9 @@ $ uvicorn app.main:app --reload
|
||||
|
||||
你将看到使用了正确路径(和前缀)和正确标签的自动化 API 文档,包括了来自所有子模块的路径:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/bigger-applications/image01.png">
|
||||
<img src="/img/tutorial/bigger-applications/image01.png">
|
||||
|
||||
## 多次使用不同的 `prefix` 包含同一个路由器
|
||||
## 多次使用不同的 `prefix` 包含同一个路由器 { #include-the-same-router-multiple-times-with-different-prefix }
|
||||
|
||||
你也可以在*同一*路由器上使用不同的前缀来多次使用 `.include_router()`。
|
||||
|
||||
@@ -519,7 +493,7 @@ $ uvicorn app.main:app --reload
|
||||
|
||||
这是一个你可能并不真正需要的高级用法,但万一你有需要了就能够用上。
|
||||
|
||||
## 在另一个 `APIRouter` 中包含一个 `APIRouter`
|
||||
## 在另一个 `APIRouter` 中包含一个 `APIRouter` { #include-an-apirouter-in-another }
|
||||
|
||||
与在 `FastAPI` 应用程序中包含 `APIRouter` 的方式相同,你也可以在另一个 `APIRouter` 中包含 `APIRouter`,通过:
|
||||
|
||||
@@ -527,4 +501,4 @@ $ uvicorn app.main:app --reload
|
||||
router.include_router(other_router)
|
||||
```
|
||||
|
||||
请确保在你将 `router` 包含到 `FastAPI` 应用程序之前进行此操作,以便 `other_router` 中的`路径操作`也能被包含进来。
|
||||
请确保在你将 `router` 包含到 `FastAPI` 应用程序之前进行此操作,以便 `other_router` 中的*路径操作*也能被包含进来。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 请求体 - 多个参数
|
||||
# 请求体 - 多个参数 { #body-multiple-parameters }
|
||||
|
||||
既然我们已经知道了如何使用 `Path` 和 `Query`,下面让我们来了解一下请求体声明的更高级用法。
|
||||
|
||||
## 混合使用 `Path`、`Query` 和请求体参数
|
||||
## 混合使用 `Path`、`Query` 和请求体参数 { #mix-path-query-and-body-parameters }
|
||||
|
||||
首先,毫无疑问地,你可以随意地混合使用 `Path`、`Query` 和请求体参数声明,**FastAPI** 会知道该如何处理。
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
请注意,在这种情况下,将从请求体获取的 `item` 是可选的。因为它的默认值为 `None`。
|
||||
|
||||
///
|
||||
|
||||
## 多个请求体参数
|
||||
## 多个请求体参数 { #multiple-body-parameters }
|
||||
|
||||
在上面的示例中,*路径操作*将期望一个具有 `Item` 的属性的 JSON 请求体,就像:
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
请注意,即使 `item` 的声明方式与之前相同,但现在它被期望通过 `item` 键内嵌在请求体中。
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
它将执行对复合数据的校验,并且像现在这样为 OpenAPI 模式和自动化文档对其进行记录。
|
||||
|
||||
## 请求体中的单一值
|
||||
## 请求体中的单一值 { #singular-values-in-body }
|
||||
|
||||
与使用 `Query` 和 `Path` 为查询参数和路径参数定义额外数据的方式相同,**FastAPI** 提供了一个同等的 `Body`。
|
||||
|
||||
@@ -72,12 +72,10 @@
|
||||
|
||||
但是你可以使用 `Body` 指示 **FastAPI** 将其作为请求体的另一个键进行处理。
|
||||
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
|
||||
|
||||
在这种情况下,**FastAPI** 将期望像这样的请求体:
|
||||
|
||||
|
||||
```JSON
|
||||
{
|
||||
"item": {
|
||||
@@ -96,27 +94,33 @@
|
||||
|
||||
同样的,它将转换数据类型,校验,生成文档等。
|
||||
|
||||
## 多个请求体参数和查询参数
|
||||
## 多个请求体参数和查询参数 { #multiple-body-params-and-query }
|
||||
|
||||
当然,除了请求体参数外,你还可以在任何需要的时候声明额外的查询参数。
|
||||
|
||||
由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 `Query`,你可以仅执行以下操作:
|
||||
由于默认情况下单一值会被解释为查询参数,因此你不必显式地添加 `Query`,你可以这样写:
|
||||
|
||||
```Python
|
||||
q: str = None
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
或者在 Python 3.9 中:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = None
|
||||
```
|
||||
|
||||
比如:
|
||||
|
||||
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *}
|
||||
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
`Body` 同样具有与 `Query`、`Path` 以及其他后面将看到的类完全相同的额外校验和元数据参数。
|
||||
|
||||
///
|
||||
|
||||
## 嵌入单个请求体参数
|
||||
## 嵌入单个请求体参数 { #embed-a-single-body-parameter }
|
||||
|
||||
假设你只有一个来自 Pydantic 模型 `Item` 的请求体参数 `item`。
|
||||
|
||||
@@ -156,7 +160,7 @@ item: Item = Body(embed=True)
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
## 总结 { #recap }
|
||||
|
||||
你可以添加多个请求体参数到*路径操作函数*中,即使一个请求只能有一个请求体。
|
||||
|
||||
|
||||
@@ -1,53 +1,42 @@
|
||||
# 请求体 - 嵌套模型
|
||||
# 请求体 - 嵌套模型 { #body-nested-models }
|
||||
|
||||
使用 **FastAPI**,你可以定义、校验、记录文档并使用任意深度嵌套的模型(归功于Pydantic)。
|
||||
|
||||
## List 字段
|
||||
## List 字段 { #list-fields }
|
||||
|
||||
你可以将一个属性定义为拥有子元素的类型。例如 Python `list`:
|
||||
你可以将一个属性定义为一个子类型。例如,Python `list`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
|
||||
|
||||
这将使 `tags` 成为一个由元素组成的列表。不过它没有声明每个元素的类型。
|
||||
|
||||
## 具有子类型的 List 字段
|
||||
## 带类型参数的 List 字段 { #list-fields-with-type-parameter }
|
||||
|
||||
但是 Python 有一种特定的方法来声明具有子类型的列表:
|
||||
不过,Python 有一种用于声明具有内部类型(类型参数)的列表的特定方式:
|
||||
|
||||
### 从 typing 导入 `List`
|
||||
### 声明带类型参数的 `list` { #declare-a-list-with-a-type-parameter }
|
||||
|
||||
首先,从 Python 的标准库 `typing` 模块中导入 `List`:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
|
||||
|
||||
### 声明具有子类型的 List
|
||||
|
||||
要声明具有子类型的类型,例如 `list`、`dict`、`tuple`:
|
||||
|
||||
* 从 `typing` 模块导入它们
|
||||
* 使用方括号 `[` 和 `]` 将子类型作为「类型参数」传入
|
||||
要声明具有类型参数(内部类型)的类型,例如 `list`、`dict`、`tuple`,使用方括号 `[` 和 `]` 传入内部类型作为「类型参数」:
|
||||
|
||||
```Python
|
||||
from typing import List
|
||||
|
||||
my_list: List[str]
|
||||
my_list: list[str]
|
||||
```
|
||||
|
||||
这完全是用于类型声明的标准 Python 语法。
|
||||
|
||||
对具有子类型的模型属性也使用相同的标准语法。
|
||||
对具有内部类型的模型属性也使用相同的标准语法。
|
||||
|
||||
因此,在我们的示例中,我们可以将 `tags` 明确地指定为一个「字符串列表」:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
|
||||
|
||||
## Set 类型
|
||||
## Set 类型 { #set-types }
|
||||
|
||||
但是随后我们考虑了一下,意识到标签不应该重复,它们很大可能会是唯一的字符串。
|
||||
|
||||
Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `set`。
|
||||
而 Python 有一种用于保存唯一元素集合的特殊数据类型 `set`。
|
||||
|
||||
然后我们可以导入 `Set` 并将 `tag` 声明为一个由 `str` 组成的 `set`:
|
||||
然后我们可以将 `tags` 声明为一个由字符串组成的 set:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
|
||||
|
||||
@@ -57,7 +46,7 @@ Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `se
|
||||
|
||||
并且还会被相应地标注 / 记录文档。
|
||||
|
||||
## 嵌套模型
|
||||
## 嵌套模型 { #nested-models }
|
||||
|
||||
Pydantic 模型的每个属性都具有类型。
|
||||
|
||||
@@ -67,13 +56,13 @@ Pydantic 模型的每个属性都具有类型。
|
||||
|
||||
上述这些都可以任意的嵌套。
|
||||
|
||||
### 定义子模型
|
||||
### 定义子模型 { #define-a-submodel }
|
||||
|
||||
例如,我们可以定义一个 `Image` 模型:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
|
||||
|
||||
### 将子模型用作类型
|
||||
### 将子模型用作类型 { #use-the-submodel-as-a-type }
|
||||
|
||||
然后我们可以将其用作一个属性的类型:
|
||||
|
||||
@@ -102,11 +91,11 @@ Pydantic 模型的每个属性都具有类型。
|
||||
* 数据校验
|
||||
* 自动生成文档
|
||||
|
||||
## 特殊的类型和校验
|
||||
## 特殊的类型和校验 { #special-types-and-validation }
|
||||
|
||||
除了普通的单一值类型(如 `str`、`int`、`float` 等)外,你还可以使用从 `str` 继承的更复杂的单一值类型。
|
||||
|
||||
要了解所有的可用选项,请查看关于 <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">来自 Pydantic 的外部类型</a> 的文档。你将在下一章节中看到一些示例。
|
||||
要了解所有的可用选项,请查看 <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Pydantic 的类型概览</a>。你将在下一章节中看到一些示例。
|
||||
|
||||
例如,在 `Image` 模型中我们有一个 `url` 字段,我们可以把它声明为 Pydantic 的 `HttpUrl`,而不是 `str`:
|
||||
|
||||
@@ -114,7 +103,7 @@ Pydantic 模型的每个属性都具有类型。
|
||||
|
||||
该字符串将被检查是否为有效的 URL,并在 JSON Schema / OpenAPI 文档中进行记录。
|
||||
|
||||
## 带有一组子模型的属性
|
||||
## 带有一组子模型的属性 { #attributes-with-lists-of-submodels }
|
||||
|
||||
你还可以将 Pydantic 模型用作 `list`、`set` 等的子类型:
|
||||
|
||||
@@ -146,49 +135,49 @@ Pydantic 模型的每个属性都具有类型。
|
||||
}
|
||||
```
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
请注意 `images` 键现在具有一组 image 对象是如何发生的。
|
||||
|
||||
///
|
||||
|
||||
## 深度嵌套模型
|
||||
## 深度嵌套模型 { #deeply-nested-models }
|
||||
|
||||
你可以定义任意深度的嵌套模型:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *}
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
请注意 `Offer` 拥有一组 `Item` 而反过来 `Item` 又是一个可选的 `Image` 列表是如何发生的。
|
||||
|
||||
///
|
||||
|
||||
## 纯列表请求体
|
||||
## 纯列表请求体 { #bodies-of-pure-lists }
|
||||
|
||||
如果你期望的 JSON 请求体的最外层是一个 JSON `array`(即 Python `list`),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样:
|
||||
|
||||
```Python
|
||||
images: List[Image]
|
||||
images: list[Image]
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
|
||||
|
||||
## 无处不在的编辑器支持
|
||||
## 无处不在的编辑器支持 { #editor-support-everywhere }
|
||||
|
||||
你可以随处获得编辑器支持。
|
||||
|
||||
即使是列表中的元素:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/body-nested-models/image01.png">
|
||||
<img src="/img/tutorial/body-nested-models/image01.png">
|
||||
|
||||
如果你直接使用 `dict` 而不是 Pydantic 模型,那你将无法获得这种编辑器支持。
|
||||
|
||||
但是你根本不必担心这两者,传入的字典会自动被转换,你的输出也会自动被转换为 JSON。
|
||||
|
||||
## 任意 `dict` 构成的请求体
|
||||
## 任意 `dict` 构成的请求体 { #bodies-of-arbitrary-dicts }
|
||||
|
||||
你也可以将请求体声明为使用某类型的键和其他类型值的 `dict`。
|
||||
|
||||
@@ -206,7 +195,7 @@ images: List[Image]
|
||||
|
||||
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
请记住 JSON 仅支持将 `str` 作为键。
|
||||
|
||||
@@ -218,7 +207,7 @@ images: List[Image]
|
||||
|
||||
///
|
||||
|
||||
## 总结
|
||||
## 总结 { #recap }
|
||||
|
||||
使用 **FastAPI** 你可以拥有 Pydantic 模型提供的极高灵活性,同时保持代码的简单、简短和优雅。
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# 请求体 - 更新数据
|
||||
# 请求体 - 更新数据 { #body-updates }
|
||||
|
||||
## 用 `PUT` 更新数据
|
||||
## 用 `PUT` 替换式更新 { #update-replacing-with-put }
|
||||
|
||||
更新数据请用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> 操作。
|
||||
更新数据可以使用 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> 操作。
|
||||
|
||||
把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 `jsonable_encoder`。例如,把 `datetime` 转换为 `str`。
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *}
|
||||
{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *}
|
||||
|
||||
`PUT` 用于接收替换现有数据的数据。
|
||||
|
||||
### 关于更新数据的警告
|
||||
### 关于替换的警告 { #warning-about-replacing }
|
||||
|
||||
用 `PUT` 把数据项 `bar` 更新为以下内容时:
|
||||
用 `PUT` 把数据项 `bar` 更新为以下请求体时:
|
||||
|
||||
```Python
|
||||
{
|
||||
@@ -22,78 +22,79 @@
|
||||
}
|
||||
```
|
||||
|
||||
因为上述数据未包含已存储的属性 `"tax": 20.2`,新的输入模型会把 `"tax": 10.5` 作为默认值。
|
||||
因为其中未包含已存储的属性 `"tax": 20.2`,输入模型会取 `"tax": 10.5` 的默认值。
|
||||
|
||||
因此,本次操作把 `tax` 的值「更新」为 `10.5`。
|
||||
因此,保存的数据会带有这个“新的” `tax` 值 `10.5`。
|
||||
|
||||
## 用 `PATCH` 进行部分更新
|
||||
## 用 `PATCH` 进行部分更新 { #partial-updates-with-patch }
|
||||
|
||||
<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> 操作用于更新 *部分* 数据。
|
||||
也可以使用 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> 操作对数据进行*部分*更新。
|
||||
|
||||
即,只发送要更新的数据,其余数据保持不变。
|
||||
也就是说,你只需发送想要更新的数据,其余数据保持不变。
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
`PATCH` 没有 `PUT` 知名,也怎么不常用。
|
||||
`PATCH` 没有 `PUT` 知名,也没那么常用。
|
||||
|
||||
很多人甚至只用 `PUT` 实现部分更新。
|
||||
很多团队甚至只用 `PUT` 实现部分更新。
|
||||
|
||||
**FastAPI** 对此没有任何限制,可以**随意**互换使用这两种操作。
|
||||
你可以**随意**选择如何使用它们,**FastAPI** 不做任何限制。
|
||||
|
||||
但本指南也会分别介绍这两种操作各自的用途。
|
||||
但本指南会大致展示它们的预期用法。
|
||||
|
||||
///
|
||||
|
||||
### 使用 Pydantic 的 `exclude_unset` 参数
|
||||
### 使用 Pydantic 的 `exclude_unset` 参数 { #using-pydantics-exclude-unset-parameter }
|
||||
|
||||
更新部分数据时,可以在 Pydantic 模型的 `.dict()` 中使用 `exclude_unset` 参数。
|
||||
如果要接收部分更新,建议在 Pydantic 模型的 `.model_dump()` 中使用 `exclude_unset` 参数。
|
||||
|
||||
比如,`item.dict(exclude_unset=True)`。
|
||||
比如,`item.model_dump(exclude_unset=True)`。
|
||||
|
||||
这段代码生成的 `dict` 只包含创建 `item` 模型时显式设置的数据,而不包括默认值。
|
||||
这会生成一个 `dict`,只包含创建 `item` 模型时显式设置的数据,不包含默认值。
|
||||
|
||||
然后再用它生成一个只含已设置(在请求中所发送)数据,且省略了默认值的 `dict`:
|
||||
然后再用它生成一个只含已设置(在请求中发送)数据、且省略默认值的 `dict`:
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial002.py hl[34] *}
|
||||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *}
|
||||
|
||||
### 使用 Pydantic 的 `update` 参数
|
||||
### 使用 Pydantic 的 `update` 参数 { #using-pydantics-update-parameter }
|
||||
|
||||
接下来,用 `.copy()` 为已有模型创建调用 `update` 参数的副本,该参数为包含更新数据的 `dict`。
|
||||
接下来,用 `.model_copy()` 为已有模型创建副本,并传入 `update` 参数,值为包含更新数据的 `dict`。
|
||||
|
||||
例如,`stored_item_model.copy(update=update_data)`:
|
||||
例如,`stored_item_model.model_copy(update=update_data)`:
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial002.py hl[35] *}
|
||||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *}
|
||||
|
||||
### 更新部分数据小结
|
||||
### 部分更新小结 { #partial-updates-recap }
|
||||
|
||||
简而言之,更新部分数据应:
|
||||
简而言之,应用部分更新应当:
|
||||
|
||||
* 使用 `PATCH` 而不是 `PUT` (可选,也可以用 `PUT`);
|
||||
* 提取存储的数据;
|
||||
* 把数据放入 Pydantic 模型;
|
||||
* 生成不含输入模型默认值的 `dict` (使用 `exclude_unset` 参数);
|
||||
* 只更新用户设置过的值,不用模型中的默认值覆盖已存储过的值。
|
||||
* 为已存储的模型创建副本,用接收的数据更新其属性 (使用 `update` 参数)。
|
||||
* (可选)使用 `PATCH` 而不是 `PUT`。
|
||||
* 提取已存储的数据。
|
||||
* 把该数据放入 Pydantic 模型。
|
||||
* 生成不含输入模型默认值的 `dict`(使用 `exclude_unset`)。
|
||||
* 这样只会更新用户实际设置的值,而不会用模型中的默认值覆盖已存储的值。
|
||||
* 为已存储的模型创建副本,用接收到的部分更新数据更新其属性(使用 `update` 参数)。
|
||||
* 把模型副本转换为可存入数据库的形式(比如,使用 `jsonable_encoder`)。
|
||||
* 这种方式与 Pydantic 模型的 `.dict()` 方法类似,但能确保把值转换为适配 JSON 的数据类型,例如, 把 `datetime` 转换为 `str` 。
|
||||
* 把数据保存至数据库;
|
||||
* 这类似于再次调用模型的 `.model_dump()` 方法,但会确保(并转换)值为可转换为 JSON 的数据类型,例如把 `datetime` 转换为 `str`。
|
||||
* 把数据保存至数据库。
|
||||
* 返回更新后的模型。
|
||||
|
||||
{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *}
|
||||
{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
实际上,HTTP `PUT` 也可以完成相同的操作。
|
||||
但本节以 `PATCH` 为例的原因是,该操作就是为了这种用例创建的。
|
||||
实际上,HTTP `PUT` 也可以使用同样的技巧。
|
||||
|
||||
但这里用 `PATCH` 举例,因为它就是为这种用例设计的。
|
||||
|
||||
///
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
注意,输入模型仍需验证。
|
||||
注意,输入模型仍会被验证。
|
||||
|
||||
因此,如果希望接收的部分更新数据可以省略其他所有属性,则要把模型中所有的属性标记为可选(使用默认值或 `None`)。
|
||||
因此,如果希望接收的部分更新可以省略所有属性,则需要一个所有属性都标记为可选(带默认值或 `None`)的模型。
|
||||
|
||||
为了区分用于**更新**所有可选值的模型与用于**创建**包含必选值的模型,请参照[更多模型](extra-models.md){.internal-link target=_blank} 一节中的思路。
|
||||
为了区分用于**更新**(全部可选)和用于**创建**(必填)的模型,可以参考[更多模型](extra-models.md){.internal-link target=_blank} 中介绍的思路。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Cookie 参数模型
|
||||
# Cookie 参数模型 { #cookie-parameter-models }
|
||||
|
||||
如果您有一组相关的 **cookie**,您可以创建一个 **Pydantic 模型**来声明它们。🍪
|
||||
|
||||
这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证方式和元数据。😎
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
自 FastAPI 版本 `0.115.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
此技术同样适用于 `Query` 、 `Cookie` 和 `Header` 。😎
|
||||
|
||||
///
|
||||
|
||||
## 带有 Pydantic 模型的 Cookie
|
||||
## 带有 Pydantic 模型的 Cookie { #cookies-with-a-pydantic-model }
|
||||
|
||||
在 **Pydantic** 模型中声明所需的 **cookie** 参数,然后将参数声明为 `Cookie` :
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
**FastAPI** 将从请求中接收到的 **cookie** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
|
||||
|
||||
## 查看文档
|
||||
## 查看文档 { #check-the-docs }
|
||||
|
||||
您可以在文档 UI 的 `/docs` 中查看定义的 cookie:
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<img src="/img/tutorial/cookie-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
请记住,由于**浏览器**以特殊方式**处理 cookie**,并在后台进行操作,因此它们**不会**轻易允许 **JavaScript** 访问这些 cookie。
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
///
|
||||
|
||||
## 禁止额外的 Cookie
|
||||
## 禁止额外的 Cookie { #forbid-extra-cookies }
|
||||
|
||||
在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 cookie。
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
|
||||
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
|
||||
{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
如果客户尝试发送一些**额外的 cookie**,他们将收到**错误**响应。
|
||||
|
||||
@@ -71,6 +71,6 @@
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
## 总结 { #summary }
|
||||
|
||||
您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 <abbr title="走之前再来块小饼干吧。 🍪">**cookie**</abbr>。😎
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# CORS(跨域资源共享)
|
||||
# CORS(跨域资源共享) { #cors-cross-origin-resource-sharing }
|
||||
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS 或者「跨域资源共享」</a> 指浏览器中运行的前端拥有与后端通信的 JavaScript 代码,而后端处于与前端不同的「源」的情况。
|
||||
|
||||
## 源
|
||||
## 源 { #origin }
|
||||
|
||||
源是协议(`http`,`https`)、域(`myapp.com`,`localhost`,`localhost.tiangolo.com`)以及端口(`80`、`443`、`8080`)的组合。
|
||||
|
||||
@@ -14,25 +14,25 @@
|
||||
|
||||
即使它们都在 `localhost` 中,但是它们使用不同的协议或者端口,所以它们都是不同的「源」。
|
||||
|
||||
## 步骤
|
||||
## 步骤 { #steps }
|
||||
|
||||
假设你的浏览器中有一个前端运行在 `http://localhost:8080`,并且它的 JavaScript 正在尝试与运行在 `http://localhost` 的后端通信(因为我们没有指定端口,浏览器会采用默认的端口 `80`)。
|
||||
|
||||
然后,浏览器会向后端发送一个 HTTP `OPTIONS` 请求,如果后端发送适当的 headers 来授权来自这个不同源(`http://localhost:8080`)的通信,浏览器将允许前端的 JavaScript 向后端发送请求。
|
||||
然后,浏览器会向 `:80` 的后端发送一个 HTTP `OPTIONS` 请求,如果后端发送适当的 headers 来授权来自这个不同源(`http://localhost:8080`)的通信,那么运行在 `:8080` 的浏览器就会允许前端中的 JavaScript 向 `:80` 的后端发送请求。
|
||||
|
||||
为此,后端必须有一个「允许的源」列表。
|
||||
为此,`:80` 的后端必须有一个「允许的源」列表。
|
||||
|
||||
在这种情况下,它必须包含 `http://localhost:8080`,前端才能正常工作。
|
||||
在这种情况下,它必须包含 `http://localhost:8080`,这样 `:8080` 的前端才能正常工作。
|
||||
|
||||
## 通配符
|
||||
## 通配符 { #wildcards }
|
||||
|
||||
也可以使用 `"*"`(一个「通配符」)声明这个列表,表示全部都是允许的。
|
||||
|
||||
但这仅允许某些类型的通信,不包括所有涉及凭据的内容:像 Cookies 以及那些使用 Bearer 令牌的授权 headers 等。
|
||||
但这仅允许某些类型的通信,不包括所有涉及凭据的内容:比如 Cookies,以及那些使用 Bearer 令牌的 Authorization 请求头等。
|
||||
|
||||
因此,为了一切都能正常工作,最好显式地指定允许的源。
|
||||
|
||||
## 使用 `CORSMiddleware`
|
||||
## 使用 `CORSMiddleware` { #use-corsmiddleware }
|
||||
|
||||
你可以在 **FastAPI** 应用中使用 `CORSMiddleware` 来配置它。
|
||||
|
||||
@@ -42,11 +42,11 @@
|
||||
|
||||
你也可以指定后端是否允许:
|
||||
|
||||
* 凭证(授权 headers,Cookies 等)。
|
||||
* 凭证(Authorization 请求头、Cookies 等)。
|
||||
* 特定的 HTTP 方法(`POST`,`PUT`)或者使用通配符 `"*"` 允许所有方法。
|
||||
* 特定的 HTTP headers 或者使用通配符 `"*"` 允许所有 headers。
|
||||
* 特定的 HTTP 请求头或者使用通配符 `"*"` 允许所有请求头。
|
||||
|
||||
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
|
||||
{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *}
|
||||
|
||||
默认情况下,这个 `CORSMiddleware` 实现所使用的默认参数较为保守,所以你需要显式地启用特定的源、方法或者 headers,以便浏览器能够在跨域上下文中使用它们。
|
||||
|
||||
@@ -55,26 +55,29 @@
|
||||
* `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` 不能设定为 `['*']`,必须指定源。
|
||||
* `allow_headers` - 一个允许跨域请求的 HTTP 请求头列表。默认为 `[]`。你可以使用 `['*']` 允许所有的请求头。`Accept`、`Accept-Language`、`Content-Language` 以及 `Content-Type` 这几个请求头在<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests" class="external-link" rel="noopener" target="_blank">简单 CORS 请求</a>中总是被允许。
|
||||
* `allow_credentials` - 指示跨域请求支持 cookies。默认是 `False`。
|
||||
|
||||
当 `allow_credentials` 设为 `True` 时,`allow_origins`、`allow_methods` 和 `allow_headers` 都不能设为 `['*']`。它们必须<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards" class="external-link" rel="noopener" target="_blank">显式指定</a>。
|
||||
|
||||
* `expose_headers` - 指示可以被浏览器访问的响应头。默认为 `[]`。
|
||||
* `max_age` - 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 `600`。
|
||||
|
||||
中间件响应两种特定类型的 HTTP 请求……
|
||||
|
||||
### CORS 预检请求
|
||||
### CORS 预检请求 { #cors-preflight-requests }
|
||||
|
||||
这是些带有 `Origin` 和 `Access-Control-Request-Method` 请求头的 `OPTIONS` 请求。
|
||||
|
||||
在这种情况下,中间件将拦截传入的请求并进行响应,出于提供信息的目的返回一个使用了适当的 CORS headers 的 `200` 或 `400` 响应。
|
||||
|
||||
### 简单请求
|
||||
### 简单请求 { #simple-requests }
|
||||
|
||||
任何带有 `Origin` 请求头的请求。在这种情况下,中间件将像平常一样传递请求,但是在响应中包含适当的 CORS headers。
|
||||
|
||||
## 更多信息
|
||||
## 更多信息 { #more-info }
|
||||
|
||||
更多关于 <abbr title="Cross-Origin Resource Sharing">CORS</abbr> 的信息,请查看 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 文档</a>。
|
||||
更多关于 <abbr title="Cross-Origin Resource Sharing - 跨域资源共享">CORS</abbr> 的信息,请查看 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 文档</a>。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# 调试
|
||||
# 调试 { #debugging }
|
||||
|
||||
你可以在编辑器中连接调试器,例如使用 Visual Studio Code 或 PyCharm。
|
||||
|
||||
## 调用 `uvicorn`
|
||||
## 调用 `uvicorn` { #call-uvicorn }
|
||||
|
||||
在你的 FastAPI 应用中直接导入 `uvicorn` 并运行:
|
||||
|
||||
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
|
||||
{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *}
|
||||
|
||||
### 关于 `__name__ == "__main__"`
|
||||
### 关于 `__name__ == "__main__"` { #about-name-main }
|
||||
|
||||
`__name__ == "__main__"` 的主要目的是使用以下代码调用文件时执行一些代码:
|
||||
|
||||
@@ -26,7 +26,7 @@ $ python myapp.py
|
||||
from myapp import app
|
||||
```
|
||||
|
||||
#### 更多细节
|
||||
#### 更多细节 { #more-details }
|
||||
|
||||
假设你的文件命名为 `myapp.py`。
|
||||
|
||||
@@ -57,7 +57,7 @@ $ python myapp.py
|
||||
```Python
|
||||
from myapp import app
|
||||
|
||||
# Some more code
|
||||
# 其他一些代码
|
||||
```
|
||||
|
||||
在这种情况下,`myapp.py` 内部的自动变量不会有值为 `"__main__"` 的变量 `__name__`。
|
||||
@@ -74,7 +74,7 @@ from myapp import app
|
||||
|
||||
///
|
||||
|
||||
## 使用你的调试器运行代码
|
||||
## 使用你的调试器运行代码 { #run-your-code-with-your-debugger }
|
||||
|
||||
由于是从代码直接运行的 Uvicorn 服务器,所以你可以从调试器直接调用 Python 程序(你的 FastAPI 应用)。
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 类作为依赖项
|
||||
# 类作为依赖项 { #classes-as-dependencies }
|
||||
|
||||
在深入探究 **依赖注入** 系统之前,让我们升级之前的例子。
|
||||
|
||||
## 来自前一个例子的`dict`
|
||||
## 来自前一个例子的`dict` { #a-dict-from-the-previous-example }
|
||||
|
||||
在前面的例子中, 我们从依赖项 ("可依赖对象") 中返回了一个 `dict`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_py310.py hl[7] *}
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
但是后面我们在路径操作函数的参数 `commons` 中得到了一个 `dict`。
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
对此,我们可以做的更好...
|
||||
|
||||
## 什么构成了依赖项?
|
||||
## 什么构成了依赖项? { #what-makes-a-dependency }
|
||||
|
||||
到目前为止,您看到的依赖项都被声明为函数。
|
||||
到目前为止,你看到的依赖项都被声明为函数。
|
||||
|
||||
但这并不是声明依赖项的唯一方法(尽管它可能是更常见的方法)。
|
||||
|
||||
@@ -38,9 +38,9 @@ something(some_argument, some_keyword_argument="foo")
|
||||
|
||||
这就是 "可调用对象"。
|
||||
|
||||
## 类作为依赖项
|
||||
## 类作为依赖项 { #classes-as-dependencies_1 }
|
||||
|
||||
您可能会注意到,要创建一个 Python 类的实例,您可以使用相同的语法。
|
||||
你可能会注意到,要创建一个 Python 类的实例,你可以使用相同的语法。
|
||||
|
||||
举个例子:
|
||||
|
||||
@@ -63,21 +63,21 @@ fluffy = Cat(name="Mr Fluffy")
|
||||
|
||||
实际上 FastAPI 检查的是它是一个 "可调用对象"(函数,类或其他任何类型)以及定义的参数。
|
||||
|
||||
如果您在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。
|
||||
如果你在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。
|
||||
|
||||
这也适用于完全没有参数的可调用对象。这与不带参数的路径操作函数一样。
|
||||
|
||||
所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_py310.py hl[9:13] *}
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *}
|
||||
|
||||
注意用于创建类实例的 `__init__` 方法:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_py310.py hl[10] *}
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *}
|
||||
|
||||
...它与我们以前的 `common_parameters` 具有相同的参数:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_py310.py hl[6] *}
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *}
|
||||
|
||||
这些参数就是 **FastAPI** 用来 "处理" 依赖项的。
|
||||
|
||||
@@ -89,26 +89,44 @@ fluffy = Cat(name="Mr Fluffy")
|
||||
|
||||
在两个例子下,数据都将被转换、验证、在 OpenAPI schema 上文档化,等等。
|
||||
|
||||
## 使用它
|
||||
## 使用它 { #use-it }
|
||||
|
||||
现在,您可以使用这个类来声明你的依赖项了。
|
||||
现在,你可以使用这个类来声明你的依赖项了。
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial002_py310.py hl[17] *}
|
||||
{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *}
|
||||
|
||||
**FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。
|
||||
|
||||
## 类型注解 vs `Depends`
|
||||
## 类型注解 vs `Depends` { #type-annotation-vs-depends }
|
||||
|
||||
注意,我们在上面的代码中编写了两次`CommonQueryParams`:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 未使用 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
最后的 `CommonQueryParams`:
|
||||
|
||||
```Python
|
||||
... = Depends(CommonQueryParams)
|
||||
... Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
...实际上是 **Fastapi** 用来知道依赖项是什么的。
|
||||
@@ -119,62 +137,152 @@ FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用
|
||||
|
||||
在本例中,第一个 `CommonQueryParams` :
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, ...
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 未使用 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams ...
|
||||
```
|
||||
|
||||
...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `= Depends(CommonQueryParams)`)。
|
||||
////
|
||||
|
||||
...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `Depends(CommonQueryParams)`)。
|
||||
|
||||
你实际上可以只这样编写:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
commons: Annotated[Any, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 未使用 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
..就像:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial003_py310.py hl[17] *}
|
||||
{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *}
|
||||
|
||||
但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image02.png">
|
||||
|
||||
## 快捷方式
|
||||
## 快捷方式 { #shortcut }
|
||||
|
||||
但是您可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次:
|
||||
但是你可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 未使用 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
**FastAPI** 为这些情况提供了一个快捷方式,在这些情况下,依赖项 *明确地* 是一个类,**FastAPI** 将 "调用" 它来创建类本身的一个实例。
|
||||
|
||||
对于这些特定的情况,您可以跟随以下操作:
|
||||
对于这些特定的情况,你可以按如下操作:
|
||||
|
||||
不是写成这样:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 未使用 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends(CommonQueryParams)
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
...而是这样写:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
commons: Annotated[CommonQueryParams, Depends()]
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 未使用 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
commons: CommonQueryParams = Depends()
|
||||
```
|
||||
|
||||
您声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 编写完整的类。
|
||||
////
|
||||
|
||||
你声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 中*再次*编写完整的类。
|
||||
|
||||
同样的例子看起来像这样:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial004_py310.py hl[17] *}
|
||||
{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *}
|
||||
|
||||
... **FastAPI** 会知道怎么处理。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
如果这看起来更加混乱而不是更加有帮助,那么请忽略它,你不*需要*它。
|
||||
|
||||
这只是一个快捷方式。因为 **FastAPI** 关心的是帮助您减少代码重复。
|
||||
这只是一个快捷方式。因为 **FastAPI** 关心的是帮助你减少代码重复。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 路径操作装饰器依赖项
|
||||
# 路径操作装饰器依赖项 { #dependencies-in-path-operation-decorators }
|
||||
|
||||
有时,我们并不需要在*路径操作函数*中使用依赖项的返回值。
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
|
||||
对于这种情况,不必在声明*路径操作函数*的参数时使用 `Depends`,而是可以在*路径操作装饰器*中添加一个由 `dependencies` 组成的 `list`。
|
||||
|
||||
## 在*路径操作装饰器*中添加 `dependencies` 参数
|
||||
## 在*路径操作装饰器*中添加 `dependencies` 参数 { #add-dependencies-to-the-path-operation-decorator }
|
||||
|
||||
*路径操作装饰器*支持可选参数 ~ `dependencies`。
|
||||
*路径操作装饰器*支持可选参数 `dependencies`。
|
||||
|
||||
该参数的值是由 `Depends()` 组成的 `list`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006.py hl[17] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *}
|
||||
|
||||
路径操作装饰器依赖项(以下简称为**“路径装饰器依赖项”**)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。
|
||||
路径操作装饰器依赖项的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -36,34 +36,34 @@
|
||||
|
||||
///
|
||||
|
||||
## 依赖项错误和返回值
|
||||
## 依赖项错误和返回值 { #dependencies-errors-and-return-values }
|
||||
|
||||
路径装饰器依赖项也可以使用普通的依赖项*函数*。
|
||||
|
||||
### 依赖项的需求项
|
||||
### 依赖项的需求项 { #dependency-requirements }
|
||||
|
||||
路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *}
|
||||
|
||||
### 触发异常
|
||||
### 触发异常 { #raise-exceptions }
|
||||
|
||||
路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *}
|
||||
|
||||
### 返回值
|
||||
### 返回值 { #return-values }
|
||||
|
||||
无论路径装饰器依赖项是否返回值,路径操作都不会使用这些值。
|
||||
|
||||
因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *}
|
||||
{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *}
|
||||
|
||||
## 为一组路径操作定义依赖项
|
||||
## 为一组路径操作定义依赖项 { #dependencies-for-a-group-of-path-operations }
|
||||
|
||||
稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=\_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。
|
||||
稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。
|
||||
|
||||
## 全局依赖项
|
||||
## 全局依赖项 { #global-dependencies }
|
||||
|
||||
接下来,我们将学习如何为 `FastAPI` 应用程序添加全局依赖项,创建应用于每个*路径操作*的依赖项。
|
||||
|
||||
@@ -1,136 +1,139 @@
|
||||
# 使用yield的依赖项
|
||||
# 使用 yield 的依赖项 { #dependencies-with-yield }
|
||||
|
||||
FastAPI支持在完成后执行一些<abbr title='有时也被称为"退出"("exit"),"清理"("cleanup"),"拆卸"("teardown"),"关闭"("close"),"上下文管理器"("context managers")。 ...'>额外步骤</abbr>的依赖项.
|
||||
FastAPI 支持那些在完成后执行一些<abbr title='有时也被称为 "exit code"、"cleanup code"、"teardown code"、"closing code"、"context manager exit code" 等'>额外步骤</abbr>的依赖项。
|
||||
|
||||
为此,你需要使用 `yield` 而不是 `return`,然后再编写这些额外的步骤(代码)。
|
||||
为此,使用 `yield` 而不是 `return`,并把这些额外步骤(代码)写在后面。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
确保在每个依赖中只使用一次 `yield`。
|
||||
确保在每个依赖里只使用一次 `yield`。
|
||||
|
||||
///
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
任何一个可以与以下内容一起使用的函数:
|
||||
任何可以与以下装饰器一起使用的函数:
|
||||
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> 或者
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> 或
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
都可以作为 **FastAPI** 的依赖项。
|
||||
|
||||
实际上,FastAPI内部就使用了这两个装饰器。
|
||||
实际上,FastAPI 在内部就是用的这两个装饰器。
|
||||
|
||||
///
|
||||
|
||||
## 使用 `yield` 的数据库依赖项
|
||||
## 使用 `yield` 的数据库依赖项 { #a-database-dependency-with-yield }
|
||||
|
||||
例如,你可以使用这种方式创建一个数据库会话,并在完成后关闭它。
|
||||
例如,你可以用这种方式创建一个数据库会话,并在完成后将其关闭。
|
||||
|
||||
在发送响应之前,只会执行 `yield` 语句及之前的代码:
|
||||
在创建响应之前,只会执行 `yield` 语句及其之前的代码:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *}
|
||||
|
||||
生成的值会注入到 *路由函数* 和其他依赖项中:
|
||||
`yield` 产生的值会注入到 *路径操作* 和其他依赖项中:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *}
|
||||
|
||||
`yield` 语句后面的代码会在创建响应后,发送响应前执行:
|
||||
`yield` 语句后面的代码会在响应之后执行:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
你可以使用 `async` 或普通函数。
|
||||
|
||||
**FastAPI** 会像处理普通依赖一样,对每个依赖做正确的处理。
|
||||
**FastAPI** 会像处理普通依赖一样对它们进行正确处理。
|
||||
|
||||
///
|
||||
|
||||
## 包含 `yield` 和 `try` 的依赖项
|
||||
## 同时使用 `yield` 和 `try` 的依赖项 { #a-dependency-with-yield-and-try }
|
||||
|
||||
如果在包含 `yield` 的依赖中使用 `try` 代码块,你会捕获到使用依赖时抛出的任何异常。
|
||||
如果你在带有 `yield` 的依赖中使用了 `try` 代码块,那么当使用该依赖时抛出的任何异常你都会收到。
|
||||
|
||||
例如,如果某段代码在另一个依赖中或在 *路由函数* 中使数据库事务"回滚"或产生任何其他错误,你将会在依赖中捕获到异常。
|
||||
例如,如果在中间的某处代码中(在另一个依赖或在某个 *路径操作* 中)发生了数据库事务“回滚”或产生了其他异常,你会在你的依赖中收到这个异常。
|
||||
|
||||
因此,你可以使用 `except SomeException` 在依赖中捕获特定的异常。
|
||||
因此,你可以在该依赖中用 `except SomeException` 来捕获这个特定异常。
|
||||
|
||||
同样,你也可以使用 `finally` 来确保退出步骤得到执行,无论是否存在异常。
|
||||
同样地,你可以使用 `finally` 来确保退出步骤一定会被执行,无论是否发生异常。
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
|
||||
## 使用 `yield` 的子依赖项
|
||||
{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *}
|
||||
|
||||
你可以声明任意数量和层级的树状依赖,而且它们中的任何一个或所有的都可以使用 `yield`。
|
||||
## 使用 `yield` 的子依赖项 { #sub-dependencies-with-yield }
|
||||
|
||||
**FastAPI** 会确保每个带有 `yield` 的依赖中的"退出代码"按正确顺序运行。
|
||||
你可以声明任意大小和形状的子依赖及其“树”,其中任意一个或全部都可以使用 `yield`。
|
||||
|
||||
例如,`dependency_c` 可以依赖于 `dependency_b`,而 `dependency_b` 则依赖于 `dependency_a`。
|
||||
**FastAPI** 会确保每个带有 `yield` 的依赖中的“退出代码”按正确的顺序运行。
|
||||
|
||||
例如,`dependency_c` 可以依赖 `dependency_b`,而 `dependency_b` 则依赖 `dependency_a`:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
|
||||
|
||||
所有这些依赖都可以使用 `yield`。
|
||||
并且它们都可以使用 `yield`。
|
||||
|
||||
在这种情况下,`dependency_c` 在执行其退出代码时需要 `dependency_b`(此处称为 `dep_b`)的值仍然可用。
|
||||
在这种情况下,`dependency_c` 在执行其退出代码时需要 `dependency_b`(此处命名为 `dep_b`)的值仍然可用。
|
||||
|
||||
而 `dependency_b` 反过来则需要 `dependency_a`(此处称为 `dep_a` )的值在其退出代码中可用。
|
||||
而 `dependency_b` 又需要 `dependency_a`(此处命名为 `dep_a`)的值在其退出代码中可用。
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
|
||||
|
||||
同样,你可以混合使用带有 `yield` 或 `return` 的依赖。
|
||||
同样地,你可以将一些依赖用 `yield`,另一些用 `return`,并让其中一些依赖依赖于另一些。
|
||||
|
||||
你也可以声明一个依赖于多个带有 `yield` 的依赖,等等。
|
||||
你也可以有一个依赖需要多个带有 `yield` 的依赖,等等。
|
||||
|
||||
你可以拥有任何你想要的依赖组合。
|
||||
|
||||
**FastAPI** 将确保按正确的顺序运行所有内容。
|
||||
**FastAPI** 将确保一切都按正确的顺序运行。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
这是由 Python 的<a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">上下文管理器</a>完成的。
|
||||
这要归功于 Python 的<a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">上下文管理器</a>。
|
||||
|
||||
**FastAPI** 在内部使用它们来实现这一点。
|
||||
|
||||
///
|
||||
|
||||
## 包含 `yield` 和 `HTTPException` 的依赖项
|
||||
## 同时使用 `yield` 和 `HTTPException` 的依赖项 { #dependencies-with-yield-and-httpexception }
|
||||
|
||||
你可以使用带有 `yield` 的依赖项,并且可以包含 `try` 代码块用于捕获异常。
|
||||
你已经看到可以在带有 `yield` 的依赖中使用 `try` 块尝试执行一些代码,然后在 `finally` 之后运行一些退出代码。
|
||||
|
||||
同样,你可以在 `yield` 之后的退出代码中抛出一个 `HTTPException` 或类似的异常。
|
||||
你也可以使用 `except` 来捕获引发的异常并对其进行处理。
|
||||
|
||||
例如,你可以抛出一个不同的异常,如 `HTTPException`。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
这是一种相对高级的技巧,在大多数情况下你并不需要使用它,因为你可以在其他代码中抛出异常(包括 `HTTPException` ),例如在 *路由函数* 中。
|
||||
这是一种相对高级的技巧,在大多数情况下你并不需要使用它,因为你可以在应用的其他代码中(例如在 *路径操作函数* 里)抛出异常(包括 `HTTPException`)。
|
||||
|
||||
但是如果你需要,你也可以在依赖项中做到这一点。🤓
|
||||
但是如果你需要,它就在这里。🤓
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
|
||||
|
||||
你还可以创建一个 [自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} 用于捕获异常(同时也可以抛出另一个 `HTTPException`)。
|
||||
如果你想捕获异常并基于它创建一个自定义响应,请创建一个[自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}。
|
||||
|
||||
## 包含 `yield` 和 `except` 的依赖项
|
||||
## 同时使用 `yield` 和 `except` 的依赖项 { #dependencies-with-yield-and-except }
|
||||
|
||||
如果你在包含 `yield` 的依赖项中使用 `except` 捕获了一个异常,然后你没有重新抛出该异常(或抛出一个新异常),与在普通的Python代码中相同,FastAPI不会注意到发生了异常。
|
||||
如果你在带有 `yield` 的依赖中使用 `except` 捕获了一个异常,并且你没有再次抛出它(或抛出一个新异常),FastAPI 将无法察觉发生过异常,就像普通的 Python 代码那样:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
|
||||
|
||||
在示例代码的情况下,客户端将会收到 *HTTP 500 Internal Server Error* 的响应,因为我们没有抛出 `HTTPException` 或者类似的异常,并且服务器也 **不会有任何日志** 或者其他提示来告诉我们错误是什么。😱
|
||||
在这种情况下,客户端会像预期那样看到一个 *HTTP 500 Internal Server Error* 响应,因为我们没有抛出 `HTTPException` 或类似异常,但服务器将**没有任何日志**或其他关于错误是什么的提示。😱
|
||||
|
||||
### 在包含 `yield` 和 `except` 的依赖项中一定要 `raise`
|
||||
### 在带有 `yield` 和 `except` 的依赖中务必 `raise` { #always-raise-in-dependencies-with-yield-and-except }
|
||||
|
||||
如果你在使用 `yield` 的依赖项中捕获到了一个异常,你应该再次抛出捕获到的异常,除非你抛出 `HTTPException` 或类似的其他异常,
|
||||
如果你在带有 `yield` 的依赖中捕获到了一个异常,除非你抛出另一个 `HTTPException` 或类似异常,**否则你应该重新抛出原始异常**。
|
||||
|
||||
你可以使用 `raise` 再次抛出捕获到的异常。
|
||||
你可以使用 `raise` 重新抛出同一个异常:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
|
||||
|
||||
现在客户端同样会得到 *HTTP 500 Internal Server Error* 响应,但是服务器日志会记录下我们自定义的 `InternalError`。
|
||||
现在客户端仍会得到同样的 *HTTP 500 Internal Server Error* 响应,但服务器日志中会有我们自定义的 `InternalError`。😎
|
||||
|
||||
## 使用 `yield` 的依赖项的执行
|
||||
## 使用 `yield` 的依赖项的执行 { #execution-of-dependencies-with-yield }
|
||||
|
||||
执行顺序大致如下时序图所示。时间轴从上到下,每一列都代表交互或者代码执行的一部分。
|
||||
执行顺序大致如下图所示。时间轴从上到下,每一列都代表交互或执行代码的一部分。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -167,63 +170,78 @@ participant tasks as Background tasks
|
||||
end
|
||||
```
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
只会向客户端发送 **一次响应** ,可能是一个错误响应,也可能是来自 *路由函数* 的响应。
|
||||
只会向客户端发送**一次响应**。它可能是某个错误响应,或者是来自 *路径操作* 的响应。
|
||||
|
||||
在发送了其中一个响应之后,就无法再发送其他响应了。
|
||||
在其中一个响应发送之后,就不能再发送其他响应了。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
这个时序图展示了 `HTTPException`,除此之外你也可以抛出任何你在使用 `yield` 的依赖项中或者[自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}中捕获的异常。
|
||||
|
||||
如果你引发任何异常,它将传递给使用 `yield` 的依赖项,包括 `HTTPException`。在大多数情况下你应当从使用 `yield` 的依赖项中重新抛出捕获的异常或者一个新的异常来确保它会被正确的处理。
|
||||
如果你在 *路径操作函数* 的代码中引发任何异常,它都会被传递给带有 `yield` 的依赖项,包括 `HTTPException`。在大多数情况下,你会希望在带有 `yield` 的依赖中重新抛出相同的异常或一个新的异常,以确保它被正确处理。
|
||||
|
||||
///
|
||||
|
||||
## 包含 `yield`, `HTTPException`, `except` 的依赖项和后台任务
|
||||
## 提前退出与 `scope` { #early-exit-and-scope }
|
||||
|
||||
/// warning | 注意
|
||||
通常,带有 `yield` 的依赖的退出代码会在响应发送给客户端**之后**执行。
|
||||
|
||||
你大概率不需要了解这些技术细节,可以跳过这一章节继续阅读后续的内容。
|
||||
但如果你知道在从 *路径操作函数* 返回之后不再需要使用该依赖,你可以使用 `Depends(scope="function")` 告诉 FastAPI:应当在 *路径操作函数* 返回后、但在**响应发送之前**关闭该依赖。
|
||||
|
||||
如果你使用的FastAPI的版本早于0.106.0,并且在使用后台任务中使用了包含 `yield` 的依赖项中的资源,那么这些细节会对你有一些用处。
|
||||
{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *}
|
||||
|
||||
///
|
||||
`Depends()` 接收一个 `scope` 参数,可为:
|
||||
|
||||
### 包含 `yield` 和 `except` 的依赖项的技术细节
|
||||
* `"function"`:在处理请求的 *路径操作函数* 之前启动依赖,在 *路径操作函数* 结束后结束依赖,但在响应发送给客户端**之前**。因此,依赖函数将围绕这个*路径操作函数*执行。
|
||||
* `"request"`:在处理请求的 *路径操作函数* 之前启动依赖(与使用 `"function"` 时类似),但在响应发送给客户端**之后**结束。因此,依赖函数将围绕这个**请求**与响应周期执行。
|
||||
|
||||
在FastAPI 0.110.0版本之前,如果使用了一个包含 `yield` 的依赖项,你在依赖项中使用 `except` 捕获了一个异常,但是你没有再次抛出该异常,这个异常会被自动抛出/转发到异常处理器或者内部服务错误处理器。
|
||||
如果未指定且依赖包含 `yield`,则默认 `scope` 为 `"request"`。
|
||||
|
||||
### 后台任务和使用 `yield` 的依赖项的技术细节
|
||||
### 子依赖的 `scope` { #scope-for-sub-dependencies }
|
||||
|
||||
在FastAPI 0.106.0版本之前,在 `yield` 后面抛出异常是不可行的,因为 `yield` 之后的退出代码是在响应被发送之后再执行,这个时候异常处理器已经执行过了。
|
||||
当你声明一个 `scope="request"`(默认)的依赖时,任何子依赖也需要有 `"request"` 的 `scope`。
|
||||
|
||||
这样设计的目的主要是为了允许在后台任务中使用被依赖项`yield`的对象,因为退出代码会在后台任务结束后再执行。
|
||||
但一个 `scope` 为 `"function"` 的依赖可以有 `scope` 为 `"function"` 和 `"request"` 的子依赖。
|
||||
|
||||
然而这也意味着在等待响应通过网络传输的同时,非必要的持有一个 `yield` 依赖项中的资源(例如数据库连接),这一行为在FastAPI 0.106.0被改变了。
|
||||
这是因为任何依赖都需要能够在子依赖之前运行其退出代码,因为它的退出代码中可能还需要使用这些子依赖。
|
||||
|
||||
/// tip | 提示
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
除此之外,后台任务通常是一组独立的逻辑,应该被单独处理,并且使用它自己的资源(例如它自己的数据库连接)。
|
||||
participant client as Client
|
||||
participant dep_req as Dep scope="request"
|
||||
participant dep_func as Dep scope="function"
|
||||
participant operation as Path Operation
|
||||
|
||||
这样也会让你的代码更加简洁。
|
||||
client ->> dep_req: Start request
|
||||
Note over dep_req: Run code up to yield
|
||||
dep_req ->> dep_func: Pass dependency
|
||||
Note over dep_func: Run code up to yield
|
||||
dep_func ->> operation: Run path operation with dependency
|
||||
operation ->> dep_func: Return from path operation
|
||||
Note over dep_func: Run code after yield
|
||||
Note over dep_func: ✅ Dependency closed
|
||||
dep_func ->> client: Send response to client
|
||||
Note over client: Response sent
|
||||
Note over dep_req: Run code after yield
|
||||
Note over dep_req: ✅ Dependency closed
|
||||
```
|
||||
|
||||
///
|
||||
## 包含 `yield`、`HTTPException`、`except` 和后台任务的依赖项 { #dependencies-with-yield-httpexception-except-and-background-tasks }
|
||||
|
||||
如果你之前依赖于这一行为,那么现在你应该在后台任务中创建并使用它自己的资源,不要在内部使用属于 `yield` 依赖项的资源。
|
||||
带有 `yield` 的依赖项随着时间演进以涵盖不同的用例并修复了一些问题。
|
||||
|
||||
例如,你应该在后台任务中创建一个新的数据库会话用于查询数据,而不是使用相同的会话。你应该将对象的ID作为参数传递给后台任务函数,然后在该函数中重新获取该对象,而不是直接将数据库对象作为参数。
|
||||
如果你想了解在不同 FastAPI 版本中发生了哪些变化,可以在进阶指南中阅读更多:[高级依赖项 —— 包含 `yield`、`HTTPException`、`except` 和后台任务的依赖项](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}。
|
||||
|
||||
## 上下文管理器
|
||||
## 上下文管理器 { #context-managers }
|
||||
|
||||
### 什么是"上下文管理器"
|
||||
### 什么是“上下文管理器” { #what-are-context-managers }
|
||||
|
||||
"上下文管理器"是你可以在 `with` 语句中使用的任何Python对象。
|
||||
“上下文管理器”是你可以在 `with` 语句中使用的任意 Python 对象。
|
||||
|
||||
例如,<a href="https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">你可以使用`with`读取文件</a>:
|
||||
例如,<a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">你可以用 `with` 来读取文件</a>:
|
||||
|
||||
```Python
|
||||
with open("./somefile.txt") as f:
|
||||
@@ -231,37 +249,39 @@ with open("./somefile.txt") as f:
|
||||
print(contents)
|
||||
```
|
||||
|
||||
在底层,`open("./somefile.txt")`创建了一个被称为"上下文管理器"的对象。
|
||||
在底层,`open("./somefile.txt")` 会创建一个“上下文管理器”对象。
|
||||
|
||||
当 `with` 代码块结束时,它会确保关闭文件,即使发生了异常也是如此。
|
||||
当 `with` 代码块结束时,它会确保文件被关闭,即使期间发生了异常。
|
||||
|
||||
当你使用 `yield` 创建一个依赖项时,**FastAPI** 会在内部将其转换为上下文管理器,并与其他相关工具结合使用。
|
||||
当你用 `yield` 创建一个依赖时,**FastAPI** 会在内部为它创建一个上下文管理器,并与其他相关工具结合使用。
|
||||
|
||||
### 在使用 `yield` 的依赖项中使用上下文管理器
|
||||
### 在带有 `yield` 的依赖中使用上下文管理器 { #using-context-managers-in-dependencies-with-yield }
|
||||
|
||||
/// warning | 注意
|
||||
/// warning | 警告
|
||||
|
||||
这是一个更为"高级"的想法。
|
||||
这算是一个“高级”概念。
|
||||
|
||||
如果你刚开始使用 **FastAPI** ,你可以暂时可以跳过它。
|
||||
如果你刚开始使用 **FastAPI**,现在可以先跳过。
|
||||
|
||||
///
|
||||
|
||||
在Python中,你可以通过<a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">创建一个带有`__enter__()`和`__exit__()`方法的类</a>来创建上下文管理器。
|
||||
在 Python 中,你可以通过<a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">创建一个带有 `__enter__()` 和 `__exit__()` 方法的类</a>来创建上下文管理器。
|
||||
|
||||
你也可以在 **FastAPI** 的 `yield` 依赖项中通过 `with` 或者 `async with` 语句来使用它们:
|
||||
你也可以在 **FastAPI** 的带有 `yield` 的依赖中,使用依赖函数内部的 `with` 或 `async with` 语句来使用它们:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
|
||||
{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
另一种创建上下文管理器的方法是:
|
||||
另一种创建上下文管理器的方式是:
|
||||
|
||||
* <a href="https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a>或者
|
||||
* <a href="https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> 或
|
||||
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
|
||||
|
||||
使用它们装饰一个只有单个 `yield` 的函数。这就是 **FastAPI** 内部对于 `yield` 依赖项的处理方式。
|
||||
用它们去装饰一个只包含单个 `yield` 的函数。
|
||||
|
||||
但是你不需要为FastAPI的依赖项使用这些装饰器(而且也不应该)。FastAPI会在内部为你处理这些。
|
||||
这正是 **FastAPI** 在内部处理带有 `yield` 的依赖时所使用的方式。
|
||||
|
||||
但你不需要(也不应该)为 FastAPI 的依赖去使用这些装饰器。FastAPI 会在内部为你处理好。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# 全局依赖项
|
||||
# 全局依赖项 { #global-dependencies }
|
||||
|
||||
有时,我们要为整个应用添加依赖项。
|
||||
|
||||
通过与定义[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。
|
||||
通过与[将 `dependencies` 添加到*路径操作装饰器*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。
|
||||
|
||||
这样一来,就可以为所有*路径操作*应用该依赖项:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial012.py hl[15] *}
|
||||
{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *}
|
||||
|
||||
[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。
|
||||
[将 `dependencies` 添加到*路径操作装饰器*](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` 参数。
|
||||
|
||||
@@ -1,85 +1,97 @@
|
||||
# 依赖项
|
||||
# 依赖项 { #dependencies }
|
||||
|
||||
FastAPI 提供了简单易用,但功能强大的**<abbr title="也称为组件、资源、提供者、服务、可注入项">依赖注入</abbr>**系统。
|
||||
**FastAPI** 提供了简单直观但功能强大的**<abbr title="也称为组件、资源、提供者、服务、可注入项">依赖注入</abbr>**系统。
|
||||
|
||||
这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 **FastAPI**。
|
||||
它被设计得非常易用,能让任何开发者都能轻松把其他组件与 **FastAPI** 集成。
|
||||
|
||||
## 什么是「依赖注入」
|
||||
## 什么是「依赖注入」 { #what-is-dependency-injection }
|
||||
|
||||
编程中的**「依赖注入」**是声明代码(本文中为*路径操作函数* )运行所需的,或要使用的「依赖」的一种方式。
|
||||
在编程中,**「依赖注入」**指的是,你的代码(本文中为*路径操作函数*)声明其运行所需并要使用的东西:“依赖”。
|
||||
|
||||
然后,由系统(本文中为 **FastAPI**)负责执行任意需要的逻辑,为代码提供这些依赖(「注入」依赖项)。
|
||||
然后,由该系统(本文中为 **FastAPI**)负责执行所有必要的逻辑,为你的代码提供这些所需的依赖(“注入”依赖)。
|
||||
|
||||
依赖注入常用于以下场景:
|
||||
当你需要以下内容时,这非常有用:
|
||||
|
||||
* 共享业务逻辑(复用相同的代码逻辑)
|
||||
* 共享业务逻辑(同一段代码逻辑反复复用)
|
||||
* 共享数据库连接
|
||||
* 实现安全、验证、角色权限
|
||||
* 等……
|
||||
* 实施安全、认证、角色权限等要求
|
||||
* 以及更多其他内容...
|
||||
|
||||
上述场景均可以使用**依赖注入**,将代码重复最小化。
|
||||
同时尽量减少代码重复。
|
||||
|
||||
## 第一步
|
||||
## 第一步 { #first-steps }
|
||||
|
||||
接下来,我们学习一个非常简单的例子,尽管它过于简单,不是很实用。
|
||||
先来看一个非常简单的例子。它现在简单到几乎没什么用。
|
||||
|
||||
但通过这个例子,您可以初步了解「依赖注入」的工作机制。
|
||||
但这样我们就可以专注于**依赖注入**系统是如何工作的。
|
||||
|
||||
### 创建依赖项
|
||||
### 创建依赖项,或“dependable” { #create-a-dependency-or-dependable }
|
||||
|
||||
首先,要关注的是依赖项。
|
||||
首先关注依赖项。
|
||||
|
||||
依赖项就是一个函数,且可以使用与*路径操作函数*相同的参数:
|
||||
它只是一个函数,且可以接收与*路径操作函数*相同的所有参数:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001.py hl[8:11] *}
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *}
|
||||
|
||||
大功告成。
|
||||
|
||||
只用了**2 行**代码。
|
||||
|
||||
依赖项函数的形式和结构与*路径操作函数*一样。
|
||||
它的形式和结构与所有*路径操作函数*相同。
|
||||
|
||||
因此,可以把依赖项当作没有「装饰器」(即,没有 `@app.get("/some-path")` )的路径操作函数。
|
||||
你可以把它当作没有“装饰器”(没有 `@app.get("/some-path")`)的*路径操作函数*。
|
||||
|
||||
依赖项可以返回各种内容。
|
||||
而且它可以返回任何你想要的内容。
|
||||
|
||||
本例中的依赖项预期接收如下参数:
|
||||
本例中的依赖项预期接收:
|
||||
|
||||
* 类型为 `str` 的可选查询参数 `q`
|
||||
* 类型为 `int` 的可选查询参数 `skip`,默认值是 `0`
|
||||
* 类型为 `int` 的可选查询参数 `limit`,默认值是 `100`
|
||||
* 类型为 `int` 的可选查询参数 `skip`,默认值 `0`
|
||||
* 类型为 `int` 的可选查询参数 `limit`,默认值 `100`
|
||||
|
||||
然后,依赖项函数返回包含这些值的 `dict`。
|
||||
然后它只需返回一个包含这些值的 `dict`。
|
||||
|
||||
### 导入 `Depends`
|
||||
/// info | 信息
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001.py hl[3] *}
|
||||
FastAPI 在 0.95.0 版本中新增了对 `Annotated` 的支持(并开始推荐使用)。
|
||||
|
||||
### 声明依赖项
|
||||
如果你的版本较旧,尝试使用 `Annotated` 会报错。
|
||||
|
||||
与在*路径操作函数*参数中使用 `Body`、`Query` 的方式相同,声明依赖项需要使用 `Depends` 和一个新的参数:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001.py hl[15,20] *}
|
||||
|
||||
虽然,在路径操作函数的参数中使用 `Depends` 的方式与 `Body`、`Query` 相同,但 `Depends` 的工作方式略有不同。
|
||||
|
||||
这里只能传给 Depends 一个参数。
|
||||
|
||||
且该参数必须是可调用对象,比如函数。
|
||||
|
||||
该函数接收的参数和*路径操作函数*的参数一样。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
下一章介绍,除了函数还有哪些「对象」可以用作依赖项。
|
||||
在使用 `Annotated` 之前,请确保[升级 FastAPI 版本](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。
|
||||
|
||||
///
|
||||
|
||||
接收到新的请求时,**FastAPI** 执行如下操作:
|
||||
### 导入 `Depends` { #import-depends }
|
||||
|
||||
* 用正确的参数调用依赖项函数(「可依赖项」)
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
### 在“dependant”中声明依赖项 { #declare-the-dependency-in-the-dependant }
|
||||
|
||||
与在*路径操作函数*的参数中使用 `Body`、`Query` 等相同,给参数使用 `Depends` 来声明一个新的依赖项:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *}
|
||||
|
||||
虽然你在函数参数中使用 `Depends` 的方式与 `Body`、`Query` 等相同,但 `Depends` 的工作方式略有不同。
|
||||
|
||||
这里只能给 `Depends` 传入一个参数。
|
||||
|
||||
这个参数必须是类似函数的可调用对象。
|
||||
|
||||
你不需要直接调用它(不要在末尾加括号),只需将其作为参数传给 `Depends()`。
|
||||
|
||||
该函数接收的参数与*路径操作函数*的参数相同。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
下一章会介绍除了函数之外,还有哪些“东西”可以用作依赖项。
|
||||
|
||||
///
|
||||
|
||||
接收到新的请求时,**FastAPI** 会负责:
|
||||
|
||||
* 用正确的参数调用你的依赖项(“dependable”)函数
|
||||
* 获取函数返回的结果
|
||||
* 把函数返回的结果赋值给*路径操作函数*的参数
|
||||
* 将该结果赋值给你的*路径操作函数*中的参数
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
@@ -92,95 +104,121 @@ common_parameters --> read_items
|
||||
common_parameters --> read_users
|
||||
```
|
||||
|
||||
这样,只编写一次代码,**FastAPI** 就可以为多个*路径操作*共享这段代码 。
|
||||
这样,你只需编写一次共享代码,**FastAPI** 会在你的*路径操作*中为你调用它。
|
||||
|
||||
/// check | 检查
|
||||
|
||||
注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。
|
||||
注意,无需创建专门的类并传给 **FastAPI** 去“注册”之类的操作。
|
||||
|
||||
只要把它传递给 `Depends`,**FastAPI** 就知道该如何执行后续操作。
|
||||
只要把它传给 `Depends`,**FastAPI** 就知道该怎么做了。
|
||||
|
||||
///
|
||||
|
||||
## 要不要使用 `async`?
|
||||
## 共享 `Annotated` 依赖项 { #share-annotated-dependencies }
|
||||
|
||||
**FastAPI** 调用依赖项的方式与*路径操作函数*一样,因此,定义依赖项函数,也要应用与路径操作函数相同的规则。
|
||||
在上面的示例中,你会发现这里有一点点**代码重复**。
|
||||
|
||||
即,既可以使用异步的 `async def`,也可以使用普通的 `def` 定义依赖项。
|
||||
当你需要使用 `common_parameters()` 这个依赖时,你必须写出完整的带类型注解和 `Depends()` 的参数:
|
||||
|
||||
在普通的 `def` *路径操作函数*中,可以声明异步的 `async def` 依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项。
|
||||
```Python
|
||||
commons: Annotated[dict, Depends(common_parameters)]
|
||||
```
|
||||
|
||||
上述这些操作都是可行的,**FastAPI** 知道该怎么处理。
|
||||
但因为我们使用了 `Annotated`,可以把这个 `Annotated` 的值存到一个变量里,在多个地方复用:
|
||||
|
||||
/// note | 笔记
|
||||
{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *}
|
||||
|
||||
如里不了解异步,请参阅[异步:*“着急了?”*](../../async.md){.internal-link target=_blank} 一章中 `async` 和 `await` 的内容。
|
||||
/// tip | 提示
|
||||
|
||||
这只是标准的 Python,叫做“类型别名”,并不是 **FastAPI** 特有的。
|
||||
|
||||
但因为 **FastAPI** 基于 Python 标准(包括 `Annotated`),你就可以在代码里使用这个技巧。😎
|
||||
|
||||
///
|
||||
|
||||
## 与 OpenAPI 集成
|
||||
这些依赖会照常工作,而**最棒的是**,**类型信息会被保留**,这意味着你的编辑器依然能提供**自动补全**、**行内报错**等。同样适用于 `mypy` 等其他工具。
|
||||
|
||||
依赖项及子依赖项的所有请求声明、验证和需求都可以集成至同一个 OpenAPI 概图。
|
||||
当你在**大型代码库**中,在**很多*路径操作***里反复使用**相同的依赖**时,这会特别有用。
|
||||
|
||||
所以,交互文档里也会显示依赖项的所有信息:
|
||||
## 要不要使用 `async`? { #to-async-or-not-to-async }
|
||||
|
||||
由于依赖项也会由 **FastAPI** 调用(与*路径操作函数*相同),因此定义函数时同样的规则也适用。
|
||||
|
||||
你可以使用 `async def` 或普通的 `def`。
|
||||
|
||||
你可以在普通的 `def` *路径操作函数*中声明 `async def` 的依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项,等等。
|
||||
|
||||
都没关系,**FastAPI** 知道该怎么处理。
|
||||
|
||||
/// note | 注意
|
||||
|
||||
如果不了解异步,请参阅文档中关于 `async` 和 `await` 的章节:[异步:*“着急了?”*](../../async.md#in-a-hurry){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
## 与 OpenAPI 集成 { #integrated-with-openapi }
|
||||
|
||||
依赖项及子依赖项中声明的所有请求、验证和需求都会集成到同一个 OpenAPI 模式中。
|
||||
|
||||
因此,交互式文档中也会包含这些依赖项的所有信息:
|
||||
|
||||
<img src="/img/tutorial/dependencies/image01.png">
|
||||
|
||||
## 简单用法
|
||||
## 简单用法 { #simple-usage }
|
||||
|
||||
观察一下就会发现,只要*路径* 和*操作*匹配,就可以使用声明的路径操作函数。然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。
|
||||
观察一下就会发现,只要*路径*和*操作*匹配,就会使用声明的*路径操作函数*。随后,**FastAPI** 会用正确的参数调用该函数,并从请求中提取数据。
|
||||
|
||||
实际上,所有(或大多数)网络框架的工作方式都是这样的。
|
||||
事实上,所有(或大多数)Web 框架的工作方式都是这样的。
|
||||
|
||||
开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。
|
||||
你从不会直接调用这些函数。它们由你的框架(此处为 **FastAPI**)调用。
|
||||
|
||||
通过依赖注入系统,只要告诉 **FastAPI** *路径操作函数* 还要「依赖」其他在*路径操作函数*之前执行的内容,**FastAPI** 就会执行函数代码,并「注入」函数返回的结果。
|
||||
通过依赖注入系统,你还可以告诉 **FastAPI**,你的*路径操作函数*还“依赖”某些应在*路径操作函数*之前执行的内容,**FastAPI** 会负责执行它并“注入”结果。
|
||||
|
||||
其他与「依赖注入」概念相同的术语为:
|
||||
“依赖注入”的其他常见术语包括:
|
||||
|
||||
* 资源(Resource)
|
||||
* 提供方(Provider)
|
||||
* 服务(Service)
|
||||
* 可注入(Injectable)
|
||||
* 组件(Component)
|
||||
* 资源(resources)
|
||||
* 提供方(providers)
|
||||
* 服务(services)
|
||||
* 可注入(injectables)
|
||||
* 组件(components)
|
||||
|
||||
## **FastAPI** 插件
|
||||
## **FastAPI** 插件 { #fastapi-plug-ins }
|
||||
|
||||
**依赖注入**系统支持构建集成和「插件」。但实际上,FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。
|
||||
可以使用**依赖注入**系统构建集成和“插件”。但实际上,根本**不需要创建“插件”**,因为通过依赖项可以声明无限多的集成与交互,使其可用于*路径操作函数*。
|
||||
|
||||
创建依赖项非常简单、直观,并且还支持导入 Python 包。毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。
|
||||
依赖项可以用非常简单直观的方式创建,你只需导入所需的 Python 包,用*字面意义上的*几行代码就能把它们与你的 API 函数集成起来。
|
||||
|
||||
下一章将详细介绍在关系型数据库、NoSQL 数据库、安全等方面使用依赖项的例子。
|
||||
在接下来的章节中,你会看到关于关系型数据库、NoSQL 数据库、安全等方面的示例。
|
||||
|
||||
## **FastAPI** 兼容性
|
||||
## **FastAPI** 兼容性 { #fastapi-compatibility }
|
||||
|
||||
依赖注入系统如此简洁的特性,让 **FastAPI** 可以与下列系统兼容:
|
||||
依赖注入系统的简洁让 **FastAPI** 能与以下内容兼容:
|
||||
|
||||
* 关系型数据库
|
||||
* 各类关系型数据库
|
||||
* NoSQL 数据库
|
||||
* 外部支持库
|
||||
* 外部包
|
||||
* 外部 API
|
||||
* 认证和鉴权系统
|
||||
* 认证与授权系统
|
||||
* API 使用监控系统
|
||||
* 响应数据注入系统
|
||||
* 等等……
|
||||
* 等等...
|
||||
|
||||
## 简单而强大
|
||||
## 簡单而强大 { #simple-and-powerful }
|
||||
|
||||
虽然,**层级式依赖注入系统**的定义与使用十分简单,但它却非常强大。
|
||||
虽然**层级式依赖注入系统**的定义与使用非常简单,但它依然非常强大。
|
||||
|
||||
比如,可以定义依赖其他依赖项的依赖项。
|
||||
你可以定义依赖其他依赖项的依赖项。
|
||||
|
||||
最后,依赖项层级树构建后,**依赖注入系统**会处理所有依赖项及其子依赖项,并为每一步操作提供(注入)结果。
|
||||
最终会构建出一个依赖项的层级树,**依赖注入**系统会处理所有这些依赖(及其子依赖),并在每一步提供(注入)相应的结果。
|
||||
|
||||
比如,下面有 4 个 API 路径操作(*端点*):
|
||||
例如,假设你有 4 个 API 路径操作(*端点*):
|
||||
|
||||
* `/items/public/`
|
||||
* `/items/private/`
|
||||
* `/users/{user_id}/activate`
|
||||
* `/items/pro/`
|
||||
|
||||
开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限:
|
||||
你可以仅通过依赖项及其子依赖项为它们添加不同的权限要求:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
@@ -205,8 +243,8 @@ admin_user --> activate_user
|
||||
paying_user --> pro_items
|
||||
```
|
||||
|
||||
## 与 **OpenAPI** 集成
|
||||
## 与 **OpenAPI** 集成 { #integrated-with-openapi_1 }
|
||||
|
||||
在声明需求时,所有这些依赖项还会把参数、验证等功能添加至路径操作。
|
||||
在声明需求的同时,所有这些依赖项也会为你的*路径操作*添加参数、验证等内容。
|
||||
|
||||
**FastAPI** 负责把上述内容全部添加到 OpenAPI 概图,并显示在交互文档中。
|
||||
**FastAPI** 会负责把这些全部添加到 OpenAPI 模式中,以便它们显示在交互式文档系统里。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 子依赖项
|
||||
# 子依赖项 { #sub-dependencies }
|
||||
|
||||
FastAPI 支持创建含**子依赖项**的依赖项。
|
||||
|
||||
@@ -6,34 +6,34 @@ FastAPI 支持创建含**子依赖项**的依赖项。
|
||||
|
||||
**FastAPI** 负责处理解析不同深度的子依赖项。
|
||||
|
||||
### 第一层依赖项
|
||||
## 第一层依赖项 “dependable” { #first-dependency-dependable }
|
||||
|
||||
下列代码创建了第一层依赖项:
|
||||
你可以创建一个第一层依赖项(“dependable”),如下:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005.py hl[8:9] *}
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *}
|
||||
|
||||
这段代码声明了类型为 `str` 的可选查询参数 `q`,然后返回这个查询参数。
|
||||
|
||||
这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。
|
||||
|
||||
### 第二层依赖项
|
||||
## 第二层依赖项,“dependable”和“dependant” { #second-dependency-dependable-and-dependant }
|
||||
|
||||
接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项(所以这也是一个「依赖项」):
|
||||
接下来,创建另一个依赖项函数(一个“dependable”),并同时为它自身再声明一个依赖项(因此它同时也是一个“dependant”):
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005.py hl[13] *}
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *}
|
||||
|
||||
这里重点说明一下声明的参数:
|
||||
|
||||
* 尽管该函数自身是依赖项,但还声明了另一个依赖项(它「依赖」于其他对象)
|
||||
* 尽管该函数自身是依赖项(“dependable”),但还声明了另一个依赖项(它“依赖”于其他对象)
|
||||
* 该函数依赖 `query_extractor`, 并把 `query_extractor` 的返回值赋给参数 `q`
|
||||
* 同时,该函数还声明了类型是 `str` 的可选 cookie(`last_query`)
|
||||
* 用户未提供查询参数 `q` 时,则使用上次使用后保存在 cookie 中的查询
|
||||
|
||||
### 使用依赖项
|
||||
## 使用依赖项 { #use-the-dependency }
|
||||
|
||||
接下来,就可以使用依赖项:
|
||||
|
||||
{* ../../docs_src/dependencies/tutorial005.py hl[22] *}
|
||||
{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *}
|
||||
|
||||
/// info | 信息
|
||||
|
||||
@@ -54,20 +54,39 @@ read_query["/items/"]
|
||||
query_extractor --> query_or_cookie_extractor --> read_query
|
||||
```
|
||||
|
||||
## 多次使用同一个依赖项
|
||||
## 多次使用同一个依赖项 { #using-the-same-dependency-multiple-times }
|
||||
|
||||
如果在同一个*路径操作* 多次声明了同一个依赖项,例如,多个依赖项共用一个子依赖项,**FastAPI** 在处理同一请求时,只调用一次该子依赖项。
|
||||
|
||||
FastAPI 不会为同一个请求多次调用同一个依赖项,而是把依赖项的返回值进行<abbr title="一个实用程序/系统来存储计算/生成的值,以便重用它们,而不是再次计算它们。">「缓存」</abbr>,并把它传递给同一请求中所有需要使用该返回值的「依赖项」。
|
||||
|
||||
在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False` :
|
||||
在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False`:
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
|
||||
return {"fresh_value": fresh_value}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 非 Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
尽可能优先使用 `Annotated` 版本。
|
||||
|
||||
///
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
|
||||
return {"fresh_value": fresh_value}
|
||||
```
|
||||
|
||||
## 小结
|
||||
////
|
||||
|
||||
## 小结 { #recap }
|
||||
|
||||
千万别被本章里这些花里胡哨的词藻吓倒了,其实**依赖注入**系统非常简单。
|
||||
|
||||
|
||||
@@ -1,64 +1,62 @@
|
||||
# 处理错误
|
||||
# 处理错误 { #handling-errors }
|
||||
|
||||
某些情况下,需要向客户端返回错误提示。
|
||||
某些情况下,需要向使用你的 API 的客户端返回错误提示。
|
||||
|
||||
这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。
|
||||
这里所谓的客户端包括前端浏览器、他人的代码、物联网设备等。
|
||||
|
||||
需要向客户端返回错误提示的场景主要如下:
|
||||
你可能需要告诉客户端:
|
||||
|
||||
- 客户端没有执行操作的权限
|
||||
- 客户端没有访问资源的权限
|
||||
- 客户端没有执行该操作的权限
|
||||
- 客户端没有访问该资源的权限
|
||||
- 客户端要访问的项目不存在
|
||||
- 等等 ...
|
||||
- 等等
|
||||
|
||||
遇到这些情况时,通常要返回 **4XX**(400 至 499)**HTTP 状态码**。
|
||||
|
||||
**4XX** 状态码与表示请求成功的 **2XX**(200 至 299) HTTP 状态码类似。
|
||||
这与表示请求成功的 **2XX**(200 至 299)HTTP 状态码类似。那些“200”状态码表示某种程度上的“成功”。
|
||||
|
||||
只不过,**4XX** 状态码表示客户端发生的错误。
|
||||
而 **4XX** 状态码表示客户端发生了错误。
|
||||
|
||||
大家都知道**「404 Not Found」**错误,还有调侃这个错误的笑话吧?
|
||||
|
||||
## 使用 `HTTPException`
|
||||
## 使用 `HTTPException` { #use-httpexception }
|
||||
|
||||
向客户端返回 HTTP 错误响应,可以使用 `HTTPException`。
|
||||
|
||||
### 导入 `HTTPException`
|
||||
### 导入 `HTTPException` { #import-httpexception }
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
|
||||
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *}
|
||||
|
||||
### 触发 `HTTPException`
|
||||
### 在代码中触发 `HTTPException` { #raise-an-httpexception-in-your-code }
|
||||
|
||||
`HTTPException` 是额外包含了和 API 有关数据的常规 Python 异常。
|
||||
|
||||
因为是 Python 异常,所以不能 `return`,只能 `raise`。
|
||||
|
||||
如在调用*路径操作函数*里的工具函数时,触发了 `HTTPException`,FastAPI 就不再继续执行*路径操作函数*中的后续代码,而是立即终止请求,并把 `HTTPException` 的 HTTP 错误发送至客户端。
|
||||
这也意味着,如果你在*路径操作函数*里调用的某个工具函数内部触发了 `HTTPException`,那么*路径操作函数*中后续的代码将不会继续执行,请求会立刻终止,并把 `HTTPException` 的 HTTP 错误发送给客户端。
|
||||
|
||||
在介绍依赖项与安全的章节中,您可以了解更多用 `raise` 异常代替 `return` 值的优势。
|
||||
在介绍依赖项与安全的章节中,你可以更直观地看到用 `raise` 异常代替 `return` 值的优势。
|
||||
|
||||
本例中,客户端用 `ID` 请求的 `item` 不存在时,触发状态码为 `404` 的异常:
|
||||
本例中,客户端用不存在的 `ID` 请求 `item` 时,触发状态码为 `404` 的异常:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
|
||||
{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *}
|
||||
|
||||
### 响应结果
|
||||
### 响应结果 { #the-resulting-response }
|
||||
|
||||
请求为 `http://example.com/items/foo`(`item_id` 为 `「foo」`)时,客户端会接收到 HTTP 状态码 - 200 及如下 JSON 响应结果:
|
||||
请求为 `http://example.com/items/foo`(`item_id` 为 `"foo"`)时,客户端会接收到 HTTP 状态码 200 及如下 JSON 响应结果:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"item": "The Foo Wrestlers"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
但如果客户端请求 `http://example.com/items/bar`(`item_id` `「bar」` 不存在时),则会接收到 HTTP 状态码 - 404(「未找到」错误)及如下 JSON 响应结果:
|
||||
但如果客户端请求 `http://example.com/items/bar`(不存在的 `item_id` `"bar"`),则会接收到 HTTP 状态码 404(“未找到”错误)及如下 JSON 响应结果:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": "Item not found"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
/// tip | 提示
|
||||
@@ -71,68 +69,67 @@
|
||||
|
||||
///
|
||||
|
||||
## 添加自定义响应头
|
||||
## 添加自定义响应头 { #add-custom-headers }
|
||||
|
||||
有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些方面的安全需要。
|
||||
有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些类型的安全需要。
|
||||
|
||||
一般情况下可能不会需要在代码中直接使用响应头。
|
||||
一般情况下你可能不会在代码中直接使用它。
|
||||
|
||||
但对于某些高级应用场景,还是需要添加自定义响应头:
|
||||
但在某些高级场景中需要时,你可以添加自定义响应头:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
|
||||
{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *}
|
||||
|
||||
## 安装自定义异常处理器
|
||||
## 安装自定义异常处理器 { #install-custom-exception-handlers }
|
||||
|
||||
添加自定义处理器,要使用 [Starlette 的异常工具](https://www.starlette.dev/exceptions/)。
|
||||
可以使用<a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">与 Starlette 相同的异常处理工具</a>添加自定义异常处理器。
|
||||
|
||||
假设要触发的自定义异常叫作 `UnicornException`。
|
||||
假设有一个自定义异常 `UnicornException`(你自己或你使用的库可能会 `raise` 它)。
|
||||
|
||||
且需要 FastAPI 实现全局处理该异常。
|
||||
并且你希望用 FastAPI 在全局处理该异常。
|
||||
|
||||
此时,可以用 `@app.exception_handler()` 添加自定义异常控制器:
|
||||
此时,可以用 `@app.exception_handler()` 添加自定义异常处理器:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
|
||||
{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *}
|
||||
|
||||
请求 `/unicorns/yolo` 时,路径操作会触发 `UnicornException`。
|
||||
这里,请求 `/unicorns/yolo` 时,路径操作会触发 `UnicornException`。
|
||||
|
||||
但该异常将会被 `unicorn_exception_handler` 处理。
|
||||
|
||||
接收到的错误信息清晰明了,HTTP 状态码为 `418`,JSON 内容如下:
|
||||
你会收到清晰的错误信息,HTTP 状态码为 `418`,JSON 内容如下:
|
||||
|
||||
```JSON
|
||||
{"message": "Oops! yolo did something. There goes a rainbow..."}
|
||||
|
||||
```
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
`from starlette.requests import Request` 和 `from starlette.responses import JSONResponse` 也可以用于导入 `Request` 和 `JSONResponse`。
|
||||
也可以使用 `from starlette.requests import Request` 和 `from starlette.responses import JSONResponse`。
|
||||
|
||||
**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应操作都可以直接从 Starlette 导入。同理,`Request` 也是如此。
|
||||
**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为便捷方式,但大多数可用的响应都直接来自 Starlette。`Request` 也是如此。
|
||||
|
||||
///
|
||||
|
||||
## 覆盖默认异常处理器
|
||||
## 覆盖默认异常处理器 { #override-the-default-exception-handlers }
|
||||
|
||||
**FastAPI** 自带了一些默认异常处理器。
|
||||
|
||||
触发 `HTTPException` 或请求无效数据时,这些处理器返回默认的 JSON 响应结果。
|
||||
当你触发 `HTTPException`,或者请求中包含无效数据时,这些处理器负责返回默认的 JSON 响应。
|
||||
|
||||
不过,也可以使用自定义处理器覆盖默认异常处理器。
|
||||
你也可以用自己的处理器覆盖它们。
|
||||
|
||||
### 覆盖请求验证异常
|
||||
### 覆盖请求验证异常 { #override-request-validation-exceptions }
|
||||
|
||||
请求中包含无效数据时,**FastAPI** 内部会触发 `RequestValidationError`。
|
||||
|
||||
该异常也内置了默认异常处理器。
|
||||
它也内置了该异常的默认处理器。
|
||||
|
||||
覆盖默认异常处理器时需要导入 `RequestValidationError`,并用 `@app.excption_handler(RequestValidationError)` 装饰异常处理器。
|
||||
要覆盖它,导入 `RequestValidationError`,并用 `@app.exception_handler(RequestValidationError)` 装饰你的异常处理器。
|
||||
|
||||
这样,异常处理器就可以接收 `Request` 与异常。
|
||||
异常处理器会接收 `Request` 和该异常。
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
|
||||
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *}
|
||||
|
||||
访问 `/items/foo`,可以看到默认的 JSON 错误信息:
|
||||
现在,访问 `/items/foo` 时,默认的 JSON 错误为:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -147,59 +144,46 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
被替换为了以下文本格式的错误信息:
|
||||
将得到如下文本内容:
|
||||
|
||||
```
|
||||
1 validation error
|
||||
path -> item_id
|
||||
value is not a valid integer (type=type_error.integer)
|
||||
|
||||
Validation errors:
|
||||
Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer
|
||||
```
|
||||
|
||||
### `RequestValidationError` vs `ValidationError`
|
||||
### 覆盖 `HTTPException` 错误处理器 { #override-the-httpexception-error-handler }
|
||||
|
||||
/// warning | 警告
|
||||
同理,也可以覆盖 `HTTPException` 的处理器。
|
||||
|
||||
如果您觉得现在还用不到以下技术细节,可以先跳过下面的内容。
|
||||
例如,只为这些错误返回纯文本响应,而不是 JSON:
|
||||
|
||||
///
|
||||
|
||||
`RequestValidationError` 是 Pydantic 的 <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a> 的子类。
|
||||
|
||||
**FastAPI** 调用的就是 `RequestValidationError` 类,因此,如果在 `response_model` 中使用 Pydantic 模型,且数据有错误时,在日志中就会看到这个错误。
|
||||
|
||||
但客户端或用户看不到这个错误。反之,客户端接收到的是 HTTP 状态码为 `500` 的「内部服务器错误」。
|
||||
|
||||
这是因为在*响应*或代码(不是在客户端的请求里)中出现的 Pydantic `ValidationError` 是代码的 bug。
|
||||
|
||||
修复错误时,客户端或用户不能访问错误的内部信息,否则会造成安全隐患。
|
||||
|
||||
### 覆盖 `HTTPException` 错误处理器
|
||||
|
||||
同理,也可以覆盖 `HTTPException` 处理器。
|
||||
|
||||
例如,只为错误返回纯文本响应,而不是返回 JSON 格式的内容:
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
|
||||
{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *}
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
还可以使用 `from starlette.responses import PlainTextResponse`。
|
||||
|
||||
**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应都可以直接从 Starlette 导入。
|
||||
**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为便捷方式,但大多数可用的响应都直接来自 Starlette。
|
||||
|
||||
///
|
||||
|
||||
### 使用 `RequestValidationError` 的请求体
|
||||
/// warning | 警告
|
||||
|
||||
`RequestValidationError` 包含其接收到的无效数据请求的 `body` 。
|
||||
请注意,`RequestValidationError` 包含发生验证错误的文件名和行号信息,你可以在需要时将其记录到日志中以提供相关信息。
|
||||
|
||||
开发时,可以用这个请求体生成日志、调试错误,并返回给用户。
|
||||
但这也意味着,如果你只是将其直接转换为字符串并返回,可能会泄露一些关于系统的细节信息。因此,这里的代码会提取并分别显示每个错误。
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
|
||||
///
|
||||
|
||||
### 使用 `RequestValidationError` 的请求体 { #use-the-requestvalidationerror-body }
|
||||
|
||||
`RequestValidationError` 包含其接收到的带有无效数据的请求体 `body`。
|
||||
|
||||
开发时,你可以用它来记录请求体、调试错误,或返回给用户等。
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *}
|
||||
|
||||
现在试着发送一个无效的 `item`,例如:
|
||||
|
||||
@@ -208,10 +192,9 @@ path -> item_id
|
||||
"title": "towel",
|
||||
"size": "XL"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
收到的响应包含 `body` 信息,并说明数据是无效的:
|
||||
收到的响应会告诉你数据无效,并包含收到的请求体:
|
||||
|
||||
```JSON hl_lines="12-15"
|
||||
{
|
||||
@@ -230,40 +213,32 @@ path -> item_id
|
||||
"size": "XL"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### FastAPI `HTTPException` vs Starlette `HTTPException`
|
||||
#### FastAPI 的 `HTTPException` vs Starlette 的 `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception }
|
||||
|
||||
**FastAPI** 也提供了自有的 `HTTPException`。
|
||||
|
||||
**FastAPI** 的 `HTTPException` 继承自 Starlette 的 `HTTPException` 错误类。
|
||||
**FastAPI** 的 `HTTPException` 错误类继承自 Starlette 的 `HTTPException` 错误类。
|
||||
|
||||
它们之间的唯一区别是,**FastAPI** 的 `HTTPException` 可以在响应中添加响应头。
|
||||
它们之间的唯一区别是,**FastAPI** 的 `HTTPException` 在 `detail` 字段中接受任意可转换为 JSON 的数据,而 Starlette 的 `HTTPException` 只接受字符串。
|
||||
|
||||
OAuth 2.0 等安全工具需要在内部调用这些响应头。
|
||||
|
||||
因此你可以继续像平常一样在代码中触发 **FastAPI** 的 `HTTPException` 。
|
||||
因此,你可以继续像平常一样在代码中触发 **FastAPI** 的 `HTTPException`。
|
||||
|
||||
但注册异常处理器时,应该注册到来自 Starlette 的 `HTTPException`。
|
||||
|
||||
这样做是为了,当 Starlette 的内部代码、扩展或插件触发 Starlette `HTTPException` 时,处理程序能够捕获、并处理此异常。
|
||||
这样做是为了,当 Starlette 的内部代码、扩展或插件触发 Starlette `HTTPException` 时,你的处理器能够捕获并处理它。
|
||||
|
||||
注意,本例代码中同时使用了这两个 `HTTPException`,此时,要把 Starlette 的 `HTTPException` 命名为 `StarletteHTTPException`:
|
||||
本例中,为了在同一份代码中同时使用两个 `HTTPException`,将 Starlette 的异常重命名为 `StarletteHTTPException`:
|
||||
|
||||
```Python
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
```
|
||||
|
||||
### 复用 **FastAPI** 异常处理器
|
||||
### 复用 **FastAPI** 的异常处理器 { #reuse-fastapis-exception-handlers }
|
||||
|
||||
FastAPI 支持先对异常进行某些处理,然后再使用 **FastAPI** 中处理该异常的默认异常处理器。
|
||||
如果你想在自定义处理后仍复用 **FastAPI** 的默认异常处理器,可以从 `fastapi.exception_handlers` 导入并复用这些默认处理器:
|
||||
|
||||
从 `fastapi.exception_handlers` 中导入要复用的默认异常处理器:
|
||||
{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *}
|
||||
|
||||
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
|
||||
|
||||
虽然,本例只是输出了夸大其词的错误信息。
|
||||
|
||||
但也足以说明,可以在处理异常之后再复用默认的异常处理器。
|
||||
虽然本例只是用非常夸张的信息打印了错误,但足以说明:你可以先处理异常,然后再复用默认的异常处理器。
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Header 参数模型
|
||||
# Header 参数模型 { #header-parameter-models }
|
||||
|
||||
如果您有一组相关的 **header 参数**,您可以创建一个 **Pydantic 模型**来声明它们。
|
||||
|
||||
这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证和元数据。😎
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
自 FastAPI 版本 `0.115.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
## 使用 Pydantic 模型的 Header 参数
|
||||
## 使用 Pydantic 模型的 Header 参数 { #header-parameters-with-a-pydantic-model }
|
||||
|
||||
在 **Pydantic 模型**中声明所需的 **header 参数**,然后将参数声明为 `Header` :
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
**FastAPI** 将从请求中接收到的 **headers** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
|
||||
|
||||
## 查看文档
|
||||
## 查看文档 { #check-the-docs }
|
||||
|
||||
您可以在文档 UI 的 `/docs` 中查看所需的 headers:
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<img src="/img/tutorial/header-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 禁止额外的 Headers
|
||||
## 禁止额外的 Headers { #forbid-extra-headers }
|
||||
|
||||
在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 headers。
|
||||
|
||||
@@ -51,6 +51,22 @@
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
## 禁用下划线转换 { #disable-convert-underscores }
|
||||
|
||||
与常规的 header 参数相同,当参数名中包含下划线时,会**自动转换为连字符**。
|
||||
|
||||
例如,如果你的代码中有一个名为 `save_data` 的 header 参数,那么预期的 HTTP 头将是 `save-data`,并且在文档中也会以这种形式显示。
|
||||
|
||||
如果由于某些原因你需要禁用这种自动转换,你也可以在用于 header 参数的 Pydantic 模型中进行设置。
|
||||
|
||||
{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *}
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
在将 `convert_underscores` 设为 `False` 之前,请注意某些 HTTP 代理和服务器不允许使用带下划线的 headers。
|
||||
|
||||
///
|
||||
|
||||
## 总结 { #summary }
|
||||
|
||||
您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **headers**。😎
|
||||
|
||||
@@ -1,66 +1,95 @@
|
||||
# 中间件
|
||||
# 中间件 { #middleware }
|
||||
|
||||
你可以向 **FastAPI** 应用添加中间件.
|
||||
你可以向 **FastAPI** 应用添加中间件。
|
||||
|
||||
"中间件"是一个函数,它在每个**请求**被特定的*路径操作*处理之前,以及在每个**响应**返回之前工作.
|
||||
“中间件”是一个函数,它会在每个特定的*路径操作*处理每个**请求**之前运行,也会在返回每个**响应**之前运行。
|
||||
|
||||
* 它接收你的应用程序的每一个**请求**.
|
||||
* 然后它可以对这个**请求**做一些事情或者执行任何需要的代码.
|
||||
* 然后它将**请求**传递给应用程序的其他部分 (通过某种*路径操作*).
|
||||
* 然后它获取应用程序生产的**响应** (通过某种*路径操作*).
|
||||
* 它可以对该**响应**做些什么或者执行任何需要的代码.
|
||||
* 然后它返回这个 **响应**.
|
||||
* 它接收你的应用的每一个**请求**。
|
||||
* 然后它可以对这个**请求**做一些事情或者执行任何需要的代码。
|
||||
* 然后它将这个**请求**传递给应用程序的其他部分(某个*路径操作*)处理。
|
||||
* 之后它获取应用程序生成的**响应**(由某个*路径操作*产生)。
|
||||
* 它可以对该**响应**做一些事情或者执行任何需要的代码。
|
||||
* 然后它返回这个**响应**。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
如果你使用了 `yield` 关键字依赖, 依赖中的退出代码将在执行中间件*后*执行.
|
||||
如果你有使用 `yield` 的依赖,依赖中的退出代码会在中间件之后运行。
|
||||
|
||||
如果有任何后台任务(稍后记录), 它们将在执行中间件*后*运行.
|
||||
如果有任何后台任务(会在[后台任务](background-tasks.md){.internal-link target=_blank}一节中介绍,你稍后会看到),它们会在所有中间件之后运行。
|
||||
|
||||
///
|
||||
|
||||
## 创建中间件
|
||||
## 创建中间件 { #create-a-middleware }
|
||||
|
||||
要创建中间件你可以在函数的顶部使用装饰器 `@app.middleware("http")`.
|
||||
要创建中间件,你可以在函数的顶部使用装饰器 `@app.middleware("http")`。
|
||||
|
||||
中间件参数接收如下参数:
|
||||
中间件函数会接收:
|
||||
|
||||
* `request`.
|
||||
* 一个函数 `call_next` 它将接收 `request` 作为参数.
|
||||
* 这个函数将 `request` 传递给相应的 *路径操作*.
|
||||
* 然后它将返回由相应的*路径操作*生成的 `response`.
|
||||
* 然后你可以在返回 `response` 前进一步修改它.
|
||||
* `request`。
|
||||
* 一个函数 `call_next`,它会把 `request` 作为参数接收。
|
||||
* 这个函数会把 `request` 传递给相应的*路径操作*。
|
||||
* 然后它返回由相应*路径操作*生成的 `response`。
|
||||
* 在返回之前,你可以进一步修改 `response`。
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
|
||||
{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *}
|
||||
|
||||
/// tip
|
||||
|
||||
请记住可以 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">用'X-' 前缀</a>添加专有自定义请求头.
|
||||
请记住可以<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">使用 `X-` 前缀</a>添加专有自定义请求头。
|
||||
|
||||
但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>文档中.
|
||||
但是如果你有希望让浏览器中的客户端可见的自定义请求头,你需要把它们加到你的 CORS 配置([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})的 `expose_headers` 参数中,参见 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette 的 CORS 文档</a>。
|
||||
|
||||
///
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
你也可以使用 `from starlette.requests import Request`.
|
||||
你也可以使用 `from starlette.requests import Request`。
|
||||
|
||||
**FastAPI** 为了开发者方便提供了该对象. 但其实它直接来自于 Starlette.
|
||||
**FastAPI** 为了开发者方便提供了该对象,但它直接来自 Starlette。
|
||||
|
||||
///
|
||||
|
||||
### 在 `response` 的前和后
|
||||
### 在 `response` 之前与之后 { #before-and-after-the-response }
|
||||
|
||||
在任何*路径操作*收到`request`前,可以添加要和请求一起运行的代码.
|
||||
你可以在任何*路径操作*接收 `request` 之前,添加要与该 `request` 一起运行的代码。
|
||||
|
||||
也可以在*响应*生成但是返回之前添加代码.
|
||||
也可以在生成 `response` 之后、返回之前添加代码。
|
||||
|
||||
例如你可以添加自定义请求头 `X-Process-Time` 包含以秒为单位的接收请求和生成响应的时间:
|
||||
例如,你可以添加一个自定义请求头 `X-Process-Time`,其值为处理请求并生成响应所花费的秒数:
|
||||
|
||||
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
|
||||
{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *}
|
||||
|
||||
## 其他中间件
|
||||
/// tip
|
||||
|
||||
你可以稍后在 [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}阅读更多关于中间件的教程.
|
||||
这里我们使用 <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> 而不是 `time.time()`,因为在这类场景中它可能更精确。🤓
|
||||
|
||||
你将在下一节中学习如何使用中间件处理 <abbr title="Cross-Origin Resource Sharing">CORS</abbr> .
|
||||
///
|
||||
|
||||
## 多个中间件的执行顺序 { #multiple-middleware-execution-order }
|
||||
|
||||
当你使用 `@app.middleware()` 装饰器或 `app.add_middleware()` 方法添加多个中间件时,每个新中间件都会包裹应用,形成一个栈。最后添加的中间件是“最外层”的,最先添加的是“最内层”的。
|
||||
|
||||
在请求路径上,最外层的中间件先运行。
|
||||
|
||||
在响应路径上,它最后运行。
|
||||
|
||||
例如:
|
||||
|
||||
```Python
|
||||
app.add_middleware(MiddlewareA)
|
||||
app.add_middleware(MiddlewareB)
|
||||
```
|
||||
|
||||
这会产生如下执行顺序:
|
||||
|
||||
* 请求:MiddlewareB → MiddlewareA → 路由
|
||||
|
||||
* 响应:路由 → MiddlewareA → MiddlewareB
|
||||
|
||||
这种栈式行为确保中间件按可预测且可控的顺序执行。
|
||||
|
||||
## 其他中间件 { #other-middlewares }
|
||||
|
||||
你可以稍后在[高级用户指南:高级中间件](../advanced/middleware.md){.internal-link target=_blank}中阅读更多关于其他中间件的内容。
|
||||
|
||||
你将在下一节中了解如何使用中间件处理 <abbr title="Cross-Origin Resource Sharing - 跨域资源共享">CORS</abbr>。
|
||||
|
||||
@@ -1,66 +1,76 @@
|
||||
# 路径操作配置
|
||||
# 路径操作配置 { #path-operation-configuration }
|
||||
|
||||
*路径操作装饰器*支持多种配置参数。
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。
|
||||
注意:以下参数应直接传递给*路径操作装饰器*,不能传递给*路径操作函数*。
|
||||
|
||||
///
|
||||
|
||||
## `status_code` 状态码
|
||||
## 响应状态码 { #response-status-code }
|
||||
|
||||
`status_code` 用于定义*路径操作*响应中的 HTTP 状态码。
|
||||
可以在*路径操作*的响应中定义(HTTP)`status_code`。
|
||||
|
||||
可以直接传递 `int` 代码, 比如 `404`。
|
||||
可以直接传递 `int` 代码,比如 `404`。
|
||||
|
||||
如果记不住数字码的涵义,也可以用 `status` 的快捷常量:
|
||||
如果记不住数字码的含义,也可以用 `status` 的快捷常量:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *}
|
||||
|
||||
状态码在响应中使用,并会被添加到 OpenAPI 概图。
|
||||
该状态码会用于响应中,并会被添加到 OpenAPI 概图。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
也可以使用 `from starlette import status` 导入状态码。
|
||||
|
||||
**FastAPI** 的`fastapi.status` 和 `starlette.status` 一样,只是快捷方式。实际上,`fastapi.status` 直接继承自 Starlette。
|
||||
**FastAPI** 提供的 `fastapi.status` 与 `starlette.status` 相同,方便你作为开发者使用。实际上它直接来自 Starlette。
|
||||
|
||||
///
|
||||
|
||||
## `tags` 参数
|
||||
## 标签 { #tags }
|
||||
|
||||
`tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签:
|
||||
可以通过传入由 `str` 组成的 `list`(通常只有一个 `str`)的参数 `tags`,为*路径操作*添加标签:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *}
|
||||
|
||||
OpenAPI 概图会自动添加标签,供 API 文档接口使用:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image01.png">
|
||||
|
||||
## `summary` 和 `description` 参数
|
||||
### 使用 Enum 的标签 { #tags-with-enums }
|
||||
|
||||
路径装饰器还支持 `summary` 和 `description` 这两个参数:
|
||||
如果你的应用很大,可能会积累出很多标签,你会希望确保相关的*路径操作*始终使用相同的标签。
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *}
|
||||
这种情况下,把标签存放在 `Enum` 中会更合适。
|
||||
|
||||
## 文档字符串(`docstring`)
|
||||
**FastAPI** 对此的支持与使用普通字符串相同:
|
||||
|
||||
描述内容比较长且占用多行时,可以在函数的 <abbr title="函数中作为第一个表达式,用于文档目的的一个多行字符串(并没有被分配个任何变量)">docstring</abbr> 中声明*路径操作*的描述,**FastAPI** 支持从文档字符串中读取描述内容。
|
||||
{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *}
|
||||
|
||||
## 摘要和描述 { #summary-and-description }
|
||||
|
||||
可以添加 `summary` 和 `description`:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *}
|
||||
|
||||
## 从 docstring 获取描述 { #description-from-docstring }
|
||||
|
||||
描述内容比较长且占用多行时,可以在函数的 <abbr title="作为函数内部的第一个表达式、用于文档用途的多行字符串(不赋给任何变量)">docstring</abbr> 中声明*路径操作*的描述,**FastAPI** 会从中读取。
|
||||
|
||||
文档字符串支持 <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a>,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *}
|
||||
|
||||
下图为 Markdown 文本在 API 文档中的显示效果:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image02.png">
|
||||
|
||||
## 响应描述
|
||||
## 响应描述 { #response-description }
|
||||
|
||||
`response_description` 参数用于定义响应的描述说明:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *}
|
||||
|
||||
/// info | 说明
|
||||
|
||||
@@ -78,11 +88,11 @@ OpenAPI 规定每个*路径操作*都要有响应描述。
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image03.png">
|
||||
|
||||
## 弃用*路径操作*
|
||||
## 弃用*路径操作* { #deprecate-a-path-operation }
|
||||
|
||||
`deprecated` 参数可以把*路径操作*标记为<abbr title="过时,建议不要使用">弃用</abbr>,无需直接删除:
|
||||
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
|
||||
{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}
|
||||
|
||||
API 文档会把该路径操作标记为弃用:
|
||||
|
||||
@@ -92,6 +102,6 @@ API 文档会把该路径操作标记为弃用:
|
||||
|
||||
<img src="/img/tutorial/path-operation-configuration/image05.png">
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
通过传递参数给*路径操作装饰器* ,即可轻松地配置*路径操作*、添加元数据。
|
||||
通过传递参数给*路径操作装饰器*,即可轻松地配置*路径操作*、添加元数据。
|
||||
|
||||
@@ -1,91 +1,128 @@
|
||||
# 路径参数和数值校验
|
||||
# 路径参数和数值校验 { #path-parameters-and-numeric-validations }
|
||||
|
||||
与使用 `Query` 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 `Path` 为路径参数声明相同类型的校验和元数据。
|
||||
|
||||
## 导入 Path
|
||||
## 导入 `Path` { #import-path }
|
||||
|
||||
首先,从 `fastapi` 导入 `Path`:
|
||||
首先,从 `fastapi` 导入 `Path`,并导入 `Annotated`:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *}
|
||||
|
||||
## 声明元数据
|
||||
/// info | 信息
|
||||
|
||||
你可以声明与 `Query` 相同的所有参数。
|
||||
FastAPI 在 0.95.0 版本添加了对 `Annotated` 的支持(并开始推荐使用它)。
|
||||
|
||||
例如,要声明路径参数 `item_id`的 `title` 元数据值,你可以输入:
|
||||
如果你使用的是更旧的版本,尝试使用 `Annotated` 会报错。
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
|
||||
|
||||
/// note
|
||||
|
||||
路径参数总是必需的,因为它必须是路径的一部分。
|
||||
|
||||
所以,你应该在声明时使用 `...` 将其标记为必需参数。
|
||||
|
||||
然而,即使你使用 `None` 声明路径参数或设置一个其他默认值也不会有任何影响,它依然会是必需参数。
|
||||
请确保在使用 `Annotated` 之前,将 FastAPI 版本[升级](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。
|
||||
|
||||
///
|
||||
|
||||
## 按需对参数排序
|
||||
## 声明元数据 { #declare-metadata }
|
||||
|
||||
假设你想要声明一个必需的 `str` 类型查询参数 `q`。
|
||||
你可以声明与 `Query` 相同的所有参数。
|
||||
|
||||
而且你不需要为该参数声明任何其他内容,所以实际上你并不需要使用 `Query`。
|
||||
例如,要为路径参数 `item_id` 声明 `title` 元数据值,你可以这样写:
|
||||
|
||||
但是你仍然需要使用 `Path` 来声明路径参数 `item_id`。
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
|
||||
|
||||
如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错。
|
||||
/// note | 注意
|
||||
|
||||
但是你可以对其重新排序,并将不带默认值的值(查询参数 `q`)放到最前面。
|
||||
路径参数总是必需的,因为它必须是路径的一部分。即使你将其声明为 `None` 或设置了默认值,也不会产生任何影响,它依然始终是必需参数。
|
||||
|
||||
对 **FastAPI** 来说这无关紧要。它将通过参数的名称、类型和默认值声明(`Query`、`Path` 等)来检测参数,而不在乎参数的顺序。
|
||||
///
|
||||
|
||||
## 按需对参数排序 { #order-the-parameters-as-you-need }
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
如果你使用 `Annotated`,这点可能不那么重要或必要。
|
||||
|
||||
///
|
||||
|
||||
假设你想要将查询参数 `q` 声明为必需的 `str`。
|
||||
|
||||
并且你不需要为该参数声明其他内容,所以实际上不需要用到 `Query`。
|
||||
|
||||
但是你仍然需要为路径参数 `item_id` 使用 `Path`。并且出于某些原因你不想使用 `Annotated`。
|
||||
|
||||
如果你将带有“默认值”的参数放在没有“默认值”的参数之前,Python 会报错。
|
||||
|
||||
不过你可以重新排序,让没有默认值的参数(查询参数 `q`)放在最前面。
|
||||
|
||||
对 **FastAPI** 来说这无关紧要。它会通过参数的名称、类型和默认值声明(`Query`、`Path` 等)来检测参数,而不关心顺序。
|
||||
|
||||
因此,你可以将函数声明为:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *}
|
||||
|
||||
## 按需对参数排序的技巧
|
||||
但请记住,如果你使用 `Annotated`,你就不会遇到这个问题,因为你没有使用 `Query()` 或 `Path()` 作为函数参数的默认值。
|
||||
|
||||
如果你想不使用 `Query` 声明没有默认值的查询参数 `q`,同时使用 `Path` 声明路径参数 `item_id`,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *}
|
||||
|
||||
传递 `*` 作为函数的第一个参数。
|
||||
## 按需对参数排序的技巧 { #order-the-parameters-as-you-need-tricks }
|
||||
|
||||
Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 <abbr title="来自:K-ey W-ord Arg-uments"><code>kwargs</code></abbr>,来调用。即使它们没有默认值。
|
||||
/// tip | 提示
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
|
||||
如果你使用 `Annotated`,这点可能不那么重要或必要。
|
||||
|
||||
## 数值校验:大于等于
|
||||
///
|
||||
|
||||
使用 `Query` 和 `Path`(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。
|
||||
这里有一个小技巧,可能会很方便,但你并不会经常需要它。
|
||||
|
||||
像下面这样,添加 `ge=1` 后,`item_id` 将必须是一个大于(`g`reater than)或等于(`e`qual)`1` 的整数。
|
||||
如果你想要:
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
|
||||
* 在没有 `Query` 且没有任何默认值的情况下声明查询参数 `q`
|
||||
* 使用 `Path` 声明路径参数 `item_id`
|
||||
* 让它们的顺序与上面不同
|
||||
* 不使用 `Annotated`
|
||||
|
||||
## 数值校验:大于和小于等于
|
||||
...Python 为此有一个小的特殊语法。
|
||||
|
||||
同样的规则适用于:
|
||||
在函数的第一个参数位置传入 `*`。
|
||||
|
||||
Python 不会对这个 `*` 做任何事,但它会知道之后的所有参数都应该作为关键字参数(键值对)来调用,也被称为 <abbr title="来自:K-ey W-ord Arg-uments"><code>kwargs</code></abbr>。即使它们没有默认值。
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *}
|
||||
|
||||
### 使用 `Annotated` 更好 { #better-with-annotated }
|
||||
|
||||
请记住,如果你使用 `Annotated`,因为你没有使用函数参数的默认值,所以你不会有这个问题,你大概率也不需要使用 `*`。
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *}
|
||||
|
||||
## 数值校验:大于等于 { #number-validations-greater-than-or-equal }
|
||||
|
||||
使用 `Query` 和 `Path`(以及你稍后会看到的其他类)你可以声明数值约束。
|
||||
|
||||
在这里,使用 `ge=1` 后,`item_id` 必须是一个整数,值要「`g`reater than or `e`qual」1。
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *}
|
||||
|
||||
## 数值校验:大于和小于等于 { #number-validations-greater-than-and-less-than-or-equal }
|
||||
|
||||
同样适用于:
|
||||
|
||||
* `gt`:大于(`g`reater `t`han)
|
||||
* `le`:小于等于(`l`ess than or `e`qual)
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *}
|
||||
|
||||
## 数值校验:浮点数、大于和小于
|
||||
## 数值校验:浮点数、大于和小于 { #number-validations-floats-greater-than-and-less-than }
|
||||
|
||||
数值校验同样适用于 `float` 值。
|
||||
|
||||
能够声明 <abbr title="大于"><code>gt</code></abbr> 而不仅仅是 <abbr title="大于等于"><code>ge</code></abbr> 在这个前提下变得重要起来。例如,你可以要求一个值必须大于 `0`,即使它小于 `1`。
|
||||
能够声明 <abbr title="greater than - 大于"><code>gt</code></abbr> 而不仅仅是 <abbr title="greater than or equal - 大于等于"><code>ge</code></abbr> 在这里变得很重要。例如,你可以要求一个值必须大于 `0`,即使它小于 `1`。
|
||||
|
||||
因此,`0.5` 将是有效值。但是 `0.0`或 `0` 不是。
|
||||
因此,`0.5` 将是有效值。但是 `0.0` 或 `0` 不是。
|
||||
|
||||
对于 <abbr title="less than"><code>lt</code></abbr> 也是一样的。
|
||||
对于 <abbr title="less than - 小于"><code>lt</code></abbr> 也是一样的。
|
||||
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *}
|
||||
{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *}
|
||||
|
||||
## 总结
|
||||
## 总结 { #recap }
|
||||
|
||||
你能够以与 [查询参数和字符串校验](query-params-str-validations.md){.internal-link target=_blank} 相同的方式使用 `Query`、`Path`(以及其他你还没见过的类)声明元数据和字符串校验。
|
||||
你能够以与[查询参数和字符串校验](query-params-str-validations.md){.internal-link target=_blank}相同的方式使用 `Query`、`Path`(以及其他你还没见过的类)声明元数据和字符串校验。
|
||||
|
||||
而且你还可以声明数值校验:
|
||||
|
||||
@@ -94,24 +131,24 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参
|
||||
* `lt`:小于(`l`ess `t`han)
|
||||
* `le`:小于等于(`l`ess than or `e`qual)
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
`Query`、`Path` 以及你后面会看到的其他类继承自一个共同的 `Param` 类(不需要直接使用它)。
|
||||
`Query`、`Path` 以及你后面会看到的其他类,都是一个通用 `Param` 类的子类。
|
||||
|
||||
而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。
|
||||
它们都共享相同的参数,用于你已看到的额外校验和元数据。
|
||||
|
||||
///
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
当你从 `fastapi` 导入 `Query`、`Path` 和其他同类对象时,它们实际上是函数。
|
||||
当你从 `fastapi` 导入 `Query`、`Path` 和其他对象时,它们实际上是函数。
|
||||
|
||||
当被调用时,它们返回同名类的实例。
|
||||
当被调用时,它们会返回同名类的实例。
|
||||
|
||||
如此,你导入 `Query` 这个函数。当你调用它时,它将返回一个同样命名为 `Query` 的类的实例。
|
||||
也就是说,你导入的是函数 `Query`。当你调用它时,它会返回一个同名的 `Query` 类的实例。
|
||||
|
||||
因为使用了这些函数(而不是直接使用类),所以你的编辑器不会标记有关其类型的错误。
|
||||
之所以使用这些函数(而不是直接使用类),是为了让你的编辑器不要因为它们的类型而标记错误。
|
||||
|
||||
这样,你可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。
|
||||
这样你就可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 路径参数
|
||||
# 路径参数 { #path-parameters }
|
||||
|
||||
FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**变量**):
|
||||
你可以使用与 Python 字符串格式化相同的语法声明路径“参数”或“变量”:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
|
||||
{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *}
|
||||
|
||||
这段代码把路径参数 `item_id` 的值传递给路径函数的参数 `item_id`。
|
||||
路径参数 `item_id` 的值会作为参数 `item_id` 传递给你的函数。
|
||||
|
||||
运行示例并访问 <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>,可获得如下响应:
|
||||
|
||||
@@ -12,11 +12,11 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**
|
||||
{"item_id":"foo"}
|
||||
```
|
||||
|
||||
## 声明路径参数的类型
|
||||
## 声明路径参数的类型 { #path-parameters-with-types }
|
||||
|
||||
使用 Python 标准类型注解,声明路径操作函数中路径参数的类型。
|
||||
使用 Python 标准类型注解,声明路径操作函数中路径参数的类型:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
|
||||
{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *}
|
||||
|
||||
本例把 `item_id` 的类型声明为 `int`。
|
||||
|
||||
@@ -26,7 +26,7 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**
|
||||
|
||||
///
|
||||
|
||||
## 数据<abbr title="也称为:序列化、解析">转换</abbr>
|
||||
## 数据<abbr title="也称为:序列化、解析、编组">转换</abbr> { #data-conversion }
|
||||
|
||||
运行示例并访问 <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>,返回的响应如下:
|
||||
|
||||
@@ -42,56 +42,57 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**
|
||||
|
||||
///
|
||||
|
||||
## 数据校验
|
||||
## 数据校验 { #data-validation }
|
||||
|
||||
通过浏览器访问 <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>,接收如下 HTTP 错误信息:
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"path",
|
||||
"item_id"
|
||||
],
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer"
|
||||
}
|
||||
]
|
||||
"detail": [
|
||||
{
|
||||
"type": "int_parsing",
|
||||
"loc": [
|
||||
"path",
|
||||
"item_id"
|
||||
],
|
||||
"msg": "Input should be a valid integer, unable to parse string as an integer",
|
||||
"input": "foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
这是因为路径参数 `item_id` 的值 (`"foo"`)的类型不是 `int`。
|
||||
这是因为路径参数 `item_id` 的值(`"foo"`)的类型不是 `int`。
|
||||
|
||||
值的类型不是 `int ` 而是浮点数(`float`)时也会显示同样的错误,比如: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2。</a>
|
||||
值的类型不是 `int` 而是浮点数(`float`)时也会显示同样的错误,比如: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2</a>
|
||||
|
||||
/// check | 检查
|
||||
|
||||
**FastAPI** 使用 Python 类型声明实现了数据校验。
|
||||
**FastAPI** 使用同样的 Python 类型声明实现了数据校验。
|
||||
|
||||
注意,上面的错误清晰地指出了未通过校验的具体原因。
|
||||
注意,上面的错误清晰地指出了未通过校验的具体位置。
|
||||
|
||||
这在开发调试与 API 交互的代码时非常有用。
|
||||
|
||||
///
|
||||
|
||||
## 查看文档
|
||||
## 文档 { #documentation }
|
||||
|
||||
访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>,查看自动生成的 API 文档:
|
||||
访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>,查看自动生成的交互式 API 文档:
|
||||
|
||||
<img src="/img/tutorial/path-params/image01.png">
|
||||
|
||||
/// check | 检查
|
||||
|
||||
还是使用 Python 类型声明,**FastAPI** 提供了(集成 Swagger UI 的)API 文档。
|
||||
还是使用 Python 类型声明,**FastAPI** 提供了(集成 Swagger UI 的)自动交互式文档。
|
||||
|
||||
注意,路径参数的类型是整数。
|
||||
|
||||
///
|
||||
|
||||
## 基于标准的好处,备选文档
|
||||
## 基于标准的好处,备选文档 { #standards-based-benefits-alternative-documentation }
|
||||
|
||||
**FastAPI** 使用 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a> 生成概图,所以能兼容很多工具。
|
||||
**FastAPI** 使用 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md" class="external-link" target="_blank">OpenAPI</a> 生成概图,所以能兼容很多工具。
|
||||
|
||||
因此,**FastAPI** 还内置了 ReDoc 生成的备选 API 文档,可在此查看 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>:
|
||||
|
||||
@@ -99,15 +100,15 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**
|
||||
|
||||
同样,还有很多兼容工具,包括多种语言的代码生成工具。
|
||||
|
||||
## Pydantic
|
||||
## Pydantic { #pydantic }
|
||||
|
||||
FastAPI 充分地利用了 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 的优势,用它在后台校验数据。众所周知,Pydantic 擅长的就是数据校验。
|
||||
|
||||
同样,`str`、`float`、`bool` 以及很多复合数据类型都可以使用类型声明。
|
||||
|
||||
下一章介绍详细内容。
|
||||
接下来的章节会介绍其中的好几种。
|
||||
|
||||
## 顺序很重要
|
||||
## 顺序很重要 { #order-matters }
|
||||
|
||||
有时,*路径操作*中的路径是写死的。
|
||||
|
||||
@@ -117,15 +118,21 @@ FastAPI 充分地利用了 <a href="https://docs.pydantic.dev/" class="external-
|
||||
|
||||
由于*路径操作*是按顺序依次运行的,因此,一定要在 `/users/{user_id}` 之前声明 `/users/me` :
|
||||
|
||||
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
|
||||
{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *}
|
||||
|
||||
否则,`/users/{user_id}` 将匹配 `/users/me`,FastAPI 会**认为**正在接收值为 `"me"` 的 `user_id` 参数。
|
||||
|
||||
## 预设值
|
||||
同样,你不能重复定义一个路径操作:
|
||||
|
||||
路径操作使用 Python 的 <abbr title="Enumeration">`Enum`</abbr> 类型接收预设的*路径参数*。
|
||||
{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *}
|
||||
|
||||
### 创建 `Enum` 类
|
||||
由于路径首先匹配,始终会使用第一个定义的。
|
||||
|
||||
## 预设值 { #predefined-values }
|
||||
|
||||
路径操作使用 Python 的 <abbr title="Enumeration">`Enum`</abbr> 类型接收预设的路径参数。
|
||||
|
||||
### 创建 `Enum` 类 { #create-an-enum-class }
|
||||
|
||||
导入 `Enum` 并创建继承自 `str` 和 `Enum` 的子类。
|
||||
|
||||
@@ -133,47 +140,41 @@ FastAPI 充分地利用了 <a href="https://docs.pydantic.dev/" class="external-
|
||||
|
||||
然后,创建包含固定值的类属性,这些固定值是可用的有效值:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
|
||||
|
||||
/// info | 说明
|
||||
|
||||
Python 3.4 及之后版本支持<a href="https://docs.python.org/zh-cn/3/library/enum.html" class="external-link" target="_blank">枚举(即 enums)</a>。
|
||||
|
||||
///
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
**AlexNet**、**ResNet**、**LeNet** 是机器学习<abbr title="技术上来说是深度学习模型架构">模型</abbr>。
|
||||
**AlexNet**、**ResNet**、**LeNet** 是机器学习<abbr title="技术上来说是深度学习模型架构">模型</abbr>的名字。
|
||||
|
||||
///
|
||||
|
||||
### 声明*路径参数*
|
||||
### 声明路径参数 { #declare-a-path-parameter }
|
||||
|
||||
使用 Enum 类(`ModelName`)创建使用类型注解的*路径参数*:
|
||||
使用 Enum 类(`ModelName`)创建使用类型注解的路径参数:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *}
|
||||
|
||||
### 查看文档
|
||||
### 查看文档 { #check-the-docs }
|
||||
|
||||
API 文档会显示预定义*路径参数*的可用值:
|
||||
API 文档会显示预定义路径参数的可用值:
|
||||
|
||||
<img src="/img/tutorial/path-params/image03.png">
|
||||
|
||||
### 使用 Python _枚举类型_
|
||||
### 使用 Python 枚举 { #working-with-python-enumerations }
|
||||
|
||||
*路径参数*的值是枚举的元素。
|
||||
路径参数的值是一个枚举成员。
|
||||
|
||||
#### 比较*枚举元素*
|
||||
#### 比较枚举成员 { #compare-enumeration-members }
|
||||
|
||||
枚举类 `ModelName` 中的*枚举元素*支持比较操作:
|
||||
可以将其与枚举类 `ModelName` 中的枚举成员进行比较:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *}
|
||||
|
||||
#### 获取*枚举值*
|
||||
#### 获取枚举值 { #get-the-enumeration-value }
|
||||
|
||||
使用 `model_name.value` 或 `your_enum_member.value` 获取实际的值(本例中为**字符串**):
|
||||
使用 `model_name.value` 或通用的 `your_enum_member.value` 获取实际的值(本例中为 `str`):
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -181,13 +182,13 @@ Python 3.4 及之后版本支持<a href="https://docs.python.org/zh-cn/3/library
|
||||
|
||||
///
|
||||
|
||||
#### 返回*枚举元素*
|
||||
#### 返回枚举成员 { #return-enumeration-members }
|
||||
|
||||
即使嵌套在 JSON 请求体里(例如, `dict`),也可以从*路径操作*返回*枚举元素*。
|
||||
即使嵌套在 JSON 请求体里(例如,`dict`),也可以从路径操作返回枚举成员。
|
||||
|
||||
返回给客户端之前,要把枚举元素转换为对应的值(本例中为字符串):
|
||||
返回给客户端之前,会把枚举成员转换为对应的值(本例中为字符串):
|
||||
|
||||
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
|
||||
{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *}
|
||||
|
||||
客户端中的 JSON 响应如下:
|
||||
|
||||
@@ -198,52 +199,52 @@ Python 3.4 及之后版本支持<a href="https://docs.python.org/zh-cn/3/library
|
||||
}
|
||||
```
|
||||
|
||||
## 包含路径的路径参数
|
||||
## 包含路径的路径参数 { #path-parameters-containing-paths }
|
||||
|
||||
假设*路径操作*的路径为 `/files/{file_path}`。
|
||||
假设路径操作的路径为 `/files/{file_path}`。
|
||||
|
||||
但需要 `file_path` 中也包含*路径*,比如,`home/johndoe/myfile.txt`。
|
||||
但需要 `file_path` 中也包含路径,比如,`home/johndoe/myfile.txt`。
|
||||
|
||||
此时,该文件的 URL 是这样的:`/files/home/johndoe/myfile.txt`。
|
||||
|
||||
### OpenAPI 支持
|
||||
### OpenAPI 支持 { #openapi-support }
|
||||
|
||||
OpenAPI 不支持声明包含路径的*路径参数*,因为这会导致测试和定义更加困难。
|
||||
OpenAPI 不支持声明包含路径的路径参数,因为这会导致测试和定义更加困难。
|
||||
|
||||
不过,仍可使用 Starlette 内置工具在 **FastAPI** 中实现这一功能。
|
||||
|
||||
而且不影响文档正常运行,但是不会添加该参数包含路径的说明。
|
||||
|
||||
### 路径转换器
|
||||
### 路径转换器 { #path-convertor }
|
||||
|
||||
直接使用 Starlette 的选项声明包含*路径*的*路径参数*:
|
||||
直接使用 Starlette 的选项声明包含路径的路径参数:
|
||||
|
||||
```
|
||||
/files/{file_path:path}
|
||||
```
|
||||
|
||||
本例中,参数名为 `file_path`,结尾部分的 `:path` 说明该参数应匹配*路径*。
|
||||
本例中,参数名为 `file_path`,结尾部分的 `:path` 说明该参数应匹配路径。
|
||||
|
||||
用法如下:
|
||||
|
||||
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
|
||||
{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
注意,包含 `/home/johndoe/myfile.txt` 的路径参数要以斜杠(`/`)开头。
|
||||
|
||||
本例中的 URL 是 `/files//home/johndoe/myfile.txt`。注意,`files` 和 `home` 之间要使用**双斜杠**(`//`)。
|
||||
本例中的 URL 是 `/files//home/johndoe/myfile.txt`。注意,`files` 和 `home` 之间要使用双斜杠(`//`)。
|
||||
|
||||
///
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
通过简短、直观的 Python 标准类型声明,**FastAPI** 可以获得:
|
||||
|
||||
- 编辑器支持:错误检查,代码自动补全等
|
||||
- 数据**<abbr title="把来自 HTTP 请求中的字符串转换为 Python 数据类型">解析</abbr>**
|
||||
- 数据 "<abbr title="将来自 HTTP 请求中的字符串转换为 Python 数据类型">解析</abbr>"
|
||||
- 数据校验
|
||||
- API 注解和 API 文档
|
||||
- API 注解和自动文档
|
||||
|
||||
只需要声明一次即可。
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 查询参数
|
||||
# 查询参数 { #query-parameters }
|
||||
|
||||
声明的参数不是路径参数时,路径操作函数会把该参数自动解释为**查询**参数。
|
||||
|
||||
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
|
||||
{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *}
|
||||
|
||||
查询字符串是键值对的集合,这些键值对位于 URL 的 `?` 之后,以 `&` 分隔。
|
||||
|
||||
@@ -26,9 +26,9 @@ http://127.0.0.1:8000/items/?skip=0&limit=10
|
||||
* (显而易见的)编辑器支持
|
||||
* 数据<abbr title="将来自 HTTP 请求的字符串转换为 Python 数据类型">**解析**</abbr>
|
||||
* 数据校验
|
||||
* API 文档
|
||||
* 自动文档
|
||||
|
||||
## 默认值
|
||||
## 默认值 { #defaults }
|
||||
|
||||
查询参数不是路径的固定内容,它是可选的,还支持默认值。
|
||||
|
||||
@@ -57,7 +57,7 @@ http://127.0.0.1:8000/items/?skip=20
|
||||
* `skip=20`:在 URL 中设定的值
|
||||
* `limit=10`:使用默认值
|
||||
|
||||
## 可选参数
|
||||
## 可选参数 { #optional-parameters }
|
||||
|
||||
同理,把默认值设为 `None` 即可声明**可选的**查询参数:
|
||||
|
||||
@@ -71,15 +71,7 @@ http://127.0.0.1:8000/items/?skip=20
|
||||
|
||||
///
|
||||
|
||||
/// note | 笔记
|
||||
|
||||
因为默认值为 `= None`,FastAPI 把 `q` 识别为可选参数。
|
||||
|
||||
FastAPI 不使用 `Optional[str]` 中的 `Optional`(只使用 `str`),但 `Optional[str]` 可以帮助编辑器发现代码中的错误。
|
||||
|
||||
///
|
||||
|
||||
## 查询参数类型转换
|
||||
## 查询参数类型转换 { #query-parameter-type-conversion }
|
||||
|
||||
参数还可以声明为 `bool` 类型,FastAPI 会自动转换参数类型:
|
||||
|
||||
@@ -116,10 +108,10 @@ 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`。
|
||||
|
||||
|
||||
## 多个路径和查询参数
|
||||
## 多个路径和查询参数 { #multiple-path-and-query-parameters }
|
||||
|
||||
**FastAPI** 可以识别同时声明的多个路径参数和查询参数。
|
||||
|
||||
@@ -129,7 +121,7 @@ FastAPI 通过参数名进行检测:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
|
||||
|
||||
## 必选查询参数
|
||||
## 必选查询参数 { #required-query-parameters }
|
||||
|
||||
为不是路径参数的参数声明默认值(至此,仅有查询参数),该参数就**不是必选**的了。
|
||||
|
||||
@@ -137,7 +129,7 @@ FastAPI 通过参数名进行检测:
|
||||
|
||||
如果要把查询参数设置为**必选**,就不要声明默认值:
|
||||
|
||||
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
|
||||
{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *}
|
||||
|
||||
这里的查询参数 `needy` 是类型为 `str` 的必选查询参数。
|
||||
|
||||
@@ -151,16 +143,17 @@ http://127.0.0.1:8000/items/foo-item
|
||||
|
||||
```JSON
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": [
|
||||
"query",
|
||||
"needy"
|
||||
],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing"
|
||||
}
|
||||
]
|
||||
"detail": [
|
||||
{
|
||||
"type": "missing",
|
||||
"loc": [
|
||||
"query",
|
||||
"needy"
|
||||
],
|
||||
"msg": "Field required",
|
||||
"input": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -191,6 +184,6 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
还可以像在[路径参数](path-params.md#_8){.internal-link target=_blank} 中那样使用 `Enum`。
|
||||
还可以像在[路径参数](path-params.md#predefined-values){.internal-link target=_blank} 中那样使用 `Enum`。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,159 +1,163 @@
|
||||
# 请求文件
|
||||
# 请求文件 { #request-files }
|
||||
|
||||
`File` 用于定义客户端的上传文件。
|
||||
你可以使用 `File` 定义由客户端上传的文件。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
因为上传文件以「表单数据」形式发送。
|
||||
要接收上传的文件,请先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
|
||||
所以接收上传文件,要预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
请确保你创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}、激活它,然后安装,例如:
|
||||
|
||||
例如: `pip install python-multipart`。
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
这是因为上传文件是以「表单数据」发送的。
|
||||
|
||||
///
|
||||
|
||||
## 导入 `File`
|
||||
## 导入 `File` { #import-file }
|
||||
|
||||
从 `fastapi` 导入 `File` 和 `UploadFile`:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001.py hl[1] *}
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## 定义 `File` 参数
|
||||
## 定义 `File` 参数 { #define-file-parameters }
|
||||
|
||||
创建文件(`File`)参数的方式与 `Body` 和 `Form` 一样:
|
||||
像为 `Body` 或 `Form` 一样创建文件参数:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001.py hl[7] *}
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *}
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
`File` 是直接继承自 `Form` 的类。
|
||||
|
||||
注意,从 `fastapi` 导入的 `Query`、`Path`、`File` 等项,实际上是返回特定类的函数。
|
||||
但要注意,从 `fastapi` 导入的 `Query`、`Path`、`File` 等项,实际上是返回特定类的函数。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
声明文件体必须使用 `File`,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。
|
||||
声明文件体必须使用 `File`,否则,这些参数会被当作查询参数或请求体(JSON)参数。
|
||||
|
||||
///
|
||||
|
||||
文件作为「表单数据」上传。
|
||||
文件将作为「表单数据」上传。
|
||||
|
||||
如果把*路径操作函数*参数的类型声明为 `bytes`,**FastAPI** 将以 `bytes` 形式读取和接收文件内容。
|
||||
如果把*路径操作函数*参数的类型声明为 `bytes`,**FastAPI** 会为你读取文件,并以 `bytes` 的形式接收其内容。
|
||||
|
||||
这种方式把文件的所有内容都存储在内存里,适用于小型文件。
|
||||
请注意,这意味着整个内容会存储在内存中,适用于小型文件。
|
||||
|
||||
不过,很多情况下,`UploadFile` 更好用。
|
||||
不过,在很多情况下,使用 `UploadFile` 会更有优势。
|
||||
|
||||
## 含 `UploadFile` 的文件参数
|
||||
## 含 `UploadFile` 的文件参数 { #file-parameters-with-uploadfile }
|
||||
|
||||
定义文件参数时使用 `UploadFile`:
|
||||
将文件参数的类型声明为 `UploadFile`:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001.py hl[12] *}
|
||||
{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *}
|
||||
|
||||
`UploadFile` 与 `bytes` 相比有更多优势:
|
||||
与 `bytes` 相比,使用 `UploadFile` 有多项优势:
|
||||
|
||||
* 使用 `spooled` 文件:
|
||||
* 存储在内存的文件超出最大上限时,FastAPI 会把文件存入磁盘;
|
||||
* 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存;
|
||||
* 可获取上传文件的元数据;
|
||||
* 自带 <a href="https://docs.python.org/zh-cn/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` 接口;
|
||||
* 暴露的 Python <a href="https://docs.python.org/zh-cn/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> 对象,可直接传递给其他预期「file-like」对象的库。
|
||||
* 无需在参数的默认值中使用 `File()`。
|
||||
* 它使用“spooled”文件:
|
||||
* 文件会先存储在内存中,直到达到最大上限,超过该上限后会写入磁盘。
|
||||
* 因此,非常适合处理图像、视频、大型二进制等大文件,而不会占用所有内存。
|
||||
* 你可以获取上传文件的元数据。
|
||||
* 它提供 <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> 的 `async` 接口。
|
||||
* 它暴露了一个实际的 Python <a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> 对象,你可以直接传给期望「file-like」对象的其他库。
|
||||
|
||||
### `UploadFile`
|
||||
### `UploadFile` { #uploadfile }
|
||||
|
||||
`UploadFile` 的属性如下:
|
||||
|
||||
* `filename`:上传文件名字符串(`str`),例如, `myimage.jpg`;
|
||||
* `content_type`:内容类型(MIME 类型 / 媒体类型)字符串(`str`),例如,`image/jpeg`;
|
||||
* `file`: <a href="https://docs.python.org/zh-cn/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>( <a href="https://docs.python.org/zh-cn/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> 对象)。其实就是 Python文件,可直接传递给其他预期 `file-like` 对象的函数或支持库。
|
||||
* `filename`:上传的原始文件名字符串(`str`),例如 `myimage.jpg`。
|
||||
* `content_type`:内容类型(MIME 类型 / 媒体类型)的字符串(`str`),例如 `image/jpeg`。
|
||||
* `file`:<a href="https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a>(一个 <a href="https://docs.python.org/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> 对象)。这是实际的 Python 文件对象,你可以直接传递给其他期望「file-like」对象的函数或库。
|
||||
|
||||
`UploadFile` 支持以下 `async` 方法,(使用内部 `SpooledTemporaryFile`)可调用相应的文件方法。
|
||||
`UploadFile` 具有以下 `async` 方法。它们都会在底层调用对应的文件方法(使用内部的 `SpooledTemporaryFile`)。
|
||||
|
||||
* `write(data)`:把 `data` (`str` 或 `bytes`)写入文件;
|
||||
* `read(size)`:按指定数量的字节或字符(`size` (`int`))读取文件内容;
|
||||
* `seek(offset)`:移动至文件 `offset` (`int`)字节处的位置;
|
||||
* 例如,`await myfile.seek(0) ` 移动到文件开头;
|
||||
* 执行 `await myfile.read()` 后,需再次读取已读取内容时,这种方法特别好用;
|
||||
* `write(data)`:将 `data`(`str` 或 `bytes`)写入文件。
|
||||
* `read(size)`:读取文件中 `size`(`int`)个字节/字符。
|
||||
* `seek(offset)`:移动到文件中字节位置 `offset`(`int`)。
|
||||
* 例如,`await myfile.seek(0)` 会移动到文件开头。
|
||||
* 如果你先运行过 `await myfile.read()`,然后需要再次读取内容时,这尤其有用。
|
||||
* `close()`:关闭文件。
|
||||
|
||||
因为上述方法都是 `async` 方法,要搭配「await」使用。
|
||||
由于这些方法都是 `async` 方法,你需要对它们使用 await。
|
||||
|
||||
例如,在 `async` *路径操作函数* 内,要用以下方式读取文件内容:
|
||||
例如,在 `async` *路径操作函数* 内,你可以这样获取内容:
|
||||
|
||||
```Python
|
||||
contents = await myfile.read()
|
||||
```
|
||||
|
||||
在普通 `def` *路径操作函数* 内,则可以直接访问 `UploadFile.file`,例如:
|
||||
如果是在普通 `def` *路径操作函数* 内,你可以直接访问 `UploadFile.file`,例如:
|
||||
|
||||
```Python
|
||||
contents = myfile.file.read()
|
||||
```
|
||||
|
||||
/// note | `async` 技术细节
|
||||
/// note | 注意
|
||||
|
||||
使用 `async` 方法时,**FastAPI** 在线程池中执行文件方法,并 `await` 操作完成。
|
||||
当你使用这些 `async` 方法时,**FastAPI** 会在线程池中运行相应的文件方法并等待其完成。
|
||||
|
||||
///
|
||||
|
||||
/// note | Starlette 技术细节
|
||||
/// note | 注意
|
||||
|
||||
**FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要功能,使之与 **Pydantic** 及 FastAPI 的其它部件兼容。
|
||||
**FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要的部分,使其与 **Pydantic** 以及 FastAPI 的其他部分兼容。
|
||||
|
||||
///
|
||||
|
||||
## 什么是 「表单数据」
|
||||
## 什么是「表单数据」 { #what-is-form-data }
|
||||
|
||||
与 JSON 不同,HTML 表单(`<form></form>`)向服务器发送数据通常使用「特殊」的编码。
|
||||
HTML 表单(`<form></form>`)向服务器发送数据的方式通常会对数据使用一种「特殊」的编码,这与 JSON 不同。
|
||||
|
||||
**FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。
|
||||
**FastAPI** 会确保从正确的位置读取这些数据,而不是从 JSON 中读取。
|
||||
|
||||
/// note | 技术细节
|
||||
/// note | 注意
|
||||
|
||||
不包含文件时,表单数据一般用 `application/x-www-form-urlencoded`「媒体类型」编码。
|
||||
当不包含文件时,来自表单的数据通常使用「媒体类型」`application/x-www-form-urlencoded` 编码。
|
||||
|
||||
但表单包含文件时,编码为 `multipart/form-data`。使用了 `File`,**FastAPI** 就知道要从请求体的正确位置获取文件。
|
||||
但当表单包含文件时,会编码为 `multipart/form-data`。如果你使用 `File`,**FastAPI** 会知道需要从请求体的正确位置获取文件。
|
||||
|
||||
编码和表单字段详见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> Web 文档的 <code>POST </code></a> 小节。
|
||||
如果你想进一步了解这些编码和表单字段,请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - Mozilla 开发者网络">MDN</abbr> web docs for <code>POST</code></a>。
|
||||
|
||||
///
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
可在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `multipart/form-data`,不是 `application/json`。
|
||||
你可以在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明希望以 JSON 接收的 `Body` 字段,因为此时请求体会使用 `multipart/form-data` 编码,而不是 `application/json`。
|
||||
|
||||
这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。
|
||||
这不是 **FastAPI** 的限制,而是 HTTP 协议的一部分。
|
||||
|
||||
///
|
||||
|
||||
## 可选文件上传
|
||||
## 可选文件上传 { #optional-file-upload }
|
||||
|
||||
您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_02_py310.py hl[7,14] *}
|
||||
{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *}
|
||||
|
||||
## 带有额外元数据的 `UploadFile`
|
||||
## 带有额外元数据的 `UploadFile` { #uploadfile-with-additional-metadata }
|
||||
|
||||
您也可以将 `File()` 与 `UploadFile` 一起使用,例如,设置额外的元数据:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial001_03.py hl[13] *}
|
||||
{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *}
|
||||
|
||||
## 多文件上传
|
||||
## 多文件上传 { #multiple-file-uploads }
|
||||
|
||||
FastAPI 支持同时上传多个文件。
|
||||
|
||||
可用同一个「表单字段」发送含多个文件的「表单数据」。
|
||||
它们会被关联到同一个通过「表单数据」发送的「表单字段」。
|
||||
|
||||
上传多个文件时,要声明含 `bytes` 或 `UploadFile` 的列表(`List`):
|
||||
要实现这一点,声明一个由 `bytes` 或 `UploadFile` 组成的列表(`List`):
|
||||
|
||||
{* ../../docs_src/request_files/tutorial002_py39.py hl[8,13] *}
|
||||
{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *}
|
||||
|
||||
接收的也是含 `bytes` 或 `UploadFile` 的列表(`list`)。
|
||||
|
||||
|
||||
/// note | 技术细节
|
||||
/// note | 注意
|
||||
|
||||
也可以使用 `from starlette.responses import HTMLResponse`。
|
||||
|
||||
@@ -161,12 +165,12 @@ FastAPI 支持同时上传多个文件。
|
||||
|
||||
///
|
||||
|
||||
### 带有额外元数据的多文件上传
|
||||
### 带有额外元数据的多文件上传 { #multiple-file-uploads-with-additional-metadata }
|
||||
|
||||
和之前的方式一样, 您可以为 `File()` 设置额外参数, 即使是 `UploadFile`:
|
||||
和之前的方式一样,你可以为 `File()` 设置额外参数,即使是 `UploadFile`:
|
||||
|
||||
{* ../../docs_src/request_files/tutorial003_py39.py hl[16] *}
|
||||
{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *}
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
本节介绍了如何用 `File` 把上传文件声明为(表单数据的)输入参数。
|
||||
使用 `File`、`bytes` 和 `UploadFile` 来声明在请求中上传的文件,它们以表单数据发送。
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
# 请求表单与文件
|
||||
# 请求表单与文件 { #request-forms-and-files }
|
||||
|
||||
FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
接收上传文件或表单数据,要预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
接收上传的文件和/或表单数据,首先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
|
||||
例如,`pip install python-multipart`。
|
||||
请先创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装,例如:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 导入 `File` 与 `Form`
|
||||
## 导入 `File` 与 `Form` { #import-file-and-form }
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *}
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## 定义 `File` 与 `Form` 参数
|
||||
## 定义 `File` 与 `Form` 参数 { #define-file-and-form-parameters }
|
||||
|
||||
创建文件和表单参数的方式与 `Body` 和 `Query` 一样:
|
||||
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *}
|
||||
|
||||
文件和表单字段作为表单数据上传与接收。
|
||||
|
||||
@@ -32,6 +36,6 @@ FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。
|
||||
|
||||
///
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
在同一个请求中接收数据和文件时,应同时使用 `File` 和 `Form`。
|
||||
|
||||
@@ -1,69 +1,73 @@
|
||||
# 表单数据
|
||||
# 表单数据 { #form-data }
|
||||
|
||||
接收的不是 JSON,而是表单字段时,要使用 `Form`。
|
||||
当你需要接收表单字段而不是 JSON 时,可以使用 `Form`。
|
||||
|
||||
/// info | 说明
|
||||
/// info
|
||||
|
||||
要使用表单,需预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
要使用表单,首先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
|
||||
例如,`pip install python-multipart`。
|
||||
请先创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再进行安装,例如:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 导入 `Form`
|
||||
## 导入 `Form` { #import-form }
|
||||
|
||||
从 `fastapi` 导入 `Form`:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001.py hl[1] *}
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *}
|
||||
|
||||
## 定义 `Form` 参数
|
||||
## 定义 `Form` 参数 { #define-form-parameters }
|
||||
|
||||
创建表单(`Form`)参数的方式与 `Body` 和 `Query` 一样:
|
||||
创建表单参数的方式与 `Body` 或 `Query` 相同:
|
||||
|
||||
{* ../../docs_src/request_forms/tutorial001.py hl[7] *}
|
||||
{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *}
|
||||
|
||||
例如,OAuth2 规范的 "密码流" 模式规定要通过表单字段发送 `username` 和 `password`。
|
||||
例如,在 OAuth2 规范的一种使用方式(称为“密码流”)中,要求将 `username` 和 `password` 作为表单字段发送。
|
||||
|
||||
<abbr title="specification">该规范</abbr>要求字段必须命名为 `username` 和 `password`,并通过表单字段发送,不能用 JSON。
|
||||
<abbr title="specification - 规范">spec</abbr> 要求这些字段必须精确命名为 `username` 和 `password`,并且作为表单字段发送,而不是 JSON。
|
||||
|
||||
使用 `Form` 可以声明与 `Body` (及 `Query`、`Path`、`Cookie`)相同的元数据和验证。
|
||||
使用 `Form` 可以像使用 `Body`(以及 `Query`、`Path`、`Cookie`)一样声明相同的配置,包括校验、示例、别名(例如将 `username` 写成 `user-name`)等。
|
||||
|
||||
/// info | 说明
|
||||
/// info
|
||||
|
||||
`Form` 是直接继承自 `Body` 的类。
|
||||
|
||||
///
|
||||
|
||||
/// tip | 提示
|
||||
/// tip
|
||||
|
||||
声明表单体要显式使用 `Form` ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。
|
||||
要声明表单请求体,必须显式使用 `Form`,否则这些参数会被当作查询参数或请求体(JSON)参数。
|
||||
|
||||
///
|
||||
|
||||
## 关于 "表单字段"
|
||||
## 关于 "表单字段" { #about-form-fields }
|
||||
|
||||
与 JSON 不同,HTML 表单(`<form></form>`)向服务器发送数据通常使用「特殊」的编码。
|
||||
HTML 表单(`<form></form>`)向服务器发送数据时通常会对数据使用一种“特殊”的编码方式,这与 JSON 不同。
|
||||
|
||||
**FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。
|
||||
**FastAPI** 会确保从正确的位置读取这些数据,而不是从 JSON 中读取。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
表单数据的「媒体类型」编码一般为 `application/x-www-form-urlencoded`。
|
||||
表单数据通常使用“媒体类型” `application/x-www-form-urlencoded` 进行编码。
|
||||
|
||||
但包含文件的表单编码为 `multipart/form-data`。文件处理详见下节。
|
||||
但当表单包含文件时,会编码为 `multipart/form-data`。你将在下一章阅读如何处理文件。
|
||||
|
||||
编码和表单字段详见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> Web 文档的 <code>POST</code></a>小节。
|
||||
如果你想了解更多关于这些编码和表单字段的信息,请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - Mozilla 开发者网络">MDN</abbr> Web 文档的 <code>POST</code></a>。
|
||||
|
||||
///
|
||||
|
||||
/// warning | 警告
|
||||
/// warning
|
||||
|
||||
可在一个*路径操作*中声明多个 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `application/x-www-form-urlencoded`,不是 `application/json`。
|
||||
你可以在一个路径操作中声明多个 `Form` 参数,但不能同时再声明要接收为 JSON 的 `Body` 字段,因为此时请求体会使用 `application/x-www-form-urlencoded` 而不是 `application/json` 进行编码。
|
||||
|
||||
这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。
|
||||
这不是 **FastAPI** 的限制,而是 HTTP 协议的一部分。
|
||||
|
||||
///
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
本节介绍了如何使用 `Form` 声明表单数据输入参数。
|
||||
使用 `Form` 来声明表单数据输入参数。
|
||||
|
||||
@@ -1,6 +1,35 @@
|
||||
# 响应模型
|
||||
# 响应模型 - 返回类型 { #response-model-return-type }
|
||||
|
||||
你可以在任意的*路径操作*中使用 `response_model` 参数来声明用于响应的模型:
|
||||
你可以通过为*路径操作函数*的**返回类型**添加注解来声明用于响应的类型。
|
||||
|
||||
和为输入数据在函数**参数**里做类型注解的方式相同,你可以使用 Pydantic 模型、`list`、`dict`、以及整数、布尔值等标量类型。
|
||||
|
||||
{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *}
|
||||
|
||||
FastAPI 会使用这个返回类型来:
|
||||
|
||||
* 对返回数据进行**校验**。
|
||||
* 如果数据无效(例如缺少某个字段),这意味着你的应用代码有问题,没有返回应有的数据,FastAPI 将返回服务器错误而不是返回错误的数据。这样你和你的客户端都可以确定会收到期望的数据及其结构。
|
||||
* 在 OpenAPI 的*路径操作*中为响应添加**JSON Schema**。
|
||||
* 它会被**自动文档**使用。
|
||||
* 它也会被自动客户端代码生成工具使用。
|
||||
|
||||
但更重要的是:
|
||||
|
||||
* 它会将输出数据**限制并过滤**为返回类型中定义的内容。
|
||||
* 这对**安全性**尤为重要,下面会进一步介绍。
|
||||
|
||||
## `response_model` 参数 { #response-model-parameter }
|
||||
|
||||
在一些情况下,你需要或希望返回的数据与声明的类型不完全一致。
|
||||
|
||||
例如,你可能希望**返回一个字典**或数据库对象,但**将其声明为一个 Pydantic 模型**。这样 Pydantic 模型就会为你返回的对象(例如字典或数据库对象)完成文档、校验等工作。
|
||||
|
||||
如果你添加了返回类型注解,工具和编辑器会(正确地)报错,提示你的函数返回的类型(例如 `dict`)与声明的类型(例如一个 Pydantic 模型)不同。
|
||||
|
||||
在这些情况下,你可以使用*路径操作装饰器*参数 `response_model`,而不是返回类型。
|
||||
|
||||
你可以在任意*路径操作*中使用 `response_model` 参数:
|
||||
|
||||
* `@app.get()`
|
||||
* `@app.post()`
|
||||
@@ -10,102 +39,209 @@
|
||||
|
||||
{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
注意,`response_model`是「装饰器」方法(`get`,`post` 等)的一个参数。不像之前的所有参数和请求体,它不属于*路径操作函数*。
|
||||
注意,`response_model` 是「装饰器」方法(`get`、`post` 等)的一个参数。不是你的*路径操作函数*的参数,不像所有查询参数和请求体那样。
|
||||
|
||||
///
|
||||
|
||||
它接收的类型与你将为 Pydantic 模型属性所声明的类型相同,因此它可以是一个 Pydantic 模型,但也可以是一个由 Pydantic 模型组成的 `list`,例如 `List[Item]`。
|
||||
`response_model` 接收的类型与为 Pydantic 模型字段声明的类型相同,因此它可以是一个 Pydantic 模型,也可以是一个由 Pydantic 模型组成的 `list`,例如 `List[Item]`。
|
||||
|
||||
FastAPI 将使用此 `response_model` 来:
|
||||
FastAPI 会使用这个 `response_model` 来完成数据文档、校验等,并且还会将输出数据**转换并过滤**为其类型声明。
|
||||
|
||||
* 将输出数据转换为其声明的类型。
|
||||
* 校验数据。
|
||||
* 在 OpenAPI 的*路径操作*中为响应添加一个 JSON Schema。
|
||||
* 并在自动生成文档系统中使用。
|
||||
/// tip | 提示
|
||||
|
||||
但最重要的是:
|
||||
如果你的编辑器、mypy 等进行严格类型检查,你可以将函数返回类型声明为 `Any`。
|
||||
|
||||
* 会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
响应模型在参数中被声明,而不是作为函数返回类型的注解,这是因为路径函数可能不会真正返回该响应模型,而是返回一个 `dict`、数据库对象或其他模型,然后再使用 `response_model` 来执行字段约束和序列化。
|
||||
这样你告诉编辑器你是有意返回任意类型。但 FastAPI 仍会使用 `response_model` 做数据文档、校验、过滤等工作。
|
||||
|
||||
///
|
||||
|
||||
## 返回与输入相同的数据
|
||||
### `response_model` 的优先级 { #response-model-priority }
|
||||
|
||||
现在我们声明一个 `UserIn` 模型,它将包含一个明文密码属性。
|
||||
如果你同时声明了返回类型和 `response_model`,`response_model` 会具有优先级并由 FastAPI 使用。
|
||||
|
||||
{* ../../docs_src/response_model/tutorial002.py hl[9,11] *}
|
||||
这样,即使你返回的类型与响应模型不同,你也可以为函数添加正确的类型注解,供编辑器和 mypy 等工具使用。同时你仍然可以让 FastAPI 使用 `response_model` 进行数据校验、文档等。
|
||||
|
||||
我们正在使用此模型声明输入数据,并使用同一模型声明输出数据:
|
||||
你也可以使用 `response_model=None` 来禁用该*路径操作*的响应模型生成;当你为一些不是有效 Pydantic 字段的东西添加类型注解时,可能需要这样做,下面的章节会有示例。
|
||||
|
||||
{* ../../docs_src/response_model/tutorial002.py hl[17:18] *}
|
||||
## 返回与输入相同的数据 { #return-the-same-input-data }
|
||||
|
||||
现在,每当浏览器使用一个密码创建用户时,API 都会在响应中返回相同的密码。
|
||||
这里我们声明一个 `UserIn` 模型,它包含一个明文密码:
|
||||
|
||||
在这个案例中,这可能不算是问题,因为用户自己正在发送密码。
|
||||
{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *}
|
||||
|
||||
但是,如果我们在其他的*路径操作*中使用相同的模型,则可能会将用户的密码发送给每个客户端。
|
||||
/// info | 信息
|
||||
|
||||
/// danger
|
||||
要使用 `EmailStr`,首先安装 <a href="https://github.com/JoshData/python-email-validator" class="external-link" target="_blank">`email-validator`</a>。
|
||||
|
||||
永远不要存储用户的明文密码,也不要在响应中发送密码。
|
||||
请先创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装,例如:
|
||||
|
||||
```console
|
||||
$ pip install email-validator
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```console
|
||||
$ pip install "pydantic[email]"
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
## 添加输出模型
|
||||
我们使用这个模型来声明输入,同时也用相同的模型来声明输出:
|
||||
|
||||
相反,我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型:
|
||||
{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *}
|
||||
|
||||
现在,每当浏览器使用密码创建用户时,API 会在响应中返回相同的密码。
|
||||
|
||||
在这个场景下,这可能不算问题,因为发送密码的是同一个用户。
|
||||
|
||||
但如果我们在其他*路径操作*中使用相同的模型,就可能会把用户的密码发送给每个客户端。
|
||||
|
||||
/// danger | 危险
|
||||
|
||||
除非你非常清楚所有注意事项并确实知道自己在做什么,否则永远不要存储用户的明文密码,也不要像这样在响应中发送它。
|
||||
|
||||
///
|
||||
|
||||
## 添加输出模型 { #add-an-output-model }
|
||||
|
||||
相反,我们可以创建一个包含明文密码的输入模型和一个不包含它的输出模型:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
|
||||
|
||||
这样,即便我们的*路径操作函数*将会返回包含密码的相同输入用户:
|
||||
这里,即使我们的*路径操作函数*返回的是包含密码的同一个输入用户:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
|
||||
|
||||
...我们已经将 `response_model` 声明为了不包含密码的 `UserOut` 模型:
|
||||
……我们仍将 `response_model` 声明为不包含密码的 `UserOut` 模型:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
|
||||
|
||||
因此,**FastAPI** 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。
|
||||
因此,**FastAPI** 会负责过滤掉输出模型中未声明的所有数据(使用 Pydantic)。
|
||||
|
||||
## 在文档中查看
|
||||
### `response_model` 还是返回类型 { #response-model-or-return-type }
|
||||
|
||||
当你查看自动化文档时,你可以检查输入模型和输出模型是否都具有自己的 JSON Schema:
|
||||
在这个例子中,因为两个模型不同,如果我们将函数返回类型注解为 `UserOut`,编辑器和工具会抱怨我们返回了无效类型,因为它们是不同的类。
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/response-model/image01.png">
|
||||
这就是为什么在这个例子里我们必须在 `response_model` 参数中声明它。
|
||||
|
||||
并且两种模型都将在交互式 API 文档中使用:
|
||||
……但继续往下读,看看如何更好地处理这种情况。
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/response-model/image02.png">
|
||||
## 返回类型与数据过滤 { #return-type-and-data-filtering }
|
||||
|
||||
## 响应模型编码参数
|
||||
延续上一个例子。我们希望**用一种类型来注解函数**,但希望从函数返回的内容实际上可以**包含更多数据**。
|
||||
|
||||
我们希望 FastAPI 继续使用响应模型来**过滤**数据。这样即使函数返回了更多数据,响应也只会包含响应模型中声明的字段。
|
||||
|
||||
在上一个例子中,因为类不同,我们不得不使用 `response_model` 参数。但这也意味着我们无法从编辑器和工具处获得对函数返回类型的检查支持。
|
||||
|
||||
不过在大多数需要这样做的场景里,我们只是希望模型像这个例子中那样**过滤/移除**一部分数据。
|
||||
|
||||
在这些场景里,我们可以使用类和继承,既利用函数的**类型注解**获取更好的编辑器和工具支持,又能获得 FastAPI 的**数据过滤**。
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *}
|
||||
|
||||
这样一来,我们既能从编辑器和 mypy 获得工具支持(这段代码在类型上是正确的),也能从 FastAPI 获得数据过滤。
|
||||
|
||||
这是如何做到的?我们来看看。🤓
|
||||
|
||||
### 类型注解与工具链 { #type-annotations-and-tooling }
|
||||
|
||||
先看看编辑器、mypy 和其他工具会如何看待它。
|
||||
|
||||
`BaseUser` 有基础字段。然后 `UserIn` 继承自 `BaseUser` 并新增了 `password` 字段,因此它包含了两个模型的全部字段。
|
||||
|
||||
我们把函数返回类型注解为 `BaseUser`,但实际上返回的是一个 `UserIn` 实例。
|
||||
|
||||
编辑器、mypy 和其他工具不会对此抱怨,因为在类型系统里,`UserIn` 是 `BaseUser` 的子类,这意味着当期望 `BaseUser` 时,返回 `UserIn` 是*合法*的。
|
||||
|
||||
### FastAPI 的数据过滤 { #fastapi-data-filtering }
|
||||
|
||||
对于 FastAPI,它会查看返回类型并确保你返回的内容**只**包含该类型中声明的字段。
|
||||
|
||||
FastAPI 在内部配合 Pydantic 做了多项处理,确保不会把类继承的这些规则用于返回数据的过滤,否则你可能会返回比预期多得多的数据。
|
||||
|
||||
这样,你就能兼得两方面的优势:带有**工具支持**的类型注解和**数据过滤**。
|
||||
|
||||
## 在文档中查看 { #see-it-in-the-docs }
|
||||
|
||||
当你查看自动文档时,你会看到输入模型和输出模型都会有各自的 JSON Schema:
|
||||
|
||||
<img src="/img/tutorial/response-model/image01.png">
|
||||
|
||||
并且两个模型都会用于交互式 API 文档:
|
||||
|
||||
<img src="/img/tutorial/response-model/image02.png">
|
||||
|
||||
## 其他返回类型注解 { #other-return-type-annotations }
|
||||
|
||||
有些情况下你会返回一些不是有效 Pydantic 字段的内容,并在函数上做了相应注解,只是为了获得工具链(编辑器、mypy 等)的支持。
|
||||
|
||||
### 直接返回 Response { #return-a-response-directly }
|
||||
|
||||
最常见的情况是[直接返回 Response,详见进阶文档](../advanced/response-directly.md){.internal-link target=_blank}。
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *}
|
||||
|
||||
这个简单场景 FastAPI 会自动处理,因为返回类型注解是 `Response`(或其子类)。
|
||||
|
||||
工具也会满意,因为 `RedirectResponse` 和 `JSONResponse` 都是 `Response` 的子类,所以类型注解是正确的。
|
||||
|
||||
### 注解 Response 的子类 { #annotate-a-response-subclass }
|
||||
|
||||
你也可以在类型注解中使用 `Response` 的子类:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *}
|
||||
|
||||
这同样可行,因为 `RedirectResponse` 是 `Response` 的子类,FastAPI 会自动处理这个简单场景。
|
||||
|
||||
### 无效的返回类型注解 { #invalid-return-type-annotations }
|
||||
|
||||
但当你返回其他任意对象(如数据库对象)而它不是有效的 Pydantic 类型,并在函数中按此进行了注解时,FastAPI 会尝试基于该类型注解创建一个 Pydantic 响应模型,但会失败。
|
||||
|
||||
如果你有一个在多个类型之间的<abbr title="多个类型的联合表示“这些类型中的任意一个”">联合类型</abbr>,其中一个或多个不是有效的 Pydantic 类型,也会发生同样的情况,例如这个会失败 💥:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}
|
||||
|
||||
……它失败是因为该类型注解不是 Pydantic 类型,也不只是单个 `Response` 类或其子类,而是 `Response` 与 `dict` 的联合类型(任意其一)。
|
||||
|
||||
### 禁用响应模型 { #disable-response-model }
|
||||
|
||||
延续上面的例子,你可能不想要 FastAPI 执行默认的数据校验、文档、过滤等。
|
||||
|
||||
但你可能仍然想在函数上保留返回类型注解,以获得编辑器和类型检查器(如 mypy)的支持。
|
||||
|
||||
在这种情况下,你可以通过设置 `response_model=None` 来禁用响应模型生成:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *}
|
||||
|
||||
这会让 FastAPI 跳过响应模型的生成,这样你就可以按需使用任意返回类型注解,而不会影响你的 FastAPI 应用。🤓
|
||||
|
||||
## 响应模型的编码参数 { #response-model-encoding-parameters }
|
||||
|
||||
你的响应模型可以具有默认值,例如:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *}
|
||||
{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *}
|
||||
|
||||
* `description: Union[str, None] = None` 具有默认值 `None`。
|
||||
* `tax: float = 10.5` 具有默认值 `10.5`.
|
||||
* `tags: List[str] = []` 具有一个空列表作为默认值: `[]`.
|
||||
* `description: Union[str, None] = None`(或在 Python 3.10 中的 `str | None = None`)默认值为 `None`。
|
||||
* `tax: float = 10.5` 默认值为 `10.5`。
|
||||
* `tags: List[str] = []` 默认值为一个空列表:`[]`。
|
||||
|
||||
但如果它们并没有存储实际的值,你可能想从结果中忽略它们的默认值。
|
||||
但如果它们并没有被实际存储,你可能希望在结果中省略这些默认值。
|
||||
|
||||
举个例子,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但你又不想发送充满默认值的很长的 JSON 响应。
|
||||
例如,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但又不想发送充满默认值的冗长 JSON 响应。
|
||||
|
||||
### 使用 `response_model_exclude_unset` 参数
|
||||
### 使用 `response_model_exclude_unset` 参数 { #use-the-response-model-exclude-unset-parameter }
|
||||
|
||||
你可以设置*路径操作装饰器*的 `response_model_exclude_unset=True` 参数:
|
||||
你可以设置*路径操作装饰器*参数 `response_model_exclude_unset=True`:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial004.py hl[24] *}
|
||||
{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *}
|
||||
|
||||
然后响应中将不会包含那些默认值,而是仅有实际设置的值。
|
||||
这样响应中将不会包含那些默认值,而只包含实际设置的值。
|
||||
|
||||
因此,如果你向*路径操作*发送 ID 为 `foo` 的商品的请求,则响应(不包括默认值)将为:
|
||||
因此,如果你向该*路径操作*请求 ID 为 `foo` 的商品,响应(不包括默认值)将为:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -114,24 +250,18 @@ FastAPI 将使用此 `response_model` 来:
|
||||
}
|
||||
```
|
||||
|
||||
/// info
|
||||
|
||||
FastAPI 通过 Pydantic 模型的 `.dict()` 配合 <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">该方法的 `exclude_unset` 参数</a> 来实现此功能。
|
||||
|
||||
///
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
你还可以使用:
|
||||
|
||||
* `response_model_exclude_defaults=True`
|
||||
* `response_model_exclude_none=True`
|
||||
|
||||
参考 <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">Pydantic 文档</a> 中对 `exclude_defaults` 和 `exclude_none` 的描述。
|
||||
详见 <a href="https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict" class="external-link" target="_blank">Pydantic 文档</a>中对 `exclude_defaults` 和 `exclude_none` 的说明。
|
||||
|
||||
///
|
||||
|
||||
#### 默认值字段有实际值的数据
|
||||
#### 默认字段有实际值的数据 { #data-with-values-for-fields-with-defaults }
|
||||
|
||||
但是,如果你的数据在具有默认值的模型字段中有实际的值,例如 ID 为 `bar` 的项:
|
||||
|
||||
@@ -146,7 +276,7 @@ FastAPI 通过 Pydantic 模型的 `.dict()` 配合 <a href="https://docs.pydanti
|
||||
|
||||
这些值将包含在响应中。
|
||||
|
||||
#### 具有与默认值相同值的数据
|
||||
#### 具有与默认值相同值的数据 { #data-with-the-same-values-as-the-defaults }
|
||||
|
||||
如果数据具有与默认值相同的值,例如 ID 为 `baz` 的项:
|
||||
|
||||
@@ -160,54 +290,54 @@ FastAPI 通过 Pydantic 模型的 `.dict()` 配合 <a href="https://docs.pydanti
|
||||
}
|
||||
```
|
||||
|
||||
即使 `description`、`tax` 和 `tags` 具有与默认值相同的值,FastAPI 足够聪明 (实际上是 Pydantic 足够聪明) 去认识到这一点,它们的值被显式地所设定(而不是取自默认值)。
|
||||
FastAPI 足够聪明(实际上是 Pydantic 足够聪明)去认识到,即使 `description`、`tax` 和 `tags` 的值与默认值相同,它们也是被显式设置的(而不是取自默认值)。
|
||||
|
||||
因此,它们将包含在 JSON 响应中。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
请注意默认值可以是任何值,而不仅是`None`。
|
||||
请注意默认值可以是任何值,而不仅是 `None`。
|
||||
|
||||
它们可以是一个列表(`[]`),一个值为 `10.5`的 `float`,等等。
|
||||
它们可以是一个列表(`[]`)、值为 `10.5` 的 `float`,等等。
|
||||
|
||||
///
|
||||
|
||||
### `response_model_include` 和 `response_model_exclude`
|
||||
### `response_model_include` 和 `response_model_exclude` { #response-model-include-and-response-model-exclude }
|
||||
|
||||
你还可以使用*路径操作装饰器*的 `response_model_include` 和 `response_model_exclude` 参数。
|
||||
|
||||
它们接收一个由属性名称 `str` 组成的 `set` 来包含(忽略其他的)或者排除(包含其他的)这些属性。
|
||||
它们接收一个由属性名 `str` 组成的 `set`,用于包含(忽略其他)或排除(包含其他)这些属性。
|
||||
|
||||
如果你只有一个 Pydantic 模型,并且想要从输出中移除一些数据,则可以使用这种快捷方法。
|
||||
当你只有一个 Pydantic 模型,并且想要从输出中移除一些数据时,这可以作为一种快捷方式。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
但是依然建议你使用上面提到的主意,使用多个类而不是这些参数。
|
||||
但仍然推荐使用上面的思路,使用多个类,而不是这些参数。
|
||||
|
||||
这是因为即使使用 `response_model_include` 或 `response_model_exclude` 来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。
|
||||
因为即使你使用 `response_model_include` 或 `response_model_exclude` 省略了一些属性,你的应用在 OpenAPI(和文档)中生成的 JSON Schema 仍然会是完整模型。
|
||||
|
||||
这也适用于作用类似的 `response_model_by_alias`。
|
||||
这同样适用于类似的 `response_model_by_alias`。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/response_model/tutorial005.py hl[31,37] *}
|
||||
{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
`{"name", "description"}` 语法创建一个具有这两个值的 `set`。
|
||||
`{"name", "description"}` 语法创建一个包含这两个值的 `set`。
|
||||
|
||||
等同于 `set(["name", "description"])`。
|
||||
|
||||
///
|
||||
|
||||
#### 使用 `list` 而不是 `set`
|
||||
#### 使用 `list` 而不是 `set` { #using-lists-instead-of-sets }
|
||||
|
||||
如果你忘记使用 `set` 而是使用 `list` 或 `tuple`,FastAPI 仍会将其转换为 `set` 并且正常工作:
|
||||
如果你忘记使用 `set` 而是使用了 `list` 或 `tuple`,FastAPI 仍会将其转换为 `set` 并正常工作:
|
||||
|
||||
{* ../../docs_src/response_model/tutorial006.py hl[31,37] *}
|
||||
{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *}
|
||||
|
||||
## 总结
|
||||
## 总结 { #recap }
|
||||
|
||||
使用*路径操作装饰器*的 `response_model` 参数来定义响应模型,特别是确保私有数据被过滤掉。
|
||||
使用*路径操作装饰器*的 `response_model` 参数来定义响应模型,尤其是确保私有数据被过滤掉。
|
||||
|
||||
使用 `response_model_exclude_unset` 来仅返回显式设定的值。
|
||||
使用 `response_model_exclude_unset` 来仅返回显式设置的值。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 响应状态码
|
||||
# 响应状态码 { #response-status-code }
|
||||
|
||||
与指定响应模型的方式相同,在以下任意*路径操作*中,可以使用 `status_code` 参数声明用于响应的 HTTP 状态码:
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* `@app.post()`
|
||||
* `@app.put()`
|
||||
* `@app.delete()`
|
||||
* 等……
|
||||
* 等...
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
注意,`status_code` 是(`get`、`post` 等)**装饰器**方法中的参数。与之前的参数和请求体不同,不是*路径操作函数*的参数。
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
`status_code` 参数接收表示 HTTP 状态码的数字。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
`status_code` 还能接收 `IntEnum` 类型,比如 Python 的 <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>。
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<img src="/img/tutorial/response-status-code/image01.png">
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
某些响应状态码表示响应没有响应体(参阅下一章)。
|
||||
|
||||
@@ -39,9 +39,9 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。
|
||||
|
||||
///
|
||||
|
||||
## 关于 HTTP 状态码
|
||||
## 关于 HTTP 状态码 { #about-http-status-codes }
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
如果已经了解 HTTP 状态码,请跳到下一章。
|
||||
|
||||
@@ -53,40 +53,40 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。
|
||||
|
||||
简言之:
|
||||
|
||||
* `100` 及以上的状态码用于返回**信息**。这类状态码很少直接使用。具有这些状态码的响应不能包含响应体
|
||||
* **`200`** 及以上的状态码用于表示**成功**。这些状态码是最常用的
|
||||
* `200` 是默认状态代码,表示一切**正常**
|
||||
* `201` 表示**已创建**,通常在数据库中创建新记录后使用
|
||||
* `204` 是一种特殊的例子,表示**无内容**。该响应在没有为客户端返回内容时使用,因此,该响应不能包含响应体
|
||||
* **`300`** 及以上的状态码用于**重定向**。具有这些状态码的响应不一定包含响应体,但 `304`**未修改**是个例外,该响应不得包含响应体
|
||||
* **`400`** 及以上的状态码用于表示**客户端错误**。这些可能是第二常用的类型
|
||||
* `404`,用于**未找到**响应
|
||||
* `100 - 199` 用于返回“信息”。这类状态码很少直接使用。具有这些状态码的响应不能包含响应体
|
||||
* **`200 - 299`** 用于表示“成功”。这些状态码是最常用的
|
||||
* `200` 是默认状态码,表示一切“OK”
|
||||
* `201` 表示“已创建”,通常在数据库中创建新记录后使用
|
||||
* `204` 是一种特殊的例子,表示“无内容”。该响应在没有为客户端返回内容时使用,因此,该响应不能包含响应体
|
||||
* **`300 - 399`** 用于“重定向”。具有这些状态码的响应不一定包含响应体,但 `304`“未修改”是个例外,该响应不得包含响应体
|
||||
* **`400 - 499`** 用于表示“客户端错误”。这些可能是第二常用的类型
|
||||
* `404`,用于“未找到”响应
|
||||
* 对于来自客户端的一般错误,可以只使用 `400`
|
||||
* `500` 及以上的状态码用于表示服务器端错误。几乎永远不会直接使用这些状态码。应用代码或服务器出现问题时,会自动返回这些状态代码
|
||||
* `500 - 599` 用于表示服务器端错误。几乎永远不会直接使用这些状态码。应用代码或服务器出现问题时,会自动返回这些状态码
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
状态码及适用场景的详情,请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN 的 HTTP 状态码</abbr>文档</a>。
|
||||
状态码及适用场景的详情,请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network - Mozilla 开发者网络">MDN</abbr> 的 HTTP 状态码文档</a>。
|
||||
|
||||
///
|
||||
|
||||
## 状态码名称快捷方式
|
||||
## 状态码名称快捷方式 { #shortcut-to-remember-the-names }
|
||||
|
||||
再看下之前的例子:
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *}
|
||||
|
||||
`201` 表示**已创建**的状态码。
|
||||
`201` 表示“已创建”的状态码。
|
||||
|
||||
但我们没有必要记住所有代码的含义。
|
||||
|
||||
可以使用 `fastapi.status` 中的快捷变量。
|
||||
|
||||
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
|
||||
{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *}
|
||||
|
||||
这只是一种快捷方式,具有相同的数字代码,但它可以使用编辑器的自动补全功能:
|
||||
|
||||
<img src="../../../../../../img/tutorial/response-status-code/image02.png">
|
||||
<img src="/img/tutorial/response-status-code/image02.png">
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
@@ -96,6 +96,6 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。
|
||||
|
||||
///
|
||||
|
||||
## 更改默认状态码
|
||||
## 更改默认状态码 { #changing-the-default }
|
||||
|
||||
[高级用户指南](../advanced/response-change-status-code.md){.internal-link target=_blank}中,将介绍如何返回与在此声明的默认状态码不同的状态码。
|
||||
|
||||
@@ -1,55 +1,202 @@
|
||||
# 模式的额外信息 - 例子
|
||||
# 声明请求示例数据 { #declare-request-example-data }
|
||||
|
||||
您可以在JSON模式中定义额外的信息。
|
||||
你可以为你的应用将接收的数据声明示例。
|
||||
|
||||
一个常见的用例是添加一个将在文档中显示的`example`。
|
||||
这里有几种实现方式。
|
||||
|
||||
有几种方法可以声明额外的 JSON 模式信息。
|
||||
## Pydantic 模型中的额外 JSON Schema 数据 { #extra-json-schema-data-in-pydantic-models }
|
||||
|
||||
## Pydantic `schema_extra`
|
||||
你可以为一个 Pydantic 模型声明 `examples`,它们会被添加到生成的 JSON Schema 中。
|
||||
|
||||
您可以使用 `Config` 和 `schema_extra` 为Pydantic模型声明一个示例,如<a href="https://docs.pydantic.dev/latest/concepts/json_schema/#schema-customization" class="external-link" target="_blank">Pydantic 文档:定制 Schema </a>中所述:
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *}
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *}
|
||||
这些额外信息会原样添加到该模型输出的 JSON Schema 中,并会在 API 文档中使用。
|
||||
|
||||
这些额外的信息将按原样添加到输出的JSON模式中。
|
||||
你可以使用属性 `model_config`,它接收一个 `dict`,详见 <a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Pydantic 文档:配置</a>。
|
||||
|
||||
## `Field` 的附加参数
|
||||
你可以设置 `"json_schema_extra"`,其值为一个 `dict`,包含你希望出现在生成 JSON Schema 中的任意附加数据,包括 `examples`。
|
||||
|
||||
在 `Field`, `Path`, `Query`, `Body` 和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 `example`:
|
||||
/// tip | 提示
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
|
||||
你也可以用同样的技巧扩展 JSON Schema,添加你自己的自定义额外信息。
|
||||
|
||||
/// warning
|
||||
|
||||
请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。
|
||||
例如,你可以用它为前端用户界面添加元数据等。
|
||||
|
||||
///
|
||||
|
||||
## `Body` 额外参数
|
||||
/// info | 信息
|
||||
|
||||
你可以通过传递额外信息给 `Field` 同样的方式操作`Path`, `Query`, `Body`等。
|
||||
OpenAPI 3.1.0(自 FastAPI 0.99.0 起使用)增加了对 `examples` 的支持,它是 JSON Schema 标准的一部分。
|
||||
|
||||
比如,你可以将请求体的一个 `example` 传递给 `Body`:
|
||||
在此之前,只支持使用单个示例的关键字 `example`。OpenAPI 3.1.0 仍然支持它,但它已被弃用,并不属于 JSON Schema 标准。因此,建议你把 `example` 迁移到 `examples`。🤓
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *}
|
||||
你可以在本页末尾阅读更多内容。
|
||||
|
||||
## 文档 UI 中的例子
|
||||
///
|
||||
|
||||
使用上面的任何方法,它在 `/docs` 中看起来都是这样的:
|
||||
## `Field` 的附加参数 { #field-additional-arguments }
|
||||
|
||||
在 Pydantic 模型中使用 `Field()` 时,你也可以声明额外的 `examples`:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
|
||||
|
||||
## JSON Schema 中的 `examples` - OpenAPI { #examples-in-json-schema-openapi }
|
||||
|
||||
在以下任意场景中使用:
|
||||
|
||||
- `Path()`
|
||||
- `Query()`
|
||||
- `Header()`
|
||||
- `Cookie()`
|
||||
- `Body()`
|
||||
- `Form()`
|
||||
- `File()`
|
||||
|
||||
你也可以声明一组 `examples`,这些带有附加信息的示例将被添加到它们在 OpenAPI 中的 JSON Schema 里。
|
||||
|
||||
### 带有 `examples` 的 `Body` { #body-with-examples }
|
||||
|
||||
这里我们向 `Body()` 传入 `examples`,其中包含一个期望的数据示例:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *}
|
||||
|
||||
### 文档 UI 中的示例 { #example-in-the-docs-ui }
|
||||
|
||||
使用上述任一方法,在 `/docs` 中看起来会是这样:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image01.png">
|
||||
|
||||
## 技术细节
|
||||
### 带有多个 `examples` 的 `Body` { #body-with-multiple-examples }
|
||||
|
||||
关于 `example` 和 `examples`...
|
||||
当然,你也可以传入多个 `examples`:
|
||||
|
||||
JSON Schema在最新的一个版本中定义了一个字段 <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> ,但是 OpenAPI 基于之前的一个旧版JSON Schema,并没有 `examples`.
|
||||
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *}
|
||||
|
||||
所以 OpenAPI为了相似的目的定义了自己的 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> (使用 `example`, 而不是 `examples`), 这也是文档 UI 所使用的 (使用 Swagger UI).
|
||||
这样做时,这些示例会成为该请求体数据内部 JSON Schema 的一部分。
|
||||
|
||||
所以,虽然 `example` 不是JSON Schema的一部分,但它是OpenAPI的一部分,这将被文档UI使用。
|
||||
不过,在<abbr title="2023-08-26">撰写本文时</abbr>,用于展示文档 UI 的 Swagger UI 并不支持显示 JSON Schema 中数据的多个示例。但请继续阅读,下面有一种变通方法。
|
||||
|
||||
## 其他信息
|
||||
### OpenAPI 特定的 `examples` { #openapi-specific-examples }
|
||||
|
||||
同样的方法,你可以添加你自己的额外信息,这些信息将被添加到每个模型的JSON模式中,例如定制前端用户界面,等等。
|
||||
在 JSON Schema 支持 `examples` 之前,OpenAPI 就已支持一个同名但不同的字段 `examples`。
|
||||
|
||||
这个面向 OpenAPI 的 `examples` 位于 OpenAPI 规范的另一处。它放在每个路径操作的详细信息中,而不是每个 JSON Schema 里。
|
||||
|
||||
而 Swagger UI 早就支持这个特定的 `examples` 字段。因此,你可以用它在文档 UI 中展示不同的示例。
|
||||
|
||||
这个 OpenAPI 特定字段 `examples` 的结构是一个包含多个示例的 `dict`(而不是一个 `list`),每个示例都包含会被添加到 OpenAPI 的额外信息。
|
||||
|
||||
这不放在 OpenAPI 内部包含的各个 JSON Schema 里,而是直接放在路径操作上。
|
||||
|
||||
### 使用 `openapi_examples` 参数 { #using-the-openapi-examples-parameter }
|
||||
|
||||
你可以在 FastAPI 中通过参数 `openapi_examples` 来声明这个 OpenAPI 特定的 `examples`,适用于:
|
||||
|
||||
- `Path()`
|
||||
- `Query()`
|
||||
- `Header()`
|
||||
- `Cookie()`
|
||||
- `Body()`
|
||||
- `Form()`
|
||||
- `File()`
|
||||
|
||||
这个 `dict` 的键用于标识每个示例,每个值是另一个 `dict`。
|
||||
|
||||
`examples` 中每个具体示例的 `dict` 可以包含:
|
||||
|
||||
- `summary`:该示例的简短描述。
|
||||
- `description`:较长描述,可以包含 Markdown 文本。
|
||||
- `value`:实际展示的示例,例如一个 `dict`。
|
||||
- `externalValue`:`value` 的替代项,指向该示例的 URL。不过它的工具支持度可能不如 `value`。
|
||||
|
||||
你可以这样使用:
|
||||
|
||||
{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *}
|
||||
|
||||
### 文档 UI 中的 OpenAPI 示例 { #openapi-examples-in-the-docs-ui }
|
||||
|
||||
当把 `openapi_examples` 添加到 `Body()` 后,`/docs` 会如下所示:
|
||||
|
||||
<img src="/img/tutorial/body-fields/image02.png">
|
||||
|
||||
## 技术细节 { #technical-details }
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
如果你已经在使用 FastAPI 版本 0.99.0 或更高版本,你大概率可以跳过这些细节。
|
||||
|
||||
它们对更早版本(OpenAPI 3.1.0 尚不可用之前)更相关。
|
||||
|
||||
你可以把这当作一堂简短的 OpenAPI 和 JSON Schema 历史课。🤓
|
||||
|
||||
///
|
||||
|
||||
/// warning | 警告
|
||||
|
||||
以下是关于 JSON Schema 和 OpenAPI 标准的非常技术性的细节。
|
||||
|
||||
如果上面的思路对你已经足够可用,你可能不需要这些细节,可以直接跳过。
|
||||
|
||||
///
|
||||
|
||||
在 OpenAPI 3.1.0 之前,OpenAPI 使用的是一个更旧且经过修改的 JSON Schema 版本。
|
||||
|
||||
当时 JSON Schema 没有 `examples`,所以 OpenAPI 在它修改过的版本中添加了自己的 `example` 字段。
|
||||
|
||||
OpenAPI 还在规范的其他部分添加了 `example` 和 `examples` 字段:
|
||||
|
||||
- <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-object" class="external-link" target="_blank">`Parameter Object`(规范中)</a>,被 FastAPI 的以下内容使用:
|
||||
- `Path()`
|
||||
- `Query()`
|
||||
- `Header()`
|
||||
- `Cookie()`
|
||||
- <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object" class="external-link" target="_blank">`Request Body Object` 中的 `content` 字段里的 `Media Type Object`(规范中)</a>,被 FastAPI 的以下内容使用:
|
||||
- `Body()`
|
||||
- `File()`
|
||||
- `Form()`
|
||||
|
||||
/// info | 信息
|
||||
|
||||
这个旧的、OpenAPI 特定的 `examples` 参数,自 FastAPI `0.103.0` 起改名为 `openapi_examples`。
|
||||
|
||||
///
|
||||
|
||||
### JSON Schema 的 `examples` 字段 { #json-schemas-examples-field }
|
||||
|
||||
后来,JSON Schema 在新版本的规范中添加了 <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> 字段。
|
||||
|
||||
随后新的 OpenAPI 3.1.0 基于最新版本(JSON Schema 2020-12),其中包含了这个新的 `examples` 字段。
|
||||
|
||||
现在,这个新的 `examples` 字段优先于旧的单个(且自定义的)`example` 字段,后者已被弃用。
|
||||
|
||||
JSON Schema 中这个新的 `examples` 字段只是一个由示例组成的 `list`,而不是像上面提到的 OpenAPI 其他位置那样带有额外元数据的 `dict`。
|
||||
|
||||
/// info | 信息
|
||||
|
||||
即使在 OpenAPI 3.1.0 发布、并与 JSON Schema 有了这种更简单的集成之后,有一段时间里,提供自动文档的 Swagger UI 并不支持 OpenAPI 3.1.0(它自 5.0.0 版本起已支持 🎉)。
|
||||
|
||||
因此,FastAPI 0.99.0 之前的版本仍然使用低于 3.1.0 的 OpenAPI 版本。
|
||||
|
||||
///
|
||||
|
||||
### Pydantic 与 FastAPI 的 `examples` { #pydantic-and-fastapi-examples }
|
||||
|
||||
当你在 Pydantic 模型中添加 `examples`,通过 `schema_extra` 或 `Field(examples=["something"])`,这些示例会被添加到该 Pydantic 模型的 JSON Schema 中。
|
||||
|
||||
这个 Pydantic 模型的 JSON Schema 会被包含到你的 API 的 OpenAPI 中,然后在文档 UI 中使用。
|
||||
|
||||
在 FastAPI 0.99.0 之前的版本(0.99.0 及以上使用更新的 OpenAPI 3.1.0),当你在其他工具(`Query()`、`Body()` 等)中使用 `example` 或 `examples` 时,这些示例不会被添加到描述该数据的 JSON Schema 中(甚至不会添加到 OpenAPI 自己的 JSON Schema 版本中),而是会直接添加到 OpenAPI 的路径操作声明中(在 OpenAPI 使用 JSON Schema 的部分之外)。
|
||||
|
||||
但现在 FastAPI 0.99.0 及以上使用 OpenAPI 3.1.0(其使用 JSON Schema 2020-12)以及 Swagger UI 5.0.0 及以上后,一切更加一致,示例会包含在 JSON Schema 中。
|
||||
|
||||
### Swagger UI 与 OpenAPI 特定的 `examples` { #swagger-ui-and-openapi-specific-examples }
|
||||
|
||||
此前,由于 Swagger UI 不支持多个 JSON Schema 示例(截至 2023-08-26),用户无法在文档中展示多个示例。
|
||||
|
||||
为了解决这个问题,FastAPI `0.103.0` 通过新增参数 `openapi_examples`,为声明同样的旧式 OpenAPI 特定 `examples` 字段提供了支持。🤓
|
||||
|
||||
### 总结 { #summary }
|
||||
|
||||
我曾经说我不太喜欢历史……结果现在在这儿上“技术史”课。😅
|
||||
|
||||
简而言之,升级到 FastAPI 0.99.0 或更高版本,一切会更简单、一致、直观,你也不必了解这些历史细节。😎
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
# 静态文件
|
||||
# 静态文件 { #static-files }
|
||||
|
||||
您可以使用 `StaticFiles`从目录中自动提供静态文件。
|
||||
你可以使用 `StaticFiles` 从目录中自动提供静态文件。
|
||||
|
||||
## 使用`StaticFiles`
|
||||
## 使用 `StaticFiles` { #use-staticfiles }
|
||||
|
||||
* 导入`StaticFiles`。
|
||||
* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。
|
||||
* 导入 `StaticFiles`。
|
||||
* 将一个 `StaticFiles()` 实例“挂载”(Mount)到指定路径。
|
||||
|
||||
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
|
||||
{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *}
|
||||
|
||||
/// note | 技术细节
|
||||
/// note | 注意
|
||||
|
||||
你也可以用 `from starlette.staticfiles import StaticFiles`。
|
||||
|
||||
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。
|
||||
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles`,只是为了方便你这个开发者。但它确实直接来自 Starlette。
|
||||
|
||||
///
|
||||
|
||||
### 什么是"挂载"(Mounting)
|
||||
### 什么是“挂载”(Mounting) { #what-is-mounting }
|
||||
|
||||
"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。
|
||||
“挂载”表示在特定路径添加一个完全“独立”的应用,然后负责处理所有子路径。
|
||||
|
||||
这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。
|
||||
这与使用 `APIRouter` 不同,因为挂载的应用是完全独立的。主应用的 OpenAPI 和文档不会包含已挂载应用的任何内容,等等。
|
||||
|
||||
你可以在**高级用户指南**中了解更多。
|
||||
你可以在[高级用户指南](../advanced/index.md){.internal-link target=_blank}中了解更多。
|
||||
|
||||
## 细节
|
||||
## 细节 { #details }
|
||||
|
||||
这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。
|
||||
第一个 `"/static"` 指的是这个“子应用”将被“挂载”到的子路径。因此,任何以 `"/static"` 开头的路径都会由它处理。
|
||||
|
||||
`directory="static"` 指向包含你的静态文件的目录名字。
|
||||
`directory="static"` 指的是包含你的静态文件的目录名称。
|
||||
|
||||
`name="static"` 提供了一个能被**FastAPI**内部使用的名字。
|
||||
`name="static"` 为它提供了一个可被 **FastAPI** 内部使用的名称。
|
||||
|
||||
所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。
|
||||
这些参数都可以不是“`static`”,请根据你的应用需求和具体细节进行调整。
|
||||
|
||||
## 更多信息
|
||||
## 更多信息 { #more-info }
|
||||
|
||||
更多细节和选择查阅 <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette's docs about Static Files</a>.
|
||||
更多细节和选项请查阅 <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette 的静态文件文档</a>。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 测试
|
||||
# 测试 { #testing }
|
||||
|
||||
感谢 <a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a>,测试**FastAPI** 应用轻松又愉快。
|
||||
|
||||
@@ -6,17 +6,21 @@
|
||||
|
||||
有了它,你可以直接与**FastAPI**一起使用 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>。
|
||||
|
||||
## 使用 `TestClient`
|
||||
## 使用 `TestClient` { #using-testclient }
|
||||
|
||||
/// info | 信息
|
||||
|
||||
要使用 `TestClient`,先要安装 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
|
||||
要使用 `TestClient`,先要安装 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>。
|
||||
|
||||
例:`pip install httpx`.
|
||||
确保你创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装,例如:
|
||||
|
||||
```console
|
||||
$ pip install httpx
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
导入 `TestClient`.
|
||||
导入 `TestClient`。
|
||||
|
||||
通过传入你的**FastAPI**应用创建一个 `TestClient` 。
|
||||
|
||||
@@ -26,7 +30,7 @@
|
||||
|
||||
为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。
|
||||
|
||||
{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
|
||||
{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -52,13 +56,13 @@
|
||||
|
||||
///
|
||||
|
||||
## 分离测试
|
||||
## 分离测试 { #separating-tests }
|
||||
|
||||
在实际应用中,你可能会把你的测试放在另一个文件里。
|
||||
|
||||
您的**FastAPI**应用程序也可能由一些文件/模块组成等等。
|
||||
|
||||
### **FastAPI** app 文件
|
||||
### **FastAPI** app 文件 { #fastapi-app-file }
|
||||
|
||||
假设你有一个像 [更大的应用](bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构:
|
||||
|
||||
@@ -72,9 +76,9 @@
|
||||
在 `main.py` 文件中你有一个 **FastAPI** app:
|
||||
|
||||
|
||||
{* ../../docs_src/app_testing/main.py *}
|
||||
{* ../../docs_src/app_testing/app_a_py39/main.py *}
|
||||
|
||||
### 测试文件
|
||||
### 测试文件 { #testing-file }
|
||||
|
||||
然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件):
|
||||
|
||||
@@ -88,15 +92,15 @@
|
||||
|
||||
因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象:
|
||||
|
||||
{* ../../docs_src/app_testing/test_main.py hl[3] *}
|
||||
{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *}
|
||||
|
||||
...然后测试代码和之前一样的。
|
||||
|
||||
## 测试:扩展示例
|
||||
## 测试:扩展示例 { #testing-extended-example }
|
||||
|
||||
现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。
|
||||
|
||||
### 扩展后的 **FastAPI** app 文件
|
||||
### 扩展后的 **FastAPI** app 文件 { #extended-fastapi-app-file }
|
||||
|
||||
让我们继续之前的文件结构:
|
||||
|
||||
@@ -116,63 +120,13 @@
|
||||
|
||||
所有*路径操作* 都需要一个`X-Token` 头。
|
||||
|
||||
//// tab | Python 3.10+
|
||||
{* ../../docs_src/app_testing/app_b_an_py310/main.py *}
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_an/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.10+ non-Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+ non-Annotated
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
Prefer to use the `Annotated` version if possible.
|
||||
|
||||
///
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/app_testing/app_b/main.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
### 扩展后的测试文件
|
||||
### 扩展后的测试文件 { #extended-testing-file }
|
||||
|
||||
然后您可以使用扩展后的测试更新`test_main.py`:
|
||||
|
||||
{* ../../docs_src/app_testing/app_b/test_main.py *}
|
||||
{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *}
|
||||
|
||||
每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。
|
||||
|
||||
@@ -196,9 +150,11 @@ Prefer to use the `Annotated` version if possible.
|
||||
|
||||
///
|
||||
|
||||
## 运行起来
|
||||
## 运行起来 { #run-it }
|
||||
|
||||
之后,你只需要安装 `pytest`:
|
||||
之后,你只需要安装 `pytest`。
|
||||
|
||||
确保你创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装,例如:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user