mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-06 20:31:56 -05:00
Compare commits
7 Commits
0.128.2
...
translate-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
400d253be8 | ||
|
|
e054f11b97 | ||
|
|
3988e68dcb | ||
|
|
d6e602f926 | ||
|
|
672f06d6c2 | ||
|
|
e8c37d1dd6 | ||
|
|
399598b0bd |
@@ -331,6 +331,8 @@ extra:
|
||||
name: tr - Türkçe
|
||||
- link: /uk/
|
||||
name: uk - українська мова
|
||||
- link: /zh/
|
||||
name: zh - 简体中文
|
||||
- link: /zh-hant/
|
||||
name: zh-hant - 繁體中文
|
||||
extra_css:
|
||||
|
||||
@@ -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. 你可以将其它标准类型注解与数据类一起用作请求体。
|
||||
|
||||
2. 使用 `pydantic.dataclasses` 直接替换 `dataclasses`;
|
||||
在本例中,它是一个 `Item` 数据类列表。
|
||||
6. 这里我们返回一个字典,里面的 `items` 是一个数据类列表。
|
||||
|
||||
3. `Author` 数据类包含 `Item` 数据类列表;
|
||||
FastAPI 仍然能够将数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON。
|
||||
7. 这里的 `response_model` 使用了 “`Author` 数据类列表” 的类型注解。
|
||||
|
||||
4. `Author` 数据类用于 `response_model` 参数;
|
||||
同样,你可以将 `dataclasses` 与标准类型注解组合使用。
|
||||
8. 注意,这个 *路径操作函数* 使用的是常规的 `def` 而不是 `async def`。
|
||||
|
||||
5. 其它带有数据类的标准类型注解也可以作为请求体;
|
||||
一如既往,在 FastAPI 中你可以按需组合 `def` 和 `async def`。
|
||||
|
||||
本例使用的是 `Item` 数据类列表;
|
||||
如果需要回顾何时用哪一个,请查看关于 [`async` 和 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 的文档中的 _“急不可待?”_ 一节。
|
||||
9. 这个 *路径操作函数* 返回的不是数据类(当然也可以返回数据类),而是包含内部数据的字典列表。
|
||||
|
||||
6. 这行代码返回的是包含 `items` 的字典,`items` 是数据类列表;
|
||||
FastAPI 会使用(包含数据类的)`response_model` 参数来转换响应。
|
||||
|
||||
FastAPI 仍能把数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON;
|
||||
你可以将 `dataclasses` 与其它类型注解以多种不同方式组合,来构建复杂的数据结构。
|
||||
|
||||
7. 这行代码中,`response_model` 的类型注解是 `Author` 数据类列表;
|
||||
更多细节请参考上面代码中的内联注释提示。
|
||||
|
||||
再一次,可以把 `dataclasses` 与标准类型注解一起使用;
|
||||
## 深入学习 { #learn-more }
|
||||
|
||||
8. 注意,*路径操作函数*使用的是普通函数,不是异步函数;
|
||||
你还可以把 `dataclasses` 与其它 Pydantic 模型组合、从它们继承、把它们包含到你自己的模型中等。
|
||||
|
||||
与往常一样,在 FastAPI 中,可以按需组合普通函数与异步函数;
|
||||
想了解更多,请查看 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">Pydantic 关于 dataclasses 的文档</a>。
|
||||
|
||||
如果不清楚何时使用异步函数或普通函数,请参阅**急不可待?**一节中对 <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank" class="internal-link">`async` 与 `await`</a> 的说明;
|
||||
## 版本 { #version }
|
||||
|
||||
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,21 +1,21 @@
|
||||
# 高级用户指南
|
||||
# 高级用户指南 { #advanced-user-guide }
|
||||
|
||||
## 额外特性
|
||||
## 附加功能 { #additional-features }
|
||||
|
||||
主要的教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 应该足以让你了解 **FastAPI** 的所有主要特性。
|
||||
主要的[教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank}足以带你了解 **FastAPI** 的所有主要特性。
|
||||
|
||||
你会在接下来的章节中了解到其他的选项、配置以及额外的特性。
|
||||
在接下来的章节中,你将看到其他选项、配置和附加功能。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
接下来的章节**并不一定是**「高级的」。
|
||||
接下来的章节不一定是“高级”的。
|
||||
|
||||
而且对于你的使用场景来说,解决方案很可能就在其中。
|
||||
对于你的用例,解决方案很可能就在其中之一。
|
||||
|
||||
///
|
||||
|
||||
## 先阅读教程
|
||||
## 先阅读教程 { #read-the-tutorial-first }
|
||||
|
||||
你可能仍会用到 **FastAPI** 主教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 中的大多数特性。
|
||||
仅凭主要[教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank}中的知识,你已经可以使用 **FastAPI** 的大多数功能。
|
||||
|
||||
接下来的章节我们认为你已经读过 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank},并且假设你已经知晓其中主要思想。
|
||||
接下来的章节默认你已经读过它,并理解其中的核心概念。
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
# HTTP 基础授权
|
||||
# HTTP 基础授权 { #http-basic-auth }
|
||||
|
||||
最简单的用例是使用 HTTP 基础授权(HTTP Basic Auth)。
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
|
||||
如果没有接收到 HTTP 基础授权,就返回 HTTP 401 `"Unauthorized"` 错误。
|
||||
|
||||
并返回含 `Basic` 值的请求头 `WWW-Authenticate`以及可选的 `realm` 参数。
|
||||
并返回响应头 `WWW-Authenticate`,其值为 `Basic`,以及可选的 `realm` 参数。
|
||||
|
||||
HTTP 基础授权让浏览器显示内置的用户名与密码提示。
|
||||
|
||||
输入用户名与密码后,浏览器会把它们自动发送至请求头。
|
||||
|
||||
## 简单的 HTTP 基础授权
|
||||
## 简单的 HTTP 基础授权 { #simple-http-basic-auth }
|
||||
|
||||
* 导入 `HTTPBasic` 与 `HTTPBasicCredentials`
|
||||
* 使用 `HTTPBasic` 创建**安全概图**
|
||||
* 使用 `HTTPBasic` 创建**安全方案**
|
||||
* 在*路径操作*的依赖项中使用 `security`
|
||||
* 返回类型为 `HTTPBasicCredentials` 的对象:
|
||||
* 包含发送的 `username` 与 `password`
|
||||
@@ -26,7 +26,7 @@ HTTP 基础授权让浏览器显示内置的用户名与密码提示。
|
||||
|
||||
<img src="/img/tutorial/security/image12.png">
|
||||
|
||||
## 检查用户名
|
||||
## 检查用户名 { #check-the-username }
|
||||
|
||||
以下是更完整的示例。
|
||||
|
||||
@@ -52,13 +52,13 @@ if not (credentials.username == "stanleyjobson") or not (credentials.password ==
|
||||
|
||||
但使用 `secrets.compare_digest()`,可以防御**时差攻击**,更加安全。
|
||||
|
||||
### 时差攻击
|
||||
### 时差攻击 { #timing-attacks }
|
||||
|
||||
什么是**时差攻击**?
|
||||
|
||||
假设攻击者试图猜出用户名与密码。
|
||||
|
||||
他们发送用户名为 `johndoe`,密码为 `love123` 的请求。
|
||||
他们发送用户名为 `johndoe`,密码为 `love123` 的请求。
|
||||
|
||||
然后,Python 代码执行如下操作:
|
||||
|
||||
@@ -80,28 +80,28 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
|
||||
|
||||
此时,Python 要对比 `stanleyjobsox` 与 `stanleyjobson` 中的 `stanleyjobso`,才能知道这两个字符串不一样。因此会多花费几微秒来返回**错误的用户或密码**。
|
||||
|
||||
#### 反应时间对攻击者的帮助
|
||||
#### 反应时间对攻击者的帮助 { #the-time-to-answer-helps-the-attackers }
|
||||
|
||||
通过服务器花费了更多微秒才发送**错误的用户或密码**响应,攻击者会知道猜对了一些内容,起码开头字母是正确的。
|
||||
|
||||
然后,他们就可以放弃 `johndoe`,再用类似 `stanleyjobsox` 的内容进行尝试。
|
||||
|
||||
#### **专业**攻击
|
||||
#### **专业**攻击 { #a-professional-attack }
|
||||
|
||||
当然,攻击者不用手动操作,而是编写每秒能执行成千上万次测试的攻击程序,每次都会找到更多正确字符。
|
||||
|
||||
但是,在您的应用的**帮助**下,攻击者利用时间差,就能在几分钟或几小时内,以这种方式猜出正确的用户名和密码。
|
||||
|
||||
#### 使用 `secrets.compare_digest()` 修补
|
||||
#### 使用 `secrets.compare_digest()` 修补 { #fix-it-with-secrets-compare-digest }
|
||||
|
||||
在此,代码中使用了 `secrets.compare_digest()`。
|
||||
|
||||
简单的说,它使用相同的时间对比 `stanleyjobsox` 和 `stanleyjobson`,还有 `johndoe` 和 `stanleyjobson`。对比密码时也一样。
|
||||
|
||||
在代码中使用 `secrets.compare_digest()` ,就可以安全地防御全面攻击了。
|
||||
在代码中使用 `secrets.compare_digest()` ,就可以安全地防御这整类安全攻击。
|
||||
|
||||
### 返回错误
|
||||
### 返回错误 { #return-the-error }
|
||||
|
||||
检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加请求头 `WWW-Authenticate`,让浏览器再次显示登录提示:
|
||||
检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加响应头 `WWW-Authenticate`,让浏览器再次显示登录提示:
|
||||
|
||||
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# 高级安全
|
||||
# 高级安全 { #advanced-security }
|
||||
|
||||
## 附加特性
|
||||
## 附加特性 { #additional-features }
|
||||
|
||||
除 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性.
|
||||
除 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性。
|
||||
|
||||
/// tip | 小贴士
|
||||
/// tip | 提示
|
||||
|
||||
接下来的章节 **并不一定是 "高级的"**.
|
||||
接下来的章节**并不一定是 "高级的"**。
|
||||
|
||||
而且对于你的使用场景来说,解决方案很可能就在其中。
|
||||
|
||||
///
|
||||
|
||||
## 先阅读教程
|
||||
## 先阅读教程 { #read-the-tutorial-first }
|
||||
|
||||
接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank}.
|
||||
接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank}。
|
||||
|
||||
它们都基于相同的概念,但支持一些额外的功能.
|
||||
它们都基于相同的概念,但支持一些额外的功能。
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
# 测试依赖项
|
||||
# 使用覆盖测试依赖项 { #testing-dependencies-with-overrides }
|
||||
|
||||
## 测试时覆盖依赖项
|
||||
## 测试时覆盖依赖项 { #overriding-dependencies-during-testing }
|
||||
|
||||
有些场景下,您可能需要在测试时覆盖依赖项。
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
反之,要在测试期间(或只是为某些特定测试)提供只用于测试的依赖项,并使用此依赖项的值替换原有依赖项的值。
|
||||
|
||||
### 用例:外部服务
|
||||
### 用例:外部服务 { #use-cases-external-service }
|
||||
|
||||
常见实例是调用外部第三方身份验证应用。
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
此时,最好覆盖调用外部验证应用的依赖项,使用返回模拟测试用户的自定义依赖项就可以了。
|
||||
|
||||
### 使用 `app.dependency_overrides` 属性
|
||||
### 使用 `app.dependency_overrides` 属性 { #use-the-app-dependency-overrides-attribute }
|
||||
|
||||
对于这些用例,**FastAPI** 应用支持 `app.dependency_overrides` 属性,该属性就是**字典**。
|
||||
|
||||
@@ -46,6 +46,7 @@ FastAPI 可以覆盖这些位置的依赖项。
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
如果只在某些测试时覆盖依赖项,您可以在测试开始时(在测试函数内)设置覆盖依赖项,并在结束时(在测试函数结尾)重置覆盖依赖项。
|
||||
|
||||
@@ -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,10 +1,10 @@
|
||||
# 并发 async / await
|
||||
# 并发 async / await { #concurrency-and-async-await }
|
||||
|
||||
有关路径操作函数的 `async def` 语法以及异步代码、并发和并行的一些背景知识。
|
||||
|
||||
## 赶时间吗?
|
||||
## 赶时间吗? { #in-a-hurry }
|
||||
|
||||
<abbr title="too long; didn't read(长文警告)"><strong>TL;DR:</strong></abbr>
|
||||
<abbr title="too long; didn't read - 太长;没看"><strong>TL;DR:</strong></abbr>
|
||||
|
||||
如果你正在使用第三方库,它们会告诉你使用 `await` 关键字来调用它们,就像这样:
|
||||
|
||||
@@ -21,7 +21,7 @@ async def read_results():
|
||||
return results
|
||||
```
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
你只能在被 `async def` 创建的函数内使用 `await`
|
||||
|
||||
@@ -40,7 +40,7 @@ def results():
|
||||
|
||||
---
|
||||
|
||||
如果你的应用程序不需要与其他任何东西通信而等待其响应,请使用 `async def`。
|
||||
如果你的应用程序不需要与其他任何东西通信而等待其响应,请使用 `async def`,即使函数内部不需要使用 `await`。
|
||||
|
||||
---
|
||||
|
||||
@@ -54,7 +54,7 @@ def results():
|
||||
|
||||
但是,通过遵循上述步骤,它将能够进行一些性能优化。
|
||||
|
||||
## 技术细节
|
||||
## 技术细节 { #technical-details }
|
||||
|
||||
Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 `await` 语法的东西来写**”异步代码“**。
|
||||
|
||||
@@ -64,7 +64,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
* **`async` 和 `await`**
|
||||
* **协程**
|
||||
|
||||
## 异步代码
|
||||
## 异步代码 { #asynchronous-code }
|
||||
|
||||
异步代码仅仅意味着编程语言 💬 有办法告诉计算机/程序 🤖 在代码中的某个点,它 🤖 将不得不等待在某些地方完成一些事情。让我们假设一些事情被称为 "慢文件"📝.
|
||||
|
||||
@@ -74,7 +74,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
接下来,它 🤖 完成第一个任务(比如是我们的"慢文件"📝) 并继续与之相关的一切。
|
||||
|
||||
这个"等待其他事情"通常指的是一些相对较慢(与处理器和 RAM 存储器的速度相比)的 <abbr title="Input and Output">I/O</abbr> 操作,比如说:
|
||||
这个"等待其他事情"通常指的是一些相对较慢(与处理器和 RAM 存储器的速度相比)的 <abbr title="Input and Output - 输入和输出">I/O</abbr> 操作,比如说:
|
||||
|
||||
* 通过网络发送来自客户端的数据
|
||||
* 客户端接收来自网络中的数据
|
||||
@@ -85,7 +85,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
* 一个数据库查询,直到返回结果
|
||||
* 等等.
|
||||
|
||||
这个执行的时间大多是在等待 <abbr title="Input and Output">I/O</abbr> 操作,因此它们被叫做 "I/O 密集型" 操作。
|
||||
这个执行的时间大多是在等待 <abbr title="Input and Output - 输入和输出">I/O</abbr> 操作,因此它们被叫做 "I/O 密集型" 操作。
|
||||
|
||||
它被称为"异步"的原因是因为计算机/程序不必与慢任务"同步",去等待任务完成的确切时刻,而在此期间不做任何事情直到能够获取任务结果才继续工作。
|
||||
|
||||
@@ -93,7 +93,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
对于"同步"(与"异步"相反),他们通常也使用"顺序"一词,因为计算机程序在切换到另一个任务之前是按顺序执行所有步骤,即使这些步骤涉及到等待。
|
||||
|
||||
### 并发与汉堡
|
||||
### 并发与汉堡 { #concurrency-and-burgers }
|
||||
|
||||
上述异步代码的思想有时也被称为“并发”,它不同于“并行”。
|
||||
|
||||
@@ -103,7 +103,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
要了解差异,请想象以下关于汉堡的故事:
|
||||
|
||||
### 并发汉堡
|
||||
### 并发汉堡 { #concurrent-burgers }
|
||||
|
||||
你和你的恋人一起去快餐店,你排队在后面,收银员从你前面的人接单。😍
|
||||
|
||||
@@ -139,7 +139,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
漂亮的插画来自 <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
@@ -163,7 +163,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
然后你去柜台🔀, 到现在初始任务已经完成⏯, 拿起汉堡,说声谢谢,然后把它们送到桌上。这就完成了与计数器交互的步骤/任务⏹. 这反过来又产生了一项新任务,即"吃汉堡"🔀 ⏯, 上一个"拿汉堡"的任务已经结束了⏹.
|
||||
|
||||
### 并行汉堡
|
||||
### 并行汉堡 { #parallel-burgers }
|
||||
|
||||
现在让我们假设不是"并发汉堡",而是"并行汉堡"。
|
||||
|
||||
@@ -205,7 +205,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
没有太多的交谈或调情,因为大部分时间 🕙 都在柜台前等待😞。
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
漂亮的插画来自 <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
|
||||
|
||||
@@ -233,7 +233,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
你可不会想带你的恋人 😍 和你一起去银行办事🏦.
|
||||
|
||||
### 汉堡结论
|
||||
### 汉堡结论 { #burger-conclusion }
|
||||
|
||||
在"你与恋人一起吃汉堡"的这个场景中,因为有很多人在等待🕙, 使用并发系统更有意义⏸🔀⏯.
|
||||
|
||||
@@ -253,7 +253,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
你可以同时拥有并行性和异步性,你可以获得比大多数经过测试的 NodeJS 框架更高的性能,并且与 Go 不相上下, Go 是一种更接近于 C 的编译语言(<a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">全部归功于 Starlette</a>)。
|
||||
|
||||
### 并发比并行好吗?
|
||||
### 并发比并行好吗? { #is-concurrency-better-than-parallelism }
|
||||
|
||||
不!这不是故事的本意。
|
||||
|
||||
@@ -277,7 +277,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和
|
||||
|
||||
在这个场景中,每个清洁工(包括你)都将是一个处理器,完成这个工作的一部分。
|
||||
|
||||
由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 <abbr title="Central Processing Unit">CPU</abbr> 完成的,所以他们称这些问题为"CPU 密集型"。
|
||||
由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 <abbr title="Central Processing Unit - 中央处理器">CPU</abbr> 完成的,所以他们称这些问题为"CPU 密集型"。
|
||||
|
||||
---
|
||||
|
||||
@@ -290,7 +290,7 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。
|
||||
* **机器学习**: 它通常需要大量的"矩阵"和"向量"乘法。想象一个包含数字的巨大电子表格,并同时将所有数字相乘;
|
||||
* **深度学习**: 这是机器学习的一个子领域,同样适用。只是没有一个数字的电子表格可以相乘,而是一个庞大的数字集合,在很多情况下,你需要使用一个特殊的处理器来构建和使用这些模型。
|
||||
|
||||
### 并发 + 并行: Web + 机器学习
|
||||
### 并发 + 并行: Web + 机器学习 { #concurrency-parallelism-web-machine-learning }
|
||||
|
||||
使用 **FastAPI**,你可以利用 Web 开发中常见的并发机制的优势(NodeJS 的主要吸引力)。
|
||||
|
||||
@@ -300,7 +300,7 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。
|
||||
|
||||
了解如何在生产环境中实现这种并行性,可查看此文 [Deployment](deployment/index.md){.internal-link target=_blank}。
|
||||
|
||||
## `async` 和 `await`
|
||||
## `async` 和 `await` { #async-and-await }
|
||||
|
||||
现代版本的 Python 有一种非常直观的方式来定义异步代码。这使它看起来就像正常的"顺序"代码,并在适当的时候"等待"。
|
||||
|
||||
@@ -316,16 +316,16 @@ burgers = await get_burgers(2)
|
||||
|
||||
```Python hl_lines="1"
|
||||
async def get_burgers(number: int):
|
||||
# Do some asynchronous stuff to create the burgers
|
||||
# 执行一些异步操作来制作汉堡
|
||||
return burgers
|
||||
```
|
||||
|
||||
...而不是 `def`:
|
||||
|
||||
```Python hl_lines="2"
|
||||
# This is not asynchronous
|
||||
# 这不是异步的
|
||||
def get_sequential_burgers(number: int):
|
||||
# Do some sequential stuff to create the burgers
|
||||
# 执行一些顺序操作来制作汉堡
|
||||
return burgers
|
||||
```
|
||||
|
||||
@@ -334,7 +334,7 @@ def get_sequential_burgers(number: int):
|
||||
当你想调用一个 `async def` 函数时,你必须"等待"它。因此,这不会起作用:
|
||||
|
||||
```Python
|
||||
# This won't work, because get_burgers was defined with: async def
|
||||
# 这样不行,因为 get_burgers 是用 async def 定义的
|
||||
burgers = get_burgers(2)
|
||||
```
|
||||
|
||||
@@ -349,7 +349,7 @@ async def read_burgers():
|
||||
return burgers
|
||||
```
|
||||
|
||||
### 更多技术细节
|
||||
### 更多技术细节 { #more-technical-details }
|
||||
|
||||
你可能已经注意到,`await` 只能在 `async def` 定义的函数内部使用。
|
||||
|
||||
@@ -361,7 +361,7 @@ async def read_burgers():
|
||||
|
||||
但如果你想在没有 FastAPI 的情况下使用 `async` / `await`,则可以这样做。
|
||||
|
||||
### 编写自己的异步代码
|
||||
### 编写自己的异步代码 { #write-your-own-async-code }
|
||||
|
||||
Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 实现的,这使得它们可以兼容 Python 的标准库 <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> 和 <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>。
|
||||
|
||||
@@ -371,7 +371,7 @@ Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/
|
||||
|
||||
我(指原作者 —— 译者注)基于 AnyIO 新建了一个库,作为一个轻量级的封装层,用来优化类型注解,同时提供了更好的**自动补全**、**内联错误提示**等功能。这个库还附带了一个友好的入门指南和教程,能帮助你**理解**并编写**自己的异步代码**:<a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>。如果你有**结合使用异步代码和常规**(阻塞/同步)代码的需求,这个库会特别有用。
|
||||
|
||||
### 其他形式的异步代码
|
||||
### 其他形式的异步代码 { #other-forms-of-asynchronous-code }
|
||||
|
||||
这种使用 `async` 和 `await` 的风格在语言中相对较新。
|
||||
|
||||
@@ -385,13 +385,13 @@ Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/
|
||||
|
||||
在以前版本的 NodeJS / 浏览器 JavaScript 中,你会使用"回调",因此也可能导致“回调地狱”。
|
||||
|
||||
## 协程
|
||||
## 协程 { #coroutines }
|
||||
|
||||
**协程**只是 `async def` 函数返回的一个非常奇特的东西的称呼。Python 知道它有点像一个函数,它可以启动,也会在某个时刻结束,而且它可能会在内部暂停 ⏸ ,只要内部有一个 `await`。
|
||||
|
||||
通过使用 `async` 和 `await` 的异步代码的所有功能大多数被概括为"协程"。它可以与 Go 的主要关键特性 "Goroutines" 相媲美。
|
||||
|
||||
## 结论
|
||||
## 结论 { #conclusion }
|
||||
|
||||
让我们再来回顾下上文所说的:
|
||||
|
||||
@@ -401,9 +401,9 @@ Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/
|
||||
|
||||
所有这些使得 FastAPI(通过 Starlette)如此强大,也是它拥有如此令人印象深刻的性能的原因。
|
||||
|
||||
## 非常技术性的细节
|
||||
## 非常技术性的细节 { #very-technical-details }
|
||||
|
||||
/// warning
|
||||
/// warning | 警告
|
||||
|
||||
你可以跳过这里。
|
||||
|
||||
@@ -413,23 +413,23 @@ Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/
|
||||
|
||||
///
|
||||
|
||||
### 路径操作函数
|
||||
### 路径操作函数 { #path-operation-functions }
|
||||
|
||||
当你使用 `def` 而不是 `async def` 来声明一个*路径操作函数*时,它运行在外部的线程池中并等待其结果,而不是直接调用(因为它会阻塞服务器)。
|
||||
|
||||
如果你使用过另一个不以上述方式工作的异步框架,并且你习惯于用普通的 `def` 定义普通的仅计算路径操作函数,以获得微小的性能增益(大约100纳秒),请注意,在 FastAPI 中,效果将完全相反。在这些情况下,最好使用 `async def`,除非路径操作函数内使用执行阻塞 <abbr title="输入/输出:磁盘读写,网络通讯.">I/O</abbr> 的代码。
|
||||
如果你使用过另一个不以上述方式工作的异步框架,并且你习惯于用普通的 `def` 定义普通的仅计算路径操作函数,以获得微小的性能增益(大约100纳秒),请注意,在 FastAPI 中,效果将完全相反。在这些情况下,最好使用 `async def`,除非路径操作函数内使用执行阻塞 <abbr title="Input/Output - 输入/输出:磁盘读写,网络通信。">I/O</abbr> 的代码。
|
||||
|
||||
在这两种情况下,与你之前的框架相比,**FastAPI** 可能[仍然很快](index.md#_11){.internal-link target=_blank}。
|
||||
在这两种情况下,与你之前的框架相比,**FastAPI** 可能[仍然很快](index.md#performance){.internal-link target=_blank}。
|
||||
|
||||
### 依赖
|
||||
### 依赖 { #dependencies }
|
||||
|
||||
这同样适用于[依赖](tutorial/dependencies/index.md){.internal-link target=_blank}。如果一个依赖是标准的 `def` 函数而不是 `async def`,它将被运行在外部线程池中。
|
||||
|
||||
### 子依赖
|
||||
### 子依赖 { #sub-dependencies }
|
||||
|
||||
你可以拥有多个相互依赖的依赖以及[子依赖](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} (作为函数的参数),它们中的一些可能是通过 `async def` 声明,也可能是通过 `def` 声明。它们仍然可以正常工作,这些通过 `def` 声明的函数将会在外部线程中调用(来自线程池),而不是"被等待"。
|
||||
|
||||
### 其他函数
|
||||
### 其他函数 { #other-utility-functions }
|
||||
|
||||
你可直接调用通过 `def` 或 `async def` 创建的任何其他函数,FastAPI 不会影响你调用它们的方式。
|
||||
|
||||
@@ -441,4 +441,4 @@ Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/
|
||||
|
||||
再次提醒,这些是非常技术性的细节,如果你来搜索它可能对你有用。
|
||||
|
||||
否则,你最好应该遵守的指导原则<a href="#_1">赶时间吗?</a>.
|
||||
否则,你最好应该遵守的指导原则<a href="#in-a-hurry">赶时间吗?</a>.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 基准测试
|
||||
# 基准测试 { #benchmarks }
|
||||
|
||||
第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">可用的最快的 Python 框架之一</a>,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*)
|
||||
第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">可用的最快的 Python 框架之一</a>,仅次于 Starlette 和 Uvicorn 本身(由 FastAPI 内部使用)。
|
||||
|
||||
但是在查看基准得分和对比时,请注意以下几点。
|
||||
|
||||
## 基准测试和速度
|
||||
## 基准测试和速度 { #benchmarks-and-speed }
|
||||
|
||||
当你查看基准测试时,几个不同类型的工具被等效地做比较是很常见的情况。
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
|
||||
* **Uvicorn**:
|
||||
* 具有最佳性能,因为除了服务器本身外,它没有太多额外的代码。
|
||||
* 您不会直接在 Uvicorn 中编写应用程序。这意味着您的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果您这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。
|
||||
* 如果要对比与 Uvicorn 对标的服务器,请将其与 Daphne,Hypercorn,uWSGI等应用服务器进行比较。
|
||||
* 你不会直接在 Uvicorn 中编写应用程序。这意味着你的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果你这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。
|
||||
* 如果你要对比 Uvicorn,请将其与 Daphne,Hypercorn,uWSGI 等应用服务器进行比较。
|
||||
* **Starlette**:
|
||||
* 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。
|
||||
* 但它为您提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。
|
||||
* 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn 运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。
|
||||
* 但它为你提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。
|
||||
* 如果想对比与 Starlette 对标的开发框架,请将其与 Sanic,Flask,Django 等网络框架(或微框架)进行比较。
|
||||
* **FastAPI**:
|
||||
* 与 Starlette 使用 Uvicorn 一样,由于 **FastAPI** 使用 Starlette,因此 FastAPI 不能比 Starlette 更快。
|
||||
* FastAPI 在 Starlette 基础上提供了更多功能。例如在开发 API 时,所需的数据验证和序列化功能。FastAPI 可以帮助您自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。
|
||||
* 如果您不使用 FastAPI 而直接使用 Starlette(或诸如 Sanic,Flask,Responder 等其它工具),您则要自己实现所有的数据验证和序列化。那么最终您的应用程序会和使用 FastAPI 构建的程序有相同的开销。一般这种数据验证和序列化的操作在您应用程序的代码中会占很大比重。
|
||||
* 因此,通过使用 FastAPI 意味着您可以节省开发时间,减少编码错误,用更少的编码实现其功能,并且相比不使用 FastAPI 您很大可能会获得相同或更好的性能(因为那样您必须在代码中实现所有相同的功能)。
|
||||
* 如果您想对比与 FastAPI 对标的开发框架,请与能够提供数据验证,序列化和带有自动文档生成的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证,序列化和自动化文档的 Flask-apispec,NestJS,Molten 等。
|
||||
* FastAPI 在 Starlette 基础上提供了更多功能。例如在开发 API 时,所需的数据验证和序列化功能。FastAPI 可以帮助你自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。
|
||||
* 如果你不使用 FastAPI 而直接使用 Starlette(或诸如 Sanic,Flask,Responder 等其它工具),你则要自己实现所有的数据验证和序列化。那么最终你的应用程序会和使用 FastAPI 构建的程序有相同的开销。一般这种数据验证和序列化的操作在你应用程序的代码中会占很大比重。
|
||||
* 因此,通过使用 FastAPI 意味着你可以节省开发时间,减少编码错误,用更少的编码实现其功能,并且相比不使用 FastAPI 你很大可能会获得相同或更好的性能(因为那样你必须在代码中实现所有相同的功能)。
|
||||
* 如果你想对比 FastAPI,请与能够提供数据验证、序列化和文档的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证、序列化和自动化文档的 Flask-apispec,NestJS,Molten 等。
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
# 在云上部署 FastAPI
|
||||
# 在云服务商上部署 FastAPI { #deploy-fastapi-on-cloud-providers }
|
||||
|
||||
您几乎可以使用**任何云服务商**来部署 FastAPI 应用程序。
|
||||
你几乎可以使用**任何云服务商**来部署你的 FastAPI 应用。
|
||||
|
||||
在大多数情况下,主要的云服务商都有部署 FastAPI 的指南。
|
||||
在大多数情况下,主流云服务商都有部署 FastAPI 的指南。
|
||||
|
||||
## 云服务商 - 赞助商
|
||||
## FastAPI Cloud { #fastapi-cloud }
|
||||
|
||||
一些云服务商 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这确保了FastAPI 及其**生态系统**持续健康地**发展**。
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** 由 **FastAPI** 背后的同一作者与团队打造。
|
||||
|
||||
这表明了他们对 FastAPI 及其**社区**(您)的真正承诺,因为他们不仅想为您提供**良好的服务**,而且还想确保您拥有一个**良好且健康的框架**:FastAPI。 🙇
|
||||
它简化了**构建**、**部署**和**访问** API 的流程,几乎不费力。
|
||||
|
||||
您可能想尝试他们的服务并阅读他们的指南.
|
||||
它把使用 FastAPI 构建应用时相同的**开发者体验**带到了将应用**部署**到云上的过程。🎉
|
||||
|
||||
FastAPI Cloud 是 *FastAPI and friends* 开源项目的主要赞助方和资金提供者。✨
|
||||
|
||||
## 云服务商 - 赞助商 { #cloud-providers-sponsors }
|
||||
|
||||
还有一些云服务商也会 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨。🙇
|
||||
|
||||
你也可以考虑按照他们的指南尝试他们的服务:
|
||||
|
||||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a>
|
||||
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 部署概念
|
||||
# 部署概念 { #deployments-concepts }
|
||||
|
||||
在部署 **FastAPI** 应用程序或任何类型的 Web API 时,有几个概念值得了解,通过掌握这些概念您可以找到**最合适的**方法来**部署您的应用程序**。
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
我们接下来了解它们将如何影响**部署**。
|
||||
|
||||
我们的最终目标是能够以**安全**的方式**为您的 API 客户端**提供服务,同时要**避免中断**,并且尽可能高效地利用**计算资源**( 例如服务器CPU资源)。 🚀
|
||||
我们的最终目标是能够以**安全**的方式**为您的 API 客户端**提供服务,同时要**避免中断**,并且尽可能高效地利用**计算资源**(例如远程服务器/虚拟机)。 🚀
|
||||
|
||||
我将在这里告诉您更多关于这些**概念**的信息,希望能给您提供**直觉**来决定如何在非常不同的环境中部署 API,甚至在是尚不存在的**未来**的环境里。
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
但现在,让我们仔细看一下这些重要的**概念**。 这些概念也适用于任何其他类型的 Web API。 💡
|
||||
|
||||
## 安全性 - HTTPS
|
||||
## 安全性 - HTTPS { #security-https }
|
||||
|
||||
在[上一章有关 HTTPS](https.md){.internal-link target=_blank} 中,我们了解了 HTTPS 如何为您的 API 提供加密。
|
||||
|
||||
@@ -31,21 +31,20 @@
|
||||
|
||||
并且必须有某个东西负责**更新 HTTPS 证书**,它可以是相同的组件,也可以是不同的组件。
|
||||
|
||||
|
||||
### HTTPS 示例工具
|
||||
### HTTPS 示例工具 { #example-tools-for-https }
|
||||
|
||||
您可以用作 TLS 终止代理的一些工具包括:
|
||||
|
||||
* Traefik
|
||||
* 自动处理证书更新 ✨
|
||||
* 自动处理证书更新 ✨
|
||||
* Caddy
|
||||
* 自动处理证书更新 ✨
|
||||
* 自动处理证书更新 ✨
|
||||
* Nginx
|
||||
* 使用 Certbot 等外部组件进行证书更新
|
||||
* 使用 Certbot 等外部组件进行证书更新
|
||||
* HAProxy
|
||||
* 使用 Certbot 等外部组件进行证书更新
|
||||
* 带有 Ingress Controller(如Nginx) 的 Kubernetes
|
||||
* 使用诸如 cert-manager 之类的外部组件来进行证书更新
|
||||
* 使用 Certbot 等外部组件进行证书更新
|
||||
* 带有 Ingress Controller(如 Nginx) 的 Kubernetes
|
||||
* 使用诸如 cert-manager 之类的外部组件来进行证书更新
|
||||
* 由云服务商内部处理,作为其服务的一部分(请阅读下文👇)
|
||||
|
||||
另一种选择是您可以使用**云服务**来完成更多工作,包括设置 HTTPS。 它可能有一些限制或向您收取更多费用等。但在这种情况下,您不必自己设置 TLS 终止代理。
|
||||
@@ -56,11 +55,11 @@
|
||||
|
||||
接下来要考虑的概念都是关于运行实际 API 的程序(例如 Uvicorn)。
|
||||
|
||||
## 程序和进程
|
||||
## 程序和进程 { #program-and-process }
|
||||
|
||||
我们将讨论很多关于正在运行的“**进程**”的内容,因此弄清楚它的含义以及与“**程序**”这个词有什么区别是很有用的。
|
||||
|
||||
### 什么是程序
|
||||
### 什么是程序 { #what-is-a-program }
|
||||
|
||||
**程序**这个词通常用来描述很多东西:
|
||||
|
||||
@@ -68,12 +67,12 @@
|
||||
* 操作系统可以**执行**的**文件**,例如:`python`、`python.exe`或`uvicorn`。
|
||||
* 在操作系统上**运行**、使用CPU 并将内容存储在内存上的特定程序。 这也被称为**进程**。
|
||||
|
||||
### 什么是进程
|
||||
### 什么是进程 { #what-is-a-process }
|
||||
|
||||
**进程** 这个词通常以更具体的方式使用,仅指在操作系统中运行的东西(如上面的最后一点):
|
||||
|
||||
* 在操作系统上**运行**的特定程序。
|
||||
* 这不是指文件,也不是指代码,它**具体**指的是操作系统正在**执行**和管理的东西。
|
||||
* 这不是指文件,也不是指代码,它**具体**指的是操作系统正在**执行**和管理的东西。
|
||||
* 任何程序,任何代码,**只有在执行时才能做事**。 因此,是当有**进程正在运行**时。
|
||||
* 该进程可以由您或操作系统**终止**(或“杀死”)。 那时,它停止运行/被执行,并且它可以**不再做事情**。
|
||||
* 您计算机上运行的每个应用程序背后都有一些进程,每个正在运行的程序,每个窗口等。并且通常在计算机打开时**同时**运行许多进程。
|
||||
@@ -89,13 +88,13 @@
|
||||
|
||||
现在我们知道了术语“进程”和“程序”之间的区别,让我们继续讨论部署。
|
||||
|
||||
## 启动时运行
|
||||
## 启动时运行 { #running-on-startup }
|
||||
|
||||
在大多数情况下,当您创建 Web API 时,您希望它**始终运行**、不间断,以便您的客户端始终可以访问它。 这是当然的,除非您有特定原因希望它仅在某些情况下运行,但大多数时候您希望它不断运行并且**可用**。
|
||||
|
||||
### 在远程服务器中
|
||||
### 在远程服务器中 { #in-a-remote-server }
|
||||
|
||||
当您设置远程服务器(云服务器、虚拟机等)时,您可以做的最简单的事情就是手动运行 Uvicorn(或类似的),就像本地开发时一样。
|
||||
当您设置远程服务器(云服务器、虚拟机等)时,您可以做的最简单的事情就是使用 `fastapi run`(它使用 Uvicorn)或类似方式,手动运行,就像本地开发时一样。
|
||||
|
||||
它将会在**开发过程中**发挥作用并发挥作用。
|
||||
|
||||
@@ -103,16 +102,15 @@
|
||||
|
||||
如果服务器重新启动(例如更新后或从云提供商迁移后),您可能**不会注意到它**。 因此,您甚至不知道必须手动重新启动该进程。 所以,你的 API 将一直处于挂掉的状态。 😱
|
||||
|
||||
|
||||
### 启动时自动运行
|
||||
### 启动时自动运行 { #run-automatically-on-startup }
|
||||
|
||||
一般来说,您可能希望服务器程序(例如 Uvicorn)在服务器启动时自动启动,并且不需要任何**人为干预**,让进程始终与您的 API 一起运行(例如 Uvicorn 运行您的 FastAPI 应用程序) 。
|
||||
|
||||
### 单独的程序
|
||||
### 单独的程序 { #separate-program }
|
||||
|
||||
为了实现这一点,您通常会有一个**单独的程序**来确保您的应用程序在启动时运行。 在许多情况下,它还可以确保其他组件或应用程序也运行,例如数据库。
|
||||
|
||||
### 启动时运行的示例工具
|
||||
### 启动时运行的示例工具 { #example-tools-to-run-at-startup }
|
||||
|
||||
可以完成这项工作的工具的一些示例是:
|
||||
|
||||
@@ -127,44 +125,43 @@
|
||||
|
||||
我将在接下来的章节中为您提供更具体的示例。
|
||||
|
||||
|
||||
## 重新启动
|
||||
## 重新启动 { #restarts }
|
||||
|
||||
与确保应用程序在启动时运行类似,您可能还想确保它在挂掉后**重新启动**。
|
||||
|
||||
### 我们会犯错误
|
||||
### 我们会犯错误 { #we-make-mistakes }
|
||||
|
||||
作为人类,我们总是会犯**错误**。 软件几乎*总是*在不同的地方隐藏着**bug**。 🐛
|
||||
|
||||
作为开发人员,当我们发现这些bug并实现新功能(也可能添加新bug😅)时,我们会不断改进代码。
|
||||
|
||||
### 自动处理小错误
|
||||
### 自动处理小错误 { #small-errors-automatically-handled }
|
||||
|
||||
使用 FastAPI 构建 Web API 时,如果我们的代码中存在错误,FastAPI 通常会将其包含到触发错误的单个请求中。 🛡
|
||||
|
||||
对于该请求,客户端将收到 **500 内部服务器错误**,但应用程序将继续处理下一个请求,而不是完全崩溃。
|
||||
|
||||
### 更大的错误 - 崩溃
|
||||
### 更大的错误 - 崩溃 { #bigger-errors-crashes }
|
||||
|
||||
尽管如此,在某些情况下,我们编写的一些代码可能会导致整个应用程序崩溃,从而导致 Uvicorn 和 Python 崩溃。 💥
|
||||
|
||||
尽管如此,您可能不希望应用程序因为某个地方出现错误而保持死机状态,您可能希望它**继续运行**,至少对于未破坏的*路径操作*。
|
||||
|
||||
### 崩溃后重新启动
|
||||
### 崩溃后重新启动 { #restart-after-crash }
|
||||
|
||||
但在那些严重错误导致正在运行的**进程**崩溃的情况下,您需要一个外部组件来负责**重新启动**进程,至少尝试几次......
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
...尽管如果整个应用程序只是**立即崩溃**,那么永远重新启动它可能没有意义。 但在这些情况下,您可能会在开发过程中注意到它,或者至少在部署后立即注意到它。
|
||||
|
||||
因此,让我们关注主要情况,在**未来**的某些特定情况下,它可能会完全崩溃,但重新启动它仍然有意义。
|
||||
因此,让我们关注主要情况,在**未来**的某些特定情况下,它可能会完全崩溃,但重新启动它仍然有意义。
|
||||
|
||||
///
|
||||
|
||||
您可能希望让这个东西作为 **外部组件** 负责重新启动您的应用程序,因为到那时,使用 Uvicorn 和 Python 的同一应用程序已经崩溃了,因此同一应用程序的相同代码中没有东西可以对此做出什么。
|
||||
|
||||
### 自动重新启动的示例工具
|
||||
### 自动重新启动的示例工具 { #example-tools-to-restart-automatically }
|
||||
|
||||
在大多数情况下,用于**启动时运行程序**的同一工具也用于处理自动**重新启动**。
|
||||
|
||||
@@ -173,25 +170,25 @@
|
||||
* Docker
|
||||
* Kubernetes
|
||||
* Docker Compose
|
||||
* Docker in Swarm mode
|
||||
* Docker in Swarm Mode
|
||||
* Systemd
|
||||
* Supervisor
|
||||
* 作为其服务的一部分由云提供商内部处理
|
||||
* 其他的...
|
||||
|
||||
## 复制 - 进程和内存
|
||||
## 复制 - 进程和内存 { #replication-processes-and-memory }
|
||||
|
||||
对于 FastAPI 应用程序,使用像 Uvicorn 这样的服务器程序,在**一个进程**中运行一次就可以同时为多个客户端提供服务。
|
||||
对于 FastAPI 应用程序,使用像 `fastapi` 命令(运行 Uvicorn)这样的服务器程序,在**一个进程**中运行一次就可以同时为多个客户端提供服务。
|
||||
|
||||
但在许多情况下,您会希望同时运行多个工作进程。
|
||||
|
||||
### 多进程 - Workers
|
||||
### 多进程 - Workers { #multiple-processes-workers }
|
||||
|
||||
如果您的客户端数量多于单个进程可以处理的数量(例如,如果虚拟机不是太大),并且服务器的 CPU 中有 **多个核心**,那么您可以让 **多个进程** 运行 同时处理同一个应用程序,并在它们之间分发所有请求。
|
||||
如果您的客户端数量多于单个进程可以处理的数量(例如,如果虚拟机不是太大),并且服务器的 CPU 中有 **多个核心**,那么您可以让 **多个进程** 同时运行同一个应用程序,并在它们之间分发所有请求。
|
||||
|
||||
当您运行同一 API 程序的**多个进程**时,它们通常称为 **workers**。
|
||||
|
||||
### 工作进程和端口
|
||||
### 工作进程和端口 { #worker-processes-and-ports }
|
||||
|
||||
还记得文档 [About HTTPS](https.md){.internal-link target=_blank} 中只有一个进程可以侦听服务器中的端口和 IP 地址的一种组合吗?
|
||||
|
||||
@@ -199,20 +196,19 @@
|
||||
|
||||
因此,为了能够同时拥有**多个进程**,必须有一个**单个进程侦听端口**,然后以某种方式将通信传输到每个工作进程。
|
||||
|
||||
### 每个进程的内存
|
||||
### 每个进程的内存 { #memory-per-process }
|
||||
|
||||
现在,当程序将内容加载到内存中时,例如,将机器学习模型加载到变量中,或者将大文件的内容加载到变量中,所有这些都会消耗服务器的一点内存 (RAM) 。
|
||||
|
||||
多个进程通常**不共享任何内存**。 这意味着每个正在运行的进程都有自己的东西、变量和内存。 如果您的代码消耗了大量内存,**每个进程**将消耗等量的内存。
|
||||
|
||||
### 服务器内存
|
||||
### 服务器内存 { #server-memory }
|
||||
|
||||
例如,如果您的代码加载 **1 GB 大小**的机器学习模型,则当您使用 API 运行一个进程时,它将至少消耗 1 GB RAM。 如果您启动 **4 个进程**(4 个工作进程),每个进程将消耗 1 GB RAM。 因此,您的 API 总共将消耗 **4 GB RAM**。
|
||||
|
||||
如果您的远程服务器或虚拟机只有 3 GB RAM,尝试加载超过 4 GB RAM 将导致问题。 🚨
|
||||
|
||||
|
||||
### 多进程 - 一个例子
|
||||
### 多进程 - 一个例子 { #multiple-processes-an-example }
|
||||
|
||||
在此示例中,有一个 **Manager Process** 启动并控制两个 **Worker Processes**。
|
||||
|
||||
@@ -224,11 +220,11 @@
|
||||
|
||||
当然,除了您的应用程序之外,同一台机器可能还运行**其他进程**。
|
||||
|
||||
一个有趣的细节是,随着时间的推移,每个进程使用的 **CPU 百分比可能会发生很大变化,但内存 (RAM) 通常会或多或少保持稳定**。
|
||||
一个有趣的细节是,随着时间的推移,每个进程使用的 **CPU 百分比**可能会发生很大变化,但**内存 (RAM)** 通常会或多或少保持**稳定**。
|
||||
|
||||
如果您有一个每次执行相当数量的计算的 API,并且您有很多客户端,那么 **CPU 利用率** 可能也会保持稳定(而不是不断快速上升和下降)。
|
||||
|
||||
### 复制工具和策略示例
|
||||
### 复制工具和策略示例 { #examples-of-replication-tools-and-strategies }
|
||||
|
||||
可以通过多种方法来实现这一目标,我将在接下来的章节中向您详细介绍具体策略,例如在谈论 Docker 和容器时。
|
||||
|
||||
@@ -236,26 +232,22 @@
|
||||
|
||||
以下是一些可能的组合和策略:
|
||||
|
||||
* **Gunicorn** 管理 **Uvicorn workers**
|
||||
* Gunicorn 将是监听 **IP** 和 **端口** 的 **进程管理器**,复制将通过 **多个 Uvicorn 工作进程** 进行
|
||||
* **Uvicorn** 管理 **Uvicorn workers**
|
||||
* 一个 Uvicorn **进程管理器** 将监听 **IP** 和 **端口**,并且它将启动 **多个 Uvicorn 工作进程**
|
||||
* 带有 `--workers` 的 **Uvicorn**
|
||||
* 一个 Uvicorn **进程管理器** 将监听 **IP** 和 **端口**,并且它将启动 **多个 Uvicorn 工作进程**。
|
||||
* **Kubernetes** 和其他分布式 **容器系统**
|
||||
* **Kubernetes** 层中的某些东西将侦听 **IP** 和 **端口**。 复制将通过拥有**多个容器**,每个容器运行**一个 Uvicorn 进程**
|
||||
* **Kubernetes** 层中的某些东西将侦听 **IP** 和 **端口**。 复制将通过拥有**多个容器**,每个容器运行**一个 Uvicorn 进程**。
|
||||
* **云服务** 为您处理此问题
|
||||
* 云服务可能**为您处理复制**。 它可能会让您定义 **要运行的进程**,或要使用的 **容器映像**,在任何情况下,它很可能是 **单个 Uvicorn 进程**,并且云服务将负责复制它。
|
||||
* 云服务可能**为您处理复制**。 它可能会让您定义 **要运行的进程**,或要使用的 **容器映像**,在任何情况下,它很可能是 **单个 Uvicorn 进程**,并且云服务将负责复制它。
|
||||
|
||||
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
如果这些关于 **容器**、Docker 或 Kubernetes 的内容还没有多大意义,请不要担心。
|
||||
|
||||
我将在以后的章节中向您详细介绍容器镜像、Docker、Kubernetes 等:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
|
||||
我将在以后的章节中向您详细介绍容器镜像、Docker、Kubernetes 等:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
## 启动之前的步骤
|
||||
## 启动之前的步骤 { #previous-steps-before-starting }
|
||||
|
||||
在很多情况下,您希望在**启动**应用程序之前执行一些步骤。
|
||||
|
||||
@@ -269,15 +261,15 @@
|
||||
|
||||
当然,也有一些情况,多次运行前面的步骤也没有问题,这样的话就好办多了。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
另外,请记住,根据您的设置,在某些情况下,您在开始应用程序之前**可能甚至不需要任何先前的步骤**。
|
||||
|
||||
在这种情况下,您就不必担心这些。 🤷
|
||||
在这种情况下,您就不必担心这些。 🤷
|
||||
|
||||
///
|
||||
|
||||
### 前面步骤策略的示例
|
||||
### 前面步骤策略的示例 { #examples-of-previous-steps-strategies }
|
||||
|
||||
这将在**很大程度上取决于您部署系统的方式**,并且可能与您启动程序、处理重启等的方式有关。
|
||||
|
||||
@@ -285,15 +277,15 @@
|
||||
|
||||
* Kubernetes 中的“Init Container”在应用程序容器之前运行
|
||||
* 一个 bash 脚本,运行前面的步骤,然后启动您的应用程序
|
||||
* 您仍然需要一种方法来启动/重新启动 bash 脚本、检测错误等。
|
||||
* 您仍然需要一种方法来启动/重新启动 bash 脚本、检测错误等。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
我将在以后的章节中为您提供使用容器执行此操作的更具体示例:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
## 资源利用率
|
||||
## 资源利用率 { #resource-utilization }
|
||||
|
||||
您的服务器是一个**资源**,您可以通过您的程序消耗或**利用**CPU 上的计算时间以及可用的 RAM 内存。
|
||||
|
||||
@@ -313,8 +305,7 @@
|
||||
|
||||
您可以使用“htop”等简单工具来查看服务器中使用的 CPU 和 RAM 或每个进程使用的数量。 或者您可以使用更复杂的监控工具,这些工具可能分布在服务器等上。
|
||||
|
||||
|
||||
## 回顾
|
||||
## 回顾 { #recap }
|
||||
|
||||
您在这里阅读了一些在决定如何部署应用程序时可能需要牢记的主要概念:
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# 容器中的 FastAPI - Docker
|
||||
# 容器中的 FastAPI - Docker { #fastapi-in-containers-docker }
|
||||
|
||||
部署 FastAPI 应用程序时,常见的方法是构建 **Linux 容器镜像**。 通常使用 <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a> 完成。 然后,你可以通过几种可能的方式之一部署该容器镜像。
|
||||
部署 FastAPI 应用时,常见做法是构建一个**Linux 容器镜像**。通常使用 <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a> 实现。然后你可以用几种方式之一部署该镜像。
|
||||
|
||||
使用 Linux 容器有几个优点,包括**安全性**、**可复制性**、**简单性**等。
|
||||
使用 Linux 容器有多种优势,包括**安全性**、**可复制性**、**简单性**等。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
赶时间并且已经知道这些东西了? 跳转到下面的 [`Dockerfile` 👇](#fastapi-docker_1)。
|
||||
赶时间并且已经了解这些?直接跳到下面的 [`Dockerfile` 👇](#build-a-docker-image-for-fastapi)。
|
||||
|
||||
///
|
||||
|
||||
<details>
|
||||
<summary>Dockerfile Preview 👀</summary>
|
||||
<summary>Dockerfile 预览 👀</summary>
|
||||
|
||||
```Dockerfile
|
||||
FROM python:3.9
|
||||
@@ -24,140 +24,127 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
COPY ./app /code/app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
|
||||
# If running behind a proxy like Nginx or Traefik add --proxy-headers
|
||||
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
|
||||
# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 什么是容器
|
||||
## 什么是容器 { #what-is-a-container }
|
||||
|
||||
容器(主要是 Linux 容器)是一种非常**轻量级**的打包应用程序的方式,其包括所有依赖项和必要的文件,同时它们可以和同一系统中的其他容器(或者其他应用程序/组件)相互隔离。
|
||||
容器(主要是 Linux 容器)是一种非常**轻量**的方式,用来打包应用及其所有依赖和必要文件,并让它们与同一系统中的其他容器(其他应用或组件)相互隔离。
|
||||
|
||||
Linux 容器使用宿主机(如物理服务器、虚拟机、云服务器等)的Linux 内核运行。 这意味着它们非常轻量(与模拟整个操作系统的完整虚拟机相比)。
|
||||
Linux 容器复用宿主机(物理机、虚拟机、云服务器等)的同一个 Linux 内核。这意味着它们非常轻量(相较于模拟整个操作系统的完整虚拟机)。
|
||||
|
||||
通过这样的方式,容器消耗**很少的资源**,与直接运行进程相当(虚拟机会消耗更多)。
|
||||
因此,容器消耗的**资源很少**,大致相当于直接运行进程(而虚拟机会多很多)。
|
||||
|
||||
容器的进程(通常只有一个)、文件系统和网络都运行在隔离的环境,这简化了部署、安全、开发等。
|
||||
容器还拥有各自**隔离**的运行进程(通常只有一个)、文件系统和网络,简化了部署、安全、开发等。
|
||||
|
||||
## 什么是容器镜像
|
||||
## 什么是容器镜像 { #what-is-a-container-image }
|
||||
|
||||
**容器**是从**容器镜像**运行的。
|
||||
|
||||
容器镜像是容器中文件、环境变量和默认命令/程序的**静态**版本。 **静态**这里的意思是容器**镜像**还没有运行,只是打包的文件和元数据。
|
||||
容器镜像是容器中所有文件、环境变量以及应该运行的默认命令/程序的一个**静态**版本。这里的**静态**指容器**镜像**本身并不在运行,仅仅是被打包的文件和元数据。
|
||||
|
||||
与存储静态内容的“**容器镜像**”相反,“**容器**”通常指正在运行的实例,即正在**执行的**。
|
||||
与存放静态内容的“**容器镜像**”相对,“**容器**”通常指一个正在运行的实例,即正在被**执行**的东西。
|
||||
|
||||
当**容器**启动并运行时(从**容器镜像**启动),它可以创建或更改文件、环境变量等。这些更改将仅存在于该容器中,而不会持久化到底层的容器镜像中(不会保存到磁盘)。
|
||||
当**容器**启动并运行(从**容器镜像**启动)后,它可以创建或修改文件、环境变量等。这些更改只存在于该容器中,不会持久化到底层的容器镜像中(不会写回磁盘)。
|
||||
|
||||
容器镜像相当于**程序**和文件,例如 `python`命令 和某些文件 如`main.py`。
|
||||
容器镜像可类比为**程序**文件及其内容,例如 `python` 和某个文件 `main.py`。
|
||||
|
||||
而**容器**本身(与**容器镜像**相反)是镜像的实际运行实例,相当于**进程**。 事实上,容器仅在有**进程运行**时才运行(通常它只是一个单独的进程)。 当容器中没有进程运行时,容器就会停止。
|
||||
而**容器**本身(相对**容器镜像**)就是该镜像的实际运行实例,可类比为**进程**。事实上,容器只有在有**进程在运行**时才处于运行状态(通常只有一个进程)。当容器中没有任何进程在运行时,容器就会停止。
|
||||
|
||||
## 容器镜像 { #container-images }
|
||||
|
||||
Docker 一直是创建和管理**容器镜像**与**容器**的主要工具之一。
|
||||
|
||||
## 容器镜像
|
||||
还有一个公共的 <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a>,其中为许多工具、环境、数据库和应用提供了预制的**官方容器镜像**。
|
||||
|
||||
Docker 一直是创建和管理**容器镜像**和**容器**的主要工具之一。
|
||||
|
||||
还有一个公共 <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> ,其中包含预制的 **官方容器镜像**, 适用于许多工具、环境、数据库和应用程序。
|
||||
|
||||
例如,有一个官方的 <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">Python 镜像</a>。
|
||||
|
||||
还有许多其他镜像用于不同的需要(例如数据库),例如:
|
||||
例如,有官方的 <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">Python 镜像</a>。
|
||||
|
||||
还有许多用于不同目的(如数据库)的镜像,例如:
|
||||
|
||||
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
|
||||
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
|
||||
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>, etc.
|
||||
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a> 等。
|
||||
|
||||
通过使用预制的容器镜像,可以很容易地**组合**并使用不同工具。例如,试用一个新的数据库。在大多数情况下,你可以直接使用**官方镜像**,只需通过环境变量配置即可。
|
||||
|
||||
通过使用预制的容器镜像,可以非常轻松地**组合**并使用不同的工具。 例如,尝试一个新的数据库。 在大多数情况下,你可以使用**官方镜像**,只需为其配置环境变量即可。
|
||||
这样,在很多场景中你可以学习容器和 Docker,并将这些知识复用到许多不同的工具和组件中。
|
||||
|
||||
这样,在许多情况下,你可以了解容器和 Docker,并通过许多不同的工具和组件重复使用这些知识。
|
||||
因此,你可以运行包含不同内容的**多个容器**,比如一个数据库、一个 Python 应用、一个带 React 前端的 Web 服务器,并通过它们的内部网络连接在一起。
|
||||
|
||||
因此,你可以运行带有不同内容的**多个容器**,例如数据库、Python 应用程序、带有 React 前端应用程序的 Web 服务器,并通过内部网络将它们连接在一起。
|
||||
所有容器管理系统(如 Docker 或 Kubernetes)都内置了这些网络功能。
|
||||
|
||||
所有容器管理系统(如 Docker 或 Kubernetes)都集成了这些网络功能。
|
||||
## 容器与进程 { #containers-and-processes }
|
||||
|
||||
## 容器和进程
|
||||
**容器镜像**通常在其元数据中包含在**容器**启动时应运行的默认程序或命令以及要传递给该程序的参数。这与命令行中做的事情非常相似。
|
||||
|
||||
**容器镜像**通常在其元数据中包含启动**容器**时应运行的默认程序或命令以及要传递给该程序的参数。 与在命令行中的情况非常相似。
|
||||
|
||||
当 **容器** 启动时,它将运行该命令/程序(尽管你可以覆盖它并使其运行不同的命令/程序)。
|
||||
当**容器**启动时,它将运行该命令/程序(尽管你可以覆盖它,让其运行不同的命令/程序)。
|
||||
|
||||
只要**主进程**(命令或程序)在运行,容器就在运行。
|
||||
|
||||
容器通常有一个**单个进程**,但也可以从主进程启动子进程,这样你就可以在同一个容器中拥有**多个进程**。
|
||||
容器通常只有**一个进程**,但也可以由主进程启动子进程,这样同一个容器中就会有**多个进程**。
|
||||
|
||||
但是,如果没有**至少一个正在运行的进程**,就不可能有一个正在运行的容器。 如果主进程停止,容器也会停止。
|
||||
但不可能在没有**至少一个运行中的进程**的情况下让容器保持运行。如果主进程停止,容器也会停止。
|
||||
|
||||
## 为 FastAPI 构建 Docker 镜像 { #build-a-docker-image-for-fastapi }
|
||||
|
||||
## 为 FastAPI 构建 Docker 镜像
|
||||
好啦,现在动手构建点东西!🚀
|
||||
|
||||
好吧,让我们现在构建一些东西! 🚀
|
||||
我将演示如何基于**官方 Python** 镜像,**从零开始**为 FastAPI 构建一个**Docker 镜像**。
|
||||
|
||||
我将向你展示如何基于 **官方 Python** 镜像 **从头开始** 为 FastAPI 构建 **Docker 镜像**。
|
||||
|
||||
这是你在**大多数情况**下想要做的,例如:
|
||||
这在**大多数情况**下都适用,例如:
|
||||
|
||||
* 使用 **Kubernetes** 或类似工具
|
||||
* 在 **Raspberry Pi** 上运行时
|
||||
* 使用可为你运行容器镜像的云服务等。
|
||||
* 运行在 **Raspberry Pi**
|
||||
* 使用某个为你运行容器镜像的云服务,等等
|
||||
|
||||
### 依赖项
|
||||
### 包依赖 { #package-requirements }
|
||||
|
||||
你通常会在某个文件中包含应用程序的**依赖项**。
|
||||
通常你会把应用的**包依赖**放在某个文件里。
|
||||
|
||||
具体做法取决于你**安装**这些依赖时所使用的工具。
|
||||
这主要取决于你用来**安装**这些依赖的工具。
|
||||
|
||||
最常见的方法是创建一个`requirements.txt`文件,其中每行包含一个包名称和它的版本。
|
||||
最常见的方式是使用 `requirements.txt` 文件,每行一个包名及其版本范围。
|
||||
|
||||
你当然也可以使用在[关于 FastAPI 版本](versions.md){.internal-link target=_blank} 中讲到的方法来设置版本范围。
|
||||
|
||||
例如,你的`requirements.txt`可能如下所示:
|
||||
当然,你也可以参考你在[关于 FastAPI 版本](versions.md){.internal-link target=_blank}中读到的思路来设置版本范围。
|
||||
|
||||
例如,你的 `requirements.txt` 可能是:
|
||||
|
||||
```
|
||||
fastapi>=0.68.0,<0.69.0
|
||||
pydantic>=1.8.0,<2.0.0
|
||||
uvicorn>=0.15.0,<0.16.0
|
||||
fastapi[standard]>=0.113.0,<0.114.0
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
```
|
||||
|
||||
你通常会使用`pip`安装这些依赖项:
|
||||
通常你会用 `pip` 安装这些依赖,例如:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install -r requirements.txt
|
||||
---> 100%
|
||||
Successfully installed fastapi pydantic uvicorn
|
||||
Successfully installed fastapi pydantic
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
还有其他文件格式和工具来定义和安装依赖项。
|
||||
|
||||
我将在下面的部分中向你展示一个使用 Poetry 的示例。 👇
|
||||
还有其他格式和工具可以定义并安装包依赖。
|
||||
|
||||
///
|
||||
|
||||
### 创建 **FastAPI** 代码
|
||||
|
||||
* 创建`app`目录并进入。
|
||||
* 创建一个空文件`__init__.py`。
|
||||
* 创建一个 `main.py` 文件:
|
||||
|
||||
### 编写 **FastAPI** 代码 { #create-the-fastapi-code }
|
||||
|
||||
* 创建 `app` 目录并进入
|
||||
* 创建空文件 `__init__.py`
|
||||
* 创建 `main.py`,内容如下:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
@@ -169,84 +156,114 @@ def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
### Dockerfile
|
||||
### Dockerfile { #dockerfile }
|
||||
|
||||
现在在相同的project目录创建一个名为`Dockerfile`的文件:
|
||||
现在在同一个项目目录下创建 `Dockerfile` 文件:
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
# (1)!
|
||||
FROM python:3.9
|
||||
|
||||
# (2)
|
||||
# (2)!
|
||||
WORKDIR /code
|
||||
|
||||
# (3)
|
||||
# (3)!
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# (4)
|
||||
# (4)!
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (5)
|
||||
# (5)!
|
||||
COPY ./app /code/app
|
||||
|
||||
# (6)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
# (6)!
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
1. 从官方Python基础镜像开始。
|
||||
1. 从官方 Python 基础镜像开始。
|
||||
|
||||
2. 将当前工作目录设置为`/code`。
|
||||
2. 将当前工作目录设置为 `/code`。
|
||||
|
||||
这是我们放置`requirements.txt`文件和`app`目录的位置。
|
||||
我们会把 `requirements.txt` 文件和 `app` 目录放在这里。
|
||||
|
||||
3. 将符合要求的文件复制到`/code`目录中。
|
||||
3. 将依赖文件复制到 `/code` 目录。
|
||||
|
||||
首先仅复制requirements.txt文件,而不复制其余代码。
|
||||
首先**只**复制依赖文件,不要复制其他代码。
|
||||
|
||||
由于此文件**不经常更改**,Docker 将检测到它并在这一步中使用**缓存**,从而为下一步启用缓存。
|
||||
因为这个文件**不常变化**,Docker 会检测并在此步骤使用**缓存**,从而也为下一步启用缓存。
|
||||
|
||||
4. 安装需求文件中的包依赖项。
|
||||
4. 安装依赖文件中的包依赖。
|
||||
|
||||
`--no-cache-dir` 选项告诉 `pip` 不要在本地保存下载的包,因为只有当 `pip` 再次运行以安装相同的包时才会这样,但在与容器一起工作时情况并非如此。
|
||||
`--no-cache-dir` 选项告诉 `pip` 不要在本地保存下载的包,只有当以后还要再次用 `pip` 安装相同包时才需要,但在容器场景下不是这样。
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
`--no-cache-dir` 仅与 `pip` 相关,与 Docker 或容器无关。
|
||||
`--no-cache-dir` 只和 `pip` 有关,与 Docker 或容器无关。
|
||||
|
||||
///
|
||||
///
|
||||
|
||||
`--upgrade` 选项告诉 `pip` 升级软件包(如果已经安装)。
|
||||
`--upgrade` 选项告诉 `pip` 如果包已安装则进行升级。
|
||||
|
||||
因为上一步复制文件可以被 **Docker 缓存** 检测到,所以此步骤也将 **使用 Docker 缓存**(如果可用)。
|
||||
由于上一步复制文件可能被 **Docker 缓存**检测到,因此这一步在可用时也会**使用 Docker 缓存**。
|
||||
|
||||
在开发过程中一次又一次构建镜像时,在此步骤中使用缓存将为你节省大量**时间**,而不是**每次**都**下载和安装**所有依赖项。
|
||||
在开发过程中反复构建镜像时,此步骤使用缓存可以为你**节省大量时间**,而不必**每次**都**下载并安装**所有依赖。
|
||||
|
||||
5. 将 `./app` 目录复制到 `/code` 目录。
|
||||
|
||||
5. 将“./app”目录复制到“/code”目录中。
|
||||
这里包含了所有**最常变化**的代码,因此 Docker **缓存**很难用于这一步或**其后的步骤**。
|
||||
|
||||
由于其中包含**更改最频繁**的所有代码,因此 Docker **缓存**不会轻易用于此操作或任何**后续步骤**。
|
||||
所以,把它放在 `Dockerfile` 的**靠后位置**,有助于优化容器镜像的构建时间。
|
||||
|
||||
因此,将其放在`Dockerfile`**接近最后**的位置非常重要,以优化容器镜像的构建时间。
|
||||
6. 设置使用 `fastapi run` 的**命令**(底层使用 Uvicorn)。
|
||||
|
||||
6. 设置**命令**来运行 `uvicorn` 服务器。
|
||||
`CMD` 接受一个字符串列表,每个字符串相当于你在命令行中用空格分隔输入的内容。
|
||||
|
||||
`CMD` 接受一个字符串列表,每个字符串都是你在命令行中输入的内容,并用空格分隔。
|
||||
该命令会从**当前工作目录**运行,也就是你用 `WORKDIR /code` 设置的 `/code` 目录。
|
||||
|
||||
该命令将从 **当前工作目录** 运行,即你上面使用`WORKDIR /code`设置的同一`/code`目录。
|
||||
/// tip | 提示
|
||||
|
||||
因为程序将从`/code`启动,并且其中包含你的代码的目录`./app`,所以**Uvicorn**将能够从`app.main`中查看并**import**`app`。
|
||||
|
||||
/// tip
|
||||
|
||||
通过单击代码中的每个数字气泡来查看每行的作用。 👆
|
||||
点击代码中的每个编号气泡查看每行的作用。👆
|
||||
|
||||
///
|
||||
|
||||
你现在应该具有如下目录结构:
|
||||
/// warning | 警告
|
||||
|
||||
务必**始终**使用 `CMD` 指令的**exec 形式**,如下所述。
|
||||
|
||||
///
|
||||
|
||||
#### 使用 `CMD` - Exec 形式 { #use-cmd-exec-form }
|
||||
|
||||
<a href="https://docs.docker.com/reference/dockerfile/#cmd" class="external-link" target="_blank">`CMD`</a> 指令有两种写法:
|
||||
|
||||
✅ **Exec** 形式:
|
||||
|
||||
```Dockerfile
|
||||
# ✅ 推荐
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
⛔️ **Shell** 形式:
|
||||
|
||||
```Dockerfile
|
||||
# ⛔️ 不要这样
|
||||
CMD fastapi run app/main.py --port 80
|
||||
```
|
||||
|
||||
务必使用**exec** 形式,以确保 FastAPI 可以优雅停机并触发[生命周期事件](../advanced/events.md){.internal-link target=_blank}。
|
||||
|
||||
你可以在 <a href="https://docs.docker.com/reference/dockerfile/#shell-and-exec-form" class="external-link" target="_blank">Docker 文档(Shell 与 Exec 形式)</a>中了解更多。
|
||||
|
||||
在使用 `docker compose` 时这一点尤为明显。更多技术细节参见该 FAQ:<a href="https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop" class="external-link" target="_blank">为什么我的服务需要 10 秒才能重新创建或停止?</a>
|
||||
|
||||
#### 目录结构 { #directory-structure }
|
||||
|
||||
此时你的目录结构应类似:
|
||||
|
||||
```
|
||||
.
|
||||
├── app
|
||||
@@ -256,56 +273,53 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
#### 在 TLS 终止代理后面 { #behind-a-tls-termination-proxy }
|
||||
|
||||
#### 在 TLS 终止代理后面
|
||||
|
||||
如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请添加选项 `--proxy-headers`,这将告诉 Uvicorn 信任该代理发送的标头,告诉它应用程序正在 HTTPS 后面运行等信息
|
||||
如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请添加 `--proxy-headers` 选项,这会通过 FastAPI CLI 告诉 Uvicorn 信任该代理发送的标头,表明应用运行在 HTTPS 后等。
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]
|
||||
```
|
||||
|
||||
#### Docker 缓存
|
||||
#### Docker 缓存 { #docker-cache }
|
||||
|
||||
这个`Dockerfile`中有一个重要的技巧,我们首先只单独复制**包含依赖项的文件**,而不是其余代码。 让我来告诉你这是为什么。
|
||||
这个 `Dockerfile` 里有个重要技巧:我们先**只复制依赖文件**,而不是其他代码。原因如下:
|
||||
|
||||
```Dockerfile
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
```
|
||||
|
||||
Docker之类的构建工具是通过**增量**的方式来构建这些容器镜像的。具体做法是从`Dockerfile`顶部开始,每一条指令生成的文件都是镜像的“一层”,同过把这些“层”一层一层地叠加到基础镜像上,最后我们就得到了最终的镜像。
|
||||
Docker 等工具是**增量**地**构建**容器镜像的,从 `Dockerfile` 顶部开始,按顺序为每条指令创建**一层叠加层**,并把每步生成的文件加入。
|
||||
|
||||
Docker 和类似工具在构建镜像时也会使用**内部缓存**,如果自上次构建容器镜像以来文件没有更改,那么它将**重新使用上次创建的同一层**,而不是再次复制文件并从头开始创建新层。
|
||||
构建镜像时,Docker 等工具也会使用**内部缓存**。如果自上次构建以来某个文件没有变更,它会**重用**上次创建的那一层,而不是再次复制文件并从头创建新层。
|
||||
|
||||
仅仅避免文件的复制不一定会有太多速度提升,但是如果在这一步使用了缓存,那么才可以**在下一步中使用缓存**。 例如,可以使用安装依赖项那条指令的缓存:
|
||||
仅仅避免复制文件并不会带来太多改进,但因为该步骤使用了缓存,它就可以**在下一步中继续使用缓存**。例如,安装依赖的这条指令也能使用缓存:
|
||||
|
||||
```Dockerfile
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
```
|
||||
|
||||
包含包依赖的文件**不会频繁变更**。仅复制该文件,Docker 就能在这一步**使用缓存**。
|
||||
|
||||
包含包依赖项的文件**不会频繁更改**。 只复制该文件(不复制其他的应用代码),Docker 才能在这一步**使用缓存**。
|
||||
随后,Docker 还能**对下一步**(下载并安装依赖)**使用缓存**。这正是我们**节省大量时间**的地方。✨ ...并避免无聊的等待。😪😆
|
||||
|
||||
Docker 进而能**使用缓存进行下一步**,即下载并安装这些依赖项。 这才是我们**节省大量时间**的地方。 ✨ ...可以避免无聊的等待。 😪😆
|
||||
下载并安装依赖**可能需要几分钟**,而使用**缓存**则**最多只需几秒**。
|
||||
|
||||
下载和安装依赖项**可能需要几分钟**,但使用**缓存**最多**只需要几秒钟**。
|
||||
而且在开发中你会反复构建镜像来验证代码变更是否生效,累计节省的时间会很多。
|
||||
|
||||
由于你在开发过程中会一次又一次地构建容器镜像以检查代码更改是否有效,因此可以累计节省大量时间。
|
||||
|
||||
在`Dockerfile`末尾附近,我们再添加复制代码的指令。 由于代码是**更改最频繁的**,所以将其放在最后,因为这一步之后的内容基本上都是无法使用缓存的。
|
||||
接着,在 `Dockerfile` 的末尾附近我们再复制所有代码。因为这是**变化最频繁**的部分,把它放在后面,这样几乎所有在它之后的步骤都不会使用到缓存。
|
||||
|
||||
```Dockerfile
|
||||
COPY ./app /code/app
|
||||
```
|
||||
|
||||
### 构建 Docker 镜像
|
||||
### 构建 Docker 镜像 { #build-the-docker-image }
|
||||
|
||||
现在所有文件都已就位,让我们构建容器镜像。
|
||||
现在所有文件都就位了,开始构建容器镜像。
|
||||
|
||||
* 转到项目目录(在`Dockerfile`所在的位置,包含`app`目录)。
|
||||
* 进入项目目录(`Dockerfile` 所在位置,包含 `app` 目录)
|
||||
* 构建你的 FastAPI 镜像:
|
||||
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
@@ -316,18 +330,17 @@ $ docker build -t myimage .
|
||||
|
||||
</div>
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
/// tip
|
||||
注意末尾的 `.`,它等价于 `./`,用于告诉 Docker 使用哪个目录来构建容器镜像。
|
||||
|
||||
注意最后的 `.`,它相当于`./`,它告诉 Docker 用于构建容器镜像的目录。
|
||||
|
||||
在本例中,它是相同的当前目录(`.`)。
|
||||
此处就是当前目录(`.`)。
|
||||
|
||||
///
|
||||
|
||||
### 启动 Docker 容器
|
||||
### 启动 Docker 容器 { #start-the-docker-container }
|
||||
|
||||
* 根据你的镜像运行容器:
|
||||
* 基于你的镜像运行一个容器:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -337,10 +350,9 @@ $ docker run -d --name mycontainer -p 80:80 myimage
|
||||
|
||||
</div>
|
||||
|
||||
## 检查一下
|
||||
## 检查一下 { #check-it }
|
||||
|
||||
|
||||
你应该能在Docker容器的URL中检查它,例如: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> 或 <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (或其他等价的,使用 Docker 主机).
|
||||
你应该能在容器暴露的 URL 访问它,例如:<a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> 或 <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a>(或其他等价地址,取决于你的 Docker 主机)。
|
||||
|
||||
你会看到类似内容:
|
||||
|
||||
@@ -348,25 +360,25 @@ $ docker run -d --name mycontainer -p 80:80 myimage
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
## 交互式 API 文档
|
||||
## 交互式 API 文档 { #interactive-api-docs }
|
||||
|
||||
现在你可以转到 <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> 或 <a href ="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (或其他等价的,使用 Docker 主机)。
|
||||
现在你可以访问 <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> 或 <a href="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a>(或其他等价地址,取决于你的 Docker 主机)。
|
||||
|
||||
你将看到自动交互式 API 文档(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a 提供) >):
|
||||
你将看到自动生成的交互式 API 文档(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> 提供):
|
||||
|
||||

|
||||
|
||||
## 备选的 API 文档
|
||||
## 备选 API 文档 { #alternative-api-docs }
|
||||
|
||||
你还可以访问 <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> 或 <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (或其他等价的,使用 Docker 主机)。
|
||||
你还可以访问 <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> 或 <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a>(或其他等价地址,取决于你的 Docker 主机)。
|
||||
|
||||
你将看到备选的自动文档(由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 提供):
|
||||
|
||||

|
||||
|
||||
## 使用单文件 FastAPI 构建 Docker 镜像
|
||||
## 使用单文件 FastAPI 构建 Docker 镜像 { #build-a-docker-image-with-a-single-file-fastapi }
|
||||
|
||||
如果你的 FastAPI 是单个文件,例如没有`./app`目录的`main.py`,则你的文件结构可能如下所示:
|
||||
如果你的 FastAPI 是单个文件,例如没有 `./app` 目录、只有 `main.py`,你的文件结构可能如下:
|
||||
|
||||
```
|
||||
.
|
||||
@@ -375,7 +387,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
然后你只需更改相应的路径即可将文件复制到`Dockerfile`中:
|
||||
然后你只需要在 `Dockerfile` 中修改相应路径来复制该文件:
|
||||
|
||||
```{ .dockerfile .annotate hl_lines="10 13" }
|
||||
FROM python:3.9
|
||||
@@ -386,375 +398,221 @@ COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (1)
|
||||
# (1)!
|
||||
COPY ./main.py /code/
|
||||
|
||||
# (2)
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
# (2)!
|
||||
CMD ["fastapi", "run", "main.py", "--port", "80"]
|
||||
```
|
||||
|
||||
1. 直接将`main.py`文件复制到`/code`目录中(不包含任何`./app`目录)。
|
||||
1. 直接将 `main.py` 复制到 `/code`(没有 `./app` 目录)。
|
||||
|
||||
2. 运行 Uvicorn 并告诉它从 `main` 导入 `app` 对象(而不是从 `app.main` 导入)。
|
||||
2. 使用 `fastapi run` 来运行单文件 `main.py` 中的应用。
|
||||
|
||||
然后调整Uvicorn命令使用新模块`main`而不是`app.main`来导入FastAPI 实例`app`。
|
||||
当你把文件传给 `fastapi run` 时,它会自动检测这是一个单文件而不是包,并知道如何导入并服务你的 FastAPI 应用。😎
|
||||
|
||||
## 部署概念
|
||||
## 部署概念 { #deployment-concepts }
|
||||
|
||||
我们再谈谈容器方面的一些相同的[部署概念](concepts.md){.internal-link target=_blank}。
|
||||
我们再从容器的角度讨论一些相同的[部署概念](concepts.md){.internal-link target=_blank}。
|
||||
|
||||
容器主要是一种简化**构建和部署**应用程序的过程的工具,但它们并不强制执行特定的方法来处理这些**部署概念**,并且有几种可能的策略。
|
||||
容器主要是简化应用**构建与部署**流程的工具,但它们并不强制采用某种特定方式来处理这些**部署概念**,可选策略有多种。
|
||||
|
||||
**好消息**是,对于每种不同的策略,都有一种方法可以涵盖所有部署概念。 🎉
|
||||
**好消息**是,不同策略下都有方式覆盖所有部署概念。🎉
|
||||
|
||||
让我们从容器的角度回顾一下这些**部署概念**:
|
||||
让我们从容器角度回顾这些**部署概念**:
|
||||
|
||||
* HTTPS
|
||||
* 启动时运行
|
||||
* 重新启动
|
||||
* 失败重启
|
||||
* 复制(运行的进程数)
|
||||
* 内存
|
||||
* 开始前的先前步骤
|
||||
* 启动前的前置步骤
|
||||
|
||||
## HTTPS { #https }
|
||||
|
||||
## HTTPS
|
||||
如果我们只关注 FastAPI 应用的**容器镜像**(以及后续运行的**容器**),HTTPS 通常由**外部**的其他工具处理。
|
||||
|
||||
如果我们只关注 FastAPI 应用程序的 **容器镜像**(以及稍后运行的 **容器**),HTTPS 通常会由另一个工具在 **外部** 处理。
|
||||
它可以是另一个容器,例如使用 <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>,处理 **HTTPS** 并**自动**获取**证书**。
|
||||
|
||||
它可以是另一个容器,例如使用 <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>,处理 **HTTPS** 和 **自动**获取**证书**。
|
||||
/// tip | 提示
|
||||
|
||||
/// tip
|
||||
|
||||
Traefik可以与 Docker、Kubernetes 等集成,因此使用它为容器设置和配置 HTTPS 非常容易。
|
||||
Traefik 与 Docker、Kubernetes 等都有集成,因此为容器设置和配置 HTTPS 非常容易。
|
||||
|
||||
///
|
||||
|
||||
或者,HTTPS 可以由云服务商作为其服务之一进行处理(同时仍在容器中运行应用程序)。
|
||||
或者,HTTPS 也可能由云服务商作为其服务之一提供(应用仍运行在容器中)。
|
||||
|
||||
## 在启动和重新启动时运行
|
||||
## 启动时运行与重启 { #running-on-startup-and-restarts }
|
||||
|
||||
通常还有另一个工具负责**启动和运行**你的容器。
|
||||
通常会有另一个工具负责**启动并运行**你的容器。
|
||||
|
||||
它可以直接是**Docker**, 或者**Docker Compose**、**Kubernetes**、**云服务**等。
|
||||
它可以是直接的 **Docker**、**Docker Compose**、**Kubernetes**、某个**云服务**等。
|
||||
|
||||
在大多数(或所有)情况下,有一个简单的选项可以在启动时运行容器并在失败时重新启动。 例如,在 Docker 中,它是命令行选项 `--restart`。
|
||||
在大多数(或全部)情况下,都有简单选项可以在开机时运行容器并在失败时启用重启。例如,在 Docker 中是命令行选项 `--restart`。
|
||||
|
||||
如果不使用容器,让应用程序在启动时运行并重新启动可能会很麻烦且困难。 但在大多数情况下,当**使用容器**时,默认情况下会包含该功能。 ✨
|
||||
如果不使用容器,要让应用开机自启并带重启可能繁琐且困难。但在**容器**场景下,这种功能通常默认就包含了。✨
|
||||
|
||||
## 复制 - 进程数
|
||||
## 复制 - 进程数 { #replication-number-of-processes }
|
||||
|
||||
如果你有一个 <abbr title="一组配置为以某种方式连接并协同工作的计算机。">集群</abbr>, 比如 **Kubernetes**、Docker Swarm、Nomad 或其他类似的复杂系统来管理多台机器上的分布式容器,那么你可能希望在**集群级别**处理复制**,而不是在每个容器中使用**进程管理器**(如带有Worker的 Gunicorn) 。
|
||||
如果你有一个由 **Kubernetes**、Docker Swarm Mode、Nomad 或其他类似的复杂系统管理的、在多台机器上运行的分布式容器<abbr title="被配置为以某种方式连接并协同工作的多台机器">集群</abbr>,那么你很可能会希望在**集群层面**来**处理复制**,而不是在每个容器中使用**进程管理**(比如让 Uvicorn 运行多个 workers)。
|
||||
|
||||
像 Kubernetes 这样的分布式容器管理系统通常有一些集成的方法来处理**容器的复制**,同时仍然支持传入请求的**负载均衡**。 全部都在**集群级别**。
|
||||
像 Kubernetes 这样的分布式容器管理系统通常都有某种内置方式来处理**容器复制**,同时对传入请求进行**负载均衡**。这一切都在**集群层面**完成。
|
||||
|
||||
在这些情况下,你可能希望从头开始构建一个 **Docker 镜像**,如[上面所解释](#dockerfile)的那样,安装依赖项并运行 **单个 Uvicorn 进程**,而不是运行 Gunicorn 和 Uvicorn workers这种。
|
||||
在这些情况下,你可能希望如[上文所述](#dockerfile)那样**从头构建 Docker 镜像**,安装依赖,并仅运行**单个 Uvicorn 进程**,而不是使用多个 Uvicorn workers。
|
||||
|
||||
### 负载均衡器 { #load-balancer }
|
||||
|
||||
### 负载均衡器
|
||||
使用容器时,通常会有某个组件**监听主端口**。它可能是另一个同时充当 **TLS 终止代理**以处理 **HTTPS** 的容器,或类似工具。
|
||||
|
||||
使用容器时,通常会有一些组件**监听主端口**。 它可能是处理 **HTTPS** 的 **TLS 终止代理** 或一些类似的工具的另一个容器。
|
||||
由于该组件会承接请求的**负载**并以(期望)**均衡**的方式在 workers 间分发,它也常被称为**负载均衡器**。
|
||||
|
||||
由于该组件将接受请求的**负载**并(希望)以**平衡**的方式在worker之间分配该请求,因此它通常也称为**负载均衡器**。
|
||||
/// tip | 提示
|
||||
|
||||
/// tip
|
||||
|
||||
用于 HTTPS **TLS 终止代理** 的相同组件也可能是 **负载均衡器**。
|
||||
用于 HTTPS 的**TLS 终止代理**组件通常也会是**负载均衡器**。
|
||||
|
||||
///
|
||||
|
||||
当使用容器时,你用来启动和管理容器的同一系统已经具有内部工具来传输来自该**负载均衡器**(也可以是**TLS 终止代理**) 的**网络通信**(例如HTTP请求)到你的应用程序容器。
|
||||
使用容器时,你用来启动和管理容器的系统本身就已有内部工具,将来自该**负载均衡器**(也可能是**TLS 终止代理**)的**网络通信**(例如 HTTP 请求)传递到你的应用容器中。
|
||||
|
||||
### 一个负载均衡器 - 多个worker容器
|
||||
### 一个负载均衡器 - 多个 worker 容器 { #one-load-balancer-multiple-worker-containers }
|
||||
|
||||
当使用 **Kubernetes** 或类似的分布式容器管理系统时,使用其内部网络机制将允许单个在主 **端口** 上侦听的 **负载均衡器** 将通信(请求)传输到可能的 **多个** 运行你应用程序的容器。
|
||||
在 **Kubernetes** 等分布式容器管理系统中,使用其内部网络机制,允许在主**端口**上监听的单个**负载均衡器**将通信(请求)转发给可能**多个**运行你应用的容器。
|
||||
|
||||
运行你的应用程序的每个容器通常**只有一个进程**(例如,运行 FastAPI 应用程序的 Uvicorn 进程)。 它们都是**相同的容器**,运行相同的东西,但每个容器都有自己的进程、内存等。这样你就可以在 CPU 的**不同核心**, 甚至在**不同的机器**充分利用**并行化(parallelization)**。
|
||||
这些运行你应用的容器通常每个只有**一个进程**(例如,一个运行 FastAPI 应用的 Uvicorn 进程)。它们都是**相同的容器**,运行相同的东西,但每个都有自己的进程、内存等。这样你就能在 CPU 的**不同核心**,甚至在**不同机器**上利用**并行化**。
|
||||
|
||||
具有**负载均衡器**的分布式容器系统将**将请求轮流分配**给你的应用程序的每个容器。 因此,每个请求都可以由运行你的应用程序的多个**复制容器**之一来处理。
|
||||
分布式容器系统配合**负载均衡器**会把请求**轮流分配**到每个应用容器。因此,每个请求都可能由多个**副本容器**之一来处理。
|
||||
|
||||
通常,这个**负载均衡器**能够处理发送到集群中的*其他*应用程序的请求(例如发送到不同的域,或在不同的 URL 路径前缀下),并正确地将该通信传输到在集群中运行的*其他*应用程序的对应容器。
|
||||
通常,这个**负载均衡器**还能处理发往集群中*其他*应用的请求(例如不同域名,或不同的 URL 路径前缀),并将通信转发到运行*那个其他*应用的正确容器。
|
||||
|
||||
### 每个容器一个进程 { #one-process-per-container }
|
||||
|
||||
在这种场景下,你大概率希望**每个容器只有一个(Uvicorn)进程**,因为你已经在集群层面处理了复制。
|
||||
|
||||
因此,这种情况下你**不希望**在容器内再启多个 workers(例如通过 `--workers` 命令行选项)。你会希望每个容器仅有一个**单独的 Uvicorn 进程**(但可能会有多个容器)。
|
||||
|
||||
在容器内再放一个进程管理器(就像启多个 workers 一样)只会引入**不必要的复杂性**,而这些你很可能已经在集群系统中处理了。
|
||||
|
||||
### 具有多个进程和特殊情况的容器 { #containers-with-multiple-processes-and-special-cases }
|
||||
|
||||
### 每个容器一个进程
|
||||
当然,也有一些**特殊情况**,你可能希望让**一个容器**里运行多个 **Uvicorn worker 进程**。
|
||||
|
||||
在这种类型的场景中,你可能希望**每个容器有一个(Uvicorn)进程**,因为你已经在集群级别处理复制。
|
||||
在这些情况下,你可以使用 `--workers` 命令行选项来设置要运行的 worker 数量:
|
||||
|
||||
因此,在这种情况下,你**不会**希望拥有像 Gunicorn 和 Uvicorn worker一样的进程管理器,或者 Uvicorn 使用自己的 Uvicorn worker。 你可能希望每个容器(但可能有多个容器)只有一个**单独的 Uvicorn 进程**。
|
||||
```{ .dockerfile .annotate }
|
||||
FROM python:3.9
|
||||
|
||||
在容器内拥有另一个进程管理器(就像使用 Gunicorn 或 Uvicorn 管理 Uvicorn 工作线程一样)只会增加**不必要的复杂性**,而你很可能已经在集群系统中处理这些复杂性了。
|
||||
WORKDIR /code
|
||||
|
||||
### 具有多个进程的容器
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
当然,在某些**特殊情况**,你可能希望拥有 **一个容器**,其中包含 **Gunicorn 进程管理器**,并在其中启动多个 **Uvicorn worker进程**。
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
在这些情况下,你可以使用 **官方 Docker 镜像**,其中包含 **Gunicorn** 作为运行多个 **Uvicorn 工作进程** 的进程管理器,以及一些默认设置来根据当前情况调整工作进程数量 自动CPU核心。 我将在下面的 [Gunicorn - Uvicorn 官方 Docker 镜像](#official-docker-image-with-gunicorn-uvicorn) 中告诉你更多相关信息。
|
||||
COPY ./app /code/app
|
||||
|
||||
下面一些什么时候这种做法有意义的示例:
|
||||
# (1)!
|
||||
CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"]
|
||||
```
|
||||
|
||||
1. 这里我们使用 `--workers` 命令行选项将 worker 数量设置为 4。
|
||||
|
||||
#### 一个简单的应用程序
|
||||
以下是这种做法可能合理的一些示例:
|
||||
|
||||
如果你的应用程序**足够简单**,你不需要(至少现在不需要)过多地微调进程数量,并且你可以使用自动默认值,那么你可能需要容器中的进程管理器 (使用官方 Docker 镜像),并且你在**单个服务器**而不是集群上运行它。
|
||||
#### 一个简单的应用 { #a-simple-app }
|
||||
|
||||
#### Docker Compose
|
||||
如果你的应用**足够简单**,可以在**单台服务器**(不是集群)上运行,你可能会希望在容器内有一个进程管理器。
|
||||
|
||||
你可以使用 **Docker Compose** 部署到**单个服务器**(而不是集群),因此你没有一种简单的方法来管理容器的复制(使用 Docker Compose),同时保留共享网络和 **负载均衡**。
|
||||
#### Docker Compose { #docker-compose }
|
||||
|
||||
然后,你可能希望拥有一个**单个容器**,其中有一个**进程管理器**,在其中启动**多个worker进程**。
|
||||
如果你使用 **Docker Compose** 部署到**单台服务器**(不是集群),那么你不会有一个简单的方法在保留共享网络与**负载均衡**的同时管理容器复制(通过 Docker Compose)。
|
||||
|
||||
#### Prometheus和其他原因
|
||||
|
||||
你还可能有**其他原因**,这将使你更容易拥有一个带有**多个进程**的**单个容器**,而不是拥有每个容器中都有**单个进程**的**多个容器**。
|
||||
|
||||
例如(取决于你的设置)你可以在同一个容器中拥有一些工具,例如 Prometheus exporter,该工具应该有权访问**每个请求**。
|
||||
|
||||
在这种情况下,如果你有**多个容器**,默认情况下,当 Prometheus 来**读取metrics**时,它每次都会获取**单个容器**的metrics(对于处理该特定请求的容器),而不是获取所有复制容器的**累积metrics**。
|
||||
|
||||
在这种情况, 这种做法会更加简单:让**一个容器**具有**多个进程**,并在同一个容器上使用本地工具(例如 Prometheus exporter)收集所有内部进程的 Prometheus 指标并公开单个容器上的这些指标。
|
||||
这种情况下,你可能希望用**单个容器**,由**进程管理器**在容器内启动**多个 worker 进程**。
|
||||
|
||||
---
|
||||
|
||||
要点是,这些都**不是**你必须盲目遵循的**一成不变的规则**。 你可以根据这些思路**评估你自己的场景**并决定什么方法是最适合你的的系统,考虑如何管理以下概念:
|
||||
要点是,这些都**不是**你必须盲目遵循的**铁律**。你可以用这些思路来**评估你自己的场景**,并决定最适合你的系统的方法,看看如何管理以下概念:
|
||||
|
||||
* 安全性 - HTTPS
|
||||
* 安全 - HTTPS
|
||||
* 启动时运行
|
||||
* 重新启动
|
||||
* 重启
|
||||
* 复制(运行的进程数)
|
||||
* 内存
|
||||
* 开始前的先前步骤
|
||||
* 启动前的前置步骤
|
||||
|
||||
## 内存
|
||||
## 内存 { #memory }
|
||||
|
||||
如果你**每个容器运行一个进程**,那么每个容器所消耗的内存或多或少是定义明确的、稳定的且有限的(如果它们是复制的,则不止一个)。
|
||||
如果你**每个容器只运行一个进程**,那么每个容器消耗的内存将更容易定义、较为稳定且有限(如果有复制则为多个容器)。
|
||||
|
||||
然后,你可以在容器管理系统的配置中设置相同的内存限制和要求(例如在 **Kubernetes** 中)。 这样,它将能够在**可用机器**中**复制容器**,同时考虑容器所需的内存量以及集群中机器中的可用内存量。
|
||||
接着,你可以在容器管理系统(例如 **Kubernetes**)的配置中设置同样的内存限制与需求。这样它就能在**可用的机器**上**复制容器**,同时考虑容器所需的内存量以及集群中机器可用的内存量。
|
||||
|
||||
如果你的应用程序很**简单**,这可能**不是问题**,并且你可能不需要指定内存限制。 但是,如果你**使用大量内存**(例如使用**机器学习**模型),则应该检查你消耗了多少内存并调整**每台机器**中运行的**容器数量**(也许可以向集群添加更多机器)。
|
||||
如果你的应用很**简单**,这可能**不成问题**,你也许不需要设置严格的内存上限。但如果你**使用大量内存**(例如使用**机器学习**模型),你应该检查自己的内存消耗,并调整**每台机器**上运行的**容器数量**(也许还需要为集群增加机器)。
|
||||
|
||||
如果你**每个容器运行多个进程**(例如使用官方 Docker 镜像),你必须确保启动的进程数量不会消耗比可用内存**更多的内存**。
|
||||
如果你**每个容器运行多个进程**,你需要确保启动的进程数量不会**消耗超过可用的内存**。
|
||||
|
||||
## 启动之前的步骤和容器
|
||||
## 启动前的前置步骤与容器 { #previous-steps-before-starting-and-containers }
|
||||
|
||||
如果你使用容器(例如 Docker、Kubernetes),那么你可以使用两种主要方法。
|
||||
如果你在使用容器(如 Docker、Kubernetes),你可以采用两种主要方式。
|
||||
|
||||
### 多个容器 { #multiple-containers }
|
||||
|
||||
### 多个容器
|
||||
如果你有**多个容器**,可能每个容器运行一个**单独进程**(例如在 **Kubernetes** 集群中),那么你可能希望使用一个**单独的容器**来执行**前置步骤**,在一个容器中运行一个进程,**在**启动那些复制的 worker 容器**之前**完成。
|
||||
|
||||
如果你有 **多个容器**,可能每个容器都运行一个 **单个进程**(例如,在 **Kubernetes** 集群中),那么你可能希望有一个 **单独的容器** 执行以下操作: 在单个容器中运行单个进程执行**先前步骤**,即运行复制的worker容器之前。
|
||||
/// info | 信息
|
||||
|
||||
/// info
|
||||
|
||||
如果你使用 Kubernetes,这可能是 <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>。
|
||||
如果你使用 Kubernetes,这通常会是一个 <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>。
|
||||
|
||||
///
|
||||
|
||||
如果在你的用例中,运行前面的步骤**并行多次**没有问题(例如,如果你没有运行数据库迁移,而只是检查数据库是否已准备好),那么你也可以将它们放在开始主进程之前在每个容器中。
|
||||
如果在你的用例中,**并行多次**运行这些前置步骤没有问题(例如你不是在跑数据库迁移,而只是检查数据库是否就绪),那么你也可以把这些步骤放在每个容器中,在启动主进程之前执行。
|
||||
|
||||
### 单容器
|
||||
### 单个容器 { #single-container }
|
||||
|
||||
如果你有一个简单的设置,使用一个**单个容器**,然后启动多个**工作进程**(或者也只是一个进程),那么你可以在启动进程之前在应用程序同一个容器中运行先前的步骤。 官方 Docker 镜像内部支持这一点。
|
||||
如果你的架构较为简单,使用一个**单个容器**,其后再启动多个**worker 进程**(或者也只有一个进程),那么你可以在同一个容器中,在启动应用进程之前执行这些前置步骤。
|
||||
|
||||
## 带有 Gunicorn 的官方 Docker 镜像 - Uvicorn
|
||||
### 基础 Docker 镜像 { #base-docker-image }
|
||||
|
||||
有一个官方 Docker 镜像,其中包含与 Uvicorn worker一起运行的 Gunicorn,如上一章所述:[服务器工作线程 - Gunicorn 与 Uvicorn](server-workers.md){.internal-link target=_blank}。
|
||||
曾经有一个官方的 FastAPI Docker 镜像:<a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>。但它现在已被弃用。⛔️
|
||||
|
||||
该镜像主要在上述情况下有用:[具有多个进程和特殊情况的容器](#containers-with-multiple-processes-and-special-cases)。
|
||||
你大概率**不应该**使用这个基础镜像(或任何其它类似的镜像)。
|
||||
|
||||
如果你使用 **Kubernetes**(或其他)并且已经在集群层面设置**复制**、使用多个**容器**,那么在这些情况下,最好如上所述**从头构建镜像**:[为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。
|
||||
|
||||
如果你需要多个 workers,可以直接使用 `--workers` 命令行选项。
|
||||
|
||||
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
|
||||
/// note | 技术细节
|
||||
|
||||
这个 Docker 镜像创建于 Uvicorn 还不支持管理与重启失效 workers 的时期,那时需要用 Gunicorn 搭配 Uvicorn,这引入了不少复杂度,只是为了让 Gunicorn 管理并重启 Uvicorn 的 worker 进程。
|
||||
|
||||
/// warning
|
||||
|
||||
你很有可能不需要此基础镜像或任何其他类似的镜像,最好从头开始构建镜像,如[上面所述:为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。
|
||||
但现在 Uvicorn(以及 `fastapi` 命令)已经支持使用 `--workers`,因此没有理由不自己构建基础镜像(代码量几乎一样 😅)。
|
||||
|
||||
///
|
||||
|
||||
该镜像包含一个**自动调整**机制,用于根据可用的 CPU 核心设置**worker进程数**。
|
||||
## 部署容器镜像 { #deploy-the-container-image }
|
||||
|
||||
它具有**合理的默认值**,但你仍然可以使用**环境变量**或配置文件更改和更新所有配置。
|
||||
|
||||
它还支持通过一个脚本运行<a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**开始前的先前步骤** </a>。
|
||||
|
||||
/// tip
|
||||
|
||||
要查看所有配置和选项,请转到 Docker 镜像页面: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank" >tiangolo/uvicorn-gunicorn-fastapi</a>。
|
||||
|
||||
///
|
||||
|
||||
### 官方 Docker 镜像上的进程数
|
||||
|
||||
此镜像上的**进程数**是根据可用的 CPU **核心**自动计算的。
|
||||
|
||||
这意味着它将尝试尽可能多地**榨取**CPU 的**性能**。
|
||||
|
||||
你还可以使用 **环境变量** 等配置来调整它。
|
||||
|
||||
但这也意味着,由于进程数量取决于容器运行的 CPU,因此**消耗的内存量**也将取决于该数量。
|
||||
|
||||
因此,如果你的应用程序消耗大量内存(例如机器学习模型),并且你的服务器有很多 CPU 核心**但内存很少**,那么你的容器最终可能会尝试使用比实际情况更多的内存 可用,并且性能会下降很多(甚至崩溃)。 🚨
|
||||
|
||||
### 创建一个`Dockerfile`
|
||||
|
||||
以下是如何根据此镜像创建`Dockerfile`:
|
||||
|
||||
|
||||
```Dockerfile
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app
|
||||
```
|
||||
|
||||
### 更大的应用程序
|
||||
|
||||
如果你按照有关创建[具有多个文件的更大应用程序](../tutorial/bigger-applications.md){.internal-link target=_blank}的部分进行操作,你的`Dockerfile`可能看起来这样:
|
||||
|
||||
```Dockerfile hl_lines="7"
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
|
||||
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
||||
|
||||
COPY ./app /app/app
|
||||
```
|
||||
|
||||
### 何时使用
|
||||
|
||||
如果你使用 **Kubernetes** (或其他)并且你已经在集群级别设置 **复制**,并且具有多个 **容器**。 在这些情况下,你最好按照上面的描述 **从头开始构建镜像**:[为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。
|
||||
|
||||
该镜像主要在[具有多个进程的容器和特殊情况](#containers-with-multiple-processes-and-special-cases)中描述的特殊情况下有用。 例如,如果你的应用程序**足够简单**,基于 CPU 设置默认进程数效果很好,你不想在集群级别手动配置复制,并且不会运行更多进程, 或者你使用 **Docker Compose** 进行部署,在单个服务器上运行等。
|
||||
|
||||
## 部署容器镜像
|
||||
|
||||
拥有容器(Docker)镜像后,有多种方法可以部署它。
|
||||
得到容器(Docker)镜像后,有多种方式可以部署。
|
||||
|
||||
例如:
|
||||
|
||||
* 在单个服务器中使用 **Docker Compose**
|
||||
* 在单台服务器上使用 **Docker Compose**
|
||||
* 使用 **Kubernetes** 集群
|
||||
* 使用 Docker Swarm 模式集群
|
||||
* 使用Nomad等其他工具
|
||||
* 使用云服务获取容器镜像并部署它
|
||||
* 使用 Docker Swarm Mode 集群
|
||||
* 使用 Nomad 等其他工具
|
||||
* 使用云服务,接收你的容器镜像并部署
|
||||
|
||||
## Docker 镜像与Poetry
|
||||
## 使用 `uv` 的 Docker 镜像 { #docker-image-with-uv }
|
||||
|
||||
如果你使用 <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> 来管理项目的依赖项,你可以使用 Docker 多阶段构建:
|
||||
如果你使用 <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a> 来安装和管理项目,可以参考他们的 <a href="https://docs.astral.sh/uv/guides/integration/docker/" class="external-link" target="_blank">uv Docker 指南</a>。
|
||||
|
||||
## 回顾 { #recap }
|
||||
|
||||
|
||||
```{ .dockerfile .annotate }
|
||||
# (1)
|
||||
FROM python:3.9 as requirements-stage
|
||||
|
||||
# (2)
|
||||
WORKDIR /tmp
|
||||
|
||||
# (3)
|
||||
RUN pip install poetry
|
||||
|
||||
# (4)
|
||||
COPY ./pyproject.toml ./poetry.lock* /tmp/
|
||||
|
||||
# (5)
|
||||
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
|
||||
# (6)
|
||||
FROM python:3.9
|
||||
|
||||
# (7)
|
||||
WORKDIR /code
|
||||
|
||||
# (8)
|
||||
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
|
||||
|
||||
# (9)
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# (10)
|
||||
COPY ./app /code/app
|
||||
|
||||
# (11)
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
1. 这是第一阶段,称为`requirements-stage`。
|
||||
|
||||
2. 将 `/tmp` 设置为当前工作目录。
|
||||
|
||||
这是我们生成文件`requirements.txt`的地方
|
||||
|
||||
3. 在此阶段安装Poetry。
|
||||
|
||||
4. 将`pyproject.toml`和`poetry.lock`文件复制到`/tmp`目录。
|
||||
|
||||
因为它使用 `./poetry.lock*` (以 `*` 结尾),所以如果该文件尚不可用,它不会崩溃。
|
||||
|
||||
5. 生成`requirements.txt`文件。
|
||||
|
||||
6. 这是最后阶段,这里的任何内容都将保留在最终的容器镜像中。
|
||||
|
||||
7. 将当前工作目录设置为`/code`。
|
||||
|
||||
8. 将 `requirements.txt` 文件复制到 `/code` 目录。
|
||||
|
||||
该文件仅存在于前一个阶段,这就是为什么我们使用 `--from-requirements-stage` 来复制它。
|
||||
|
||||
9. 安装生成的`requirements.txt`文件中的依赖项。
|
||||
|
||||
10. 将`app`目录复制到`/code`目录。
|
||||
|
||||
11. 运行`uvicorn`命令,告诉它使用从`app.main`导入的`app`对象。
|
||||
|
||||
/// tip
|
||||
|
||||
单击气泡数字可查看每行的作用。
|
||||
|
||||
///
|
||||
|
||||
**Docker stage** 是 `Dockerfile` 的一部分,用作 **临时容器镜像**,仅用于生成一些稍后使用的文件。
|
||||
|
||||
第一阶段仅用于 **安装 Poetry** 并使用 Poetry 的 `pyproject.toml` 文件中的项目依赖项 **生成 `requirements.txt`**。
|
||||
|
||||
此`requirements.txt`文件将在**下一阶段**与`pip`一起使用。
|
||||
|
||||
在最终的容器镜像中**仅保留最后阶段**。 之前的阶段将被丢弃。
|
||||
|
||||
使用 Poetry 时,使用 **Docker 多阶段构建** 是有意义的,因为你实际上并不需要在最终的容器镜像中安装 Poetry 及其依赖项,你 **只需要** 生成用于安装项目依赖项的`requirements.txt`文件。
|
||||
|
||||
然后,在下一个(也是最后一个)阶段,你将或多或少地以与前面描述的相同的方式构建镜像。
|
||||
|
||||
### 在TLS 终止代理后面 - Poetry
|
||||
|
||||
同样,如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请将选项`--proxy-headers`添加到命令中:
|
||||
|
||||
|
||||
```Dockerfile
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
```
|
||||
|
||||
## 回顾
|
||||
|
||||
使用容器系统(例如使用**Docker**和**Kubernetes**),处理所有**部署概念**变得相当简单:
|
||||
使用容器系统(例如 **Docker** 与 **Kubernetes**)后,处理所有**部署概念**会变得相当直接:
|
||||
|
||||
* HTTPS
|
||||
* 启动时运行
|
||||
* 重新启动
|
||||
* 失败重启
|
||||
* 复制(运行的进程数)
|
||||
* 内存
|
||||
* 开始前的先前步骤
|
||||
* 启动前的前置步骤
|
||||
|
||||
在大多数情况下,你可能不想使用任何基础镜像,而是基于官方 Python Docker 镜像 **从头开始构建容器镜像** 。
|
||||
在大多数情况下,你可能不想使用任何基础镜像,而是基于官方 Python Docker 镜像**从头构建容器镜像**。
|
||||
|
||||
处理好`Dockerfile`和 **Docker 缓存**中指令的**顺序**,你可以**最小化构建时间**,从而最大限度地提高生产力(并避免无聊)。 😎
|
||||
|
||||
在某些特殊情况下,你可能需要使用 FastAPI 的官方 Docker 镜像。 🤓
|
||||
注意 `Dockerfile` 中指令的**顺序**并利用好**Docker 缓存**,可以**最小化构建时间**,以最大化生产力(并避免无聊)。😎
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 关于 HTTPS
|
||||
# 关于 HTTPS { #about-https }
|
||||
|
||||
人们很容易认为 HTTPS 仅仅是“启用”或“未启用”的东西。
|
||||
|
||||
但实际情况比这复杂得多。
|
||||
|
||||
/// note | 提示
|
||||
/// tip | 提示
|
||||
|
||||
如果你很赶时间或不在乎,请继续阅读下一部分,下一部分会提供一个step-by-step的教程,告诉你怎么使用不同技术来把一切都配置好。
|
||||
如果你很赶时间或不在乎,请继续阅读后续章节,它们会提供逐步的教程,告诉你怎么使用不同技术把一切都配置好。
|
||||
|
||||
///
|
||||
|
||||
@@ -15,24 +15,24 @@
|
||||
现在,从**开发人员的视角**,在了解 HTTPS 时需要记住以下几点:
|
||||
|
||||
* 要使用 HTTPS,**服务器**需要拥有由**第三方**生成的**"证书(certificate)"**。
|
||||
* 这些证书实际上是从第三方**获取**的,而不是“生成”的。
|
||||
* 这些证书实际上是从第三方**获取**的,而不是“生成”的。
|
||||
* 证书有**生命周期**。
|
||||
* 它们会**过期**。
|
||||
* 然后它们需要**更新**,**再次从第三方获取**。
|
||||
* 它们会**过期**。
|
||||
* 然后它们需要**更新**,**再次从第三方获取**。
|
||||
* 连接的加密发生在 **TCP 层**。
|
||||
* 这是 HTTP 协议**下面的一层**。
|
||||
* 因此,**证书和加密**处理是在 **HTTP之前**完成的。
|
||||
* 这是 HTTP 协议**下面的一层**。
|
||||
* 因此,**证书和加密**处理是在 **HTTP之前**完成的。
|
||||
* **TCP 不知道域名**。 仅仅知道 IP 地址。
|
||||
* 有关所请求的 **特定域名** 的信息位于 **HTTP 数据**中。
|
||||
* 有关所请求的 **特定域名** 的信息位于 **HTTP 数据**中。
|
||||
* **HTTPS 证书**“证明”**某个域名**,但协议和加密发生在 TCP 层,在知道正在处理哪个域名**之前**。
|
||||
* **默认情况下**,这意味着你**每个 IP 地址只能拥有一个 HTTPS 证书**。
|
||||
* 无论你的服务器有多大,或者服务器上的每个应用程序有多小。
|
||||
* 不过,对此有一个**解决方案**。
|
||||
* **TLS** 协议(在 HTTP 之下的TCP 层处理加密的协议)有一个**扩展**,称为 **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="服务器名称指示">SNI</abbr></a>**。
|
||||
* SNI 扩展允许一台服务器(具有 **单个 IP 地址**)拥有 **多个 HTTPS 证书** 并提供 **多个 HTTPS 域名/应用程序**。
|
||||
* 为此,服务器上会有**单独**的一个组件(程序)侦听**公共 IP 地址**,这个组件必须拥有服务器中的**所有 HTTPS 证书**。
|
||||
* 无论你的服务器有多大,或者服务器上的每个应用程序有多小。
|
||||
* 不过,对此有一个**解决方案**。
|
||||
* **TLS** 协议(在 HTTP 之下的 TCP 层处理加密的协议)有一个**扩展**,称为 **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="Server Name Indication - 服务器名称指示">SNI</abbr></a>**。
|
||||
* SNI 扩展允许一台服务器(具有 **单个 IP 地址**)拥有 **多个 HTTPS 证书** 并提供 **多个 HTTPS 域名/应用程序**。
|
||||
* 为此,服务器上会有**单独**的一个组件(程序)侦听**公共 IP 地址**,这个组件必须拥有服务器中的**所有 HTTPS 证书**。
|
||||
* **获得安全连接后**,通信协议**仍然是HTTP**。
|
||||
* 内容是 **加密过的**,即使它们是通过 **HTTP 协议** 发送的。
|
||||
* 内容是 **加密过的**,即使它们是通过 **HTTP 协议** 发送的。
|
||||
|
||||
通常的做法是在服务器上运行**一个程序/HTTP 服务器**并**管理所有 HTTPS 部分**:接收**加密的 HTTPS 请求**, 将 **解密的 HTTP 请求** 发送到在同一服务器中运行的实际 HTTP 应用程序(在本例中为 **FastAPI** 应用程序),从应用程序中获取 **HTTP 响应**, 使用适当的 **HTTPS 证书**对其进行加密并使用 **HTTPS** 将其发送回客户端。 此服务器通常被称为 **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">TLS 终止代理(TLS Termination Proxy)</a>**。
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
* Nginx
|
||||
* HAProxy
|
||||
|
||||
## Let's Encrypt
|
||||
## Let's Encrypt { #lets-encrypt }
|
||||
|
||||
在 Let's Encrypt 之前,这些 **HTTPS 证书** 由受信任的第三方出售。
|
||||
|
||||
@@ -57,16 +57,15 @@
|
||||
|
||||
我们的想法是自动获取和更新这些证书,以便你可以永远免费拥有**安全的 HTTPS**。
|
||||
|
||||
## 面向开发人员的 HTTPS
|
||||
## 面向开发人员的 HTTPS { #https-for-developers }
|
||||
|
||||
这里有一个 HTTPS API 看起来是什么样的示例,我们会分步说明,并且主要关注对开发人员重要的部分。
|
||||
|
||||
|
||||
### 域名
|
||||
### 域名 { #domain-name }
|
||||
|
||||
第一步我们要先**获取**一些**域名(Domain Name)**。 然后可以在 DNS 服务器(可能是你的同一家云服务商提供的)中配置它。
|
||||
|
||||
你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个<abbr title="That isn't Change">固定</abbr> **公共IP地址**。
|
||||
你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个<abbr title="不会改变">固定</abbr> **公共IP地址**。
|
||||
|
||||
在 DNS 服务器中,你可以配置一条记录(“A 记录”)以将 **你的域名** 指向你服务器的公共 **IP 地址**。
|
||||
|
||||
@@ -78,7 +77,7 @@
|
||||
|
||||
///
|
||||
|
||||
### DNS
|
||||
### DNS { #dns }
|
||||
|
||||
现在让我们关注真正的 HTTPS 部分。
|
||||
|
||||
@@ -88,7 +87,7 @@ DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是
|
||||
|
||||
<img src="/img/deployment/https/https01.drawio.svg">
|
||||
|
||||
### TLS 握手开始
|
||||
### TLS 握手开始 { #tls-handshake-start }
|
||||
|
||||
然后,浏览器将在**端口 443**(HTTPS 端口)上与该 IP 地址进行通信。
|
||||
|
||||
@@ -98,7 +97,7 @@ DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是
|
||||
|
||||
客户端和服务器之间建立 TLS 连接的过程称为 **TLS 握手**。
|
||||
|
||||
### 带有 SNI 扩展的 TLS
|
||||
### 带有 SNI 扩展的 TLS { #tls-with-sni-extension }
|
||||
|
||||
**服务器中只有一个进程**可以侦听特定 **IP 地址**的特定 **端口**。 可能有其他进程在同一 IP 地址的其他端口上侦听,但每个 IP 地址和端口组合只有一个进程。
|
||||
|
||||
@@ -128,7 +127,7 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。
|
||||
|
||||
///
|
||||
|
||||
### HTTPS 请求
|
||||
### HTTPS 请求 { #https-request }
|
||||
|
||||
现在客户端和服务器(特别是浏览器和 TLS 终止代理)具有 **加密的 TCP 连接**,它们可以开始 **HTTP 通信**。
|
||||
|
||||
@@ -136,19 +135,19 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。
|
||||
|
||||
<img src="/img/deployment/https/https04.drawio.svg">
|
||||
|
||||
### 解密请求
|
||||
### 解密请求 { #decrypt-the-request }
|
||||
|
||||
TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(解密的)HTTP 请求**传输到运行应用程序的进程(例如运行 FastAPI 应用的 Uvicorn 进程)。
|
||||
|
||||
<img src="/img/deployment/https/https05.drawio.svg">
|
||||
|
||||
### HTTP 响应
|
||||
### HTTP 响应 { #http-response }
|
||||
|
||||
应用程序将处理请求并向 TLS 终止代理发送**(未加密)HTTP 响应**。
|
||||
|
||||
<img src="/img/deployment/https/https06.drawio.svg">
|
||||
|
||||
### HTTPS 响应
|
||||
### HTTPS 响应 { #https-response }
|
||||
|
||||
然后,TLS 终止代理将使用之前协商的加密算法(以`someapp.example.com`的证书开头)对响应进行加密,并将其发送回浏览器。
|
||||
|
||||
@@ -158,7 +157,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(
|
||||
|
||||
客户端(浏览器)将知道响应来自正确的服务器,因为它使用了他们之前使用 **HTTPS 证书** 协商出的加密算法。
|
||||
|
||||
### 多个应用程序
|
||||
### 多个应用程序 { #multiple-applications }
|
||||
|
||||
在同一台(或多台)服务器中,可能存在**多个应用程序**,例如其他 API 程序或数据库。
|
||||
|
||||
@@ -168,7 +167,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(
|
||||
|
||||
这样,TLS 终止代理就可以为多个应用程序处理**多个域名**的 HTTPS 和证书,然后在每种情况下将请求传输到正确的应用程序。
|
||||
|
||||
### 证书更新
|
||||
### 证书更新 { #certificate-renewal }
|
||||
|
||||
在未来的某个时候,每个证书都会**过期**(大约在获得证书后 3 个月)。
|
||||
|
||||
@@ -183,16 +182,48 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(
|
||||
有多种方法可以做到这一点。 一些流行的方式是:
|
||||
|
||||
* **修改一些DNS记录**。
|
||||
* 为此,续订程序需要支持 DNS 提供商的 API,因此,要看你使用的 DNS 提供商是否提供这一功能。
|
||||
* 为此,续订程序需要支持 DNS 提供商的 API,因此,要看你使用的 DNS 提供商是否提供这一功能。
|
||||
* **在与域名关联的公共 IP 地址上作为服务器运行**(至少在证书获取过程中)。
|
||||
* 正如我们上面所说,只有一个进程可以监听特定的 IP 和端口。
|
||||
* 这就是当同一个 TLS 终止代理还负责证书续订过程时它非常有用的原因之一。
|
||||
* 否则,你可能需要暂时停止 TLS 终止代理,启动续订程序以获取证书,然后使用 TLS 终止代理配置它们,然后重新启动 TLS 终止代理。 这并不理想,因为你的应用程序在 TLS 终止代理关闭期间将不可用。
|
||||
* 正如我们上面所说,只有一个进程可以监听特定的 IP 和端口。
|
||||
* 这就是当同一个 TLS 终止代理还负责证书续订过程时它非常有用的原因之一。
|
||||
* 否则,你可能需要暂时停止 TLS 终止代理,启动续订程序以获取证书,然后使用 TLS 终止代理配置它们,然后重新启动 TLS 终止代理。 这并不理想,因为你的应用程序在 TLS 终止代理关闭期间将不可用。
|
||||
|
||||
通过拥有一个**单独的系统来使用 TLS 终止代理来处理 HTTPS**, 而不是直接将 TLS 证书与应用程序服务器一起使用 (例如 Uvicorn),你可以在
|
||||
更新证书的过程中同时保持提供服务。
|
||||
|
||||
## 回顾
|
||||
## 代理转发请求头 { #proxy-forwarded-headers }
|
||||
|
||||
当使用代理来处理 HTTPS 时,你的**应用服务器**(例如通过 FastAPI CLI 运行的 Uvicorn)对 HTTPS 过程并不了解,它只通过纯 HTTP 与 **TLS 终止代理**通信。
|
||||
|
||||
这个**代理**通常会在将请求转发给**应用服务器**之前,临时设置一些 HTTP 请求头,以便让应用服务器知道该请求是由代理**转发**过来的。
|
||||
|
||||
/// 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>
|
||||
|
||||
///
|
||||
|
||||
不过,由于**应用服务器**并不知道自己位于受信任的**代理**之后,默认情况下,它不会信任这些请求头。
|
||||
|
||||
但你可以配置**应用服务器**去信任由**代理**发送的这些“转发”请求头。如果你在使用 FastAPI CLI,可以使用命令行选项 `--forwarded-allow-ips` 指定它应该信任哪些 IP 发来的这些“转发”请求头。
|
||||
|
||||
例如,如果**应用服务器**只接收来自受信任**代理**的通信,你可以设置 `--forwarded-allow-ips="*"`,让它信任所有传入的 IP,因为它只会接收来自**代理**所使用 IP 的请求。
|
||||
|
||||
这样,应用就能知道自己的公共 URL、是否使用 HTTPS、域名等信息。
|
||||
|
||||
这在需要正确处理重定向等场景时很有用。
|
||||
|
||||
/// tip
|
||||
|
||||
你可以在文档中了解更多:[在代理之后 - 启用代理转发请求头](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank}
|
||||
|
||||
///
|
||||
|
||||
## 回顾 { #recap }
|
||||
|
||||
拥有**HTTPS** 非常重要,并且在大多数情况下相当**关键**。 作为开发人员,你围绕 HTTPS 所做的大部分努力就是**理解这些概念**以及它们的工作原理。
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
# 部署
|
||||
# 部署 { #deployment }
|
||||
|
||||
部署 **FastAPI** 应用程序相对容易。
|
||||
|
||||
## 部署是什么意思
|
||||
## 部署是什么意思 { #what-does-deployment-mean }
|
||||
|
||||
**部署**应用程序意味着执行必要的步骤以使其**可供用户使用**。
|
||||
|
||||
对于**Web API**来说,通常涉及将上传到**云服务器**中,搭配一个性能和稳定性都不错的**服务器程序**,以便你的**用户**可以高效地**访问**你的应用程序,而不会出现中断或其他问题。
|
||||
对于**Web API**来说,通常涉及将其放到一台**远程机器**中,搭配一个性能和稳定性都不错的**服务器程序**,以便你的**用户**可以高效地**访问**你的应用程序,而不会出现中断或其他问题。
|
||||
|
||||
这与**开发**阶段形成鲜明对比,在**开发**阶段,你不断更改代码、破坏代码、修复代码, 来回停止和重启服务器等。
|
||||
这与**开发**阶段形成鲜明对比,在**开发**阶段,你不断更改代码、破坏代码、修复代码,来回停止和重启开发服务器等。
|
||||
|
||||
## 部署策略
|
||||
## 部署策略 { #deployment-strategies }
|
||||
|
||||
根据你的使用场景和使用的工具,有多种方法可以实现此目的。
|
||||
|
||||
你可以使用一些工具自行**部署服务器**,你也可以使用能为你完成部分工作的**云服务**,或其他可能的选项。
|
||||
|
||||
例如,我们(FastAPI 团队)构建了 <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a>,让将 FastAPI 应用部署到云端尽可能流畅,并且保持与使用 FastAPI 开发时相同的开发者体验。
|
||||
|
||||
我将向你展示在部署 **FastAPI** 应用程序时你可能应该记住的一些主要概念(尽管其中大部分适用于任何其他类型的 Web 应用程序)。
|
||||
|
||||
在接下来的部分中,你将看到更多需要记住的细节以及一些技巧。 ✨
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 手动运行服务器
|
||||
# 手动运行服务器 { #run-a-server-manually }
|
||||
|
||||
## 使用 `fastapi run` 命令
|
||||
## 使用 `fastapi run` 命令 { #use-the-fastapi-run-command }
|
||||
|
||||
简而言之,使用 `fastapi run` 来运行您的 FastAPI 应用程序:
|
||||
|
||||
@@ -42,11 +42,11 @@ $ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid
|
||||
|
||||
例如,您可以使用该命令在容器、服务器等环境中启动您的 **FastAPI** 应用。
|
||||
|
||||
## ASGI 服务器
|
||||
## ASGI 服务器 { #asgi-servers }
|
||||
|
||||
让我们深入了解一些细节。
|
||||
|
||||
FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称为 <abbr title="Asynchronous Server Gateway Interface,异步服务器网关接口">ASGI</abbr>。FastAPI 本质上是一个 ASGI Web 框架。
|
||||
FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称为 <abbr title="Asynchronous Server Gateway Interface - 异步服务器网关接口">ASGI</abbr>。FastAPI 本质上是一个 ASGI Web 框架。
|
||||
|
||||
要在远程服务器上运行 **FastAPI** 应用(或任何其他 ASGI 应用),您需要一个 ASGI 服务器程序,例如 **Uvicorn**。它是 `fastapi` 命令默认使用的 ASGI 服务器。
|
||||
|
||||
@@ -58,7 +58,7 @@ FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称
|
||||
* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>:基于 Rust 的 HTTP 服务器,专为 Python 应用设计。
|
||||
* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>:NGINX Unit 是一个轻量级且灵活的 Web 应用运行时环境。
|
||||
|
||||
## 服务器主机和服务器程序
|
||||
## 服务器主机和服务器程序 { #server-machine-and-server-program }
|
||||
|
||||
关于名称,有一个小细节需要记住。 💡
|
||||
|
||||
@@ -68,8 +68,7 @@ FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称
|
||||
|
||||
当提到远程主机时,通常将其称为**服务器**,但也称为**机器**(machine)、**VM**(虚拟机)、**节点**。 这些都是指某种类型的远程计算机,通常运行 Linux,您可以在其中运行程序。
|
||||
|
||||
|
||||
## 安装服务器程序
|
||||
## 安装服务器程序 { #install-the-server-program }
|
||||
|
||||
当您安装 FastAPI 时,它自带一个生产环境服务器——Uvicorn,并且您可以使用 `fastapi run` 命令来启动它。
|
||||
|
||||
@@ -101,7 +100,7 @@ $ pip install "uvicorn[standard]"
|
||||
|
||||
///
|
||||
|
||||
## 运行服务器程序
|
||||
## 运行服务器程序 { #run-the-server-program }
|
||||
|
||||
如果您手动安装了 ASGI 服务器,通常需要以特定格式传递一个导入字符串,以便服务器能够正确导入您的 FastAPI 应用:
|
||||
|
||||
@@ -142,7 +141,7 @@ Uvicorn 和其他服务器支持 `--reload` 选项,该选项在开发过程中
|
||||
|
||||
///
|
||||
|
||||
## 部署概念
|
||||
## 部署概念 { #deployment-concepts }
|
||||
|
||||
这些示例运行服务器程序(例如 Uvicorn),启动**单个进程**,在所有 IP(`0.0.0.0`)上监听预定义端口(例如`80`)。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 服务器工作进程(Workers) - 使用 Uvicorn 的多工作进程模式
|
||||
# 服务器工作进程(Workers) - 使用 Uvicorn 的多工作进程模式 { #server-workers-uvicorn-with-workers }
|
||||
|
||||
让我们回顾一下之前的部署概念:
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
在本章节中,我将向您展示如何使用 `fastapi` 命令或直接使用 `uvicorn` 命令以**多工作进程模式**运行 **Uvicorn**。
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
如果您正在使用容器,例如 Docker 或 Kubernetes,我将在下一章中告诉您更多相关信息:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
///
|
||||
|
||||
## 多个工作进程
|
||||
## 多个工作进程 { #multiple-workers }
|
||||
|
||||
您可以使用 `--workers` 命令行选项来启动多个工作进程:
|
||||
|
||||
@@ -111,7 +111,7 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
|
||||
|
||||
您还可以看到它显示了每个进程的 **PID**,父进程(这是**进程管理器**)的 PID 为`27365`,每个工作进程的 PID 为:`27368`、`27369`, `27370`和`27367`。
|
||||
|
||||
## 部署概念
|
||||
## 部署概念 { #deployment-concepts }
|
||||
|
||||
在这里,您学习了如何使用多个**工作进程(workers)**来让应用程序的执行**并行化**,充分利用 CPU 的**多核性能**,并能够处理**更多的请求**。
|
||||
|
||||
@@ -124,13 +124,13 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
|
||||
* **内存**
|
||||
* **启动之前的先前步骤**
|
||||
|
||||
## 容器和 Docker
|
||||
## 容器和 Docker { #containers-and-docker }
|
||||
|
||||
在关于 [容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank} 的下一章中,我将介绍一些可用于处理其他**部署概念**的策略。
|
||||
|
||||
我将向您展示如何**从零开始构建自己的镜像**,以运行一个单独的 Uvicorn 进程。这个过程相对简单,并且在使用 **Kubernetes** 等分布式容器管理系统时,这通常是您需要采取的方法。
|
||||
|
||||
## 回顾
|
||||
## 回顾 { #recap }
|
||||
|
||||
您可以在使用 `fastapi` 或 `uvicorn` 命令时,通过 `--workers` CLI 选项启用多个工作进程(workers),以充分利用**多核 CPU**,以**并行运行多个进程**。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 关于 FastAPI 版本
|
||||
# 关于 FastAPI 版本 { #about-fastapi-versions }
|
||||
|
||||
**FastAPI** 已在许多应用程序和系统的生产环境中使用。 并且测试覆盖率保持在100%。 但其开发进度仍在快速推进。
|
||||
|
||||
@@ -8,41 +8,41 @@
|
||||
|
||||
你现在就可以使用 **FastAPI** 创建生产环境应用程序(你可能已经这样做了一段时间),你只需确保使用的版本可以与其余代码正确配合即可。
|
||||
|
||||
## 固定你的 `fastapi` 版本
|
||||
## 固定你的 `fastapi` 版本 { #pin-your-fastapi-version }
|
||||
|
||||
你应该做的第一件事是将你正在使用的 **FastAPI** 版本“固定”到你知道适用于你的应用程序的特定最新版本。
|
||||
|
||||
例如,假设你在应用程序中使用版本`0.45.0`。
|
||||
例如,假设你在应用程序中使用版本`0.112.0`。
|
||||
|
||||
如果你使用`requirements.txt`文件,你可以使用以下命令指定版本:
|
||||
|
||||
````txt
|
||||
fastapi==0.45.0
|
||||
````
|
||||
```txt
|
||||
fastapi[standard]==0.112.0
|
||||
```
|
||||
|
||||
这意味着你将使用版本`0.45.0`。
|
||||
这意味着你将使用版本`0.112.0`。
|
||||
|
||||
或者你也可以将其固定为:
|
||||
|
||||
````txt
|
||||
fastapi>=0.45.0,<0.46.0
|
||||
````
|
||||
```txt
|
||||
fastapi[standard]>=0.112.0,<0.113.0
|
||||
```
|
||||
|
||||
这意味着你将使用`0.45.0`或更高版本,但低于`0.46.0`,例如,版本`0.45.2`仍会被接受。
|
||||
这意味着你将使用`0.112.0`或更高版本,但低于`0.113.0`,例如,版本`0.112.2`仍会被接受。
|
||||
|
||||
如果你使用任何其他工具来管理你的安装,例如 Poetry、Pipenv 或其他工具,它们都有一种定义包的特定版本的方法。
|
||||
如果你使用任何其他工具来管理你的安装,例如 `uv`、Poetry、Pipenv 或其他工具,它们都有一种定义包的特定版本的方法。
|
||||
|
||||
## 可用版本
|
||||
## 可用版本 { #available-versions }
|
||||
|
||||
你可以在[发行说明](../release-notes.md){.internal-link target=_blank}中查看可用版本(例如查看当前最新版本)。
|
||||
|
||||
## 关于版本
|
||||
## 关于版本 { #about-versions }
|
||||
|
||||
遵循语义版本控制约定,任何低于`1.0.0`的版本都可能会添加 breaking changes。
|
||||
|
||||
FastAPI 还遵循这样的约定:任何`PATCH`版本更改都是为了bug修复和non-breaking changes。
|
||||
FastAPI 还遵循这样的约定:任何"PATCH"版本更改都是为了bug修复和non-breaking changes。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
"PATCH"是最后一个数字,例如,在`0.2.3`中,PATCH版本是`3`。
|
||||
|
||||
@@ -56,13 +56,13 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
"MINOR"版本中会添加breaking changes和新功能。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
"MINOR"是中间的数字,例如,在`0.2.3`中,MINOR版本是`2`。
|
||||
|
||||
///
|
||||
|
||||
## 升级FastAPI版本
|
||||
## 升级FastAPI版本 { #upgrading-the-fastapi-versions }
|
||||
|
||||
你应该为你的应用程序添加测试。
|
||||
|
||||
@@ -72,7 +72,7 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
如果一切正常,或者在进行必要的更改之后,并且所有测试都通过了,那么你可以将`fastapi`固定到新的版本。
|
||||
|
||||
## 关于Starlette
|
||||
## 关于Starlette { #about-starlette }
|
||||
|
||||
你不应该固定`starlette`的版本。
|
||||
|
||||
@@ -80,14 +80,14 @@ fastapi>=0.45.0,<0.46.0
|
||||
|
||||
因此,**FastAPI** 自己可以使用正确的 Starlette 版本。
|
||||
|
||||
## 关于 Pydantic
|
||||
## 关于 Pydantic { #about-pydantic }
|
||||
|
||||
Pydantic 包含针对 **FastAPI** 的测试及其自己的测试,因此 Pydantic 的新版本(`1.0.0`以上)始终与 FastAPI 兼容。
|
||||
|
||||
你可以将 Pydantic 固定到适合你的`1.0.0`以上和`2.0.0`以下的任何版本。
|
||||
你可以将 Pydantic 固定到任何高于 `1.0.0` 且适合你的版本。
|
||||
|
||||
例如:
|
||||
|
||||
````txt
|
||||
pydantic>=1.2.0,<2.0.0
|
||||
````
|
||||
```txt
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 环境变量
|
||||
# 环境变量 { #environment-variables }
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
如果你已经知道什么是“环境变量”并且知道如何使用它们,你可以放心跳过这一部分。
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
环境变量对于处理应用程序**设置**、作为 Python **安装**的一部分等方面非常有用。
|
||||
|
||||
## 创建和使用环境变量
|
||||
## 创建和使用环境变量 { #create-and-use-env-vars }
|
||||
|
||||
你在 **shell(终端)**中就可以**创建**和使用环境变量,并不需要用到 Python:
|
||||
|
||||
@@ -50,7 +50,7 @@ Hello Wade Wilson
|
||||
|
||||
////
|
||||
|
||||
## 在 Python 中读取环境变量
|
||||
## 在 Python 中读取环境变量 { #read-env-vars-in-python }
|
||||
|
||||
你也可以在 Python **之外**的终端中创建环境变量(或使用任何其他方法),然后在 Python 中**读取**它们。
|
||||
|
||||
@@ -63,9 +63,9 @@ name = os.getenv("MY_NAME", "World")
|
||||
print(f"Hello {name} from Python")
|
||||
```
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
第二个参数是 <a href="https://docs.python.org/zh-cn/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的默认返回值。
|
||||
第二个参数是 <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的默认返回值。
|
||||
|
||||
如果没有提供,默认值为 `None`,这里我们提供 `"World"` 作为默认值。
|
||||
|
||||
@@ -151,21 +151,21 @@ Hello World from Python
|
||||
|
||||
</div>
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
你可以在 <a href="https://12factor.net/zh_cn/config" class="external-link" target="_blank">The Twelve-Factor App: 配置</a>中了解更多信息。
|
||||
你可以在 <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: 配置</a>中了解更多信息。
|
||||
|
||||
///
|
||||
|
||||
## 类型和验证
|
||||
## 类型和验证 { #types-and-validation }
|
||||
|
||||
这些环境变量只能处理**文本字符串**,因为它们是处于 Python 范畴之外的,必须与其他程序和操作系统的其余部分兼容(甚至与不同的操作系统兼容,如 Linux、Windows、macOS)。
|
||||
|
||||
这意味着从环境变量中读取的**任何值**在 Python 中都将是一个 `str`,任何类型转换或验证都必须在代码中完成。
|
||||
|
||||
你将在[高级用户指南 - 设置和环境变量](./advanced/settings.md)中了解更多关于使用环境变量处理**应用程序设置**的信息。
|
||||
你将在[高级用户指南 - 设置和环境变量](./advanced/settings.md){.internal-link target=_blank}中了解更多关于使用环境变量处理**应用程序设置**的信息。
|
||||
|
||||
## `PATH` 环境变量
|
||||
## `PATH` 环境变量 { #path-environment-variable }
|
||||
|
||||
有一个**特殊的**环境变量称为 **`PATH`**,操作系统(Linux、macOS、Windows)用它来查找要运行的程序。
|
||||
|
||||
@@ -209,7 +209,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3
|
||||
|
||||
如果找到了,那么操作系统将**使用它**;否则,操作系统会继续在**其他目录**中查找。
|
||||
|
||||
### 安装 Python 和更新 `PATH`
|
||||
### 安装 Python 和更新 `PATH` { #installing-python-and-updating-the-path }
|
||||
|
||||
安装 Python 时,可能会询问你是否要更新 `PATH` 环境变量。
|
||||
|
||||
@@ -233,7 +233,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3
|
||||
|
||||
假设你安装 Python 并最终将其安装在了目录 `C:\opt\custompython\bin` 中。
|
||||
|
||||
如果你同意更新 `PATH` 环境变量 (在 Python 安装程序中,这个操作是名为 `Add Python x.xx to PATH` 的复选框 —— 译者注),那么安装程序将会将 `C:\opt\custompython\bin` 添加到 `PATH` 环境变量中。
|
||||
如果你同意更新 `PATH` 环境变量,那么安装程序将会将 `C:\opt\custompython\bin` 添加到 `PATH` 环境变量中。
|
||||
|
||||
```plaintext
|
||||
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
|
||||
@@ -285,13 +285,13 @@ $ C:\opt\custompython\bin\python
|
||||
|
||||
////
|
||||
|
||||
当学习[虚拟环境](virtual-environments.md)时,这些信息将会很有用。
|
||||
当学习[虚拟环境](virtual-environments.md){.internal-link target=_blank}时,这些信息将会很有用。
|
||||
|
||||
## 结论
|
||||
## 结论 { #conclusion }
|
||||
|
||||
通过这个教程,你应该对**环境变量**是什么以及如何在 Python 中使用它们有了基本的了解。
|
||||
|
||||
你也可以在<a href="https://zh.wikipedia.org/wiki/%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" class="external-link" target="_blank">环境变量 - 维基百科</a> (<a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia for Environment Variable</a>) 中了解更多关于它们的信息。
|
||||
你也可以在<a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">环境变量 - 维基百科</a>中了解更多关于它们的信息。
|
||||
|
||||
在许多情况下,环境变量的用途和适用性并不是很明显。但是在开发过程中,它们会在许多不同的场景中出现,因此了解它们是很有必要的。
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# FastAPI CLI
|
||||
# FastAPI CLI { #fastapi-cli }
|
||||
|
||||
**FastAPI CLI** 是一个命令行程序,你可以用它来部署和运行你的 FastAPI 应用程序,管理你的 FastAPI 项目,等等。
|
||||
|
||||
当你安装 FastAPI 时(例如使用 `pip install FastAPI` 命令),会包含一个名为 `fastapi-cli` 的软件包,该软件包在终端中提供 `fastapi` 命令。
|
||||
当你安装 FastAPI 时(例如使用 `pip install "fastapi[standard]"`),会包含一个名为 `fastapi-cli` 的软件包,该软件包在终端中提供 `fastapi` 命令。
|
||||
|
||||
要在开发环境中运行你的 FastAPI 应用,你可以使用 `fastapi dev` 命令:
|
||||
|
||||
@@ -48,32 +48,28 @@ $ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid
|
||||
|
||||
该命令行程序 `fastapi` 就是 **FastAPI CLI**。
|
||||
|
||||
FastAPI CLI 接收你的 Python 程序路径,自动检测包含 FastAPI 的变量(通常命名为 `app`)及其导入方式,然后启动服务。
|
||||
FastAPI CLI 接收你的 Python 程序路径(例如 `main.py`),自动检测 `FastAPI` 实例(通常命名为 `app`),确定正确的导入方式,然后启动服务。
|
||||
|
||||
在生产环境中,你应该使用 `fastapi run` 命令。🚀
|
||||
|
||||
在内部,**FastAPI CLI** 使用了 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>,这是一个高性能、适用于生产环境的 ASGI 服务器。😎
|
||||
|
||||
## `fastapi dev`
|
||||
## `fastapi dev` { #fastapi-dev }
|
||||
|
||||
当你运行 `fastapi dev` 时,它将以开发模式运行。
|
||||
|
||||
默认情况下,它会启用**自动重载**,因此当你更改代码时,它会自动重新加载服务器。该功能是资源密集型的,且相较不启用时更不稳定,因此你应该仅在开发环境下使用它。
|
||||
默认情况下,它会启用**自动重载**,因此当你更改代码时,它会自动重新加载服务器。该功能是资源密集型的,且相较不启用时更不稳定,因此你应该仅在开发环境下使用它。它还会监听 IP 地址 `127.0.0.1`,这是你的机器仅与自身通信的 IP(`localhost`)。
|
||||
|
||||
默认情况下,它将监听 IP 地址 `127.0.0.1`,这是你的机器与自身通信的 IP 地址(`localhost`)。
|
||||
|
||||
## `fastapi run`
|
||||
## `fastapi run` { #fastapi-run }
|
||||
|
||||
当你运行 `fastapi run` 时,它默认以生产环境模式运行。
|
||||
|
||||
默认情况下,**自动重载是禁用的**。
|
||||
|
||||
它将监听 IP 地址 `0.0.0.0`,即所有可用的 IP 地址,这样任何能够与该机器通信的人都可以公开访问它。这通常是你在生产环境中运行它的方式,例如在容器中运行。
|
||||
默认情况下,**自动重载是禁用的**。它将监听 IP 地址 `0.0.0.0`,即所有可用的 IP 地址,这样任何能够与该机器通信的人都可以公开访问它。这通常是你在生产环境中运行它的方式,例如在容器中运行。
|
||||
|
||||
在大多数情况下,你会(且应该)有一个“终止代理”在上层为你处理 HTTPS,这取决于你如何部署应用程序,你的服务提供商可能会为你处理此事,或者你可能需要自己设置。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
你可以在 [deployment documentation](deployment/index.md){.internal-link target=_blank} 获得更多信息。
|
||||
你可以在[部署文档](deployment/index.md){.internal-link target=_blank}中了解更多。
|
||||
|
||||
///
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
# 特性
|
||||
# 特性 { #features }
|
||||
|
||||
## FastAPI 特性
|
||||
## FastAPI 特性 { #fastapi-features }
|
||||
|
||||
**FastAPI** 提供了以下内容:
|
||||
|
||||
### 基于开放标准
|
||||
### 基于开放标准 { #based-on-open-standards }
|
||||
|
||||
|
||||
* 用于创建 API 的 <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> 包含了<abbr title="也被叫做: endpoints, routes">路径</abbr><abbr title="也叫做HTTP方法, 例如 POST, GET, PUT, DELETE">操作</abbr>,请求参数,请求体,安全性等的声明。
|
||||
* 使用 <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (因为 OpenAPI 本身就是基于 JSON Schema 的)自动生成数据模型文档。
|
||||
* 用于创建 API 的 <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a>,包含对<abbr title="也被叫做: endpoints, routes">路径</abbr> <abbr title="也叫做 HTTP 方法,如 POST、GET、PUT、DELETE">操作</abbr>、参数、请求体、安全等的声明。
|
||||
* 使用 <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> 自动生成数据模型文档(因为 OpenAPI 本身就是基于 JSON Schema 的)。
|
||||
* 经过了缜密的研究后围绕这些标准而设计。并非狗尾续貂。
|
||||
* 这也允许了在很多语言中自动**生成客户端代码**。
|
||||
|
||||
### 自动生成文档
|
||||
### 自动生成文档 { #automatic-docs }
|
||||
|
||||
交互式 API 文档以及具探索性 web 界面。因为该框架是基于 OpenAPI,所以有很多可选项,FastAPI 默认自带两个交互式 API 文档。
|
||||
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>,可交互式操作,能在浏览器中直接调用和测试你的 API 。
|
||||
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>,可交互式操作,能在浏览器中直接调用和测试你的 API。
|
||||
|
||||

|
||||
|
||||
@@ -24,11 +23,11 @@
|
||||
|
||||

|
||||
|
||||
### 更主流的 Python
|
||||
### 更主流的 Python { #just-modern-python }
|
||||
|
||||
全部都基于标准的 **Python 3.6 类型**声明(感谢 Pydantic )。没有新的语法需要学习。只需要标准的 Python 。
|
||||
全部都基于标准的 **Python 类型** 声明(感谢 Pydantic)。没有新的语法需要学习。只需要标准的现代 Python。
|
||||
|
||||
如果你需要2分钟来学习如何使用 Python 类型(即使你不使用 FastAPI ),看看这个简短的教程:[Python Types](python-types.md){.internal-link target=_blank}。
|
||||
如果你需要2分钟来学习如何使用 Python 类型(即使你不使用 FastAPI),看看这个简短的教程:[Python Types](python-types.md){.internal-link target=_blank}。
|
||||
|
||||
编写带有类型标注的标准 Python:
|
||||
|
||||
@@ -37,13 +36,13 @@ from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Declare a variable as a str
|
||||
# and get editor support inside the function
|
||||
# 将变量声明为 str
|
||||
# 并在函数内获得编辑器支持
|
||||
def main(user_id: str):
|
||||
return user_id
|
||||
|
||||
|
||||
# A Pydantic model
|
||||
# 一个 Pydantic 模型
|
||||
class User(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
@@ -65,19 +64,19 @@ my_second_user: User = User(**second_user_data)
|
||||
```
|
||||
|
||||
|
||||
/// info
|
||||
/// info | 信息
|
||||
|
||||
`**second_user_data` 意思是:
|
||||
`**second_user_data` 意思是:
|
||||
|
||||
直接将`second_user_data`字典的键和值直接作为key-value参数传递,等同于:`User(id=4, name="Mary", joined="2018-11-30")`
|
||||
直接将 `second_user_data` 字典的键和值作为 key-value 参数传入,等同于:`User(id=4, name="Mary", joined="2018-11-30")`
|
||||
|
||||
///
|
||||
|
||||
### 编辑器支持
|
||||
### 编辑器支持 { #editor-support }
|
||||
|
||||
整个框架都被设计得易于使用且直观,所有的决定都在开发之前就在多个编辑器上进行了测试,来确保最佳的开发体验。
|
||||
|
||||
在最近的 Python 开发者调查中,我们能看到 <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank"> 被使用最多的功能是"自动补全"</a>。
|
||||
在最近的 Python 开发者调查中,我们能看到 <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank">被使用最多的功能是“自动补全”</a>。
|
||||
|
||||
整个 **FastAPI** 框架就是基于这一点的。任何地方都可以进行自动补全。
|
||||
|
||||
@@ -85,62 +84,58 @@ my_second_user: User = User(**second_user_data)
|
||||
|
||||
在这里,你的编辑器可能会这样帮助你:
|
||||
|
||||
* <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a> 中:
|
||||
* 在 <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a> 中:
|
||||
|
||||

|
||||
|
||||
* <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 中:
|
||||
* 在 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 中:
|
||||
|
||||

|
||||
|
||||
你将能进行代码补全,这是在之前你可能曾认为不可能的事。例如,在来自请求 JSON 体(可能是嵌套的)中的键 `price`。
|
||||
|
||||
不会再输错键名,来回翻看文档,或者来回滚动寻找你最后使用的 `username` 或者 `user_name` 。
|
||||
不会再输错键名,来回翻看文档,或者来回滚动寻找你最后使用的 `username` 或者 `user_name`。
|
||||
|
||||
|
||||
|
||||
### 简洁
|
||||
### 简洁 { #short }
|
||||
|
||||
任何类型都有合理的**默认值**,任何和地方都有可选配置。所有的参数被微调,来满足你的需求,定义成你需要的 API。
|
||||
|
||||
但是默认情况下,一切都能**“顺利工作”**。
|
||||
|
||||
### 验证
|
||||
### 验证 { #validation }
|
||||
|
||||
* 校验大部分(甚至所有?)的 Python **数据类型**,包括:
|
||||
* JSON 对象 (`dict`).
|
||||
* JSON 对象 (`dict`)。
|
||||
* JSON 数组 (`list`) 定义成员类型。
|
||||
* 字符串 (`str`) 字段, 定义最小或最大长度。
|
||||
* 数字 (`int`, `float`) 有最大值和最小值, 等等。
|
||||
* 字符串 (`str`) 字段,定义最小或最大长度。
|
||||
* 数字 (`int`, `float`) 有最大值和最小值,等等。
|
||||
|
||||
* 校验外来类型, 比如:
|
||||
* URL.
|
||||
* Email.
|
||||
* UUID.
|
||||
* ...及其他.
|
||||
* 校验外来类型,比如:
|
||||
* URL。
|
||||
* Email。
|
||||
* UUID。
|
||||
* ...及其他。
|
||||
|
||||
所有的校验都由完善且强大的 **Pydantic** 处理。
|
||||
|
||||
### 安全性及身份验证
|
||||
### 安全性及身份验证 { #security-and-authentication }
|
||||
|
||||
集成了安全性和身份认证。杜绝数据库或者数据模型的渗透风险。
|
||||
|
||||
OpenAPI 中定义的安全模式,包括:
|
||||
|
||||
* HTTP 基本认证。
|
||||
* **OAuth2** (也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。
|
||||
* **OAuth2**(也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。
|
||||
* API 密钥,在:
|
||||
* 请求头。
|
||||
* 查询参数。
|
||||
* Cookies, 等等。
|
||||
* Cookies,等等。
|
||||
|
||||
加上来自 Starlette(包括 **session cookie**)的所有安全特性。
|
||||
|
||||
所有的这些都是可复用的工具和组件,可以轻松与你的系统,数据仓库,关系型以及 NoSQL 数据库等等集成。
|
||||
|
||||
|
||||
|
||||
### 依赖注入
|
||||
### 依赖注入 { #dependency-injection }
|
||||
|
||||
FastAPI 有一个使用非常简单,但是非常强大的<abbr title='也叫做 "components", "resources", "services", "providers"'><strong>依赖注入</strong></abbr>系统。
|
||||
|
||||
@@ -149,48 +144,47 @@ FastAPI 有一个使用非常简单,但是非常强大的<abbr title='也叫
|
||||
* 所有的依赖关系都可以从请求中获取数据,并且**增加了路径操作**约束和自动文档生成。
|
||||
* 即使在依赖项中被定义的*路径操作* 也会**自动验证**。
|
||||
* 支持复杂的用户身份认证系统,**数据库连接**等等。
|
||||
* **不依赖**数据库,前端等。 但是和它们集成很简单。
|
||||
* **不依赖**数据库,前端等。但是和它们集成很简单。
|
||||
|
||||
### 无限制"插件"
|
||||
### 无限制的“插件” { #unlimited-plug-ins }
|
||||
|
||||
或者说,导入并使用你需要的代码,而不需要它们。
|
||||
|
||||
任何集成都被设计得被易于使用(用依赖关系),你可以用和*路径操作*相同的结构和语法,在两行代码中为你的应用创建一个“插件”。
|
||||
|
||||
### 测试
|
||||
### 测试 { #tested }
|
||||
|
||||
* 100% <abbr title="自动测试的代码量">测试覆盖</abbr>。
|
||||
* 代码库100% <abbr title="Python类型注解,有了这个你的编辑器和外部工具可以给你更好的支持">类型注释</abbr>。
|
||||
* 用于生产应用。
|
||||
|
||||
## Starlette 特性
|
||||
## Starlette 特性 { #starlette-features }
|
||||
|
||||
**FastAPI** 和 <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a> 完全兼容(并基于)。所以,你有的其他的 Starlette 代码也能正常工作。`FastAPI` 实际上是 `Starlette`的一个子类。所以,如果你已经知道或者使用 Starlette,大部分的功能会以相同的方式工作。
|
||||
**FastAPI** 和 <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a> 完全兼容(并基于)。所以,你有的其他的 Starlette 代码也能正常工作。`FastAPI` 实际上是 `Starlette` 的一个子类。所以,如果你已经知道或者使用 Starlette,大部分的功能会以相同的方式工作。
|
||||
|
||||
通过 **FastAPI** 你可以获得所有 **Starlette** 的特性 ( FastAPI 就像加强版的 Starlette ):
|
||||
通过 **FastAPI** 你可以获得所有 **Starlette** 的特性(FastAPI 就像加强版的 Starlette):
|
||||
|
||||
* 令人惊叹的性能。它是 <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">Python 可用的最快的框架之一,和 **NodeJS** 及 **Go** 相当</a>。
|
||||
* **支持 WebSocket** 。
|
||||
* **支持 GraphQL** 。
|
||||
* 后台任务处理。
|
||||
* 令人惊叹的性能。它是 <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">Python 可用的最快的框架之一,和 **NodeJS** 及 **Go** 相当</a>。
|
||||
* **支持 WebSocket**。
|
||||
* 进程内后台任务。
|
||||
* Startup 和 shutdown 事件。
|
||||
* 测试客户端基于 HTTPX。
|
||||
* **CORS**, GZip, 静态文件, 流响应。
|
||||
* 支持 **Session 和 Cookie** 。
|
||||
* **CORS**、GZip、静态文件、流响应。
|
||||
* 支持 **Session 和 Cookie**。
|
||||
* 100% 测试覆盖率。
|
||||
* 代码库 100% 类型注释。
|
||||
|
||||
## Pydantic 特性
|
||||
## Pydantic 特性 { #pydantic-features }
|
||||
|
||||
**FastAPI** 和 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a> 完全兼容(并基于)。所以,你有的其他的 Pydantic 代码也能正常工作。
|
||||
**FastAPI** 和 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a> 完全兼容(并基于)。所以,你有的其他的 Pydantic 代码也能正常工作。
|
||||
|
||||
兼容包括基于 Pydantic 的外部库, 例如用与数据库的 <abbr title="对象关系映射">ORM</abbr>s, <abbr title="对象文档映射">ODM</abbr>s。
|
||||
兼容包括基于 Pydantic 的外部库,例如用于数据库的 <abbr title="Object-Relational Mapper - 对象关系映射">ORM</abbr>s、<abbr title="Object-Document Mapper - 对象文档映射">ODM</abbr>s。
|
||||
|
||||
这也意味着在很多情况下,你可以将从请求中获得的相同对象**直接传到数据库**,因为所有的验证都是自动的。
|
||||
|
||||
反之亦然,在很多情况下,你也可以将从数据库中获取的对象**直接传到客户端**。
|
||||
|
||||
通过 **FastAPI** 你可以获得所有 **Pydantic** (FastAPI 基于 Pydantic 做了所有的数据处理):
|
||||
通过 **FastAPI** 你可以获得所有 **Pydantic**(FastAPI 基于 Pydantic 做了所有的数据处理):
|
||||
|
||||
* **更简单**:
|
||||
* 没有新的模式定义 micro-language 需要学习。
|
||||
@@ -198,8 +192,8 @@ FastAPI 有一个使用非常简单,但是非常强大的<abbr title='也叫
|
||||
* 和你 **<abbr title="集成开发环境,和代码编辑器类似">IDE</abbr>/<abbr title="一个检查代码错误的程序">linter</abbr>/brain** 适配:
|
||||
* 因为 pydantic 数据结构仅仅是你定义的类的实例;自动补全,linting,mypy 以及你的直觉应该可以和你验证的数据一起正常工作。
|
||||
* 验证**复杂结构**:
|
||||
* 使用分层的 Pydantic 模型, Python `typing`的 `List` 和 `Dict` 等等。
|
||||
* 验证器使我们能够简单清楚的将复杂的数据模式定义、检查并记录为 JSON Schema。
|
||||
* 使用分层的 Pydantic 模型,Python `typing` 的 `List` 和 `Dict` 等等。
|
||||
* 验证器使我们能够简单清楚地将复杂的数据模式定义、检查并记录为 JSON Schema。
|
||||
* 你可以拥有深度**嵌套的 JSON** 对象并对它们进行验证和注释。
|
||||
* **可扩展**:
|
||||
* Pydantic 允许定义自定义数据类型或者你可以用验证器装饰器对被装饰的模型上的方法扩展验证。
|
||||
|
||||
@@ -1,147 +1,254 @@
|
||||
# 帮助 FastAPI 与求助
|
||||
# 帮助 FastAPI - 获取帮助 { #help-fastapi-get-help }
|
||||
|
||||
您喜欢 **FastAPI** 吗?
|
||||
你喜欢 **FastAPI** 吗?
|
||||
|
||||
想帮助 FastAPI?其它用户?还有项目作者?
|
||||
想帮助 FastAPI、其他用户和作者吗?
|
||||
|
||||
或要求助怎么使用 **FastAPI**?
|
||||
或者你想获取 **FastAPI** 的帮助?
|
||||
|
||||
以下几种帮助的方式都非常简单(有些只需要点击一两下鼠标)。
|
||||
有很多非常简单的方式可以帮忙(有些只需点一两下)。
|
||||
|
||||
求助的渠道也很多。
|
||||
同样,也有多种途径可以获得帮助。
|
||||
|
||||
## 订阅新闻邮件
|
||||
## 订阅新闻邮件 { #subscribe-to-the-newsletter }
|
||||
|
||||
您可以订阅 [**FastAPI 和它的小伙伴** 新闻邮件](newsletter.md){.internal-link target=_blank}(不会经常收到)
|
||||
你可以订阅(不频繁的)[**FastAPI and friends** 新闻邮件](newsletter.md){.internal-link target=_blank},获取如下更新:
|
||||
|
||||
* FastAPI 及其小伙伴的新闻 🚀
|
||||
* 指南 📝
|
||||
* 功能 ✨
|
||||
* 破坏性更改 🚨
|
||||
* 开发技巧 ✅
|
||||
* 使用技巧 ✅
|
||||
|
||||
## 在推特上关注 FastAPI
|
||||
## 在 X (Twitter) 上关注 FastAPI { #follow-fastapi-on-x-twitter }
|
||||
|
||||
<a href="https://x.com/fastapi" class="external-link" target="_blank">在 **X (Twitter)** 上关注 @fastapi</a> 获取 **FastAPI** 的最新消息。🐦
|
||||
<a href="https://x.com/fastapi" class="external-link" target="_blank">在 **X (Twitter)** 上关注 @fastapi</a> 获取 **FastAPI** 的最新动态。🐦
|
||||
|
||||
## 在 GitHub 上为 **FastAPI** 加星
|
||||
## 在 GitHub 上为 **FastAPI** 加星 { #star-fastapi-in-github }
|
||||
|
||||
您可以在 GitHub 上 **Star** FastAPI(只要点击右上角的星星就可以了): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi。</a>⭐️
|
||||
你可以在 GitHub 上为 FastAPI 点亮「星标」(点击右上角的星形按钮):<a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>。⭐️
|
||||
|
||||
**Star** 以后,其它用户就能更容易找到 FastAPI,并了解到已经有其他用户在使用它了。
|
||||
点亮星标后,其他用户更容易发现它,并看到它已经对许多人有帮助。
|
||||
|
||||
## 关注 GitHub 资源库的版本发布
|
||||
## 关注 GitHub 资源库的版本发布 { #watch-the-github-repository-for-releases }
|
||||
|
||||
您还可以在 GitHub 上 **Watch** FastAPI,(点击右上角的 **Watch** 按钮)<a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi。</a>👀
|
||||
你可以在 GitHub 上「关注」FastAPI(点击右上角的「watch」按钮):<a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>。👀
|
||||
|
||||
您可以选择只关注发布(**Releases only**)。
|
||||
在那里你可以选择「Releases only」。
|
||||
|
||||
这样,您就可以(在电子邮件里)接收到 **FastAPI** 新版发布的通知,及时了解 bug 修复与新功能。
|
||||
这样做之后,每当 **FastAPI** 发布新版本(包含修复和新功能),你都会收到通知(邮件)。
|
||||
|
||||
## 联系作者
|
||||
## 联系作者 { #connect-with-the-author }
|
||||
|
||||
您可以联系项目作者,就是<a href="https://tiangolo.com" class="external-link" target="_blank">我(Sebastián Ramírez / `tiangolo`</a>)。
|
||||
你可以联系<a href="https://tiangolo.com" class="external-link" target="_blank">我(Sebastián Ramírez / `tiangolo`)</a>,作者本人。
|
||||
|
||||
您可以:
|
||||
你可以:
|
||||
|
||||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">在 **GitHub** 上关注我</a>
|
||||
* 了解其它我创建的开源项目,或许对您会有帮助
|
||||
* 关注我什么时候创建新的开源项目
|
||||
* <a href="https://x.com/tiangolo" class="external-link" target="_blank">在 **X (Twitter)** 上关注我</a>
|
||||
* 告诉我您使用 FastAPI(我非常乐意听到这种消息)
|
||||
* 接收我发布公告或新工具的消息
|
||||
* 您还可以关注<a href="https://x.com/fastapi" class="external-link" target="_blank">@fastapi on X (Twitter)</a>,这是个独立的账号
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">在**领英**上联系我</a>
|
||||
* 接收我发布公告或新工具的消息(虽然我用 X (Twitter) 比较多)
|
||||
* 阅读我在 <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> 或 <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a> 上的文章,或关注我
|
||||
* 阅读我的其它想法、文章,了解我创建的工具
|
||||
* 关注我,这样就可以随时看到我发布的新文章
|
||||
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">在 **GitHub** 上关注我</a>。
|
||||
* 了解我创建的其他开源项目,也许对你有帮助。
|
||||
* 关注我何时创建新的开源项目。
|
||||
* 关注我在 <a href="https://x.com/tiangolo" class="external-link" target="_blank">**X (Twitter)**</a> 或 <a href="https://fosstodon.org/@tiangolo" class="external-link" target="_blank">Mastodon</a>。
|
||||
* 告诉我你如何使用 FastAPI(我很喜欢听这些)。
|
||||
* 获取我发布公告或新工具的消息。
|
||||
* 你也可以<a href="https://x.com/fastapi" class="external-link" target="_blank">关注 @fastapi on X (Twitter)</a>(独立账号)。
|
||||
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">在 **LinkedIn** 上关注我</a>。
|
||||
* 获取我发布公告或新工具的消息(不过我更常用 X (Twitter) 🤷♂)。
|
||||
* 阅读我在 <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> 或 <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a> 上的文章(或关注我)。
|
||||
* 阅读我的其他想法、文章,以及我创建的工具。
|
||||
* 关注我,这样当我发布新文章时你会第一时间看到。
|
||||
|
||||
## Tweet about **FastAPI**
|
||||
## 发推谈谈 **FastAPI** { #tweet-about-fastapi }
|
||||
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Tweet about **FastAPI**</a> 让我和大家知道您为什么喜欢 FastAPI。🎉
|
||||
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Tweet about **FastAPI**</a>,告诉我和大家你为什么喜欢它。🎉
|
||||
|
||||
知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。
|
||||
我很高兴听到 **FastAPI** 的使用情况、你喜欢它的哪些点、你在哪个项目/公司使用它,等等。
|
||||
|
||||
## 为 FastAPI 投票
|
||||
## 为 FastAPI 投票 { #vote-for-fastapi }
|
||||
|
||||
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">在 Slant 上为 **FastAPI** 投票</a>
|
||||
* <a href="https://alternativeto.net/software/fastapi/" class="external-link" target="_blank">在 AlternativeTo 上为 **FastAPI** 投票</a>
|
||||
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">在 Slant 上为 **FastAPI** 投票</a>。
|
||||
* <a href="https://alternativeto.net/software/fastapi/about/" class="external-link" target="_blank">在 AlternativeTo 上为 **FastAPI** 投票</a>。
|
||||
* <a href="https://stackshare.io/pypi-fastapi" class="external-link" target="_blank">在 StackShare 上标注你在用 **FastAPI**</a>。
|
||||
|
||||
## 在 GitHub 上帮助其他人解决问题
|
||||
## 在 GitHub 上帮别人解答问题 { #help-others-with-questions-in-github }
|
||||
|
||||
您可以查看<a href="https://github.com/fastapi/fastapi/issues" class="external-link" target="_blank">现有 issues</a>,并尝试帮助其他人解决问题,说不定您能解决这些问题呢。🤓
|
||||
你可以尝试在以下地方帮助他人解答问题:
|
||||
|
||||
如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#_3){.internal-link target=_blank}。🎉
|
||||
* <a href="https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered" class="external-link" target="_blank">GitHub Discussions</a>
|
||||
* <a href="https://github.com/fastapi/fastapi/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aquestion+-label%3Aanswered+" class="external-link" target="_blank">GitHub Issues</a>
|
||||
|
||||
## 监听 GitHub 资源库
|
||||
很多情况下,你也许已经知道这些问题的答案了。🤓
|
||||
|
||||
您可以在 GitHub 上「监听」FastAPI(点击右上角的 "watch" 按钮): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
|
||||
如果你帮助了很多人解答问题,你会成为官方的 [FastAPI 专家](fastapi-people.md#fastapi-experts){.internal-link target=_blank}。🎉
|
||||
|
||||
如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。
|
||||
只要记住,最重要的一点是:尽量友善。人们带着挫败感而来,很多时候他们的提问方式并不理想,但请尽你所能地友好对待。🤗
|
||||
|
||||
然后您就可以尝试并帮助他们解决问题。
|
||||
我们的目标是让 **FastAPI** 社区友好且包容。同时,也不要接受对他人的霸凌或不尊重。我们需要彼此照顾。
|
||||
|
||||
## 创建 Issue
|
||||
---
|
||||
|
||||
您可以在 GitHub 资源库中<a href="https://github.com/fastapi/fastapi/issues/new/choose" class="external-link" target="_blank">创建 Issue</a>,例如:
|
||||
以下是如何帮助他人解答问题(在 Discussions 或 Issues 中):
|
||||
|
||||
* 提出**问题**或**意见**
|
||||
* 提出新**特性**建议
|
||||
### 理解问题 { #understand-the-question }
|
||||
|
||||
**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。😉
|
||||
* 看看你是否能理解提问者的**目的**和使用场景。
|
||||
|
||||
## 创建 PR
|
||||
* 然后检查问题(绝大多数是提问)是否**清晰**。
|
||||
|
||||
您可以创建 PR 为源代码做[贡献](contributing.md){.internal-link target=_blank},例如:
|
||||
* 很多时候,问题是围绕提问者想象中的解决方案,但可能有**更好的**方案。如果你更好地理解了问题和使用场景,你就可能提出更**合适的替代方案**。
|
||||
|
||||
* 修改文档错别字
|
||||
* <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">编辑这个文件</a>,分享 FastAPI 的文章、视频、博客,不论是您自己的,还是您看到的都成
|
||||
* 注意,添加的链接要放在对应区块的开头
|
||||
* [翻译文档](contributing.md#_8){.internal-link target=_blank}
|
||||
* 审阅别人翻译的文档
|
||||
* 添加新的文档内容
|
||||
* 修复现有问题/Bug
|
||||
* 添加新功能
|
||||
* 如果你没能理解问题,请请求更多**细节**。
|
||||
|
||||
## 加入聊天
|
||||
### 复现问题 { #reproduce-the-problem }
|
||||
|
||||
快加入 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">Discord 聊天服务器</a> 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。
|
||||
在大多数情况下与问题相关的都是提问者的**原始代码**。
|
||||
|
||||
/// tip | 提示
|
||||
很多时候他们只会粘贴一小段代码,但这不足以**复现问题**。
|
||||
|
||||
如有问题,请在 <a href="https://github.com/fastapi/fastapi/issues/new/choose" class="external-link" target="_blank">GitHub Issues</a> 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank}的帮助。
|
||||
* 你可以请他们提供一个<a href="https://stackoverflow.com/help/minimal-reproducible-example" class="external-link" target="_blank">可最小复现的示例</a>,你可以**复制粘贴**并在本地运行,看到与他们相同的错误或行为,或者更好地理解他们的用例。
|
||||
|
||||
聊天室仅供闲聊。
|
||||
* 如果你非常热心,你也可以尝试仅根据问题描述自己**构造一个示例**。不过要记住,这可能会花很多时间,通常先请他们澄清问题会更好。
|
||||
|
||||
### 提出解决方案 { #suggest-solutions }
|
||||
|
||||
* 在能够理解问题之后,你可以给出一个可能的**答案**。
|
||||
|
||||
* 很多情况下,更好的是去理解他们**底层的问题或场景**,因为可能存在比他们尝试的方法更好的解决方式。
|
||||
|
||||
### 请求关闭问题 { #ask-to-close }
|
||||
|
||||
如果他们回复了,很有可能你已经解决了他们的问题,恭喜,**你是英雄**!🦸
|
||||
|
||||
* 现在,如果问题已解决,你可以请他们:
|
||||
* 在 GitHub Discussions 中:将你的评论标记为**答案**。
|
||||
* 在 GitHub Issues 中:**关闭**该 issue。
|
||||
|
||||
## 关注 GitHub 资源库 { #watch-the-github-repository }
|
||||
|
||||
你可以在 GitHub 上「关注」FastAPI(点击右上角的「watch」按钮):<a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>。👀
|
||||
|
||||
如果你选择「Watching」而非「Releases only」,当有人创建新的 issue 或问题时你会收到通知。你也可以指定只通知新 issues、discussions、PR 等。
|
||||
|
||||
然后你就可以尝试帮助他们解决这些问题。
|
||||
|
||||
## 提问 { #ask-questions }
|
||||
|
||||
你可以在 GitHub 资源库中<a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">创建一个新问题(Question)</a>,例如:
|
||||
|
||||
* 提出一个**问题**或关于某个**问题**的求助。
|
||||
* 建议一个新的**功能**。
|
||||
|
||||
**注意**:如果你这么做了,我也会请你去帮助其他人。😉
|
||||
|
||||
## 审阅 Pull Request { #review-pull-requests }
|
||||
|
||||
你可以帮我审阅他人的 Pull Request。
|
||||
|
||||
再次提醒,请尽力保持友善。🤗
|
||||
|
||||
---
|
||||
|
||||
下面是需要注意的点,以及如何审阅一个 Pull Request:
|
||||
|
||||
### 理解问题 { #understand-the-problem }
|
||||
|
||||
* 首先,确保你**理解这个 PR 要解决的问题**。它可能在 GitHub Discussion 或 issue 中有更长的讨论。
|
||||
|
||||
* 也有很大可能这个 PR 实际上并不需要,因为问题可以用**不同方式**解决。这种情况下你可以提出或询问该方案。
|
||||
|
||||
### 不用过分担心风格 { #dont-worry-about-style }
|
||||
|
||||
* 不用太在意提交信息风格等,我会在合并时 squash 并手动调整提交信息。
|
||||
|
||||
* 也不用过分担心代码风格规则,已经有自动化工具在检查。
|
||||
|
||||
如果还有其他风格或一致性需求,我会直接提出,或者我会在其上追加提交做必要修改。
|
||||
|
||||
### 检查代码 { #check-the-code }
|
||||
|
||||
* 检查并阅读代码,看看是否说得通,**在本地运行**并确认它确实解决了问题。
|
||||
|
||||
* 然后**评论**说明你已经这样做了,这样我就知道你确实检查过。
|
||||
|
||||
/// info | 信息
|
||||
|
||||
不幸的是,我不能仅仅信任那些有很多人批准的 PR。
|
||||
|
||||
多次发生过这样的情况:PR 有 3、5 个甚至更多的批准,可能是因为描述很吸引人,但当我检查时,它们实际上是坏的、有 bug,或者并没有解决它声称要解决的问题。😅
|
||||
|
||||
所以,真正重要的是你确实读过并运行过代码,并在评论里告诉我你做过这些。🤓
|
||||
|
||||
///
|
||||
|
||||
### 别在聊天室里提问
|
||||
* 如果 PR 可以在某些方面简化,你可以提出建议,但没必要过分挑剔,很多东西比较主观(我也会有我自己的看法 🙈),因此尽量关注关键点更好。
|
||||
|
||||
注意,聊天室更倾向于“闲聊”,经常有人会提出一些笼统得让人难以回答的问题,所以在这里提问一般没人回答。
|
||||
### 测试 { #tests }
|
||||
|
||||
GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。但在聊天室里,我就没功夫这么做了。😅
|
||||
* 帮我检查 PR 是否包含**测试**。
|
||||
|
||||
聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。
|
||||
* 确认在合并 PR 之前,测试**会失败**。🚨
|
||||
|
||||
另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。😄
|
||||
* 然后确认合并 PR 之后,测试**能通过**。✅
|
||||
|
||||
## 赞助作者
|
||||
* 很多 PR 没有测试,你可以**提醒**他们添加测试,或者你甚至可以自己**建议**一些测试。这是最耗时的部分之一,你能在这方面帮上大忙。
|
||||
|
||||
您还可以通过 <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub 赞助商</a>资助本项目的作者(就是我)。
|
||||
* 然后也请评论你做了哪些验证,这样我就知道你检查过。🤓
|
||||
|
||||
给我买杯咖啡 ☕️ 以示感谢 😄
|
||||
## 创建 Pull Request { #create-a-pull-request }
|
||||
|
||||
当然您也可以成为 FastAPI 的金牌或银牌赞助商。🏅🎉
|
||||
你可以通过 Pull Request 为源代码[做贡献](contributing.md){.internal-link target=_blank},例如:
|
||||
|
||||
## 赞助 FastAPI 使用的工具
|
||||
* 修正文档中的一个错别字。
|
||||
* 通过<a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">编辑这个文件</a>分享你创建或发现的关于 FastAPI 的文章、视频或播客。
|
||||
* 请确保把你的链接添加到相应区块的开头。
|
||||
* 帮助把[文档翻译](contributing.md#translations){.internal-link target=_blank}成你的语言。
|
||||
* 你也可以审阅他人创建的翻译。
|
||||
* 提议新增文档章节。
|
||||
* 修复现有 issue/bug。
|
||||
* 记得添加测试。
|
||||
* 添加新功能。
|
||||
* 记得添加测试。
|
||||
* 如果相关,记得补充文档。
|
||||
|
||||
如您在本文档中所见,FastAPI 站在巨人的肩膀上,它们分别是 Starlette 和 Pydantic。
|
||||
## 帮忙维护 FastAPI { #help-maintain-fastapi }
|
||||
|
||||
您还可以赞助:
|
||||
帮我一起维护 **FastAPI** 吧!🤓
|
||||
|
||||
* <a href="https://github.com/sponsors/samuelcolvin" class="external-link" target="_blank">Samuel Colvin (Pydantic)</a>
|
||||
* <a href="https://github.com/sponsors/encode" class="external-link" target="_blank">Encode (Starlette, Uvicorn)</a>
|
||||
有很多工作要做,其中大部分其实**你**都能做。
|
||||
|
||||
你现在就能做的主要事情有:
|
||||
|
||||
* [在 GitHub 上帮别人解答问题](#help-others-with-questions-in-github){.internal-link target=_blank}(见上面的章节)。
|
||||
* [审阅 Pull Request](#review-pull-requests){.internal-link target=_blank}(见上面的章节)。
|
||||
|
||||
这两项工作是**最耗时**的。这也是维护 FastAPI 的主要工作。
|
||||
|
||||
如果你能在这方面帮我,**你就是在帮我维护 FastAPI**,并确保它**更快更好地前进**。🚀
|
||||
|
||||
## 加入聊天 { #join-the-chat }
|
||||
|
||||
加入 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">Discord 聊天服务器</a> 👥,和 FastAPI 社区的小伙伴们一起交流。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
关于提问,请在 <a href="https://github.com/fastapi/fastapi/discussions/new?category=questions" class="external-link" target="_blank">GitHub Discussions</a> 中发布,这样更有机会得到 [FastAPI 专家](fastapi-people.md#fastapi-experts){.internal-link target=_blank} 的帮助。
|
||||
|
||||
聊天仅用于其他日常交流。
|
||||
|
||||
///
|
||||
|
||||
### 别在聊天里提问 { #dont-use-the-chat-for-questions }
|
||||
|
||||
请记住,聊天更偏向“自由交流”,很容易提出过于笼统、难以回答的问题,因此你可能收不到解答。
|
||||
|
||||
在 GitHub 中,模板会引导你写出恰当的问题,从而更容易获得好的回答,甚至在提问之前就能自己解决。而且在 GitHub 里,我能尽量确保最终回复每个问题,即使这需要一些时间。对聊天系统来说,我个人做不到这一点。😅
|
||||
|
||||
聊天系统中的对话也不像 GitHub 那样容易搜索,因此问答可能在聊天中淹没。而且只有在 GitHub 中的问答才会计入成为 [FastAPI 专家](fastapi-people.md#fastapi-experts){.internal-link target=_blank} 的贡献,所以你在 GitHub 上更可能获得关注。
|
||||
|
||||
另一方面,聊天系统里有成千上万的用户,你几乎随时都能在那里找到聊得来的人。😄
|
||||
|
||||
## 赞助作者 { #sponsor-the-author }
|
||||
|
||||
如果你的**产品/公司**依赖或与 **FastAPI** 相关,并且你想触达它的用户,你可以通过 <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub sponsors</a> 赞助作者(我)。根据赞助层级,你还可能获得一些额外福利,比如在文档中展示徽章。🎁
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 历史、设计、未来
|
||||
# 历史、设计、未来 { #history-design-and-future }
|
||||
|
||||
不久前,<a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">曾有 **FastAPI** 用户问过</a>:
|
||||
|
||||
> 这个项目有怎样的历史?好像它只用了几周就从默默无闻变得众所周知……
|
||||
> 这个项目有怎样的历史?好像它只用了几周就从默默无闻变得众所周知...
|
||||
|
||||
在此,我们简单回顾一下 **FastAPI** 的历史。
|
||||
|
||||
## 备选方案
|
||||
## 备选方案 { #alternatives }
|
||||
|
||||
有那么几年,我曾领导数个开发团队为诸多复杂需求创建各种 API,这些需求包括机器学习、分布系统、异步任务、NoSQL 数据库等领域。
|
||||
|
||||
@@ -24,10 +24,9 @@
|
||||
在那几年中,我一直回避创建新的框架。首先,我尝试使用各种框架、插件、工具解决 **FastAPI** 现在的功能。
|
||||
|
||||
但到了一定程度之后,我别无选择,只能从之前的工具中汲取最优思路,并以尽量好的方式把这些思路整合在一起,使用之前甚至是不支持的语言特性(Python 3.6+ 的类型提示),从而创建一个能满足我所有需求的框架。
|
||||
|
||||
</blockquote>
|
||||
|
||||
## 调研
|
||||
## 调研 { #investigation }
|
||||
|
||||
通过使用之前所有的备选方案,我有机会从它们之中学到了很多东西,获取了很多想法,并以我和我的开发团队能想到的最好方式把这些思路整合成一体。
|
||||
|
||||
@@ -37,7 +36,7 @@
|
||||
|
||||
因此,甚至在开发 **FastAPI** 前,我就花了几个月的时间研究 OpenAPI、JSON Schema、OAuth2 等规范。深入理解它们之间的关系、重叠及区别之处。
|
||||
|
||||
## 设计
|
||||
## 设计 { #design }
|
||||
|
||||
然后,我又花了一些时间从用户角度(使用 FastAPI 的开发者)设计了开发者 **API**。
|
||||
|
||||
@@ -51,7 +50,7 @@
|
||||
|
||||
所有这些都是为了给开发者提供最佳的开发体验。
|
||||
|
||||
## 需求项
|
||||
## 需求项 { #requirements }
|
||||
|
||||
经过测试多种备选方案,我最终决定使用 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a>,并充分利用它的优势。
|
||||
|
||||
@@ -59,11 +58,11 @@
|
||||
|
||||
在开发期间,我还为 <a href="https://www.starlette.dev/" class="external-link" target="_blank">**Starlette**</a> 做了不少贡献,这是另一个关键需求项。
|
||||
|
||||
## 开发
|
||||
## 开发 { #development }
|
||||
|
||||
当我启动 **FastAPI** 开发的时候,绝大多数部件都已经就位,设计已经定义,需求项和工具也已经准备就绪,相关标准与规范的知识储备也非常清晰而新鲜。
|
||||
|
||||
## 未来
|
||||
## 未来 { #future }
|
||||
|
||||
至此,**FastAPI** 及其理念已经为很多人所用。
|
||||
|
||||
@@ -73,6 +72,6 @@
|
||||
|
||||
但,**FastAPI** 仍有很多改进的余地,也还需要添加更多的功能。
|
||||
|
||||
总之,**FastAPI** 前景光明。
|
||||
**FastAPI** 前景光明。
|
||||
|
||||
在此,我们衷心感谢[您的帮助](help-fastapi.md){.internal-link target=_blank}。
|
||||
|
||||
@@ -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,39 +1,39 @@
|
||||
# 通用 - 如何操作 - 诀窍
|
||||
# 通用 - 如何操作 - 诀窍 { #general-how-to-recipes }
|
||||
|
||||
这里是一些指向文档中其他部分的链接,用于解答一般性或常见问题。
|
||||
|
||||
## 数据过滤 - 安全性
|
||||
## 数据过滤 - 安全性 { #filter-data-security }
|
||||
|
||||
为确保不返回超过需要的数据,请阅读 [教程 - 响应模型 - 返回类型](../tutorial/response-model.md){.internal-link target=_blank} 文档。
|
||||
|
||||
## 文档的标签 - OpenAPI
|
||||
## 文档的标签 - OpenAPI { #documentation-tags-openapi }
|
||||
|
||||
在文档界面中添加**路径操作**的标签和进行分组,请阅读 [教程 - 路径操作配置 - Tags 参数](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} 文档。
|
||||
|
||||
## 文档的概要和描述 - OpenAPI
|
||||
## 文档的概要和描述 - OpenAPI { #documentation-summary-and-description-openapi }
|
||||
|
||||
在文档界面中添加**路径操作**的概要和描述,请阅读 [教程 - 路径操作配置 - Summary 和 Description 参数](../tutorial/path-operation-configuration.md#summary-description){.internal-link target=_blank} 文档。
|
||||
在文档界面中添加**路径操作**的概要和描述,请阅读 [教程 - 路径操作配置 - Summary 和 Description 参数](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} 文档。
|
||||
|
||||
## 文档的响应描述 - OpenAPI
|
||||
## 文档的响应描述 - OpenAPI { #documentation-response-description-openapi }
|
||||
|
||||
在文档界面中定义并显示响应描述,请阅读 [教程 - 路径操作配置 - 响应描述](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} 文档。
|
||||
|
||||
## 文档弃用**路径操作** - OpenAPI
|
||||
## 文档弃用**路径操作** - OpenAPI { #documentation-deprecate-a-path-operation-openapi }
|
||||
|
||||
在文档界面中显示弃用的**路径操作**,请阅读 [教程 - 路径操作配置 - 弃用](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} 文档。
|
||||
|
||||
## 将任何数据转换为 JSON 兼容格式
|
||||
## 将任何数据转换为 JSON 兼容格式 { #convert-any-data-to-json-compatible }
|
||||
|
||||
要将任何数据转换为 JSON 兼容格式,请阅读 [教程 - JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank} 文档。
|
||||
|
||||
## OpenAPI 元数据 - 文档
|
||||
## OpenAPI 元数据 - 文档 { #openapi-metadata-docs }
|
||||
|
||||
要添加 OpenAPI 的元数据,包括许可证、版本、联系方式等,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md){.internal-link target=_blank} 文档。
|
||||
|
||||
## OpenAPI 自定义 URL
|
||||
## OpenAPI 自定义 URL { #openapi-custom-url }
|
||||
|
||||
要自定义 OpenAPI 的 URL(或删除它),请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} 文档。
|
||||
|
||||
## OpenAPI 文档 URL
|
||||
## OpenAPI 文档 URL { #openapi-docs-urls }
|
||||
|
||||
要更改用于自动生成文档的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
|
||||
要更改用于自动生成文档的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 如何操作 - 诀窍
|
||||
# 如何操作 - 诀窍 { #how-to-recipes }
|
||||
|
||||
在这里,你将看到关于**多个主题**的不同诀窍或“如何操作”指南。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
如果某些内容看起来对你的项目有用,请继续查阅,否则请直接跳过它们。
|
||||
|
||||
/// tip | 小技巧
|
||||
/// tip | 提示
|
||||
|
||||
如果你想以系统的方式**学习 FastAPI**(推荐),请阅读 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 的每一章节。
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# FastAPI
|
||||
# FastAPI { #fastapi }
|
||||
|
||||
<style>
|
||||
.md-content .md-typeset h1 { display: none; }
|
||||
</style>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
|
||||
<a href="https://fastapi.tiangolo.com/zh"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<em>FastAPI 框架,高性能,易于学习,高效编码,生产可用</em>
|
||||
@@ -27,135 +27,140 @@
|
||||
|
||||
---
|
||||
|
||||
**文档**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
**文档**: <a href="https://fastapi.tiangolo.com/zh" target="_blank">https://fastapi.tiangolo.com</a>
|
||||
|
||||
**源码**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
|
||||
|
||||
---
|
||||
|
||||
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。
|
||||
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 Web 框架,使用 Python 并基于标准的 Python 类型提示。
|
||||
|
||||
关键特性:
|
||||
关键特性:
|
||||
|
||||
* **快速**:可与 **NodeJS** 和 **Go** 并肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。
|
||||
* **快速**:极高性能,可与 **NodeJS** 和 **Go** 并肩(归功于 Starlette 和 Pydantic)。[最快的 Python 框架之一](#performance)。
|
||||
* **高效编码**:功能开发速度提升约 200% ~ 300%。*
|
||||
* **更少 bug**:人为(开发者)错误减少约 40%。*
|
||||
* **直观**:极佳的编辑器支持。处处皆可<abbr title="也被称为:自动完成、自动补全、IntelliSense">自动补全</abbr>。更少的调试时间。
|
||||
* **易用**:为易用和易学而设计。更少的文档阅读时间。
|
||||
* **简短**:最小化代码重复。一次参数声明即可获得多种功能。更少的 bug。
|
||||
* **健壮**:生产可用级代码。并带有自动生成的交互式文档。
|
||||
* **标准化**:基于(并完全兼容)API 的开放标准:<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a>(以前称为 Swagger)和 <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>。
|
||||
|
||||
* **高效编码**:提高功能开发速度约 200% 至 300%。*
|
||||
* **更少 bug**:减少约 40% 的人为(开发者)导致错误。*
|
||||
* **智能**:极佳的编辑器支持。处处皆可<abbr title="也被称为自动完成、智能感知">自动补全</abbr>,减少调试时间。
|
||||
* **简单**:设计的易于使用和学习,阅读文档的时间更短。
|
||||
* **简短**:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
|
||||
* **健壮**:生产可用级别的代码。还有自动生成的交互式文档。
|
||||
* **标准化**:基于(并完全兼容)API 的相关开放标准:<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (以前被称为 Swagger) 和 <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>。
|
||||
<small>* 基于某内部开发团队在构建生产应用时的测试估算。</small>
|
||||
|
||||
<small>* 根据对某个构建线上应用的内部开发团队所进行的测试估算得出。</small>
|
||||
|
||||
## Sponsors
|
||||
## 赞助商 { #sponsors }
|
||||
|
||||
<!-- sponsors -->
|
||||
|
||||
{% if sponsors %}
|
||||
### Keystone 赞助商 { #keystone-sponsor }
|
||||
|
||||
{% for sponsor in sponsors.keystone -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
|
||||
### 金牌和银牌赞助商 { #gold-and-silver-sponsors }
|
||||
|
||||
{% for sponsor in sponsors.gold -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor -%}
|
||||
{%- for sponsor in sponsors.silver -%}
|
||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- /sponsors -->
|
||||
|
||||
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a>
|
||||
<a href="https://fastapi.tiangolo.com/zh/fastapi-people/#sponsors" class="external-link" target="_blank">其他赞助商</a>
|
||||
|
||||
## 评价
|
||||
## 评价 { #opinions }
|
||||
|
||||
「_[...] 最近我一直在使用 **FastAPI**。[...] 实际上我正在计划将其用于我所在的**微软**团队的所有**机器学习服务**。其中一些服务正被集成进核心 **Windows** 产品和一些 **Office** 产品。_」
|
||||
「_[...] 最近我大量使用 **FastAPI**。[...] 我实际上计划把它用于我团队在 **微软** 的所有 **机器学习服务**。其中一些正在集成进核心 **Windows** 产品以及一些 **Office** 产品。_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>微软</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
「_我们选择了 **FastAPI** 来创建用于获取**预测结果**的 **REST** 服务。[用于 Ludwig]_」
|
||||
「_我们采用 **FastAPI** 来构建可查询以获取**预测结果**的 **REST** 服务器。[用于 Ludwig]_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino,Yaroslav Dudin 和 Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
<div style="text-align: right; margin-right: 10%;">Piero Molino,Yaroslav Dudin,Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
「_**Netflix** 非常高兴地宣布,正式开源我们的**危机管理**编排框架:**Dispatch**![使用 **FastAPI** 构建]_」
|
||||
「_**Netflix** 很高兴宣布开源我们的**危机管理**编排框架:**Dispatch**![使用 **FastAPI** 构建]_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Kevin Glisson,Marc Vilanova,Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
「_**FastAPI** 让我兴奋的欣喜若狂。它太棒了!_」
|
||||
「_我对 **FastAPI** 兴奋到飞起。它太有趣了!_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> 播客主持人</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
「_老实说,你的作品看起来非常可靠和优美。在很多方面,这就是我想让 **Hug** 成为的样子 - 看到有人实现了它真的很鼓舞人心。_」
|
||||
「_老实说,你构建的东西非常稳健而且打磨得很好。从很多方面看,这就是我想让 **Hug** 成为的样子 —— 看到有人把它做出来真的很鼓舞人心。_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> 作者</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
「_如果你正打算学习一个**现代框架**用来构建 REST API,来看下 **FastAPI** [...] 它快速、易用且易于学习 [...]_」
|
||||
「_如果你想学一个用于构建 REST API 的**现代框架**,看看 **FastAPI** [...] 它快速、易用且易学 [...]_」
|
||||
|
||||
「_我们已经将 **API** 服务切换到了 **FastAPI** [...] 我认为你会喜欢它的 [...]_」
|
||||
「_我们已经把我们的 **API** 切换到 **FastAPI** [...] 我想你会喜欢它 [...]_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> 创始人 - <a href="https://spacy.io" target="_blank">spaCy</a> 作者</strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## **Typer**,命令行中的 FastAPI
|
||||
「_如果有人正在构建生产级的 Python API,我强烈推荐 **FastAPI**。它**设计优雅**、**使用简单**且**高度可扩展**,已经成为我们 API 优先开发战略中的**关键组件**,并驱动了许多自动化和服务,比如我们的 Virtual TAC Engineer。_」
|
||||
|
||||
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
|
||||
|
||||
---
|
||||
|
||||
## FastAPI 迷你纪录片 { #fastapi-mini-documentary }
|
||||
|
||||
在 2025 年末发布了一部<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" class="external-link" target="_blank">FastAPI 迷你纪录片</a>,你可以在线观看:
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=mpR8ngthqiE" target="_blank"><img src="https://fastapi.tiangolo.com/img/fastapi-documentary.jpg" alt="FastAPI Mini Documentary"></a>
|
||||
|
||||
## **Typer**,命令行中的 FastAPI { #typer-the-fastapi-of-clis }
|
||||
|
||||
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
|
||||
|
||||
如果你正在开发一个在终端中运行的<abbr title="Command Line Interface">命令行</abbr>应用而不是 web API,不妨试下 <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>。
|
||||
如果你要开发一个用于终端的 <abbr title="Command Line Interface">命令行</abbr>应用而不是 Web API,看看 <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>。
|
||||
|
||||
**Typer** 是 FastAPI 的小同胞。它想要成为**命令行中的 FastAPI**。 ⌨️ 🚀
|
||||
**Typer** 是 FastAPI 的小同胞。它的目标是成为**命令行中的 FastAPI**。⌨️ 🚀
|
||||
|
||||
## 依赖
|
||||
## 依赖 { #requirements }
|
||||
|
||||
Python 及更高版本
|
||||
FastAPI 站在巨人的肩膀之上:
|
||||
|
||||
FastAPI 站在以下巨人的肩膀之上:
|
||||
|
||||
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> 负责 web 部分。
|
||||
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> 负责 Web 部分。
|
||||
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 负责数据部分。
|
||||
|
||||
## 安装
|
||||
## 安装 { #installation }
|
||||
|
||||
创建并激活一个<a href="https://fastapi.tiangolo.com/zh/virtual-environments/" class="external-link" target="_blank">虚拟环境</a>,然后安装 FastAPI:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install fastapi
|
||||
$ pip install "fastapi[standard]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
你还会需要一个 ASGI 服务器,生产环境可以使用 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> 或者 <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>。
|
||||
**Note**: 请确保把 `"fastapi[standard]"` 用引号包起来,以保证在所有终端中都能正常工作。
|
||||
|
||||
<div class="termy">
|
||||
## 示例 { #example }
|
||||
|
||||
```console
|
||||
$ pip install "uvicorn[standard]"
|
||||
### 创建 { #create-it }
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## 示例
|
||||
|
||||
### 创建
|
||||
|
||||
* 创建一个 `main.py` 文件并写入以下内容:
|
||||
创建文件 `main.py`,内容如下:
|
||||
|
||||
```Python
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
@@ -167,18 +172,16 @@ def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
<details markdown="1">
|
||||
<summary>或者使用 <code>async def</code>...</summary>
|
||||
|
||||
如果你的代码里会出现 `async` / `await`,请使用 `async def`:
|
||||
|
||||
```Python hl_lines="9 14"
|
||||
from typing import Union
|
||||
如果你的代码里会用到 `async` / `await`,请使用 `async def`:
|
||||
|
||||
```Python hl_lines="7 12"
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
@@ -190,28 +193,41 @@ async def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: int, q: Union[str, None] = None):
|
||||
async def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
```
|
||||
|
||||
**Note**:
|
||||
|
||||
如果你不知道是否会用到,可以查看文档的 _"In a hurry?"_ 章节中 <a href="https://fastapi.tiangolo.com/zh/async/#in-a-hurry" target="_blank">关于 `async` 和 `await` 的部分</a>。
|
||||
如果你不确定,请查看文档中 _"In a hurry?"_ 章节的<a href="https://fastapi.tiangolo.com/zh/async/#in-a-hurry" target="_blank">`async` 和 `await`</a>部分。
|
||||
|
||||
</details>
|
||||
|
||||
### 运行
|
||||
### 运行 { #run-it }
|
||||
|
||||
通过以下命令运行服务器:
|
||||
用下面的命令运行服务器:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ uvicorn main:app --reload
|
||||
$ fastapi dev main.py
|
||||
|
||||
╭────────── FastAPI CLI - Development mode ───────────╮
|
||||
│ │
|
||||
│ Serving at: http://127.0.0.1:8000 │
|
||||
│ │
|
||||
│ API docs: http://127.0.0.1:8000/docs │
|
||||
│ │
|
||||
│ Running in development mode, for production use: │
|
||||
│ │
|
||||
│ fastapi run │
|
||||
│ │
|
||||
╰─────────────────────────────────────────────────────╯
|
||||
|
||||
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
|
||||
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
INFO: Started reloader process [28720]
|
||||
INFO: Started server process [28722]
|
||||
INFO: Started reloader process [2248755] using WatchFiles
|
||||
INFO: Started server process [2248757]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
```
|
||||
@@ -219,58 +235,56 @@ INFO: Application startup complete.
|
||||
</div>
|
||||
|
||||
<details markdown="1">
|
||||
<summary>关于 <code>uvicorn main:app --reload</code> 命令......</summary>
|
||||
<summary>关于命令 <code>fastapi dev main.py</code>...</summary>
|
||||
|
||||
`uvicorn main:app` 命令含义如下:
|
||||
`fastapi dev` 命令会读取你的 `main.py` 文件,检测其中的 **FastAPI** 应用,并使用 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> 启动服务器。
|
||||
|
||||
* `main`:`main.py` 文件(一个 Python "模块")。
|
||||
* `app`:在 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。
|
||||
* `--reload`:让服务器在更新代码后重新启动。仅在开发时使用该选项。
|
||||
默认情况下,`fastapi dev` 会在本地开发时启用自动重载。
|
||||
|
||||
你可以在 <a href="https://fastapi.tiangolo.com/zh/fastapi-cli/" target="_blank">FastAPI CLI 文档</a>中了解更多。
|
||||
|
||||
</details>
|
||||
|
||||
### 检查
|
||||
### 检查 { #check-it }
|
||||
|
||||
使用浏览器访问 <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>。
|
||||
用浏览器打开 <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>。
|
||||
|
||||
你将会看到如下 JSON 响应:
|
||||
你会看到如下 JSON 响应:
|
||||
|
||||
```JSON
|
||||
{"item_id": 5, "q": "somequery"}
|
||||
```
|
||||
|
||||
你已经创建了一个具有以下功能的 API:
|
||||
你已经创建了一个 API,它可以:
|
||||
|
||||
* 通过 _路径_ `/` 和 `/items/{item_id}` 接受 HTTP 请求。
|
||||
* 以上 _路径_ 都接受 `GET` <em>操作</em>(也被称为 HTTP _方法_)。
|
||||
* `/items/{item_id}` _路径_ 有一个 _路径参数_ `item_id` 并且应该为 `int` 类型。
|
||||
* `/items/{item_id}` _路径_ 有一个可选的 `str` 类型的 _查询参数_ `q`。
|
||||
* 在路径 `/` 和 `/items/{item_id}` 接收 HTTP 请求。
|
||||
* 以上两个路径都接受 `GET` <em>操作</em>(也称为 HTTP <em>方法</em>)。
|
||||
* 路径 `/items/{item_id}` 有一个应为 `int` 的<em>路径参数</em> `item_id`。
|
||||
* 路径 `/items/{item_id}` 有一个可选的 `str` 类型<em>查询参数</em> `q`。
|
||||
|
||||
### 交互式 API 文档
|
||||
### 交互式 API 文档 { #interactive-api-docs }
|
||||
|
||||
现在访问 <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="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>生成):
|
||||
你会看到自动生成的交互式 API 文档(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> 提供):
|
||||
|
||||

|
||||
|
||||
### 可选的 API 文档
|
||||
### 可选的 API 文档 { #alternative-api-docs }
|
||||
|
||||
访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
|
||||
然后访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
|
||||
|
||||
你会看到另一个自动生成的文档(由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 生成):
|
||||
你会看到另一个自动生成的文档(由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 提供):
|
||||
|
||||

|
||||
|
||||
## 示例升级
|
||||
## 示例升级 { #example-upgrade }
|
||||
|
||||
现在修改 `main.py` 文件来从 `PUT` 请求中接收请求体。
|
||||
现在修改 `main.py` 文件来接收来自 `PUT` 请求的请求体。
|
||||
|
||||
我们借助 Pydantic 来使用标准的 Python 类型声明请求体。
|
||||
|
||||
```Python hl_lines="4 9-12 25-27"
|
||||
from typing import Union
|
||||
借助 Pydantic,使用标准的 Python 类型来声明请求体。
|
||||
|
||||
```Python hl_lines="2 7-10 23-25"
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -280,7 +294,7 @@ app = FastAPI()
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
is_offer: Union[bool, None] = None
|
||||
is_offer: bool | None = None
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@@ -289,7 +303,7 @@ def read_root():
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
def read_item(item_id: int, q: str | None = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
|
||||
|
||||
@@ -298,173 +312,248 @@ def update_item(item_id: int, item: Item):
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
服务器将会自动重载(因为在上面的步骤中你向 `uvicorn` 命令添加了 `--reload` 选项)。
|
||||
`fastapi dev` 服务器会自动重载。
|
||||
|
||||
### 交互式 API 文档升级
|
||||
### 交互式 API 文档升级 { #interactive-api-docs-upgrade }
|
||||
|
||||
访问 <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 文档将会自动更新,并加入新的请求体:
|
||||
* 交互式 API 文档会自动更新,并包含新的请求体:
|
||||
|
||||

|
||||
|
||||
* 点击「Try it out」按钮,之后你可以填写参数并直接调用 API:
|
||||
* 点击「Try it out」按钮,它允许你填写参数并直接与 API 交互:
|
||||
|
||||

|
||||
|
||||
* 然后点击「Execute」按钮,用户界面将会和 API 进行通信,发送参数,获取结果并在屏幕上展示:
|
||||
* 然后点击「Execute」按钮,界面会与你的 API 通信、发送参数、获取结果并在屏幕上展示:
|
||||
|
||||

|
||||
|
||||
### 可选文档升级
|
||||
### 可选文档升级 { #alternative-api-docs-upgrade }
|
||||
|
||||
访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
|
||||
再访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
|
||||
|
||||
* 可选文档同样会体现新加入的请求参数和请求体:
|
||||
* 可选文档同样会体现新的查询参数和请求体:
|
||||
|
||||

|
||||
|
||||
### 总结
|
||||
### 总结 { #recap }
|
||||
|
||||
总的来说,你就像声明函数的参数类型一样只声明了**一次**请求参数、请求体等的类型。
|
||||
总之,你只需要把参数、请求体等的类型作为函数参数**声明一次**。
|
||||
|
||||
你使用了标准的现代 Python 类型来完成声明。
|
||||
这些都使用标准的现代 Python 类型即可。
|
||||
|
||||
你不需要去学习新的语法、了解特定库的方法或类,等等。
|
||||
你不需要学习新的语法、某个特定库的方法或类等。
|
||||
|
||||
只需要使用标准的 **Python 及更高版本**。
|
||||
只需要标准的 **Python**。
|
||||
|
||||
举个例子,比如声明 `int` 类型:
|
||||
例如,一个 `int`:
|
||||
|
||||
```Python
|
||||
item_id: int
|
||||
```
|
||||
|
||||
或者一个更复杂的 `Item` 模型:
|
||||
或者更复杂的 `Item` 模型:
|
||||
|
||||
```Python
|
||||
item: Item
|
||||
```
|
||||
|
||||
......在进行一次声明之后,你将获得:
|
||||
……通过一次声明,你将获得:
|
||||
|
||||
* 编辑器支持,包括:
|
||||
* 自动补全
|
||||
* 类型检查
|
||||
* 自动补全。
|
||||
* 类型检查。
|
||||
* 数据校验:
|
||||
* 在校验失败时自动生成清晰的错误信息
|
||||
* 对多层嵌套的 JSON 对象依然执行校验
|
||||
* <abbr title="也被称为:序列化或解析">转换</abbr> 来自网络请求的输入数据为 Python 数据类型。包括以下数据:
|
||||
* JSON
|
||||
* 路径参数
|
||||
* 查询参数
|
||||
* Cookies
|
||||
* 请求头
|
||||
* 表单
|
||||
* 文件
|
||||
* <abbr title="也被称为:序列化或解析">转换</abbr> 输出的数据:转换 Python 数据类型为供网络传输的 JSON 数据:
|
||||
* 转换 Python 基础类型 (`str`、 `int`、 `float`、 `bool`、 `list` 等)
|
||||
* `datetime` 对象
|
||||
* `UUID` 对象
|
||||
* 数据库模型
|
||||
* ......以及更多其他类型
|
||||
* 当数据无效时自动生成清晰的错误信息。
|
||||
* 即便是多层嵌套的 JSON 对象也会进行校验。
|
||||
* 输入数据的<abbr title="也被称为:序列化、解析、编组">转换</abbr>:从网络读取到 Python 数据和类型。读取来源:
|
||||
* JSON。
|
||||
* 路径参数。
|
||||
* 查询参数。
|
||||
* Cookies。
|
||||
* Headers。
|
||||
* Forms。
|
||||
* Files。
|
||||
* 输出数据的<abbr title="也被称为:序列化、解析、编组">转换</abbr>:从 Python 数据和类型转换为网络数据(JSON):
|
||||
* 转换 Python 类型(`str`、`int`、`float`、`bool`、`list` 等)。
|
||||
* `datetime` 对象。
|
||||
* `UUID` 对象。
|
||||
* 数据库模型。
|
||||
* ……以及更多。
|
||||
* 自动生成的交互式 API 文档,包括两种可选的用户界面:
|
||||
* Swagger UI
|
||||
* ReDoc
|
||||
* Swagger UI。
|
||||
* ReDoc。
|
||||
|
||||
---
|
||||
|
||||
回到前面的代码示例,**FastAPI** 将会:
|
||||
回到之前的代码示例,**FastAPI** 将会:
|
||||
|
||||
* 校验 `GET` 和 `PUT` 请求的路径中是否含有 `item_id`。
|
||||
* 校验 `GET` 和 `PUT` 请求的路径中是否包含 `item_id`。
|
||||
* 校验 `GET` 和 `PUT` 请求中的 `item_id` 是否为 `int` 类型。
|
||||
* 如果不是,客户端将会收到清晰有用的错误信息。
|
||||
* 检查 `GET` 请求中是否有命名为 `q` 的可选查询参数(比如 `http://127.0.0.1:8000/items/foo?q=somequery`)。
|
||||
* 因为 `q` 被声明为 `= None`,所以它是可选的。
|
||||
* 如果没有 `None` 它将会是必需的 (如 `PUT` 例子中的请求体)。
|
||||
* 对于访问 `/items/{item_id}` 的 `PUT` 请求,将请求体读取为 JSON 并:
|
||||
* 检查是否有必需属性 `name` 并且值为 `str` 类型 。
|
||||
* 检查是否有必需属性 `price` 并且值为 `float` 类型。
|
||||
* 检查是否有可选属性 `is_offer`, 如果有的话值应该为 `bool` 类型。
|
||||
* 以上过程对于多层嵌套的 JSON 对象同样也会执行
|
||||
* 自动对 JSON 进行转换或转换成 JSON。
|
||||
* 通过 OpenAPI 文档来记录所有内容,可被用于:
|
||||
* 交互式文档系统
|
||||
* 许多编程语言的客户端代码自动生成系统
|
||||
* 直接提供 2 种交互式文档 web 界面。
|
||||
* 如果不是,客户端会看到清晰有用的错误信息。
|
||||
* 对于 `GET` 请求,检查是否存在名为 `q` 的可选查询参数(如 `http://127.0.0.1:8000/items/foo?q=somequery`)。
|
||||
* 因为参数 `q` 被声明为 `= None`,所以它是可选的。
|
||||
* 如果没有 `None`,它就是必需的(就像 `PUT` 情况下的请求体)。
|
||||
* 对于发送到 `/items/{item_id}` 的 `PUT` 请求,把请求体作为 JSON 读取:
|
||||
* 检查是否存在必需属性 `name`,且为 `str`。
|
||||
* 检查是否存在必需属性 `price`,且为 `float`。
|
||||
* 检查是否存在可选属性 `is_offer`,如果存在则应为 `bool`。
|
||||
* 对于多层嵌套的 JSON 对象,同样适用。
|
||||
* 自动完成 JSON 的读取与输出转换。
|
||||
* 使用 OpenAPI 记录所有内容,可用于:
|
||||
* 交互式文档系统。
|
||||
* 多语言的客户端代码自动生成系统。
|
||||
* 直接提供 2 种交互式文档 Web 界面。
|
||||
|
||||
---
|
||||
|
||||
虽然我们才刚刚开始,但其实你已经了解了这一切是如何工作的。
|
||||
我们只是浅尝辄止,但你已经大致了解其工作方式了。
|
||||
|
||||
尝试更改下面这行代码:
|
||||
尝试把这一行:
|
||||
|
||||
```Python
|
||||
return {"item_name": item.name, "item_id": item_id}
|
||||
```
|
||||
|
||||
......从:
|
||||
……从:
|
||||
|
||||
```Python
|
||||
... "item_name": item.name ...
|
||||
```
|
||||
|
||||
......改为:
|
||||
……改为:
|
||||
|
||||
```Python
|
||||
... "item_price": item.price ...
|
||||
```
|
||||
|
||||
......注意观察编辑器是如何自动补全属性并且还知道它们的类型:
|
||||
……看看你的编辑器如何自动补全属性并知道它们的类型:
|
||||
|
||||

|
||||
|
||||
<a href="https://fastapi.tiangolo.com/zh/tutorial/">教程 - 用户指南</a> 中有包含更多特性的更完整示例。
|
||||
更多包含更多特性的完整示例,请参阅 <a href="https://fastapi.tiangolo.com/zh/tutorial/">教程 - 用户指南</a>。
|
||||
|
||||
**剧透警告**: 教程 - 用户指南中的内容有:
|
||||
**剧透警告**:教程 - 用户指南包括:
|
||||
|
||||
* 对来自不同地方的参数进行声明,如:**请求头**、**cookies**、**form 表单**以及**上传的文件**。
|
||||
* 如何设置**校验约束**如 `maximum_length` 或者 `regex`。
|
||||
* 一个强大并易于使用的 **<abbr title="也被称为 components, resources, providers, services, injectables">依赖注入</abbr>** 系统。
|
||||
* 安全性和身份验证,包括通过 **JWT 令牌**和 **HTTP 基本身份认证**来支持 **OAuth2**。
|
||||
* 更进阶(但同样简单)的技巧来声明 **多层嵌套 JSON 模型** (借助 Pydantic)。
|
||||
* 许多额外功能(归功于 Starlette)比如:
|
||||
* 来自不同位置的**参数**声明:**headers**、**cookies**、**form 字段**和**文件**。
|
||||
* 如何设置**校验约束**,如 `maximum_length` 或 `regex`。
|
||||
* 功能强大且易用的 **<abbr title="也被称为 components、resources、providers、services、injectables">依赖注入</abbr>** 系统。
|
||||
* 安全与认证,包括对 **OAuth2**、**JWT tokens** 和 **HTTP Basic** 认证的支持。
|
||||
* 更高级(但同样简单)的 **多层嵌套 JSON 模型** 声明技巧(得益于 Pydantic)。
|
||||
* 通过 <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> 等库进行 **GraphQL** 集成。
|
||||
* 许多额外特性(归功于 Starlette),例如:
|
||||
* **WebSockets**
|
||||
* **GraphQL**
|
||||
* 基于 HTTPX 和 `pytest` 的极其简单的测试
|
||||
* **CORS**
|
||||
* **Cookie Sessions**
|
||||
* ......以及更多
|
||||
* ……以及更多。
|
||||
|
||||
## 性能
|
||||
### 部署你的应用(可选) { #deploy-your-app-optional }
|
||||
|
||||
独立机构 TechEmpower 所作的基准测试结果显示,基于 Uvicorn 运行的 **FastAPI** 程序是 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">最快的 Python web 框架之一</a>,仅次于 Starlette 和 Uvicorn 本身(FastAPI 内部使用了它们)。(*)
|
||||
你可以选择把 FastAPI 应用部署到 <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>,如果还没有的话去加入候补名单吧。🚀
|
||||
|
||||
想了解更多,请查阅 <a href="https://fastapi.tiangolo.com/zh/benchmarks/" class="internal-link" target="_blank">基准测试</a> 章节。
|
||||
如果你已经有 **FastAPI Cloud** 账号(我们从候补名单邀请了你 😉),你可以用一个命令部署你的应用。
|
||||
|
||||
## 可选依赖
|
||||
部署前,先确认已登录:
|
||||
|
||||
用于 Pydantic:
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
然后部署你的应用:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
就这样!现在你可以通过该 URL 访问你的应用了。✨
|
||||
|
||||
#### 关于 FastAPI Cloud { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** 由 **FastAPI** 的同一位作者和团队打造。
|
||||
|
||||
它让你以最小的工作量就能**构建**、**部署**并**访问**一个 API。
|
||||
|
||||
它把用 FastAPI 构建应用时的**开发者体验**带到了部署到云上的过程。🎉
|
||||
|
||||
FastAPI Cloud 是「FastAPI and friends」开源项目的主要赞助方和资金提供者。✨
|
||||
|
||||
#### 部署到其他云厂商 { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI 是开源且基于标准的。你可以部署 FastAPI 应用到你选择的任意云厂商。
|
||||
|
||||
按照你的云厂商的指南部署 FastAPI 应用即可。🤓
|
||||
|
||||
## 性能 { #performance }
|
||||
|
||||
独立机构 TechEmpower 的基准测试显示,运行在 Uvicorn 下的 **FastAPI** 应用是<a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">最快的 Python 框架之一</a>,仅次于 Starlette 和 Uvicorn 本身(FastAPI 内部使用它们)。(*)
|
||||
|
||||
想了解更多,请参阅<a href="https://fastapi.tiangolo.com/zh/benchmarks/" class="internal-link" target="_blank">基准测试</a>章节。
|
||||
|
||||
## 依赖项 { #dependencies }
|
||||
|
||||
FastAPI 依赖 Pydantic 和 Starlette。
|
||||
|
||||
### `standard` 依赖 { #standard-dependencies }
|
||||
|
||||
当你通过 `pip install "fastapi[standard]"` 安装 FastAPI 时,会包含 `standard` 组的一些可选依赖:
|
||||
|
||||
Pydantic 使用:
|
||||
|
||||
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - 用于 email 校验。
|
||||
|
||||
用于 Starlette:
|
||||
Starlette 使用:
|
||||
|
||||
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - 使用 `TestClient` 时安装。
|
||||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - 使用默认模板配置时安装。
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - 需要通过 `request.form()` 对表单进行<abbr title="将来自 HTTP 请求中的字符串转换为 Python 数据类型">「解析」</abbr>时安装。
|
||||
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - 需要 `SessionMiddleware` 支持时安装。
|
||||
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - 使用 Starlette 提供的 `SchemaGenerator` 时安装(有 FastAPI 你可能并不需要它)。
|
||||
* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - 需要 `GraphQLApp` 支持时安装。
|
||||
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - 使用 `TestClient` 时需要。
|
||||
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - 使用默认模板配置时需要。
|
||||
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - 使用 `request.form()` 支持表单<abbr title="将 HTTP 请求中的字符串转换为 Python 数据">「解析」</abbr>时需要。
|
||||
|
||||
用于 FastAPI / Starlette:
|
||||
FastAPI 使用:
|
||||
|
||||
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - 用于加载和运行你的应用程序的服务器。
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - 使用 `ORJSONResponse` 时安装。
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - 使用 `UJSONResponse` 时安装。
|
||||
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - 加载并提供你的应用的服务器。包含 `uvicorn[standard]`,其中包含高性能服务所需的一些依赖(例如 `uvloop`)。
|
||||
* `fastapi-cli[standard]` - 提供 `fastapi` 命令。
|
||||
* 其中包含 `fastapi-cloud-cli`,它允许你将 FastAPI 应用部署到 <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>。
|
||||
|
||||
你可以通过 `pip install "fastapi[all]"` 命令来安装以上所有依赖。
|
||||
### 不包含 `standard` 依赖 { #without-standard-dependencies }
|
||||
|
||||
## 许可协议
|
||||
如果你不想包含这些 `standard` 可选依赖,可以使用 `pip install fastapi`,而不是 `pip install "fastapi[standard]"`。
|
||||
|
||||
### 不包含 `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
|
||||
|
||||
如果你想安装带有 standard 依赖但不包含 `fastapi-cloud-cli` 的 FastAPI,可以使用 `pip install "fastapi[standard-no-fastapi-cloud-cli]"`。
|
||||
|
||||
### 其他可选依赖 { #additional-optional-dependencies }
|
||||
|
||||
还有一些你可能想安装的可选依赖。
|
||||
|
||||
额外的 Pydantic 可选依赖:
|
||||
|
||||
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - 用于配置管理。
|
||||
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - 用于在 Pydantic 中使用的额外类型。
|
||||
|
||||
额外的 FastAPI 可选依赖:
|
||||
|
||||
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - 使用 `ORJSONResponse` 时需要。
|
||||
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - 使用 `UJSONResponse` 时需要。
|
||||
|
||||
## 许可协议 { #license }
|
||||
|
||||
该项目遵循 MIT 许可协议。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 学习
|
||||
# 学习 { #learn }
|
||||
|
||||
以下是学习 **FastAPI** 的介绍部分和教程。
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
# FastAPI全栈模板
|
||||
# FastAPI全栈模板 { #full-stack-fastapi-template }
|
||||
|
||||
模板通常带有特定的设置,而且被设计为灵活和可定制的。这允许您根据项目的需求修改和调整它们,使它们成为一个很好的起点。🏁
|
||||
模板通常带有特定的设置,但它们被设计为灵活且可定制。这样你可以根据项目需求进行修改和调整,使其成为很好的起点。🏁
|
||||
|
||||
您可以使用此模板开始,因为它包含了许多已经为您完成的初始设置、安全性、数据库和一些API端点。
|
||||
你可以使用此模板开始,它已经为你完成了大量的初始设置、安全性、数据库以及一些 API 端点。
|
||||
|
||||
代码仓: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a>
|
||||
GitHub 仓库: <a href="https://github.com/tiangolo/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a>
|
||||
|
||||
## FastAPI全栈模板 - 技术栈和特性
|
||||
## FastAPI全栈模板 - 技术栈和特性 { #full-stack-fastapi-template-technology-stack-and-features }
|
||||
|
||||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) 用于Python后端API.
|
||||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于Python和SQL数据库的集成(ORM)。
|
||||
- 🔍 [Pydantic](https://docs.pydantic.dev) FastAPI的依赖项之一,用于数据验证和配置管理。
|
||||
- 💾 [PostgreSQL](https://www.postgresql.org) 作为SQL数据库。
|
||||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/zh) 用于 Python 后端 API。
|
||||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于 Python 与 SQL 数据库的交互(ORM)。
|
||||
- 🔍 [Pydantic](https://docs.pydantic.dev),FastAPI 使用,用于数据验证与配置管理。
|
||||
- 💾 [PostgreSQL](https://www.postgresql.org) 作为 SQL 数据库。
|
||||
- 🚀 [React](https://react.dev) 用于前端。
|
||||
- 💃 使用了TypeScript、hooks、[Vite](https://vitejs.dev)和其他一些现代化的前端技术栈。
|
||||
- 🎨 [Chakra UI](https://chakra-ui.com) 用于前端组件。
|
||||
- 🤖 一个自动化生成的前端客户端。
|
||||
- 🧪 [Playwright](https://playwright.dev)用于端到端测试。
|
||||
- 🦇 支持暗黑主题(Dark mode)。
|
||||
- 🐋 [Docker Compose](https://www.docker.com) 用于开发环境和生产环境。
|
||||
- 🔒 默认使用密码哈希来保证安全。
|
||||
- 🔑 JWT令牌用于权限验证。
|
||||
- 📫 使用邮箱来进行密码恢复。
|
||||
- ✅ 单元测试用了[Pytest](https://pytest.org).
|
||||
- 📞 [Traefik](https://traefik.io) 用于反向代理和负载均衡。
|
||||
- 🚢 部署指南(Docker Compose)包含了如何起一个Traefik前端代理来自动化HTTPS认证。
|
||||
- 🏭 CI(持续集成)和 CD(持续部署)基于GitHub Actions。
|
||||
- 💃 使用 TypeScript、hooks、Vite 以及现代前端技术栈的其他部分。
|
||||
- 🎨 [Tailwind CSS](https://tailwindcss.com) 与 [shadcn/ui](https://ui.shadcn.com) 用于前端组件。
|
||||
- 🤖 自动生成的前端客户端。
|
||||
- 🧪 [Playwright](https://playwright.dev) 用于端到端测试。
|
||||
- 🦇 支持暗黑模式。
|
||||
- 🐋 [Docker Compose](https://www.docker.com) 用于开发与生产。
|
||||
- 🔒 默认启用安全的密码哈希。
|
||||
- 🔑 JWT(JSON Web Token)认证。
|
||||
- 📫 基于邮箱的密码找回。
|
||||
- ✅ 使用 [Pytest](https://pytest.org) 进行测试。
|
||||
- 📞 [Traefik](https://traefik.io) 用作反向代理/负载均衡。
|
||||
- 🚢 使用 Docker Compose 的部署指南,包括如何设置前端 Traefik 代理以自动处理 HTTPS 证书。
|
||||
- 🏭 基于 GitHub Actions 的 CI(持续集成)与 CD(持续部署)。
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
# Python 类型提示简介
|
||||
# Python 类型提示简介 { #python-types-intro }
|
||||
|
||||
**Python 3.6+ 版本**加入了对"类型提示"的支持。
|
||||
Python 支持可选的“类型提示”(也叫“类型注解”)。
|
||||
|
||||
这些**"类型提示"**是一种新的语法(在 Python 3.6 版本加入)用来声明一个变量的<abbr title="例如:str、int、float、bool">类型</abbr>。
|
||||
这些“类型提示”或注解是一种特殊语法,用来声明变量的<abbr title="例如:str、int、float、bool">类型</abbr>。
|
||||
|
||||
通过声明变量的类型,编辑器和一些工具能给你提供更好的支持。
|
||||
通过为变量声明类型,编辑器和工具可以为你提供更好的支持。
|
||||
|
||||
这只是一个关于 Python 类型提示的**快速入门 / 复习**。它仅涵盖与 **FastAPI** 一起使用所需的最少部分...实际上只有很少一点。
|
||||
这只是一个关于 Python 类型提示的快速入门/复习。它只涵盖与 **FastAPI** 一起使用所需的最少部分……实际上非常少。
|
||||
|
||||
整个 **FastAPI** 都基于这些类型提示构建,它们带来了许多优点和好处。
|
||||
**FastAPI** 完全基于这些类型提示构建,它们带来了许多优势和好处。
|
||||
|
||||
但即使你不会用到 **FastAPI**,了解一下类型提示也会让你从中受益。
|
||||
但即使你从不使用 **FastAPI**,了解一些类型提示也会让你受益。
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
如果你已经精通 Python,并且了解关于类型提示的一切知识,直接跳到下一章节吧。
|
||||
如果你已经是 Python 专家,并且对类型提示了如指掌,可以跳到下一章。
|
||||
|
||||
///
|
||||
|
||||
## 动机
|
||||
## 动机 { #motivation }
|
||||
|
||||
让我们从一个简单的例子开始:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py *}
|
||||
{* ../../docs_src/python_types/tutorial001_py39.py *}
|
||||
|
||||
|
||||
运行这段程序将输出:
|
||||
运行这个程序会输出:
|
||||
|
||||
```
|
||||
John Doe
|
||||
@@ -33,38 +32,37 @@ John Doe
|
||||
|
||||
这个函数做了下面这些事情:
|
||||
|
||||
* 接收 `first_name` 和 `last_name` 参数。
|
||||
* 通过 `title()` 将每个参数的第一个字母转换为大写形式。
|
||||
* 中间用一个空格来<abbr title="将它们按顺序放置组合成一个整体。">拼接</abbr>它们。
|
||||
* 接收 `first_name` 和 `last_name`。
|
||||
* 通过 `title()` 将每个参数的第一个字母转换为大写。
|
||||
* 用一个空格将它们<abbr title="把它们合在一起成为一个,内容一个接在另一个后面。">拼接</abbr>起来。
|
||||
|
||||
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
|
||||
{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
|
||||
|
||||
|
||||
### 修改示例
|
||||
### 修改它 { #edit-it }
|
||||
|
||||
这是一个非常简单的程序。
|
||||
|
||||
现在假设你将从头开始编写这段程序。
|
||||
但现在想象你要从零开始写它。
|
||||
|
||||
在某一时刻,你开始定义函数,并且准备好了参数...。
|
||||
在某个时刻你开始定义函数,并且准备好了参数……
|
||||
|
||||
现在你需要调用一个"将第一个字母转换为大写形式的方法"。
|
||||
接下来你需要调用“那个把首字母变大写的方法”。
|
||||
|
||||
等等,那个方法是什么来着?`upper`?还是 `uppercase`?`first_uppercase`?`capitalize`?
|
||||
是 `upper`?是 `uppercase`?`first_uppercase`?还是 `capitalize`?
|
||||
|
||||
然后你尝试向程序员老手的朋友——编辑器自动补全寻求帮助。
|
||||
然后,你试试程序员的老朋友——编辑器的自动补全。
|
||||
|
||||
输入函数的第一个参数 `first_name`,输入点号(`.`)然后敲下 `Ctrl+Space` 来触发代码补全。
|
||||
你输入函数的第一个参数 `first_name`,再输入一个点(`.`),然后按下 `Ctrl+Space` 触发补全。
|
||||
|
||||
但遗憾的是并没有起什么作用:
|
||||
但很遗憾,没有什么有用的提示:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/python-types/image01.png">
|
||||
<img src="/img/python-types/image01.png">
|
||||
|
||||
### 添加类型
|
||||
### 添加类型 { #add-types }
|
||||
|
||||
让我们来修改上面例子的一行代码。
|
||||
我们来改前一个版本的一行代码。
|
||||
|
||||
我们将把下面这段代码中的函数参数从:
|
||||
把函数参数从:
|
||||
|
||||
```Python
|
||||
first_name, last_name
|
||||
@@ -78,227 +76,389 @@ John Doe
|
||||
|
||||
就是这样。
|
||||
|
||||
这些就是"类型提示":
|
||||
这些就是“类型提示”:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *}
|
||||
|
||||
|
||||
这和声明默认值是不同的,例如:
|
||||
这和声明默认值不同,比如:
|
||||
|
||||
```Python
|
||||
first_name="john", last_name="doe"
|
||||
```
|
||||
|
||||
这两者不一样。
|
||||
这是两码事。
|
||||
|
||||
我们用的是冒号(`:`),不是等号(`=`)。
|
||||
|
||||
而且添加类型提示一般不会改变原来的运行结果。
|
||||
而且添加类型提示通常不会改变代码本来的行为。
|
||||
|
||||
现在假设我们又一次正在创建这个函数,这次添加了类型提示。
|
||||
现在,再想象你又在编写这个函数了,不过这次加上了类型提示。
|
||||
|
||||
在同样的地方,通过 `Ctrl+Space` 触发自动补全,你会发现:
|
||||
在同样的位置,你用 `Ctrl+Space` 触发自动补全,就能看到:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/python-types/image02.png">
|
||||
<img src="/img/python-types/image02.png">
|
||||
|
||||
这样,你可以滚动查看选项,直到你找到看起来眼熟的那个:
|
||||
这样,你可以滚动查看选项,直到找到那个“看着眼熟”的:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/python-types/image03.png">
|
||||
<img src="/img/python-types/image03.png">
|
||||
|
||||
## 更多动机
|
||||
## 更多动机 { #more-motivation }
|
||||
|
||||
下面是一个已经有类型提示的函数:
|
||||
看这个已经带有类型提示的函数:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *}
|
||||
|
||||
因为编辑器知道变量的类型,你不仅能得到补全,还能获得错误检查:
|
||||
|
||||
因为编辑器已经知道了这些变量的类型,所以不仅能对代码进行补全,还能检查其中的错误:
|
||||
<img src="/img/python-types/image04.png">
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/python-types/image04.png">
|
||||
现在你知道需要修复它,用 `str(age)` 把 `age` 转成字符串:
|
||||
|
||||
现在你知道了必须先修复这个问题,通过 `str(age)` 把 `age` 转换成字符串:
|
||||
{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *}
|
||||
|
||||
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
|
||||
## 声明类型 { #declaring-types }
|
||||
|
||||
你刚刚看到的是声明类型提示的主要位置:函数参数。
|
||||
|
||||
## 声明类型
|
||||
这也是你在 **FastAPI** 中使用它们的主要场景。
|
||||
|
||||
你刚刚看到的就是声明类型提示的主要场景。用于函数的参数。
|
||||
### 简单类型 { #simple-types }
|
||||
|
||||
这也是你将在 **FastAPI** 中使用它们的主要场景。
|
||||
你不仅可以声明 `str`,还可以声明所有标准的 Python 类型。
|
||||
|
||||
### 简单类型
|
||||
|
||||
不只是 `str`,你能够声明所有的标准 Python 类型。
|
||||
|
||||
比如以下类型:
|
||||
例如:
|
||||
|
||||
* `int`
|
||||
* `float`
|
||||
* `bool`
|
||||
* `bytes`
|
||||
|
||||
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
|
||||
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
|
||||
|
||||
### 带类型参数的泛型类型 { #generic-types-with-type-parameters }
|
||||
|
||||
### 嵌套类型
|
||||
有些数据结构可以包含其他值,比如 `dict`、`list`、`set` 和 `tuple`。而内部的值也会有自己的类型。
|
||||
|
||||
有些容器数据结构可以包含其他的值,比如 `dict`、`list`、`set` 和 `tuple`。它们内部的值也会拥有自己的类型。
|
||||
这些带有内部类型的类型称为“泛型”(generic)类型。可以把它们连同内部类型一起声明出来。
|
||||
|
||||
你可以使用 Python 的 `typing` 标准库来声明这些类型以及子类型。
|
||||
要声明这些类型以及内部类型,你可以使用 Python 标准库模块 `typing`。它就是为支持这些类型提示而存在的。
|
||||
|
||||
它专门用来支持这些类型提示。
|
||||
#### 更新的 Python 版本 { #newer-versions-of-python }
|
||||
|
||||
#### 列表
|
||||
使用 `typing` 的语法与所有版本兼容,从 Python 3.6 到最新版本(包括 Python 3.9、Python 3.10 等)。
|
||||
|
||||
例如,让我们来定义一个由 `str` 组成的 `list` 变量。
|
||||
随着 Python 的发展,更新的版本对这些类型注解的支持更好,在很多情况下你甚至不需要导入和使用 `typing` 模块来声明类型注解。
|
||||
|
||||
从 `typing` 模块导入 `List`(注意是大写的 `L`):
|
||||
如果你可以为项目选择更高版本的 Python,你将能享受到这种额外的简化。
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006.py hl[1] *}
|
||||
在整个文档中,会根据不同 Python 版本提供相应的示例(当存在差异时)。
|
||||
|
||||
比如“Python 3.6+”表示兼容 Python 3.6 及以上(包括 3.7、3.8、3.9、3.10 等)。而“Python 3.9+”表示兼容 Python 3.9 及以上(包括 3.10 等)。
|
||||
|
||||
同样以冒号(`:`)来声明这个变量。
|
||||
如果你可以使用最新的 Python 版本,请使用最新版本的示例,它们将拥有最简洁的语法,例如“Python 3.10+”。
|
||||
|
||||
输入 `List` 作为类型。
|
||||
#### 列表 { #list }
|
||||
|
||||
由于列表是带有"子类型"的类型,所以我们把子类型放在方括号中:
|
||||
例如,我们来定义一个由 `str` 组成的 `list` 变量。
|
||||
|
||||
{* ../../docs_src/python_types/tutorial006.py hl[4] *}
|
||||
用同样的冒号(`:`)语法声明变量。
|
||||
|
||||
类型写 `list`。
|
||||
|
||||
这表示:"变量 `items` 是一个 `list`,并且这个列表里的每一个元素都是 `str`"。
|
||||
因为 list 是一种包含内部类型的类型,把内部类型写在方括号里:
|
||||
|
||||
这样,即使在处理列表中的元素时,你的编辑器也可以提供支持。
|
||||
{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *}
|
||||
|
||||
没有类型,几乎是不可能实现下面这样:
|
||||
/// info | 信息
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/python-types/image05.png">
|
||||
方括号中的这些内部类型称为“类型参数”(type parameters)。
|
||||
|
||||
注意,变量 `item` 是列表 `items` 中的元素之一。
|
||||
在这个例子中,`str` 是传给 `list` 的类型参数。
|
||||
|
||||
而且,编辑器仍然知道它是一个 `str`,并为此提供了支持。
|
||||
///
|
||||
|
||||
#### 元组和集合
|
||||
这表示:“变量 `items` 是一个 `list`,并且列表中的每一个元素都是 `str`”。
|
||||
|
||||
声明 `tuple` 和 `set` 的方法也是一样的:
|
||||
这样,即使是在处理列表中的元素时,编辑器也能给你提供支持:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
|
||||
<img src="/img/python-types/image05.png">
|
||||
|
||||
没有类型的话,这几乎是不可能做到的。
|
||||
|
||||
注意,变量 `item` 是列表 `items` 中的一个元素。
|
||||
|
||||
即便如此,编辑器仍然知道它是 `str`,并为此提供支持。
|
||||
|
||||
#### 元组和集合 { #tuple-and-set }
|
||||
|
||||
声明 `tuple` 和 `set` 的方式类似:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *}
|
||||
|
||||
这表示:
|
||||
|
||||
* 变量 `items_t` 是一个 `tuple`,其中的前两个元素都是 `int` 类型, 最后一个元素是 `str` 类型。
|
||||
* 变量 `items_s` 是一个 `set`,其中的每个元素都是 `bytes` 类型。
|
||||
* 变量 `items_t` 是一个含有 3 个元素的 `tuple`,分别是一个 `int`、另一个 `int`,以及一个 `str`。
|
||||
* 变量 `items_s` 是一个 `set`,其中每个元素的类型是 `bytes`。
|
||||
|
||||
#### 字典
|
||||
#### 字典 { #dict }
|
||||
|
||||
定义 `dict` 时,需要传入两个子类型,用逗号进行分隔。
|
||||
定义 `dict` 时,需要传入 2 个类型参数,用逗号分隔。
|
||||
|
||||
第一个子类型声明 `dict` 的所有键。
|
||||
第一个类型参数用于字典的键。
|
||||
|
||||
第二个子类型声明 `dict` 的所有值:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
|
||||
第二个类型参数用于字典的值:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *}
|
||||
|
||||
这表示:
|
||||
|
||||
* 变量 `prices` 是一个 `dict`:
|
||||
* 这个 `dict` 的所有键为 `str` 类型(可以看作是字典内每个元素的名称)。
|
||||
* 这个 `dict` 的所有值为 `float` 类型(可以看作是字典内每个元素的价格)。
|
||||
* 这个 `dict` 的键是 `str` 类型(比如,每个条目的名称)。
|
||||
* 这个 `dict` 的值是 `float` 类型(比如,每个条目的价格)。
|
||||
|
||||
### 类作为类型
|
||||
#### Union { #union }
|
||||
|
||||
你也可以将类声明为变量的类型。
|
||||
你可以声明一个变量可以是若干种类型中的任意一种,比如既可以是 `int` 也可以是 `str`。
|
||||
|
||||
假设你有一个名为 `Person` 的类,拥有 name 属性:
|
||||
在 Python 3.6 及以上(包括 Python 3.10),你可以使用 `typing` 中的 `Union`,把可能的类型放到方括号里。
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
|
||||
|
||||
|
||||
接下来,你可以将一个变量声明为 `Person` 类型:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
|
||||
|
||||
|
||||
然后,你将再次获得所有的编辑器支持:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/python-types/image06.png">
|
||||
|
||||
## Pydantic 模型
|
||||
|
||||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 是一个用来执行数据校验的 Python 库。
|
||||
|
||||
你可以将数据的"结构"声明为具有属性的类。
|
||||
|
||||
每个属性都拥有类型。
|
||||
|
||||
接着你用一些值来创建这个类的实例,这些值会被校验,并被转换为适当的类型(在需要的情况下),返回一个包含所有数据的对象。
|
||||
|
||||
然后,你将获得这个对象的所有编辑器支持。
|
||||
|
||||
下面的例子来自 Pydantic 官方文档:
|
||||
在 Python 3.10 中还有一种新的语法,可以用<abbr title='也叫“按位或运算符(bitwise or operator)”,但这里与该含义无关'>竖线(`|`)</abbr>把可能的类型分隔开。
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011_py310.py!}
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011_py39.py!}
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.8+
|
||||
两种方式的含义一致:`item` 可以是 `int` 或 `str`。
|
||||
|
||||
```Python
|
||||
{!> ../../docs_src/python_types/tutorial011.py!}
|
||||
#### 可能为 `None` { #possibly-none }
|
||||
|
||||
你可以声明一个值的类型是某种类型(比如 `str`),但它也可能是 `None`。
|
||||
|
||||
在 Python 3.6 及以上(包括 Python 3.10),你可以通过从 `typing` 模块导入并使用 `Optional` 来声明:
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!../../docs_src/python_types/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
使用 `Optional[str]` 而不是仅仅 `str`,可以让编辑器帮助你发现把值当成总是 `str` 的错误(实际上它也可能是 `None`)。
|
||||
|
||||
`Optional[Something]` 实际上是 `Union[Something, None]` 的简写,它们等价。
|
||||
|
||||
这也意味着在 Python 3.10 中,你可以使用 `Something | None`:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python hl_lines="1"
|
||||
{!> ../../docs_src/python_types/tutorial009_py310.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
/// info
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009_py39.py!}
|
||||
```
|
||||
|
||||
想进一步了解 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic,请阅读其文档</a>.
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+ 另一种写法
|
||||
|
||||
```Python hl_lines="1 4"
|
||||
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
|
||||
```
|
||||
|
||||
////
|
||||
|
||||
#### 使用 `Union` 或 `Optional` { #using-union-or-optional }
|
||||
|
||||
如果你使用的是 3.10 以下的 Python 版本,这里有个来自我非常主观的建议:
|
||||
|
||||
* 🚨 避免使用 `Optional[SomeType]`
|
||||
* 改用 ✨**`Union[SomeType, None]`**✨
|
||||
|
||||
两者等价,底层相同,但我更推荐 `Union` 而不是 `Optional`,因为“optional(可选)”这个词看起来像是在说“参数可选”,而它实际上表示“它可以是 `None`”,即使它并不是可选的,仍然是必填的。
|
||||
|
||||
我认为 `Union[SomeType, None]` 更明确地表达了它的意思。
|
||||
|
||||
这只是关于词语和名字。但这些词会影响你和你的队友如何理解代码。
|
||||
|
||||
例如,看下面这个函数:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
|
||||
|
||||
参数 `name` 被定义为 `Optional[str]`,但它并不是“可选”的,你不能不传这个参数就调用函数:
|
||||
|
||||
```Python
|
||||
say_hi() # 哦不,这会抛错!😱
|
||||
```
|
||||
|
||||
参数 `name` 仍然是必填的(不是“可选”),因为它没有默认值。不过,`name` 接受 `None` 作为值:
|
||||
|
||||
```Python
|
||||
say_hi(name=None) # 这样可以,None 是有效值 🎉
|
||||
```
|
||||
|
||||
好消息是,一旦你使用 Python 3.10,就无需再为此操心,因为你可以直接用 `|` 来定义类型联合:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
|
||||
|
||||
这样你就不必再考虑 `Optional` 和 `Union` 这些名字了。😎
|
||||
|
||||
#### 泛型类型 { #generic-types }
|
||||
|
||||
这些在方括号中接收类型参数的类型称为“泛型类型”(Generic types)或“泛型”(Generics),例如:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
你可以把同样的内建类型作为泛型使用(带方括号和内部类型):
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
以及与之前的 Python 版本一样,来自 `typing` 模块的:
|
||||
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ……以及其他。
|
||||
|
||||
在 Python 3.10 中,作为使用泛型 `Union` 和 `Optional` 的替代,你可以使用<abbr title='也叫“按位或运算符(bitwise or operator)”,但这里与该含义无关'>竖线(`|`)</abbr>来声明类型联合,这更好也更简单。
|
||||
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
你可以把同样的内建类型作为泛型使用(带方括号和内部类型):
|
||||
|
||||
* `list`
|
||||
* `tuple`
|
||||
* `set`
|
||||
* `dict`
|
||||
|
||||
以及来自 `typing` 模块的泛型:
|
||||
|
||||
* `Union`
|
||||
* `Optional`
|
||||
* ……以及其他。
|
||||
|
||||
////
|
||||
|
||||
### 类作为类型 { #classes-as-types }
|
||||
|
||||
你也可以把类声明为变量的类型。
|
||||
|
||||
假设你有一个名为 `Person` 的类,带有 name:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *}
|
||||
|
||||
然后你可以声明一个变量是 `Person` 类型:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *}
|
||||
|
||||
接着,你会再次获得所有的编辑器支持:
|
||||
|
||||
<img src="/img/python-types/image06.png">
|
||||
|
||||
注意,这表示“`one_person` 是类 `Person` 的一个实例(instance)”。
|
||||
|
||||
它并不表示“`one_person` 是名为 `Person` 的类本身(class)”。
|
||||
|
||||
## Pydantic 模型 { #pydantic-models }
|
||||
|
||||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 是一个用于执行数据校验的 Python 库。
|
||||
|
||||
你将数据的“结构”声明为带有属性的类。
|
||||
|
||||
每个属性都有一个类型。
|
||||
|
||||
然后你用一些值创建这个类的实例,它会校验这些值,并在需要时把它们转换为合适的类型,返回一个包含所有数据的对象。
|
||||
|
||||
你还能对这个结果对象获得完整的编辑器支持。
|
||||
|
||||
下面是来自 Pydantic 官方文档的一个示例:
|
||||
|
||||
{* ../../docs_src/python_types/tutorial011_py310.py *}
|
||||
|
||||
/// info | 信息
|
||||
|
||||
想了解更多关于 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic 的信息,请查看其文档</a>。
|
||||
|
||||
///
|
||||
|
||||
整个 **FastAPI** 建立在 Pydantic 的基础之上。
|
||||
**FastAPI** 完全建立在 Pydantic 之上。
|
||||
|
||||
实际上你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 看到很多这种情况。
|
||||
你会在[教程 - 用户指南](tutorial/index.md){.internal-link target=_blank}中看到更多的实战示例。
|
||||
|
||||
## **FastAPI** 中的类型提示
|
||||
/// tip | 提示
|
||||
|
||||
**FastAPI** 利用这些类型提示来做下面几件事。
|
||||
|
||||
使用 **FastAPI** 时用类型提示声明参数可以获得:
|
||||
|
||||
* **编辑器支持**。
|
||||
* **类型检查**。
|
||||
|
||||
...并且 **FastAPI** 还会用这些类型声明来:
|
||||
|
||||
* **定义参数要求**:声明对请求路径参数、查询参数、请求头、请求体、依赖等的要求。
|
||||
* **转换数据**:将来自请求的数据转换为需要的类型。
|
||||
* **校验数据**: 对于每一个请求:
|
||||
* 当数据校验失败时自动生成**错误信息**返回给客户端。
|
||||
* 使用 OpenAPI **记录** API:
|
||||
* 然后用于自动生成交互式文档的用户界面。
|
||||
|
||||
听上去有点抽象。不过不用担心。你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 中看到所有的实战。
|
||||
|
||||
最重要的是,通过使用标准的 Python 类型,只需要在一个地方声明(而不是添加更多的类、装饰器等),**FastAPI** 会为你完成很多的工作。
|
||||
|
||||
/// info
|
||||
|
||||
如果你已经阅读了所有教程,回过头来想了解有关类型的更多信息,<a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">来自 `mypy` 的"速查表"</a>是不错的资源。
|
||||
当你在没有默认值的情况下使用 `Optional` 或 `Union[Something, None]` 时,Pydantic 有一个特殊行为,你可以在 Pydantic 文档的 <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">必填的 Optional 字段</a> 中了解更多。
|
||||
|
||||
///
|
||||
|
||||
## 带元数据注解的类型提示 { #type-hints-with-metadata-annotations }
|
||||
|
||||
Python 还提供了一个特性,可以使用 `Annotated` 在这些类型提示中放入额外的<abbr title="关于数据的数据,在这里是关于类型的信息,例如描述。">元数据</abbr>。
|
||||
|
||||
从 Python 3.9 起,`Annotated` 是标准库的一部分,因此可以从 `typing` 导入。
|
||||
|
||||
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}
|
||||
|
||||
Python 本身不会对这个 `Annotated` 做任何处理。对于编辑器和其他工具,类型仍然是 `str`。
|
||||
|
||||
但你可以在 `Annotated` 中为 **FastAPI** 提供额外的元数据,来描述你希望应用如何行为。
|
||||
|
||||
重要的是要记住:传给 `Annotated` 的第一个类型参数才是实际类型。其余的只是给其他工具用的元数据。
|
||||
|
||||
现在你只需要知道 `Annotated` 的存在,并且它是标准 Python。😎
|
||||
|
||||
稍后你会看到它有多么强大。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
这是标准 Python,这意味着你仍然可以在编辑器里获得尽可能好的开发体验,并能和你用来分析、重构代码的工具良好协作等。✨
|
||||
|
||||
同时你的代码也能与许多其他 Python 工具和库高度兼容。🚀
|
||||
|
||||
///
|
||||
|
||||
## **FastAPI** 中的类型提示 { #type-hints-in-fastapi }
|
||||
|
||||
**FastAPI** 利用这些类型提示来完成多件事情。
|
||||
|
||||
在 **FastAPI** 中,用类型提示来声明参数,你将获得:
|
||||
|
||||
* 编辑器支持。
|
||||
* 类型检查。
|
||||
|
||||
……并且 **FastAPI** 会使用相同的声明来:
|
||||
|
||||
* 定义要求:从请求路径参数、查询参数、请求头、请求体、依赖等。
|
||||
* 转换数据:把请求中的数据转换为所需类型。
|
||||
* 校验数据:对于每个请求:
|
||||
* 当数据无效时,自动生成错误信息返回给客户端。
|
||||
* 使用 OpenAPI 记录 API:
|
||||
* 然后用于自动生成交互式文档界面。
|
||||
|
||||
这些听起来可能有点抽象。别担心。你会在[教程 - 用户指南](tutorial/index.md){.internal-link target=_blank}中看到所有这些的实际效果。
|
||||
|
||||
重要的是,通过使用标准的 Python 类型,而且只在一个地方声明(而不是添加更多类、装饰器等),**FastAPI** 会为你完成大量工作。
|
||||
|
||||
/// info | 信息
|
||||
|
||||
如果你已经读完所有教程,又回来想进一步了解类型,一个不错的资源是 <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">`mypy` 的“速查表”</a>。
|
||||
|
||||
///
|
||||
|
||||
@@ -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-fields }
|
||||
|
||||
与在*路径操作函数*中使用 `Query`、`Path` 、`Body` 声明校验与元数据的方式一样,可以使用 Pydantic 的 `Field` 在 Pydantic 模型内部声明校验和元数据。
|
||||
|
||||
## 导入 `Field`
|
||||
## 导入 `Field` { #import-field }
|
||||
|
||||
首先,从 Pydantic 中导入 `Field`:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
///
|
||||
|
||||
## 声明模型属性
|
||||
## 声明模型属性 { #declare-model-attributes }
|
||||
|
||||
然后,使用 `Field` 定义模型的属性:
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
/// note | 技术细节
|
||||
|
||||
实际上,`Query`、`Path` 都是 `Params` 的子类,而 `Params` 类又是 Pydantic 中 `FieldInfo` 的子类。
|
||||
实际上,`Query`、`Path` 以及你接下来会看到的其它对象,会创建公共 `Param` 类的子类的对象,而 `Param` 本身是 Pydantic 中 `FieldInfo` 的子类。
|
||||
|
||||
Pydantic 的 `Field` 返回也是 `FieldInfo` 的类实例。
|
||||
|
||||
@@ -40,13 +40,20 @@ Pydantic 的 `Field` 返回也是 `FieldInfo` 的类实例。
|
||||
|
||||
///
|
||||
|
||||
## 添加更多信息
|
||||
## 添加更多信息 { #add-extra-information }
|
||||
|
||||
`Field`、`Query`、`Body` 等对象里可以声明更多信息,并且 JSON Schema 中也会集成这些信息。
|
||||
|
||||
*声明示例*一章中将详细介绍添加更多信息的知识。
|
||||
|
||||
## 小结
|
||||
/// warning | 警告
|
||||
|
||||
传递给 `Field` 的额外键也会出现在你的应用生成的 OpenAPI 架构中。
|
||||
由于这些键不一定属于 OpenAPI 规范的一部分,某些 OpenAPI 工具(例如 [OpenAPI 验证器](https://validator.swagger.io/))可能无法处理你生成的架构。
|
||||
|
||||
///
|
||||
|
||||
## 小结 { #recap }
|
||||
|
||||
Pydantic 的 `Field` 可以为模型属性声明更多校验和元数据。
|
||||
|
||||
|
||||
@@ -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,30 +1,30 @@
|
||||
# 请求体
|
||||
# 请求体 { #request-body }
|
||||
|
||||
FastAPI 使用**请求体**从客户端(例如浏览器)向 API 发送数据。
|
||||
当你需要从客户端(比如浏览器)向你的 API 发送数据时,会把它作为**请求体**发送。
|
||||
|
||||
**请求体**是客户端发送给 API 的数据。**响应体**是 API 发送给客户端的数据。
|
||||
**请求体**是客户端发送给你的 API 的数据。**响应体**是你的 API 发送给客户端的数据。
|
||||
|
||||
API 基本上肯定要发送**响应体**,但是客户端不一定发送**请求体**。
|
||||
你的 API 几乎总是需要发送**响应体**。但客户端不一定总是要发送**请求体**,有时它们只请求某个路径,可能带一些查询参数,但不会发送请求体。
|
||||
|
||||
使用 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 模型声明**请求体**,能充分利用它的功能和优点。
|
||||
使用 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 模型来声明**请求体**,能充分利用它的功能和优点。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
发送数据使用 `POST`(最常用)、`PUT`、`DELETE`、`PATCH` 等操作。
|
||||
发送数据应使用以下之一:`POST`(最常见)、`PUT`、`DELETE` 或 `PATCH`。
|
||||
|
||||
规范中没有定义使用 `GET` 发送请求体的操作,但不管怎样,FastAPI 也支持这种方式,只不过仅用于非常复杂或极端的用例。
|
||||
规范中没有定义用 `GET` 请求发送请求体的行为,但 FastAPI 仍支持这种方式,只用于非常复杂/极端的用例。
|
||||
|
||||
我们不建议使用 `GET`,因此,在 Swagger UI 交互文档中不会显示有关 `GET` 的内容,而且代理协议也不一定支持 `GET`。
|
||||
由于不推荐,在使用 `GET` 时,Swagger UI 的交互式文档不会显示请求体的文档,而且中间的代理可能也不支持它。
|
||||
|
||||
///
|
||||
|
||||
## 导入 Pydantic 的 `BaseModel`
|
||||
## 导入 Pydantic 的 `BaseModel` { #import-pydantics-basemodel }
|
||||
|
||||
从 `pydantic` 中导入 `BaseModel`:
|
||||
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
|
||||
|
||||
## 创建数据模型
|
||||
## 创建数据模型 { #create-your-data-model }
|
||||
|
||||
把数据模型声明为继承 `BaseModel` 的类。
|
||||
|
||||
@@ -32,9 +32,9 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请
|
||||
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
|
||||
|
||||
与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。默认值为 `None` 的模型属性也是可选的。
|
||||
与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。把默认值设为 `None` 可使其变为可选。
|
||||
|
||||
例如,上述模型声明如下 JSON **对象**(即 Python **字典**):
|
||||
例如,上述模型声明如下 JSON "object"(即 Python `dict`):
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -45,7 +45,7 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请
|
||||
}
|
||||
```
|
||||
|
||||
……由于 `description` 和 `tax` 是可选的(默认值为 `None`),下面的 JSON **对象**也有效:
|
||||
...由于 `description` 和 `tax` 是可选的(默认值为 `None`),下面的 JSON "object" 也有效:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -54,40 +54,40 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请
|
||||
}
|
||||
```
|
||||
|
||||
## 声明请求体参数
|
||||
## 声明为参数 { #declare-it-as-a-parameter }
|
||||
|
||||
使用与声明路径和查询参数相同的方式声明请求体,把请求体添加至*路径操作*:
|
||||
使用与声明路径和查询参数相同的方式,把它添加至*路径操作*:
|
||||
|
||||
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
|
||||
|
||||
……此处,请求体参数的类型为 `Item` 模型。
|
||||
...并把其类型声明为你创建的模型 `Item`。
|
||||
|
||||
## 结论
|
||||
## 结果 { #results }
|
||||
|
||||
仅使用 Python 类型声明,**FastAPI** 就可以:
|
||||
仅使用这些 Python 类型声明,**FastAPI** 就可以:
|
||||
|
||||
* 以 JSON 形式读取请求体
|
||||
* (在必要时)把请求体转换为对应的类型
|
||||
* 校验数据:
|
||||
* 数据无效时返回错误信息,并指出错误数据的确切位置和内容
|
||||
* 把接收的数据赋值给参数 `item`
|
||||
* 把函数中请求体参数的类型声明为 `Item`,还能获得代码补全等编辑器支持
|
||||
* 为模型生成 <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a>,在项目中所需的位置使用
|
||||
* 这些概图是 OpenAPI 概图的部件,用于 API 文档 <abbr title="用户界面">UI</abbr>
|
||||
* 以 JSON 形式读取请求体。
|
||||
* (在必要时)把请求体转换为对应的类型。
|
||||
* 校验数据。
|
||||
* 数据无效时返回清晰的错误信息,并指出错误数据的确切位置和内容。
|
||||
* 把接收的数据赋值给参数 `item`。
|
||||
* 因为你把函数中的参数类型声明为 `Item`,所以还能获得所有属性及其类型的编辑器支持(补全等)。
|
||||
* 为你的模型生成 <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a> 定义,如果对你的项目有意义,还可以在其他地方使用它们。
|
||||
* 这些 schema 会成为生成的 OpenAPI Schema 的一部分,并被自动文档的 <abbr title="User Interfaces - 用户界面">UIs</abbr> 使用。
|
||||
|
||||
## API 文档
|
||||
## 自动文档 { #automatic-docs }
|
||||
|
||||
Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文档中显示:
|
||||
你的模型的 JSON Schema 会成为生成的 OpenAPI Schema 的一部分,并显示在交互式 API 文档中:
|
||||
|
||||
<img src="/img/tutorial/body/image01.png">
|
||||
|
||||
而且,还会用于 API 文档中使用了概图的*路径操作*:
|
||||
并且,还会用于需要它们的每个*路径操作*的 API 文档中:
|
||||
|
||||
<img src="/img/tutorial/body/image02.png">
|
||||
|
||||
## 编辑器支持
|
||||
## 编辑器支持 { #editor-support }
|
||||
|
||||
在编辑器中,函数内部均可使用类型提示、代码补全(如果接收的不是 Pydantic 模型,而是**字典**,就没有这样的支持):
|
||||
在编辑器中,函数内部你会在各处得到类型提示与补全(如果接收的不是 Pydantic 模型,而是 `dict`,就不会有这样的支持):
|
||||
|
||||
<img src="/img/tutorial/body/image03.png">
|
||||
|
||||
@@ -95,23 +95,23 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文
|
||||
|
||||
<img src="/img/tutorial/body/image04.png">
|
||||
|
||||
这并非偶然,整个 **FastAPI** 框架都是围绕这种思路精心设计的。
|
||||
这并非偶然,整个框架都是围绕这种设计构建的。
|
||||
|
||||
并且,在 FastAPI 的设计阶段,我们就已经进行了全面测试,以确保 FastAPI 可以获得所有编辑器的支持。
|
||||
并且在设计阶段、实现之前就进行了全面测试,以确保它能在所有编辑器中正常工作。
|
||||
|
||||
我们还改进了 Pydantic,让它也支持这些功能。
|
||||
我们甚至对 Pydantic 本身做了一些改动以支持这些功能。
|
||||
|
||||
虽然上面的截图取自 <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>。
|
||||
上面的截图来自 <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>。
|
||||
|
||||
但 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 和大多数 Python 编辑器也支持同样的功能:
|
||||
但使用 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 和大多数其他 Python 编辑器,你也会获得相同的编辑器支持:
|
||||
|
||||
<img src="/img/tutorial/body/image05.png">
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
使用 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 编辑器时,推荐安装 <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm 插件</a>。
|
||||
如果你使用 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 作为编辑器,可以使用 <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm 插件</a>。
|
||||
|
||||
该插件用于完善 PyCharm 对 Pydantic 模型的支持,优化的功能如下:
|
||||
它能改进对 Pydantic 模型的编辑器支持,包括:
|
||||
|
||||
* 自动补全
|
||||
* 类型检查
|
||||
@@ -121,42 +121,44 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文
|
||||
|
||||
///
|
||||
|
||||
## 使用模型
|
||||
## 使用模型 { #use-the-model }
|
||||
|
||||
在*路径操作*函数内部直接访问模型对象的属性:
|
||||
在*路径操作*函数内部直接访问模型对象的所有属性:
|
||||
|
||||
{* ../../docs_src/body/tutorial002_py310.py hl[19] *}
|
||||
{* ../../docs_src/body/tutorial002_py310.py *}
|
||||
|
||||
## 请求体 + 路径参数
|
||||
## 请求体 + 路径参数 { #request-body-path-parameters }
|
||||
|
||||
**FastAPI** 支持同时声明路径参数和请求体。
|
||||
可以同时声明路径参数和请求体。
|
||||
|
||||
**FastAPI** 能识别与**路径参数**匹配的函数参数,还能识别从**请求体**中获取的类型为 Pydantic 模型的函数参数。
|
||||
**FastAPI** 能识别与**路径参数**匹配的函数参数应该**从路径中获取**,而声明为 Pydantic 模型的函数参数应该**从请求体中获取**。
|
||||
|
||||
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
|
||||
|
||||
## 请求体 + 路径参数 + 查询参数
|
||||
## 请求体 + 路径 + 查询参数 { #request-body-path-query-parameters }
|
||||
|
||||
**FastAPI** 支持同时声明**请求体**、**路径参数**和**查询参数**。
|
||||
也可以同时声明**请求体**、**路径**和**查询**参数。
|
||||
|
||||
**FastAPI** 能够正确识别这三种参数,并从正确的位置获取数据。
|
||||
**FastAPI** 会分别识别它们,并从正确的位置获取数据。
|
||||
|
||||
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
|
||||
|
||||
函数参数按如下规则进行识别:
|
||||
|
||||
- **路径**中声明了相同参数的参数,是路径参数
|
||||
- 类型是(`int`、`float`、`str`、`bool` 等)**单类型**的参数,是**查询**参数
|
||||
- 类型是 **Pydantic 模型**的参数,是**请求体**
|
||||
* 如果该参数也在**路径**中声明了,它就是路径参数。
|
||||
* 如果该参数是(`int`、`float`、`str`、`bool` 等)**单一类型**,它会被当作**查询**参数。
|
||||
* 如果该参数的类型声明为 **Pydantic 模型**,它会被当作请求**体**。
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
因为默认值是 `None`, FastAPI 会把 `q` 当作可选参数。
|
||||
FastAPI 会根据默认值 `= None` 知道 `q` 的值不是必填的。
|
||||
|
||||
FastAPI 不使用 `Optional[str]` 中的 `Optional`, 但 `Optional` 可以让编辑器提供更好的支持,并检测错误。
|
||||
`str | None`(Python 3.10+)或 `Union[str, None]`(Python 3.9+ 中的 `Union`)并不是 FastAPI 用来判断是否必填的依据;是否必填由是否有默认值 `= None` 决定。
|
||||
|
||||
但添加这些类型注解可以让你的编辑器提供更好的支持并检测错误。
|
||||
|
||||
///
|
||||
|
||||
## 不使用 Pydantic
|
||||
## 不使用 Pydantic { #without-pydantic }
|
||||
|
||||
即便不使用 Pydantic 模型也能使用 **Body** 参数。详见[请求体 - 多参数:请求体中的单值](body-multiple-params.md#_2){.internal-link target=\_blank}。
|
||||
即便不使用 Pydantic 模型也能使用 **Body** 参数。详见[请求体 - 多参数:请求体中的单值](body-multiple-params.md#singular-values-in-body){.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,20 +1,19 @@
|
||||
# Cookie 参数
|
||||
# Cookie 参数 { #cookie-parameters }
|
||||
|
||||
定义 `Cookie` 参数与定义 `Query` 和 `Path` 参数一样。
|
||||
定义 `Cookie` 参数与定义 `Query` 和 `Path` 参数一样。
|
||||
|
||||
## 导入 `Cookie`
|
||||
## 导入 `Cookie` { #import-cookie }
|
||||
|
||||
首先,导入 `Cookie`:
|
||||
|
||||
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## 声明 `Cookie` 参数
|
||||
## 声明 `Cookie` 参数 { #declare-cookie-parameters }
|
||||
|
||||
声明 `Cookie` 参数的方式与声明 `Query` 和 `Path` 参数相同。
|
||||
|
||||
第一个值是默认值,还可以传递所有验证参数或注释参数:
|
||||
|
||||
|
||||
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
|
||||
|
||||
/// note | 技术细节
|
||||
@@ -25,12 +24,22 @@
|
||||
|
||||
///
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
必须使用 `Cookie` 声明 cookie 参数,否则该参数会被解释为查询参数。
|
||||
|
||||
///
|
||||
|
||||
## 小结
|
||||
/// info | 信息
|
||||
|
||||
请注意,由于**浏览器会以特殊方式并在幕后处理 cookies**,它们**不会**轻易允许**JavaScript**访问它们。
|
||||
|
||||
如果你前往位于 `/docs` 的**API 文档界面**,你可以看到你的*路径操作*中有关 cookies 的**文档**。
|
||||
|
||||
但即使你**填写了数据**并点击 "Execute",由于文档界面依赖于**JavaScript**工作,cookies 也不会被发送,你会看到一个**错误**消息,好像你没有填写任何值一样。
|
||||
|
||||
///
|
||||
|
||||
## 小结 { #recap }
|
||||
|
||||
使用 `Cookie` 声明 cookie 参数的方式与 `Query` 和 `Path` 相同。
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
# JSON 兼容编码器
|
||||
# JSON 兼容编码器 { #json-compatible-encoder }
|
||||
|
||||
在某些情况下,您可能需要将数据类型(如Pydantic模型)转换为与JSON兼容的数据类型(如`dict`、`list`等)。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
对于这种要求, **FastAPI**提供了`jsonable_encoder()`函数。
|
||||
|
||||
## 使用`jsonable_encoder`
|
||||
## 使用`jsonable_encoder` { #using-the-jsonable-encoder }
|
||||
|
||||
让我们假设你有一个数据库名为`fake_db`,它只能接收与JSON兼容的数据。
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
这个操作不会返回一个包含JSON格式(作为字符串)数据的庞大的`str`。它将返回一个Python标准数据结构(例如`dict`),其值和子值都与JSON兼容。
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
`jsonable_encoder`实际上是FastAPI内部用来转换数据的。但是它在许多其他场景中也很有用。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 额外数据类型
|
||||
# 额外数据类型 { #extra-data-types }
|
||||
|
||||
到目前为止,您一直在使用常见的数据类型,如:
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
* 传入请求的数据转换。
|
||||
* 响应数据转换。
|
||||
* 数据验证。
|
||||
* 自动补全和文档。
|
||||
* 自动注解和文档。
|
||||
|
||||
## 其他数据类型
|
||||
## 其他数据类型 { #other-data-types }
|
||||
|
||||
下面是一些你可以使用的其他数据类型:
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
* `datetime.timedelta`:
|
||||
* 一个 Python `datetime.timedelta`.
|
||||
* 在请求和响应中将表示为 `float` 代表总秒数。
|
||||
* Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">查看文档了解更多信息</a>。
|
||||
* Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers" class="external-link" target="_blank">查看文档了解更多信息</a>。
|
||||
* `frozenset`:
|
||||
* 在请求和响应中,作为 `set` 对待:
|
||||
* 在请求中,列表将被读取,消除重复,并将其转换为一个 `set`。
|
||||
* 在响应中 `set` 将被转换为 `list` 。
|
||||
* 产生的模式将指定那些 `set` 的值是唯一的 (使用 JSON 模式的 `uniqueItems`)。
|
||||
* 产生的模式将指定那些 `set` 的值是唯一的 (使用 JSON Schema 的 `uniqueItems`)。
|
||||
* `bytes`:
|
||||
* 标准的 Python `bytes`。
|
||||
* 在请求和响应中被当作 `str` 处理。
|
||||
@@ -49,9 +49,9 @@
|
||||
* `Decimal`:
|
||||
* 标准的 Python `Decimal`。
|
||||
* 在请求和响应中被当做 `float` 一样处理。
|
||||
* 您可以在这里检查所有有效的pydantic数据类型: <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Pydantic data types</a>.
|
||||
* 您可以在这里检查所有有效的 Pydantic 数据类型: <a href="https://docs.pydantic.dev/latest/usage/types/types/" class="external-link" target="_blank">Pydantic data types</a>.
|
||||
|
||||
## 例子
|
||||
## 例子 { #example }
|
||||
|
||||
下面是一个*路径操作*的示例,其中的参数使用了上面的一些类型。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 更多模型
|
||||
# 更多模型 { #extra-models }
|
||||
|
||||
书接上文,多个关联模型这种情况很常见。
|
||||
|
||||
@@ -6,29 +6,29 @@
|
||||
|
||||
* **输入模型**应该含密码
|
||||
* **输出模型**不应含密码
|
||||
* **数据库模型**需要加密的密码
|
||||
* **数据库模型**可能需要包含哈希后的密码
|
||||
|
||||
/// danger | 危险
|
||||
/// danger
|
||||
|
||||
千万不要存储用户的明文密码。始终存储可以进行验证的**安全哈希值**。
|
||||
不要存储用户的明文密码。始终只存储之后可用于校验的“安全哈希”。
|
||||
|
||||
如果不了解这方面的知识,请参阅[安全性中的章节](security/simple-oauth2.md#password-hashing){.internal-link target=_blank},了解什么是**密码哈希**。
|
||||
如果你还不了解,可以在[安全性章节](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}中学习什么是“密码哈希”。
|
||||
|
||||
///
|
||||
|
||||
## 多个模型
|
||||
## 多个模型 { #multiple-models }
|
||||
|
||||
下面的代码展示了不同模型处理密码字段的方式,及使用位置的大致思路:
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
|
||||
|
||||
### `**user_in.dict()` 简介
|
||||
### 关于 `**user_in.model_dump()` { #about-user-in-model-dump }
|
||||
|
||||
#### Pydantic 的 `.dict()`
|
||||
#### Pydantic 的 `.model_dump()` { #pydantics-model-dump }
|
||||
|
||||
`user_in` 是类 `UserIn` 的 Pydantic 模型。
|
||||
|
||||
Pydantic 模型支持 `.dict()` 方法,能返回包含模型数据的**字典**。
|
||||
Pydantic 模型有 `.model_dump()` 方法,会返回包含模型数据的 `dict`。
|
||||
|
||||
因此,如果使用如下方式创建 Pydantic 对象 `user_in`:
|
||||
|
||||
@@ -39,10 +39,10 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com
|
||||
就能以如下方式调用:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
user_dict = user_in.model_dump()
|
||||
```
|
||||
|
||||
现在,变量 `user_dict`中的就是包含数据的**字典**(变量 `user_dict` 是字典,不是 Pydantic 模型对象)。
|
||||
现在,变量 `user_dict` 中的是包含数据的 `dict`(它是 `dict`,不是 Pydantic 模型对象)。
|
||||
|
||||
以如下方式调用:
|
||||
|
||||
@@ -50,7 +50,7 @@ user_dict = user_in.dict()
|
||||
print(user_dict)
|
||||
```
|
||||
|
||||
输出的就是 Python **字典**:
|
||||
输出的就是 Python `dict`:
|
||||
|
||||
```Python
|
||||
{
|
||||
@@ -61,9 +61,9 @@ print(user_dict)
|
||||
}
|
||||
```
|
||||
|
||||
#### 解包 `dict`
|
||||
#### 解包 `dict` { #unpacking-a-dict }
|
||||
|
||||
把**字典** `user_dict` 以 `**user_dict` 形式传递给函数(或类),Python 会执行**解包**操作。它会把 `user_dict` 的键和值作为关键字参数直接传递。
|
||||
把 `dict`(如 `user_dict`)以 `**user_dict` 形式传递给函数(或类),Python 会执行“解包”。它会把 `user_dict` 的键和值作为关键字参数直接传递。
|
||||
|
||||
因此,接着上面的 `user_dict` 继续编写如下代码:
|
||||
|
||||
@@ -82,7 +82,7 @@ UserInDB(
|
||||
)
|
||||
```
|
||||
|
||||
或更精准,直接把可能会用到的内容与 `user_dict` 一起使用:
|
||||
或更精准,直接使用 `user_dict`(无论它将来包含什么字段):
|
||||
|
||||
```Python
|
||||
UserInDB(
|
||||
@@ -93,31 +93,31 @@ UserInDB(
|
||||
)
|
||||
```
|
||||
|
||||
#### 用其它模型中的内容生成 Pydantic 模型
|
||||
#### 用另一个模型的内容生成 Pydantic 模型 { #a-pydantic-model-from-the-contents-of-another }
|
||||
|
||||
上例中 ,从 `user_in.dict()` 中得到了 `user_dict`,下面的代码:
|
||||
上例中 ,从 `user_in.model_dump()` 中得到了 `user_dict`,下面的代码:
|
||||
|
||||
```Python
|
||||
user_dict = user_in.dict()
|
||||
user_dict = user_in.model_dump()
|
||||
UserInDB(**user_dict)
|
||||
```
|
||||
|
||||
等效于:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict())
|
||||
UserInDB(**user_in.model_dump())
|
||||
```
|
||||
|
||||
……因为 `user_in.dict()` 是字典,在传递给 `UserInDB` 时,把 `**` 加在 `user_in.dict()` 前,可以让 Python 进行**解包**。
|
||||
……因为 `user_in.model_dump()` 是 `dict`,在传递给 `UserInDB` 时,把 `**` 加在 `user_in.model_dump()` 前,可以让 Python 进行解包。
|
||||
|
||||
这样,就可以用其它 Pydantic 模型中的数据生成 Pydantic 模型。
|
||||
|
||||
#### 解包 `dict` 和更多关键字
|
||||
#### 解包 `dict` 并添加额外关键字参数 { #unpacking-a-dict-and-extra-keywords }
|
||||
|
||||
接下来,继续添加关键字参数 `hashed_password=hashed_password`,例如:
|
||||
|
||||
```Python
|
||||
UserInDB(**user_in.dict(), hashed_password=hashed_password)
|
||||
UserInDB(**user_in.model_dump(), hashed_password=hashed_password)
|
||||
```
|
||||
|
||||
……输出结果如下:
|
||||
@@ -132,68 +132,80 @@ UserInDB(
|
||||
)
|
||||
```
|
||||
|
||||
/// warning | 警告
|
||||
/// warning
|
||||
|
||||
辅助的附加函数只是为了演示可能的数据流,但它们显然不能提供任何真正的安全机制。
|
||||
配套的辅助函数 `fake_password_hasher` 和 `fake_save_user` 仅用于演示可能的数据流,当然并不提供真实的安全性。
|
||||
|
||||
///
|
||||
|
||||
## 减少重复
|
||||
## 减少重复 { #reduce-duplication }
|
||||
|
||||
**FastAPI** 的核心思想就是减少代码重复。
|
||||
减少代码重复是 **FastAPI** 的核心思想之一。
|
||||
|
||||
代码重复会导致 bug、安全问题、代码失步等问题(更新了某个位置的代码,但没有同步更新其它位置的代码)。
|
||||
|
||||
上面的这些模型共享了大量数据,拥有重复的属性名和类型。
|
||||
|
||||
FastAPI 可以做得更好。
|
||||
我们可以做得更好。
|
||||
|
||||
声明 `UserBase` 模型作为其它模型的基类。然后,用该类衍生出继承其属性(类型声明、验证等)的子类。
|
||||
声明 `UserBase` 模型作为其它模型的基类。然后,用该类衍生出继承其属性(类型声明、校验等)的子类。
|
||||
|
||||
所有数据转换、校验、文档等功能仍将正常运行。
|
||||
|
||||
这样,就可以仅声明模型之间的差异部分(具有明文的 `password`、具有 `hashed_password` 以及不包括密码)。
|
||||
|
||||
通过这种方式,可以只声明模型之间的区别(分别包含明文密码、哈希密码,以及无密码的模型)。
|
||||
这样,就可以仅声明模型之间的差异部分(具有明文的 `password`、具有 `hashed_password` 以及不包括密码):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
|
||||
|
||||
## `Union` 或者 `anyOf`
|
||||
## `Union` 或 `anyOf` { #union-or-anyof }
|
||||
|
||||
响应可以声明为两种类型的 `Union` 类型,即该响应可以是两种类型中的任意类型。
|
||||
响应可以声明为两个或多个类型的 `Union`,即该响应可以是这些类型中的任意一种。
|
||||
|
||||
在 OpenAPI 中可以使用 `anyOf` 定义。
|
||||
在 OpenAPI 中会用 `anyOf` 表示。
|
||||
|
||||
为此,请使用 Python 标准类型提示 <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>:
|
||||
|
||||
/// note | 笔记
|
||||
/// note
|
||||
|
||||
定义 <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a> 类型时,要把详细的类型写在前面,然后是不太详细的类型。下例中,更详细的 `PlaneItem` 位于 `Union[PlaneItem,CarItem]` 中的 `CarItem` 之前。
|
||||
定义 <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a> 类型时,要把更具体的类型写在前面,然后是不太具体的类型。下例中,更具体的 `PlaneItem` 位于 `Union[PlaneItem, CarItem]` 中的 `CarItem` 之前。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
|
||||
|
||||
## 模型列表
|
||||
### Python 3.10 中的 `Union` { #union-in-python-3-10 }
|
||||
|
||||
使用同样的方式也可以声明由对象列表构成的响应。
|
||||
在这个示例中,我们把 `Union[PlaneItem, CarItem]` 作为参数 `response_model` 的值传入。
|
||||
|
||||
为此,请使用标准的 Python `typing.List`:
|
||||
因为这是作为“参数的值”而不是放在“类型注解”中,所以即使在 Python 3.10 也必须使用 `Union`。
|
||||
|
||||
如果是在类型注解中,我们就可以使用竖线:
|
||||
|
||||
```Python
|
||||
some_variable: PlaneItem | CarItem
|
||||
```
|
||||
|
||||
但如果把它写成赋值 `response_model=PlaneItem | CarItem`,就会报错,因为 Python 会尝试在 `PlaneItem` 和 `CarItem` 之间执行一个“无效的运算”,而不是把它当作类型注解来解析。
|
||||
|
||||
## 模型列表 { #list-of-models }
|
||||
|
||||
同样地,你可以声明由对象列表构成的响应。
|
||||
|
||||
为此,请使用标准的 Python `typing.List`(在 Python 3.9+ 中也可以直接用 `list`):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
|
||||
|
||||
## 任意 `dict` 构成的响应
|
||||
## 任意 `dict` 的响应 { #response-with-arbitrary-dict }
|
||||
|
||||
任意的 `dict` 都能用于声明响应,只要声明键和值的类型,无需使用 Pydantic 模型。
|
||||
你也可以使用普通的任意 `dict` 来声明响应,只需声明键和值的类型,无需使用 Pydantic 模型。
|
||||
|
||||
事先不知道可用的字段 / 属性名时(Pydantic 模型必须知道字段是什么),这种方式特别有用。
|
||||
如果你事先不知道有效的字段/属性名(Pydantic 模型需要预先知道字段)时,这很有用。
|
||||
|
||||
此时,可以使用 `typing.Dict`:
|
||||
此时,可以使用 `typing.Dict`(在 Python 3.9+ 中也可以直接用 `dict`):
|
||||
|
||||
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
针对不同场景,可以随意使用不同的 Pydantic 模型继承定义的基类。
|
||||
针对不同场景,可以随意使用不同的 Pydantic 模型并通过继承复用。
|
||||
|
||||
实体必须具有不同的**状态**时,不必为不同状态的实体单独定义数据模型。例如,用户**实体**就有包含 `password`、包含 `password_hash` 以及不含密码等多种状态。
|
||||
当一个实体需要具备不同的“状态”时,无需只为该实体定义一个数据模型。例如,用户“实体”就可能有包含 `password`、包含 `password_hash` 以及不含密码等多种状态。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 第一步
|
||||
# 第一步 { #first-steps }
|
||||
|
||||
最简单的 FastAPI 文件可能像下面这样:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py *}
|
||||
|
||||
将其复制到 `main.py` 文件中。
|
||||
|
||||
@@ -56,7 +56,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
|
||||
该行显示了你的应用在本机所提供服务的 URL 地址。
|
||||
|
||||
### 查看
|
||||
### 查看 { #check-it }
|
||||
|
||||
打开浏览器访问 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
|
||||
|
||||
@@ -66,7 +66,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
{"message": "Hello World"}
|
||||
```
|
||||
|
||||
### 交互式 API 文档
|
||||
### 交互式 API 文档 { #interactive-api-docs }
|
||||
|
||||
跳转到 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
|
||||
|
||||
@@ -74,7 +74,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
|
||||

|
||||
|
||||
### 可选的 API 文档
|
||||
### 可选的 API 文档 { #alternative-api-docs }
|
||||
|
||||
前往 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
|
||||
|
||||
@@ -82,35 +82,35 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
|
||||

|
||||
|
||||
### OpenAPI
|
||||
### OpenAPI { #openapi }
|
||||
|
||||
**FastAPI** 使用定义 API 的 **OpenAPI** 标准将你的所有 API 转换成「模式」。
|
||||
|
||||
#### 「模式」
|
||||
#### 「模式」 { #schema }
|
||||
|
||||
「模式」是对事物的一种定义或描述。它并非具体的实现代码,而只是抽象的描述。
|
||||
|
||||
#### API「模式」
|
||||
#### API「模式」 { #api-schema }
|
||||
|
||||
在这种场景下,<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> 是一种规定如何定义 API 模式的规范。
|
||||
|
||||
「模式」的定义包括你的 API 路径,以及它们可能使用的参数等等。
|
||||
|
||||
#### 数据「模式」
|
||||
#### 数据「模式」 { #data-schema }
|
||||
|
||||
「模式」这个术语也可能指的是某些数据比如 JSON 的结构。
|
||||
|
||||
在这种情况下,它可以表示 JSON 的属性及其具有的数据类型,等等。
|
||||
|
||||
#### OpenAPI 和 JSON Schema
|
||||
#### OpenAPI 和 JSON Schema { #openapi-and-json-schema }
|
||||
|
||||
OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送和接收的数据的定义(或称为「模式」),这些定义通过 JSON 数据模式标准 **JSON Schema** 所生成。
|
||||
|
||||
#### 查看 `openapi.json`
|
||||
#### 查看 `openapi.json` { #check-the-openapi-json }
|
||||
|
||||
如果你对原始的 OpenAPI 模式长什么样子感到好奇,FastAPI 自动生成了包含所有 API 描述的 JSON(模式)。
|
||||
|
||||
你可以直接在:<a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a> 看到它。
|
||||
你可以直接在:<a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:800api.json</a> 看到它。
|
||||
|
||||
它将显示以如下内容开头的 JSON:
|
||||
|
||||
@@ -135,7 +135,7 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送
|
||||
...
|
||||
```
|
||||
|
||||
#### OpenAPI 的用途
|
||||
#### OpenAPI 的用途 { #what-is-openapi-for }
|
||||
|
||||
驱动 FastAPI 内置的 2 个交互式文档系统的正是 OpenAPI 模式。
|
||||
|
||||
@@ -143,11 +143,47 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送
|
||||
|
||||
你还可以使用它自动生成与你的 API 进行通信的客户端代码。例如 web 前端,移动端或物联网嵌入程序。
|
||||
|
||||
## 分步概括
|
||||
### 部署你的应用(可选) { #deploy-your-app-optional }
|
||||
|
||||
### 步骤 1:导入 `FastAPI`
|
||||
你可以选择将 FastAPI 应用部署到 <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>,如果还没有,先去加入候补名单。🚀
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
|
||||
如果你已经拥有 **FastAPI Cloud** 账户(我们从候补名单邀请了你 😉),你可以用一条命令部署应用。
|
||||
|
||||
部署前,先确保已登录:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi login
|
||||
|
||||
You are logged in to FastAPI Cloud 🚀
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
然后部署你的应用:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi deploy
|
||||
|
||||
Deploying to FastAPI Cloud...
|
||||
|
||||
✅ Deployment successful!
|
||||
|
||||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
就这些!现在你可以通过该 URL 访问你的应用了。✨
|
||||
|
||||
## 分步概括 { #recap-step-by-step }
|
||||
|
||||
### 步骤 1:导入 `FastAPI` { #step-1-import-fastapi }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *}
|
||||
|
||||
`FastAPI` 是一个为你的 API 提供了所有功能的 Python 类。
|
||||
|
||||
@@ -159,17 +195,17 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送
|
||||
|
||||
///
|
||||
|
||||
### 步骤 2:创建一个 `FastAPI`「实例」
|
||||
### 步骤 2:创建一个 `FastAPI`「实例」 { #step-2-create-a-fastapi-instance }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *}
|
||||
|
||||
这里的变量 `app` 会是 `FastAPI` 类的一个「实例」。
|
||||
|
||||
这个实例将是创建你所有 API 的主要交互对象。
|
||||
|
||||
### 步骤 3:创建一个*路径操作*
|
||||
### 步骤 3:创建一个*路径操作* { #step-3-create-a-path-operation }
|
||||
|
||||
#### 路径
|
||||
#### 路径 { #path }
|
||||
|
||||
这里的「路径」指的是 URL 中从第一个 `/` 起的后半部分。
|
||||
|
||||
@@ -193,7 +229,7 @@ https://example.com/items/foo
|
||||
|
||||
开发 API 时,「路径」是用来分离「关注点」和「资源」的主要手段。
|
||||
|
||||
#### 操作
|
||||
#### 操作 { #operation }
|
||||
|
||||
这里的「操作」指的是一种 HTTP「方法」。
|
||||
|
||||
@@ -228,9 +264,9 @@ https://example.com/items/foo
|
||||
|
||||
我们也打算称呼它们为「操作」。
|
||||
|
||||
#### 定义一个*路径操作装饰器*
|
||||
#### 定义一个*路径操作装饰器* { #define-a-path-operation-decorator }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *}
|
||||
|
||||
`@app.get("/")` 告诉 **FastAPI** 在它下方的函数负责处理如下访问请求:
|
||||
|
||||
@@ -276,7 +312,7 @@ https://example.com/items/foo
|
||||
|
||||
///
|
||||
|
||||
### 步骤 4:定义**路径操作函数**
|
||||
### 步骤 4:定义**路径操作函数** { #step-4-define-the-path-operation-function }
|
||||
|
||||
这是我们的「**路径操作函数**」:
|
||||
|
||||
@@ -284,7 +320,7 @@ https://example.com/items/foo
|
||||
* **操作**:是 `get`。
|
||||
* **函数**:是位于「装饰器」下方的函数(位于 `@app.get("/")` 下方)。
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *}
|
||||
|
||||
这是一个 Python 函数。
|
||||
|
||||
@@ -296,17 +332,17 @@ https://example.com/items/foo
|
||||
|
||||
你也可以将其定义为常规函数而不使用 `async def`:
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
|
||||
{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *}
|
||||
|
||||
/// note
|
||||
|
||||
如果你不知道两者的区别,请查阅 [并发: *赶时间吗?*](../async.md#_1){.internal-link target=_blank}。
|
||||
如果你不知道两者的区别,请查阅 [并发: *赶时间吗?*](../async.md#in-a-hurry){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
### 步骤 5:返回内容
|
||||
### 步骤 5:返回内容 { #step-5-return-the-content }
|
||||
|
||||
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
|
||||
{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *}
|
||||
|
||||
你可以返回一个 `dict`、`list`,像 `str`、`int` 一样的单个值,等等。
|
||||
|
||||
@@ -314,10 +350,31 @@ https://example.com/items/foo
|
||||
|
||||
还有许多其他将会自动转换为 JSON 的对象和模型(包括 ORM 对象等)。尝试下使用你最喜欢的一种,它很有可能已经被支持。
|
||||
|
||||
## 总结
|
||||
### 步骤 6:部署 { #step-6-deploy-it }
|
||||
|
||||
用一条命令将你的应用部署到 **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>**:`fastapi deploy`。🎉
|
||||
|
||||
#### 关于 FastAPI Cloud { #about-fastapi-cloud }
|
||||
|
||||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** 由 **FastAPI** 的作者和团队打造。
|
||||
|
||||
它以最小的投入简化了 **构建**、**部署** 和 **访问** API 的流程。
|
||||
|
||||
它把使用 FastAPI 构建应用的相同**开发者体验**带到了将应用**部署**到云端的过程。🎉
|
||||
|
||||
FastAPI Cloud 是 *FastAPI 及其朋友们* 开源项目的主要赞助和资金提供方。✨
|
||||
|
||||
#### 部署到其他云服务商 { #deploy-to-other-cloud-providers }
|
||||
|
||||
FastAPI 是开源并基于标准的。你可以将 FastAPI 应用部署到你选择的任何云服务商。
|
||||
|
||||
按照你的云服务商的指南部署 FastAPI 应用即可。🤓
|
||||
|
||||
## 总结 { #recap }
|
||||
|
||||
* 导入 `FastAPI`。
|
||||
* 创建一个 `app` 实例。
|
||||
* 编写一个**路径操作装饰器**,如 `@app.get("/")`。
|
||||
* 定义一个**路径操作函数**,如 `def root(): ...`。
|
||||
* 使用命令 `fastapi dev` 运行开发服务器。
|
||||
* 可选:使用 `fastapi deploy` 部署你的应用。
|
||||
|
||||
@@ -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,14 +1,14 @@
|
||||
# Header 参数
|
||||
# Header 参数 { #header-parameters }
|
||||
|
||||
定义 `Header` 参数的方式与定义 `Query`、`Path`、`Cookie` 参数相同。
|
||||
|
||||
## 导入 `Header`
|
||||
## 导入 `Header` { #import-header }
|
||||
|
||||
首先,导入 `Header`:
|
||||
|
||||
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
|
||||
|
||||
## 声明 `Header` 参数
|
||||
## 声明 `Header` 参数 { #declare-header-parameters }
|
||||
|
||||
然后,使用和 `Path`、`Query`、`Cookie` 一样的结构定义 header 参数。
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
|
||||
///
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
必须使用 `Header` 声明 header 参数,否则该参数会被解释为查询参数。
|
||||
|
||||
///
|
||||
|
||||
## 自动转换
|
||||
## 自动转换 { #automatic-conversion }
|
||||
|
||||
`Header` 比 `Path`、`Query` 和 `Cookie` 提供了更多功能。
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
///
|
||||
|
||||
## 重复的请求头
|
||||
## 重复的请求头 { #duplicate-headers }
|
||||
|
||||
有时,可能需要接收重复的请求头。即同一个请求头有多个值。
|
||||
|
||||
@@ -84,7 +84,7 @@ X-Token: bar
|
||||
}
|
||||
```
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
使用 `Header` 声明请求头的方式与 `Query`、`Path` 、`Cookie` 相同。
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 教程 - 用户指南
|
||||
# 教程 - 用户指南 { #tutorial-user-guide }
|
||||
|
||||
本教程将一步步向您展示如何使用 **FastAPI** 的绝大部分特性。
|
||||
|
||||
各个章节的内容循序渐进,但是又围绕着单独的主题,所以您可以直接跳转到某个章节以解决您的特定需求。
|
||||
各个章节的内容循序渐进,但是又围绕着单独的主题,所以您可以直接跳转到某个章节以解决您的特定 API 需求。
|
||||
|
||||
本教程同样可以作为将来的参考手册,所以您可以随时回到本教程并查阅您需要的内容。
|
||||
|
||||
## 运行代码
|
||||
## 运行代码 { #run-the-code }
|
||||
|
||||
所有代码片段都可以复制后直接使用(它们实际上是经过测试的 Python 文件)。
|
||||
|
||||
@@ -58,7 +58,7 @@ $ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid
|
||||
|
||||
---
|
||||
|
||||
## 安装 FastAPI
|
||||
## 安装 FastAPI { #install-fastapi }
|
||||
|
||||
第一个步骤是安装 FastAPI.
|
||||
|
||||
@@ -74,15 +74,17 @@ $ pip install "fastapi[standard]"
|
||||
|
||||
</div>
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
当您使用 `pip install "fastapi[standard]"` 进行安装时,它会附带一些默认的可选标准依赖项。
|
||||
当您使用 `pip install "fastapi[standard]"` 安装时,它会附带一些默认的可选标准依赖项,其中包括 `fastapi-cloud-cli`,它可以让您部署到 <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>。
|
||||
|
||||
如果您不想安装这些可选依赖,可以选择安装 `pip install fastapi`。
|
||||
|
||||
如果您想安装标准依赖但不包含 `fastapi-cloud-cli`,可以使用 `pip install "fastapi[standard-no-fastapi-cloud-cli]"` 安装。
|
||||
|
||||
///
|
||||
|
||||
## 进阶用户指南
|
||||
## 进阶用户指南 { #advanced-user-guide }
|
||||
|
||||
在本**教程-用户指南**之后,您可以阅读**进阶用户指南**。
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
# 元数据和文档 URL
|
||||
# 元数据和文档 URL { #metadata-and-docs-urls }
|
||||
|
||||
你可以在 FastAPI 应用程序中自定义多个元数据配置。
|
||||
|
||||
## API 元数据
|
||||
## API 元数据 { #metadata-for-api }
|
||||
|
||||
你可以在设置 OpenAPI 规范和自动 API 文档 UI 中使用的以下字段:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------------|------|-------------|
|
||||
| `title` | `str` | API 的标题。 |
|
||||
| `summary` | `str` | API 的简短摘要。 <small>自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。.</small> |
|
||||
| `description` | `str` | API 的简短描述。可以使用Markdown。 |
|
||||
| `version` | `string` | API 的版本。这是您自己的应用程序的版本,而不是 OpenAPI 的版本。例如 `2.5.0` 。 |
|
||||
| `summary` | `str` | API 的简短摘要。 <small>自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。</small> |
|
||||
| `description` | `str` | API 的简短描述。可以使用 Markdown。 |
|
||||
| `version` | `string` | API 的版本。这是您自己的应用程序的版本,而不是 OpenAPI 的版本。例如 `2.5.0`。 |
|
||||
| `terms_of_service` | `str` | API 服务条款的 URL。如果提供,则必须是 URL。 |
|
||||
| `contact` | `dict` | 公开的 API 的联系信息。它可以包含多个字段。<details><summary><code>contact</code> 字段</summary><table><thead><tr><th>参数</th><th>Type</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>联系人/组织的识别名称。</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>指向联系信息的 URL。必须采用 URL 格式。</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>联系人/组织的电子邮件地址。必须采用电子邮件地址的格式。</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | 公开的 API 的许可证信息。它可以包含多个字段。<details><summary><code>license_info</code> 字段</summary><table><thead><tr><th>参数</th><th>类型</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>必须的</strong> (如果设置了<code>license_info</code>). 用于 API 的许可证名称。</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>一个API的<a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a>许可证表达。 The <code>identifier</code> field is mutually exclusive of the <code>url</code> field. <small>自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>用于 API 的许可证的 URL。必须采用 URL 格式。</td></tr></tbody></table></details> |
|
||||
| `contact` | `dict` | 公开的 API 的联系信息。它可以包含多个字段。<details><summary><code>contact</code> 字段</summary><table><thead><tr><th>参数</th><th>类型</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>联系人/组织的识别名称。</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>指向联系信息的 URL。必须采用 URL 格式。</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>联系人/组织的电子邮件地址。必须采用电子邮件地址的格式。</td></tr></tbody></table></details> |
|
||||
| `license_info` | `dict` | 公开的 API 的许可证信息。它可以包含多个字段。<details><summary><code>license_info</code> 字段</summary><table><thead><tr><th>参数</th><th>类型</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>必须</strong>(如果设置了 <code>license_info</code>)。用于 API 的许可证名称。</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>API 的 <a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a> 许可证表达式。字段 <code>identifier</code> 与字段 <code>url</code> 互斥。<small>自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>用于 API 的许可证的 URL。必须采用 URL 格式。</td></tr></tbody></table></details> |
|
||||
|
||||
你可以按如下方式设置它们:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001.py hl[4:6] *}
|
||||
{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
您可以在 `description` 字段中编写 Markdown,它将在输出中呈现。
|
||||
你可以在 `description` 字段中编写 Markdown,它会在输出中渲染。
|
||||
|
||||
///
|
||||
|
||||
@@ -30,15 +30,35 @@
|
||||
|
||||
<img src="/img/tutorial/metadata/image01.png">
|
||||
|
||||
## 标签元数据
|
||||
## 许可证标识符 { #license-identifier }
|
||||
|
||||
### 创建标签元数据
|
||||
自 OpenAPI 3.1.0 和 FastAPI 0.99.0 起,你还可以在 `license_info` 中使用 `identifier` 而不是 `url`。
|
||||
|
||||
例如:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *}
|
||||
|
||||
## 标签元数据 { #metadata-for-tags }
|
||||
|
||||
你也可以通过参数 `openapi_tags` 为用于分组路径操作的不同标签添加额外的元数据。
|
||||
|
||||
它接收一个列表,列表中每个标签对应一个字典。
|
||||
|
||||
每个字典可以包含:
|
||||
|
||||
- `name`(必填):一个 `str`,与在你的*路径操作*和 `APIRouter` 的 `tags` 参数中使用的标签名相同。
|
||||
- `description`:一个 `str`,该标签的简短描述。可以使用 Markdown,并会显示在文档 UI 中。
|
||||
- `externalDocs`:一个 `dict`,描述外部文档,包含:
|
||||
- `description`:一个 `str`,该外部文档的简短描述。
|
||||
- `url`(必填):一个 `str`,该外部文档的 URL。
|
||||
|
||||
### 创建标签元数据 { #create-metadata-for-tags }
|
||||
|
||||
让我们在带有标签的示例中为 `users` 和 `items` 试一下。
|
||||
|
||||
创建标签元数据并把它传递给 `openapi_tags` 参数:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
|
||||
{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *}
|
||||
|
||||
注意你可以在描述内使用 Markdown,例如「login」会显示为粗体(**login**)以及「fancy」会显示为斜体(_fancy_)。
|
||||
|
||||
@@ -48,11 +68,11 @@
|
||||
|
||||
///
|
||||
|
||||
### 使用你的标签
|
||||
### 使用你的标签 { #use-your-tags }
|
||||
|
||||
将 `tags` 参数和*路径操作*(以及 `APIRouter`)一起使用,将其分配给不同的标签:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
|
||||
{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *}
|
||||
|
||||
/// info | 信息
|
||||
|
||||
@@ -60,19 +80,19 @@
|
||||
|
||||
///
|
||||
|
||||
### 查看文档
|
||||
### 查看文档 { #check-the-docs }
|
||||
|
||||
如果你现在查看文档,它们会显示所有附加的元数据:
|
||||
|
||||
<img src="/img/tutorial/metadata/image02.png">
|
||||
|
||||
### 标签顺序
|
||||
### 标签顺序 { #order-of-tags }
|
||||
|
||||
每个标签元数据字典的顺序也定义了在文档用户界面显示的顺序。
|
||||
|
||||
例如按照字母顺序,即使 `users` 排在 `items` 之后,它也会显示在前面,因为我们将它的元数据添加为列表内的第一个字典。
|
||||
|
||||
## OpenAPI URL
|
||||
## OpenAPI URL { #openapi-url }
|
||||
|
||||
默认情况下,OpenAPI 模式服务于 `/openapi.json`。
|
||||
|
||||
@@ -80,21 +100,21 @@
|
||||
|
||||
例如,将其设置为服务于 `/api/v1/openapi.json`:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
|
||||
{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *}
|
||||
|
||||
如果你想完全禁用 OpenAPI 模式,可以将其设置为 `openapi_url=None`,这样也会禁用使用它的文档用户界面。
|
||||
|
||||
## 文档 URLs
|
||||
## 文档 URLs { #docs-urls }
|
||||
|
||||
你可以配置两个文档用户界面,包括:
|
||||
|
||||
* **Swagger UI**:服务于 `/docs`。
|
||||
* 可以使用参数 `docs_url` 设置它的 URL。
|
||||
* 可以通过设置 `docs_url=None` 禁用它。
|
||||
* ReDoc:服务于 `/redoc`。
|
||||
* 可以使用参数 `redoc_url` 设置它的 URL。
|
||||
* 可以通过设置 `redoc_url=None` 禁用它。
|
||||
- **Swagger UI**:服务于 `/docs`。
|
||||
- 可以使用参数 `docs_url` 设置它的 URL。
|
||||
- 可以通过设置 `docs_url=None` 禁用它。
|
||||
- **ReDoc**:服务于 `/redoc`。
|
||||
- 可以使用参数 `redoc_url` 设置它的 URL。
|
||||
- 可以通过设置 `redoc_url=None` 禁用它。
|
||||
|
||||
例如,设置 Swagger UI 服务于 `/documentation` 并禁用 ReDoc:
|
||||
|
||||
{* ../../docs_src/metadata/tutorial003.py hl[3] *}
|
||||
{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *}
|
||||
|
||||
@@ -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,16 +1,16 @@
|
||||
# 查询参数模型
|
||||
# 查询参数模型 { #query-parameter-models }
|
||||
|
||||
如果你有一组具有相关性的**查询参数**,你可以创建一个 **Pydantic 模型**来声明它们。
|
||||
|
||||
这将允许你在**多个地方**去**复用模型**,并且一次性为所有参数声明验证和元数据。😎
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓
|
||||
|
||||
///
|
||||
|
||||
## 使用 Pydantic 模型的查询参数
|
||||
## 使用 Pydantic 模型的查询参数 { #query-parameters-with-a-pydantic-model }
|
||||
|
||||
在一个 **Pydantic 模型**中声明你需要的**查询参数**,然后将参数声明为 `Query`:
|
||||
|
||||
@@ -18,7 +18,7 @@ FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓
|
||||
|
||||
**FastAPI** 将会从请求的**查询参数**中**提取**出**每个字段**的数据,并将其提供给你定义的 Pydantic 模型。
|
||||
|
||||
## 查看文档
|
||||
## 查看文档 { #check-the-docs }
|
||||
|
||||
你可以在 `/docs` 页面的 UI 中查看查询参数:
|
||||
|
||||
@@ -26,11 +26,11 @@ FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓
|
||||
<img src="/img/tutorial/query-param-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 禁止额外的查询参数
|
||||
## 禁止额外的查询参数 { #forbid-extra-query-parameters }
|
||||
|
||||
在一些特殊的使用场景中(可能不是很常见),你可能希望**限制**你要接收的查询参数。
|
||||
|
||||
你可以使用 Pydantic 的模型配置来 `forbid`(意为禁止 —— 译者注)任何 `extra`(意为额外的 —— 译者注)字段:
|
||||
你可以使用 Pydantic 的模型配置来 `forbid` 任何 `extra` 字段:
|
||||
|
||||
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
|
||||
|
||||
@@ -57,11 +57,11 @@ https://example.com/items/?limit=10&tool=plumbus
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
## 总结 { #summary }
|
||||
|
||||
你可以使用 **Pydantic 模型**在 **FastAPI** 中声明**查询参数**。😎
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
剧透警告:你也可以使用 Pydantic 模型来声明 cookie 和 headers,但你将在本教程的后面部分阅读到这部分内容。🤫
|
||||
|
||||
|
||||
@@ -1,142 +1,271 @@
|
||||
# 查询参数和字符串校验
|
||||
# 查询参数和字符串校验 { #query-parameters-and-string-validations }
|
||||
|
||||
**FastAPI** 允许你为参数声明额外的信息和校验。
|
||||
|
||||
让我们以下面的应用程序为例:
|
||||
让我们以下面的应用为例:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
|
||||
|
||||
查询参数 `q` 的类型为 `str`,默认值为 `None`,因此它是可选的。
|
||||
查询参数 `q` 的类型为 `str | None`,这意味着它是 `str` 类型,但也可以是 `None`。其默认值确实为 `None`,所以 FastAPI 会知道它不是必填的。
|
||||
|
||||
## 额外的校验
|
||||
/// note | 注意
|
||||
|
||||
我们打算添加约束条件:即使 `q` 是可选的,但只要提供了该参数,则该参数值**不能超过50个字符的长度**。
|
||||
FastAPI 会因为默认值 `= None` 而知道 `q` 的值不是必填的。
|
||||
|
||||
### 导入 `Query`
|
||||
|
||||
为此,首先从 `fastapi` 导入 `Query`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[1] *}
|
||||
|
||||
## 使用 `Query` 作为默认值
|
||||
|
||||
现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *}
|
||||
|
||||
由于我们必须用 `Query(default=None)` 替换默认值 `None`,`Query` 的第一个参数同样也是用于定义默认值。
|
||||
|
||||
所以:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = Query(default=None)
|
||||
```
|
||||
|
||||
...使得参数可选,等同于:
|
||||
|
||||
```Python
|
||||
q: str = None
|
||||
```
|
||||
|
||||
但是 `Query` 显式地将其声明为查询参数。
|
||||
|
||||
然后,我们可以将更多的参数传递给 `Query`。在本例中,适用于字符串的 `max_length` 参数:
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = Query(default=None, max_length=50)
|
||||
```
|
||||
|
||||
将会校验数据,在数据无效时展示清晰的错误信息,并在 OpenAPI 模式的*路径操作*中记录该参数。
|
||||
|
||||
## 添加更多校验
|
||||
|
||||
你还可以添加 `min_length` 参数:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *}
|
||||
|
||||
## 添加正则表达式
|
||||
|
||||
你可以定义一个参数值必须匹配的<abbr title="正则表达式或正则是定义字符串搜索模式的字符序列。">正则表达式</abbr>:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *}
|
||||
|
||||
这个指定的正则表达式通过以下规则检查接收到的参数值:
|
||||
|
||||
* `^`:以该符号之后的字符开头,符号之前没有字符。
|
||||
* `fixedquery`: 值精确地等于 `fixedquery`。
|
||||
* `$`: 到此结束,在 `fixedquery` 之后没有更多字符。
|
||||
|
||||
如果你对所有的这些**「正则表达式」**概念感到迷茫,请不要担心。对于许多人来说这都是一个困难的主题。你仍然可以在无需正则表达式的情况下做很多事情。
|
||||
|
||||
但是,一旦你需要用到并去学习它们时,请了解你已经可以在 **FastAPI** 中直接使用它们。
|
||||
|
||||
## 默认值
|
||||
|
||||
你可以向 `Query` 的第一个参数传入 `None` 用作查询参数的默认值,以同样的方式你也可以传递其他默认值。
|
||||
|
||||
假设你想要声明查询参数 `q`,使其 `min_length` 为 `3`,并且默认值为 `fixedquery`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *}
|
||||
|
||||
/// note
|
||||
|
||||
具有默认值还会使该参数成为可选参数。
|
||||
将类型标注为 `str | None` 能让你的编辑器提供更好的辅助和错误检测。
|
||||
|
||||
///
|
||||
|
||||
## 声明为必需参数
|
||||
## 额外校验 { #additional-validation }
|
||||
|
||||
当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 `q` 参数成为必需参数,例如:
|
||||
我们打算添加约束:即使 `q` 是可选的,但只要提供了该参数,**其长度不能超过 50 个字符**。
|
||||
|
||||
### 导入 `Query` 和 `Annotated` { #import-query-and-annotated }
|
||||
|
||||
为此,先导入:
|
||||
|
||||
- 从 `fastapi` 导入 `Query`
|
||||
- 从 `typing` 导入 `Annotated`
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *}
|
||||
|
||||
/// info | 信息
|
||||
|
||||
FastAPI 在 0.95.0 版本中添加了对 `Annotated` 的支持(并开始推荐使用)。
|
||||
|
||||
如果你的版本更旧,使用 `Annotated` 会报错。
|
||||
|
||||
在使用 `Annotated` 之前,请确保先[升级 FastAPI 版本](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。
|
||||
|
||||
///
|
||||
|
||||
## 在 `q` 参数的类型中使用 `Annotated` { #use-annotated-in-the-type-for-the-q-parameter }
|
||||
|
||||
还记得我之前在[Python 类型简介](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}中说过可以用 `Annotated` 给参数添加元数据吗?
|
||||
|
||||
现在正是与 FastAPI 搭配使用它的时候。🚀
|
||||
|
||||
我们之前的类型标注是:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
q: str
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
代替:
|
||||
////
|
||||
|
||||
//// tab | Python 3.9+
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = None
|
||||
```
|
||||
|
||||
但是现在我们正在用 `Query` 声明它,例如:
|
||||
////
|
||||
|
||||
我们要做的是用 `Annotated` 把它包起来,变成:
|
||||
|
||||
//// tab | Python 3.10+
|
||||
|
||||
```Python
|
||||
q: Union[str, None] = Query(default=None, min_length=3)
|
||||
q: Annotated[str | None] = None
|
||||
```
|
||||
|
||||
因此,当你在使用 `Query` 且需要声明一个值是必需的时,只需不声明默认参数:
|
||||
////
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *}
|
||||
//// tab | Python 3.9+
|
||||
|
||||
### 使用`None`声明必需参数
|
||||
```Python
|
||||
q: Annotated[Union[str, None]] = None
|
||||
```
|
||||
|
||||
你可以声明一个参数可以接收`None`值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是`None`。
|
||||
////
|
||||
|
||||
为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...`:
|
||||
这两种写法含义相同,`q` 是一个可以是 `str` 或 `None` 的参数,默认是 `None`。
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *}
|
||||
现在进入更有趣的部分。🎉
|
||||
|
||||
/// tip
|
||||
## 在 `q` 的 `Annotated` 中添加 `Query` { #add-query-to-annotated-in-the-q-parameter }
|
||||
|
||||
Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没有设默认值的情况下使用 `Optional` 或 `Union[Something, None]` 时,它具有特殊行为,你可以在 Pydantic 文档中阅读有关<a href="https://docs.pydantic.dev/latest/concepts/models/#required-optional-fields" class="external-link" target="_blank">必需可选字段</a>的更多信息。
|
||||
有了 `Annotated` 之后,我们就可以放入更多信息(本例中是额外的校验)。在 `Annotated` 中添加 `Query`,并把参数 `max_length` 设为 `50`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *}
|
||||
|
||||
注意默认值依然是 `None`,所以该参数仍是可选的。
|
||||
|
||||
但现在把 `Query(max_length=50)` 放到 `Annotated` 里,我们就在告诉 FastAPI,这个值需要**额外校验**,最大长度为 50 个字符。😎
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
这里用的是 `Query()`,因为这是一个**查询参数**。稍后我们还会看到 `Path()`、`Body()`、`Header()` 和 `Cookie()`,它们也接受与 `Query()` 相同的参数。
|
||||
|
||||
///
|
||||
|
||||
## 查询参数列表 / 多个值
|
||||
FastAPI 现在会:
|
||||
|
||||
当你使用 `Query` 显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。
|
||||
- 对数据进行**校验**,确保最大长度为 50 个字符
|
||||
- 当数据无效时向客户端展示**清晰的错误**
|
||||
- 在 OpenAPI 模式的*路径操作*中**记录**该参数(因此会出现在**自动文档 UI** 中)
|
||||
|
||||
## 另一种(旧的)方式:把 `Query` 作为默认值 { #alternative-old-query-as-the-default-value }
|
||||
|
||||
早期版本的 FastAPI(<abbr title="早于 2023-03">0.95.0</abbr> 之前)要求你把 `Query` 作为参数的默认值,而不是放在 `Annotated` 里。你很可能会在别处看到这种写法,所以我也给你解释一下。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
对于新代码以及在可能的情况下,请按上文所述使用 `Annotated`。它有多项优势(如下所述),没有劣势。🍰
|
||||
|
||||
///
|
||||
|
||||
像这样把 `Query()` 作为函数参数的默认值,并把参数 `max_length` 设为 50:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *}
|
||||
|
||||
由于这种情况下(不使用 `Annotated`)我们必须把函数中的默认值 `None` 替换为 `Query()`,因此需要通过参数 `Query(default=None)` 来设置默认值,它起到同样的作用(至少对 FastAPI 来说)。
|
||||
|
||||
所以:
|
||||
|
||||
```Python
|
||||
q: str | None = Query(default=None)
|
||||
```
|
||||
|
||||
……会让参数变成可选,默认值为 `None`,等同于:
|
||||
|
||||
```Python
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
但使用 `Query` 的版本会显式把它声明为一个查询参数。
|
||||
|
||||
然后,我们可以向 `Query` 传入更多参数。本例中是适用于字符串的 `max_length` 参数:
|
||||
|
||||
```Python
|
||||
q: str | None = Query(default=None, max_length=50)
|
||||
```
|
||||
|
||||
这会校验数据、在数据无效时展示清晰的错误,并在 OpenAPI 模式的*路径操作*中记录该参数。
|
||||
|
||||
### 在默认值中使用 `Query` 或在 `Annotated` 中使用 `Query` { #query-as-the-default-value-or-in-annotated }
|
||||
|
||||
注意,当你在 `Annotated` 中使用 `Query` 时,不能再给 `Query` 传 `default` 参数。
|
||||
|
||||
相反,应使用函数参数本身的实际默认值。否则会不一致。
|
||||
|
||||
例如,下面这样是不允许的:
|
||||
|
||||
```Python
|
||||
q: Annotated[str, Query(default="rick")] = "morty"
|
||||
```
|
||||
|
||||
……因为不清楚默认值应该是 `"rick"` 还是 `"morty"`。
|
||||
|
||||
因此,你应该这样用(推荐):
|
||||
|
||||
```Python
|
||||
q: Annotated[str, Query()] = "rick"
|
||||
```
|
||||
|
||||
……或者在旧代码库中你会见到:
|
||||
|
||||
```Python
|
||||
q: str = Query(default="rick")
|
||||
```
|
||||
|
||||
### `Annotated` 的优势 { #advantages-of-annotated }
|
||||
|
||||
**推荐使用 `Annotated`**,而不是把 `Query` 放在函数参数的默认值里,这样做在多方面都**更好**。🤓
|
||||
|
||||
函数参数的**默认值**就是**真正的默认值**,这与 Python 的直觉更一致。😌
|
||||
|
||||
你可以在**其他地方**不通过 FastAPI **直接调用**这个函数,而且它会**按预期工作**。如果有**必填**参数(没有默认值),你的**编辑器**会报错提示;如果在运行时没有传入必填参数,**Python** 也会报错。
|
||||
|
||||
当你不使用 `Annotated` 而是使用**(旧的)默认值风格**时,如果你在**其他地方**不通过 FastAPI 调用该函数,你必须**记得**给函数传参,否则得到的值会和预期不同(例如得到 `QueryInfo` 之类的对象而不是 `str`)。而你的编辑器不会报错,Python 也不会在调用时报错,只有在函数内部的操作出错时才会暴露问题。
|
||||
|
||||
由于 `Annotated` 可以包含多个元数据标注,你甚至可以用同一个函数与其他工具配合,例如 <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">Typer</a>。🚀
|
||||
|
||||
## 添加更多校验 { #add-more-validations }
|
||||
|
||||
你还可以添加 `min_length` 参数:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *}
|
||||
|
||||
## 添加正则表达式 { #add-regular-expressions }
|
||||
|
||||
你可以定义一个参数必须匹配的 <abbr title="正则表达式(regex 或 regexp)是用于定义字符串搜索模式的字符序列。">正则表达式</abbr> `pattern`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
|
||||
|
||||
这个特定的正则表达式通过以下规则检查接收到的参数值:
|
||||
|
||||
- `^`:必须以接下来的字符开头,前面没有其他字符。
|
||||
- `fixedquery`:值必须精确等于 `fixedquery`。
|
||||
- `$`:到此结束,在 `fixedquery` 之后没有更多字符。
|
||||
|
||||
如果你对这些**「正则表达式」**概念感到迷茫,不必担心。对很多人来说这都是个难点。你仍然可以在不使用正则表达式的情况下做很多事情。
|
||||
|
||||
现在你知道了,一旦需要时,你可以在 **FastAPI** 中直接使用它们。
|
||||
|
||||
## 默认值 { #default-values }
|
||||
|
||||
当然,你也可以使用 `None` 以外的默认值。
|
||||
|
||||
假设你想要声明查询参数 `q` 的 `min_length` 为 `3`,并且默认值为 `"fixedquery"`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *}
|
||||
|
||||
/// note | 注意
|
||||
|
||||
任何类型的默认值(包括 `None`)都会让该参数变为可选(非必填)。
|
||||
|
||||
///
|
||||
|
||||
## 必填参数 { #required-parameters }
|
||||
|
||||
当我们不需要声明更多校验或元数据时,只需不声明默认值就可以让查询参数 `q` 成为必填参数,例如:
|
||||
|
||||
```Python
|
||||
q: str
|
||||
```
|
||||
|
||||
而不是:
|
||||
|
||||
```Python
|
||||
q: str | None = None
|
||||
```
|
||||
|
||||
但现在我们用 `Query` 来声明它,例如:
|
||||
|
||||
```Python
|
||||
q: Annotated[str | None, Query(min_length=3)] = None
|
||||
```
|
||||
|
||||
因此,在使用 `Query` 的同时需要把某个值声明为必填时,只需不声明默认值:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *}
|
||||
|
||||
### 必填,但可以为 `None` { #required-can-be-none }
|
||||
|
||||
你可以声明一个参数可以接收 `None`,但它仍然是必填的。这将强制客户端必须发送一个值,即使该值是 `None`。
|
||||
|
||||
为此,你可以声明 `None` 是有效类型,但不声明默认值:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *}
|
||||
|
||||
## 查询参数列表 / 多个值 { #query-parameter-list-multiple-values }
|
||||
|
||||
当你用 `Query` 显式地定义查询参数时,你还可以声明它接收一个值列表,换句话说,接收多个值。
|
||||
|
||||
例如,要声明一个可在 URL 中出现多次的查询参数 `q`,你可以这样写:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *}
|
||||
|
||||
然后,输入如下网址:
|
||||
然后,访问如下 URL:
|
||||
|
||||
```
|
||||
http://localhost:8000/items/?q=foo&q=bar
|
||||
```
|
||||
|
||||
你会在*路径操作函数*的*函数参数* `q` 中以一个 Python `list` 的形式接收到*查询参数* `q` 的多个值(`foo` 和 `bar`)。
|
||||
你会在*路径操作函数*的*函数参数* `q` 中以一个 Python `list` 的形式接收到多个 `q` *查询参数* 的值(`foo` 和 `bar`)。
|
||||
|
||||
因此,该 URL 的响应将会是:
|
||||
|
||||
@@ -149,21 +278,21 @@ http://localhost:8000/items/?q=foo&q=bar
|
||||
}
|
||||
```
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
要声明类型为 `list` 的查询参数,如上例所示,你需要显式地使用 `Query`,否则该参数将被解释为请求体。
|
||||
要声明类型为 `list` 的查询参数(如上例),你需要显式地使用 `Query`,否则它会被解释为请求体。
|
||||
|
||||
///
|
||||
|
||||
交互式 API 文档将会相应地进行更新,以允许使用多个值:
|
||||
交互式 API 文档会相应更新,以支持多个值:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/query-params-str-validations/image02.png">
|
||||
<img src="/img/tutorial/query-params-str-validations/image02.png">
|
||||
|
||||
### 具有默认值的查询参数列表 / 多个值
|
||||
### 具有默认值的查询参数列表 / 多个值 { #query-parameter-list-multiple-values-with-defaults }
|
||||
|
||||
你还可以定义在没有任何给定值时的默认 `list` 值:
|
||||
你还可以定义在没有给定值时的默认 `list`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *}
|
||||
|
||||
如果你访问:
|
||||
|
||||
@@ -182,93 +311,163 @@ http://localhost:8000/items/
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用 `list`
|
||||
#### 只使用 `list` { #using-just-list }
|
||||
|
||||
你也可以直接使用 `list` 代替 `List [str]`:
|
||||
你也可以直接使用 `list`,而不是 `list[str]`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *}
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
请记住,在这种情况下 FastAPI 将不会检查列表的内容。
|
||||
请记住,在这种情况下 FastAPI 不会检查列表的内容。
|
||||
|
||||
例如,`List[int]` 将检查(并记录到文档)列表的内容必须是整数。但是单独的 `list` 不会。
|
||||
例如,`list[int]` 会检查(并记录到文档)列表的内容必须是整数。但仅用 `list` 不会。
|
||||
|
||||
///
|
||||
|
||||
## 声明更多元数据
|
||||
## 声明更多元数据 { #declare-more-metadata }
|
||||
|
||||
你可以添加更多有关该参数的信息。
|
||||
|
||||
这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。
|
||||
这些信息会包含在生成的 OpenAPI 中,并被文档用户界面和外部工具使用。
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
请记住,不同的工具对 OpenAPI 的支持程度可能不同。
|
||||
|
||||
其中一些可能不会展示所有已声明的额外信息,尽管在大多数情况下,缺少的这部分功能已经计划进行开发。
|
||||
其中一些可能还不会展示所有已声明的额外信息,尽管在大多数情况下,缺失的功能已经在计划开发中。
|
||||
|
||||
///
|
||||
|
||||
你可以添加 `title`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *}
|
||||
|
||||
以及 `description`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *}
|
||||
|
||||
## 别名参数
|
||||
## 别名参数 { #alias-parameters }
|
||||
|
||||
假设你想要查询参数为 `item-query`。
|
||||
假设你想要参数名为 `item-query`。
|
||||
|
||||
像下面这样:
|
||||
像这样:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8000/items/?item-query=foobaritems
|
||||
```
|
||||
|
||||
但是 `item-query` 不是一个有效的 Python 变量名称。
|
||||
但 `item-query` 不是有效的 Python 变量名。
|
||||
|
||||
最接近的有效名称是 `item_query`。
|
||||
|
||||
但是你仍然要求它在 URL 中必须是 `item-query`...
|
||||
但你仍然需要它在 URL 中就是 `item-query`……
|
||||
|
||||
这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值:
|
||||
这时可以用 `alias` 参数声明一个别名,FastAPI 会用该别名在 URL 中查找参数值:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *}
|
||||
|
||||
## 弃用参数
|
||||
## 弃用参数 { #deprecating-parameters }
|
||||
|
||||
现在假设你不再喜欢此参数。
|
||||
现在假设你不再喜欢这个参数了。
|
||||
|
||||
你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为<abbr title ="已过时,建议不要使用它">已弃用</abbr>。
|
||||
由于还有客户端在使用它,你不得不保留一段时间,但你希望文档清楚地将其展示为<abbr title="已过时,不推荐使用">已弃用</abbr>。
|
||||
|
||||
那么将参数 `deprecated=True` 传入 `Query`:
|
||||
那么将参数 `deprecated=True` 传给 `Query`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *}
|
||||
{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *}
|
||||
|
||||
文档将会像下面这样展示它:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/query-params-str-validations/image01.png">
|
||||
<img src="/img/tutorial/query-params-str-validations/image01.png">
|
||||
|
||||
## 总结
|
||||
## 从 OpenAPI 中排除参数 { #exclude-parameters-from-openapi }
|
||||
|
||||
你可以为查询参数声明额外的校验和元数据。
|
||||
要把某个查询参数从生成的 OpenAPI 模式中排除(从而也不会出现在自动文档系统中),将 `Query` 的参数 `include_in_schema` 设为 `False`:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *}
|
||||
|
||||
## 自定义校验 { #custom-validation }
|
||||
|
||||
有些情况下你需要做一些无法通过上述参数完成的**自定义校验**。
|
||||
|
||||
在这些情况下,你可以使用**自定义校验函数**,该函数会在正常校验之后应用(例如,在先校验值是 `str` 之后)。
|
||||
|
||||
你可以在 `Annotated` 中使用 <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator" class="external-link" target="_blank">Pydantic 的 `AfterValidator`</a> 来实现。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
Pydantic 还有 <a href="https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator" class="external-link" target="_blank">`BeforeValidator`</a> 等。🤓
|
||||
|
||||
///
|
||||
|
||||
例如,这个自定义校验器会检查条目 ID 是否以 `isbn-`(用于 <abbr title="ISBN 的含义是 International Standard Book Number(国际标准书号)">ISBN</abbr> 书号)或 `imdb-`(用于 <abbr title="IMDB(Internet Movie Database)是一个包含电影信息的网站">IMDB</abbr> 电影 URL 的 ID)开头:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
|
||||
|
||||
/// info | 信息
|
||||
|
||||
这在 Pydantic 2 或更高版本中可用。😎
|
||||
|
||||
///
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
如果你需要进行任何需要与**外部组件**通信的校验,例如数据库或其他 API,你应该改用 **FastAPI 依赖项**,稍后你会学到它们。
|
||||
|
||||
这些自定义校验器用于只需检查请求中**同一份数据**即可完成的事情。
|
||||
|
||||
///
|
||||
|
||||
### 理解这段代码 { #understand-that-code }
|
||||
|
||||
关键点仅仅是:在 `Annotated` 中使用带函数的 **`AfterValidator`**。不感兴趣可以跳过这一节。🤸
|
||||
|
||||
---
|
||||
|
||||
但如果你对这个具体示例好奇,并且还愿意继续看,这里有一些额外细节。
|
||||
|
||||
#### 字符串与 `value.startswith()` { #string-with-value-startswith }
|
||||
|
||||
注意到了吗?字符串的 `value.startswith()` 可以接收一个元组,它会检查元组中的每个值:
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *}
|
||||
|
||||
#### 一个随机条目 { #a-random-item }
|
||||
|
||||
使用 `data.items()` 我们会得到一个包含每个字典项键和值的元组的 <abbr title="可以用 for 循环迭代的对象,例如 list、set 等">可迭代对象</abbr>。
|
||||
|
||||
我们用 `list(data.items())` 把这个可迭代对象转换成一个真正的 `list`。
|
||||
|
||||
然后用 `random.choice()` 可以从该列表中获取一个**随机值**,也就是一个 `(id, name)` 的元组。它可能像 `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")` 这样。
|
||||
|
||||
接着我们把这个元组的**两个值**分别赋给变量 `id` 和 `name`。
|
||||
|
||||
所以,即使用户没有提供条目 ID,他们仍然会收到一个随机推荐。
|
||||
|
||||
……而我们把这些都放在**一行简单的代码**里完成。🤯 你不爱 Python 吗?🐍
|
||||
|
||||
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *}
|
||||
|
||||
## 总结 { #recap }
|
||||
|
||||
你可以为参数声明额外的校验和元数据。
|
||||
|
||||
通用的校验和元数据:
|
||||
|
||||
* `alias`
|
||||
* `title`
|
||||
* `description`
|
||||
* `deprecated`
|
||||
- `alias`
|
||||
- `title`
|
||||
- `description`
|
||||
- `deprecated`
|
||||
|
||||
特定于字符串的校验:
|
||||
字符串特有的校验:
|
||||
|
||||
* `min_length`
|
||||
* `max_length`
|
||||
* `regex`
|
||||
- `min_length`
|
||||
- `max_length`
|
||||
- `pattern`
|
||||
|
||||
在这些示例中,你了解了如何声明对 `str` 值的校验。
|
||||
也可以使用 `AfterValidator` 进行自定义校验。
|
||||
|
||||
请参阅下一章节,以了解如何声明对其他类型例如数值的校验。
|
||||
在这些示例中,你看到了如何为 `str` 值声明校验。
|
||||
|
||||
参阅下一章节,了解如何为其他类型(例如数值)声明校验。
|
||||
|
||||
@@ -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,12 +1,12 @@
|
||||
# 表单模型
|
||||
# 表单模型 { #form-models }
|
||||
|
||||
您可以使用 **Pydantic 模型**在 FastAPI 中声明**表单字段**。
|
||||
你可以在 FastAPI 中使用 **Pydantic 模型**声明**表单字段**。
|
||||
|
||||
/// 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}后再安装。
|
||||
确保你创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},激活它,然后再安装,例如:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
@@ -14,51 +14,51 @@ $ pip install python-multipart
|
||||
|
||||
///
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
自 FastAPI 版本 `0.113.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
## 表单的 Pydantic 模型
|
||||
## 表单的 Pydantic 模型 { #pydantic-models-for-forms }
|
||||
|
||||
您只需声明一个 **Pydantic 模型**,其中包含您希望接收的**表单字段**,然后将参数声明为 `Form` :
|
||||
你只需声明一个 **Pydantic 模型**,其中包含你希望接收的**表单字段**,然后将参数声明为 `Form`:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
|
||||
|
||||
**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
|
||||
**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供你定义的 Pydantic 模型。
|
||||
|
||||
## 检查文档
|
||||
## 检查文档 { #check-the-docs }
|
||||
|
||||
您可以在文档 UI 中验证它,地址为 `/docs` :
|
||||
你可以在文档 UI 中验证它,地址为 `/docs`:
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/request-form-models/image01.png">
|
||||
</div>
|
||||
|
||||
## 禁止额外的表单字段
|
||||
## 禁止额外的表单字段 { #forbid-extra-form-fields }
|
||||
|
||||
在某些特殊使用情况下(可能并不常见),您可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。
|
||||
在某些特殊使用情况下(可能并不常见),你可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。
|
||||
|
||||
/// note
|
||||
/// note | 注意
|
||||
|
||||
自 FastAPI 版本 `0.114.0` 起支持此功能。🤓
|
||||
|
||||
///
|
||||
|
||||
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
|
||||
你可以使用 Pydantic 的模型配置来 `forbid` 任何 `extra` 字段:
|
||||
|
||||
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
|
||||
|
||||
如果客户端尝试发送一些额外的数据,他们将收到**错误**响应。
|
||||
|
||||
例如,如果客户端尝试发送这样的表单字段:
|
||||
例如,客户端尝试发送如下表单字段:
|
||||
|
||||
* `username`: `Rick`
|
||||
* `password`: `Portal Gun`
|
||||
* `extra`: `Mr. Poopybutthole`
|
||||
|
||||
他们将收到一条错误响应,表明字段 `extra` 是不被允许的:
|
||||
他们将收到一条错误响应,表明字段 `extra` 不被允许:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -73,6 +73,6 @@ $ pip install python-multipart
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
## 总结 { #summary }
|
||||
|
||||
您可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎
|
||||
你可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎
|
||||
|
||||
@@ -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,197 +1,203 @@
|
||||
# 安全 - 第一步
|
||||
# 安全 - 第一步 { #security-first-steps }
|
||||
|
||||
假设**后端** API 在某个域。
|
||||
假设你的**后端** API 位于某个域名下。
|
||||
|
||||
**前端**在另一个域,或(移动应用中)在同一个域的不同路径下。
|
||||
而**前端**在另一个域名,或同一域名的不同路径(或在移动应用中)。
|
||||
|
||||
并且,前端要使用后端的 **username** 与 **password** 验证用户身份。
|
||||
你希望前端能通过**username** 和 **password** 与后端进行身份验证。
|
||||
|
||||
固然,**FastAPI** 支持 **OAuth2** 身份验证。
|
||||
我们可以用 **OAuth2** 在 **FastAPI** 中实现它。
|
||||
|
||||
但为了节省开发者的时间,不要只为了查找很少的内容,不得不阅读冗长的规范文档。
|
||||
但为了节省你的时间,不必为获取少量信息而通读冗长的规范。
|
||||
|
||||
我们建议使用 **FastAPI** 的安全工具。
|
||||
我们直接使用 **FastAPI** 提供的安全工具。
|
||||
|
||||
## 概览
|
||||
## 效果预览 { #how-it-looks }
|
||||
|
||||
首先,看看下面的代码是怎么运行的,然后再回过头来了解其背后的原理。
|
||||
先直接运行代码看看效果,之后再回过头理解其背后的原理。
|
||||
|
||||
## 创建 `main.py`
|
||||
## 创建 `main.py` { #create-main-py }
|
||||
|
||||
把下面的示例代码复制到 `main.py`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py *}
|
||||
|
||||
## 运行
|
||||
## 运行 { #run-it }
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
|
||||
当你使用命令 `pip install "fastapi[standard]"` 安装 **FastAPI** 时,<a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> 包会自动安装。
|
||||
|
||||
安装命令: `pip install python-multipart`。
|
||||
但是,如果你使用 `pip install fastapi`,默认不会包含 `python-multipart` 包。
|
||||
|
||||
这是因为 **OAuth2** 使用**表单数据**发送 `username` 与 `password`。
|
||||
如需手动安装,请先创建并激活[虚拟环境](../../virtual-environments.md){.internal-link target=_blank},然后执行:
|
||||
|
||||
```console
|
||||
$ pip install python-multipart
|
||||
```
|
||||
|
||||
这是因为 **OAuth2** 使用“表单数据”来发送 `username` 和 `password`。
|
||||
|
||||
///
|
||||
|
||||
用下面的命令运行该示例:
|
||||
用下面的命令运行示例:
|
||||
|
||||
<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>
|
||||
|
||||
## 查看文档
|
||||
## 查看 { #check-it }
|
||||
|
||||
打开 API 文档: <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>。
|
||||
|
||||
界面如下图所示:
|
||||
你会看到类似这样的界面:
|
||||
|
||||
<img src="/img/tutorial/security/image01.png">
|
||||
|
||||
/// check | Authorize 按钮!
|
||||
|
||||
页面右上角出现了一个「**Authorize**」按钮。
|
||||
页面右上角已经有一个崭新的“Authorize”按钮。
|
||||
|
||||
*路径操作*的右上角也出现了一个可以点击的小锁图标。
|
||||
你的*路径操作*右上角还有一个可点击的小锁图标。
|
||||
|
||||
///
|
||||
|
||||
点击 **Authorize** 按钮,弹出授权表单,输入 `username` 与 `password` 及其它可选字段:
|
||||
点击它,会弹出一个授权表单,可输入 `username` 和 `password`(以及其它可选字段):
|
||||
|
||||
<img src="/img/tutorial/security/image02.png">
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
目前,在表单中输入内容不会有任何反应,后文会介绍相关内容。
|
||||
目前无论在表单中输入什么都不会生效,我们稍后就会实现它。
|
||||
|
||||
///
|
||||
|
||||
虽然此文档不是给前端最终用户使用的,但这个自动工具非常实用,可在文档中与所有 API 交互。
|
||||
这当然不是面向最终用户的前端,但它是一个很棒的自动化工具,可交互式地为整个 API 提供文档。
|
||||
|
||||
前端团队(可能就是开发者本人)可以使用本工具。
|
||||
前端团队(也可能就是你自己)可以使用它。
|
||||
|
||||
第三方应用与系统也可以调用本工具。
|
||||
第三方应用和系统也可以使用它。
|
||||
|
||||
开发者也可以用它来调试、检查、测试应用。
|
||||
你也可以用它来调试、检查和测试同一个应用。
|
||||
|
||||
## 密码流
|
||||
## `password` 流 { #the-password-flow }
|
||||
|
||||
现在,我们回过头来介绍这段代码的原理。
|
||||
现在回过头来理解这些内容。
|
||||
|
||||
`Password` **流**是 OAuth2 定义的,用于处理安全与身份验证的方式(**流**)。
|
||||
`password` “流”(flow)是 OAuth2 定义的处理安全与身份验证的一种方式。
|
||||
|
||||
OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户身份。
|
||||
OAuth2 的设计目标是让后端或 API 与负责用户认证的服务器解耦。
|
||||
|
||||
但在本例中,**FastAPI** 应用会处理 API 与身份验证。
|
||||
但在这个例子中,**FastAPI** 应用同时处理 API 和认证。
|
||||
|
||||
下面,我们来看一下简化的运行流程:
|
||||
从这个简化的角度来看看流程:
|
||||
|
||||
- 用户在前端输入 `username` 与`password`,并点击**回车**
|
||||
- (用户浏览器中运行的)前端把 `username` 与`password` 发送至 API 中指定的 URL(使用 `tokenUrl="token"` 声明)
|
||||
- API 检查 `username` 与`password`,并用令牌(`Token`) 响应(暂未实现此功能):
|
||||
- 令牌只是用于验证用户的字符串
|
||||
- 一般来说,令牌会在一段时间后过期
|
||||
- 过时后,用户要再次登录
|
||||
- 这样一来,就算令牌被人窃取,风险也较低。因为它与永久密钥不同,**在绝大多数情况下**不会长期有效
|
||||
- 前端临时将令牌存储在某个位置
|
||||
- 用户点击前端,前往前端应用的其它部件
|
||||
- 前端需要从 API 中提取更多数据:
|
||||
- 为指定的端点(Endpoint)进行身份验证
|
||||
- 因此,用 API 验证身份时,要发送值为 `Bearer` + 令牌的请求头 `Authorization`
|
||||
- 假如令牌为 `foobar`,`Authorization` 请求头就是: `Bearer foobar`
|
||||
* 用户在前端输入 `username` 和 `password`,然后按下 `Enter`。
|
||||
* 前端(运行在用户浏览器中)把 `username` 和 `password` 发送到我们 API 中的特定 URL(使用 `tokenUrl="token"` 声明)。
|
||||
* API 校验 `username` 和 `password`,并返回一个“令牌”(这些我们尚未实现)。
|
||||
* “令牌”只是一个字符串,包含一些内容,之后可用来验证该用户。
|
||||
* 通常,令牌会在一段时间后过期。
|
||||
* 因此,用户过一段时间需要重新登录。
|
||||
* 如果令牌被窃取,风险也更小。它不像一把永久有效的钥匙(在大多数情况下)。
|
||||
* 前端会把令牌临时存储在某处。
|
||||
* 用户在前端中点击跳转到前端应用的其他部分。
|
||||
* 前端需要从 API 获取更多数据。
|
||||
* 但该端点需要身份验证。
|
||||
* 因此,为了与我们的 API 进行身份验证,它会发送一个 `Authorization` 请求头,值为 `Bearer ` 加上令牌。
|
||||
* 如果令牌内容是 `foobar`,`Authorization` 请求头的内容就是:`Bearer foobar`。
|
||||
|
||||
## **FastAPI** 的 `OAuth2PasswordBearer`
|
||||
## **FastAPI** 的 `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer }
|
||||
|
||||
**FastAPI** 提供了不同抽象级别的安全工具。
|
||||
**FastAPI** 在不同抽象层级提供了多种安全工具。
|
||||
|
||||
本例使用 **OAuth2** 的 **Password** 流以及 **Bearer** 令牌(`Token`)。为此要使用 `OAuth2PasswordBearer` 类。
|
||||
本示例将使用 **OAuth2** 的 **Password** 流程并配合 **Bearer** 令牌,通过 `OAuth2PasswordBearer` 类来实现。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
`Bearer` 令牌不是唯一的选择。
|
||||
“Bearer” 令牌并非唯一选项。
|
||||
|
||||
但它是最适合这个用例的方案。
|
||||
但它非常适合我们的用例。
|
||||
|
||||
甚至可以说,它是适用于绝大多数用例的最佳方案,除非您是 OAuth2 的专家,知道为什么其它方案更合适。
|
||||
对于大多数用例,它也可能是最佳选择,除非你是 OAuth2 专家,并明确知道为何其他方案更适合你的需求。
|
||||
|
||||
本例中,**FastAPI** 还提供了构建工具。
|
||||
在那种情况下,**FastAPI** 同样提供了相应的构建工具。
|
||||
|
||||
///
|
||||
|
||||
创建 `OAuth2PasswordBearer` 的类实例时,要传递 `tokenUrl` 参数。该参数包含客户端(用户浏览器中运行的前端) 的 URL,用于发送 `username` 与 `password`,并获取令牌。
|
||||
创建 `OAuth2PasswordBearer` 类实例时,需要传入 `tokenUrl` 参数。该参数包含客户端(运行在用户浏览器中的前端)用来发送 `username` 和 `password` 以获取令牌的 URL。
|
||||
|
||||
{* ../../docs_src/security/tutorial001.py hl[6] *}
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
在此,`tokenUrl="token"` 指向的是暂未创建的相对 URL `token`。这个相对 URL 相当于 `./token`。
|
||||
这里的 `tokenUrl="token"` 指向的是尚未创建的相对 URL `token`,等价于 `./token`。
|
||||
|
||||
因为使用的是相对 URL,如果 API 位于 `https://example.com/`,则指向 `https://example.com/token`。但如果 API 位于 `https://example.com/api/v1/`,它指向的就是`https://example.com/api/v1/token`。
|
||||
因为使用的是相对 URL,若你的 API 位于 `https://example.com/`,它将指向 `https://example.com/token`;若你的 API 位于 `https://example.com/api/v1/`,它将指向 `https://example.com/api/v1/token`。
|
||||
|
||||
使用相对 URL 非常重要,可以确保应用在遇到[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}这样的高级用例时,也能正常运行。
|
||||
使用相对 URL 很重要,这能确保你的应用在诸如[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}等高级用例中依然正常工作。
|
||||
|
||||
///
|
||||
|
||||
该参数不会创建端点或*路径操作*,但会声明客户端用来获取令牌的 URL `/token` 。此信息用于 OpenAPI 及 API 文档。
|
||||
这个参数不会创建该端点/*路径操作*,而是声明客户端应使用 `/token` 这个 URL 来获取令牌。这些信息会用于 OpenAPI,进而用于交互式 API 文档系统。
|
||||
|
||||
接下来,学习如何创建实际的路径操作。
|
||||
我们很快也会创建对应的实际路径操作。
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
严苛的 **Pythonista** 可能不喜欢用 `tokenUrl` 这种命名风格代替 `token_url`。
|
||||
如果你是非常严格的 “Pythonista”,可能不喜欢使用参数名 `tokenUrl` 而不是 `token_url`。
|
||||
|
||||
这种命名方式是因为要使用与 OpenAPI 规范中相同的名字。以便在深入校验安全方案时,能通过复制粘贴查找更多相关信息。
|
||||
这是因为它使用了与 OpenAPI 规范中相同的名称。这样当你需要深入了解这些安全方案时,可以直接复制粘贴去查找更多信息。
|
||||
|
||||
///
|
||||
|
||||
`oauth2_scheme` 变量是 `OAuth2PasswordBearer` 的实例,也是**可调用项**。
|
||||
`oauth2_scheme` 变量是 `OAuth2PasswordBearer` 的一个实例,同时它也是“可调用”的。
|
||||
|
||||
以如下方式调用:
|
||||
可以像这样调用:
|
||||
|
||||
```Python
|
||||
oauth2_scheme(some, parameters)
|
||||
```
|
||||
|
||||
因此,`Depends` 可以调用 `oauth2_scheme` 变量。
|
||||
因此,它可以与 `Depends` 一起使用。
|
||||
|
||||
### 使用
|
||||
### 使用 { #use-it }
|
||||
|
||||
接下来,使用 `Depends` 把 `oauth2_scheme` 传入依赖项。
|
||||
现在你可以通过 `Depends` 将 `oauth2_scheme` 作为依赖传入。
|
||||
|
||||
{* ../../docs_src/security/tutorial001.py hl[10] *}
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
|
||||
该依赖项使用字符串(`str`)接收*路径操作函数*的参数 `token` 。
|
||||
该依赖会提供一个 `str`,赋值给*路径操作函数*的参数 `token`。
|
||||
|
||||
**FastAPI** 使用依赖项在 OpenAPI 概图(及 API 文档)中定义**安全方案**。
|
||||
**FastAPI** 会据此在 OpenAPI 架构(以及自动生成的 API 文档)中定义一个“安全方案”。
|
||||
|
||||
/// info | 技术细节
|
||||
|
||||
**FastAPI** 使用(在依赖项中声明的)类 `OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,这是因为它继承自 `fastapi.security.oauth2.OAuth2`,而该类又是继承自`fastapi.security.base.SecurityBase`。
|
||||
**FastAPI** 之所以知道可以使用(在依赖中声明的)`OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,是因为它继承自 `fastapi.security.oauth2.OAuth2`,而后者又继承自 `fastapi.security.base.SecurityBase`。
|
||||
|
||||
所有与 OpenAPI(及 API 文档)集成的安全工具都继承自 `SecurityBase`, 这就是为什么 **FastAPI** 能把它们集成至 OpenAPI 的原因。
|
||||
所有与 OpenAPI(以及自动 API 文档)集成的安全工具都继承自 `SecurityBase`,这就是 **FastAPI** 能将它们集成到 OpenAPI 的方式。
|
||||
|
||||
///
|
||||
|
||||
## 实现的操作
|
||||
## 它做了什么 { #what-it-does }
|
||||
|
||||
FastAPI 校验请求中的 `Authorization` 请求头,核对请求头的值是不是由 `Bearer ` + 令牌组成, 并返回令牌字符串(`str`)。
|
||||
它会在请求中查找 `Authorization` 请求头,检查其值是否为 `Bearer ` 加上一些令牌,并将该令牌作为 `str` 返回。
|
||||
|
||||
如果没有找到 `Authorization` 请求头,或请求头的值不是 `Bearer ` + 令牌。FastAPI 直接返回 401 错误状态码(`UNAUTHORIZED`)。
|
||||
如果没有 `Authorization` 请求头,或者其值不包含 `Bearer ` 令牌,它会直接返回 401 状态码错误(`UNAUTHORIZED`)。
|
||||
|
||||
开发者不需要检查错误信息,查看令牌是否存在,只要该函数能够执行,函数中就会包含令牌字符串。
|
||||
你甚至无需检查令牌是否存在即可返回错误;只要你的函数被执行,就可以确定会拿到一个 `str` 类型的令牌。
|
||||
|
||||
正如下图所示,API 文档已经包含了这项功能:
|
||||
你已经可以在交互式文档中试试了:
|
||||
|
||||
<img src="/img/tutorial/security/image03.png">
|
||||
|
||||
目前,暂时还没有实现验证令牌是否有效的功能,不过后文很快就会介绍的。
|
||||
我们还没有验证令牌是否有效,但这已经是一个良好的开端。
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
看到了吧,只要多写三四行代码,就可以添加基础的安全表单。
|
||||
只需增加三四行代码,你就已经拥有了一种初步的安全机制。
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
# 获取当前用户
|
||||
# 获取当前用户 { #get-current-user }
|
||||
|
||||
上一章中,(基于依赖注入系统的)安全系统向*路径操作函数*传递了 `str` 类型的 `token`:
|
||||
|
||||
{* ../../docs_src/security/tutorial001.py hl[10] *}
|
||||
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
|
||||
|
||||
但这并不实用。
|
||||
|
||||
接下来,我们学习如何返回当前用户。
|
||||
|
||||
|
||||
## 创建用户模型
|
||||
## 创建用户模型 { #create-a-user-model }
|
||||
|
||||
首先,创建 Pydantic 用户模型。
|
||||
|
||||
与使用 Pydantic 声明请求体相同,并且可在任何位置使用:
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[5,12:16] *}
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
|
||||
|
||||
## 创建 `get_current_user` 依赖项
|
||||
## 创建 `get_current_user` 依赖项 { #create-a-get-current-user-dependency }
|
||||
|
||||
创建 `get_current_user` 依赖项。
|
||||
|
||||
@@ -27,19 +27,19 @@
|
||||
|
||||
与之前直接在路径操作中的做法相同,新的 `get_current_user` 依赖项从子依赖项 `oauth2_scheme` 中接收 `str` 类型的 `token`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[25] *}
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
|
||||
|
||||
## 获取用户
|
||||
## 获取用户 { #get-the-user }
|
||||
|
||||
`get_current_user` 使用创建的(伪)工具函数,该函数接收 `str` 类型的令牌,并返回 Pydantic 的 `User` 模型:
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *}
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
|
||||
|
||||
## 注入当前用户
|
||||
## 注入当前用户 { #inject-the-current-user }
|
||||
|
||||
在*路径操作* 的 `Depends` 中使用 `get_current_user`:
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[31] *}
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
|
||||
|
||||
注意,此处把 `current_user` 的类型声明为 Pydantic 的 `User` 模型。
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
///
|
||||
|
||||
## 其它模型
|
||||
## 其它模型 { #other-models }
|
||||
|
||||
接下来,直接在*路径操作函数*中获取当前用户,并用 `Depends` 在**依赖注入**系统中处理安全机制。
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
尽管使用应用所需的任何模型、类、数据库。**FastAPI** 通过依赖注入系统都能帮您搞定。
|
||||
|
||||
|
||||
## 代码大小
|
||||
## 代码大小 { #code-size }
|
||||
|
||||
这个示例看起来有些冗长。毕竟这个文件同时包含了安全、数据模型的工具函数,以及路径操作等代码。
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
|
||||
所有*路径操作*只需 3 行代码就可以了:
|
||||
|
||||
{* ../../docs_src/security/tutorial002.py hl[30:32] *}
|
||||
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
现在,我们可以直接在*路径操作函数*中获取当前用户。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 安全性
|
||||
# 安全性 { #security }
|
||||
|
||||
有许多方法可以处理安全性、身份认证和授权等问题。
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
但首先,让我们来看一些小的概念。
|
||||
|
||||
## 没有时间?
|
||||
## 没有时间? { #in-a-hurry }
|
||||
|
||||
如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到下一章。
|
||||
如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到接下来的章节。
|
||||
|
||||
## OAuth2
|
||||
## OAuth2 { #oauth2 }
|
||||
|
||||
OAuth2是一个规范,它定义了几种处理身份认证和授权的方法。
|
||||
|
||||
@@ -24,7 +24,7 @@ OAuth2是一个规范,它定义了几种处理身份认证和授权的方法
|
||||
|
||||
这就是所有带有「使用 Facebook,Google,X (Twitter),GitHub 登录」的系统背后所使用的机制。
|
||||
|
||||
### OAuth 1
|
||||
### OAuth 1 { #oauth-1 }
|
||||
|
||||
有一个 OAuth 1,它与 OAuth2 完全不同,并且更为复杂,因为它直接包含了有关如何加密通信的规范。
|
||||
|
||||
@@ -32,13 +32,13 @@ OAuth2是一个规范,它定义了几种处理身份认证和授权的方法
|
||||
|
||||
OAuth2 没有指定如何加密通信,它期望你为应用程序使用 HTTPS 进行通信。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
在有关**部署**的章节中,你将了解如何使用 Traefik 和 Let's Encrypt 免费设置 HTTPS。
|
||||
|
||||
///
|
||||
|
||||
## OpenID Connect
|
||||
## OpenID Connect { #openid-connect }
|
||||
|
||||
OpenID Connect 是另一个基于 **OAuth2** 的规范。
|
||||
|
||||
@@ -48,7 +48,7 @@ OpenID Connect 是另一个基于 **OAuth2** 的规范。
|
||||
|
||||
但是 Facebook 登录不支持 OpenID Connect。它具有自己的 OAuth2 风格。
|
||||
|
||||
### OpenID(非「OpenID Connect」)
|
||||
### OpenID(非「OpenID Connect」) { #openid-not-openid-connect }
|
||||
|
||||
还有一个「OpenID」规范。它试图解决与 **OpenID Connect** 相同的问题,但它不是基于 OAuth2。
|
||||
|
||||
@@ -56,7 +56,7 @@ OpenID Connect 是另一个基于 **OAuth2** 的规范。
|
||||
|
||||
如今它已经不是很流行,没有被广泛使用了。
|
||||
|
||||
## OpenAPI
|
||||
## OpenAPI { #openapi }
|
||||
|
||||
OpenAPI(以前称为 Swagger)是用于构建 API 的开放规范(现已成为 Linux Foundation 的一部分)。
|
||||
|
||||
@@ -75,7 +75,7 @@ OpenAPI 定义了以下安全方案:
|
||||
* 请求头。
|
||||
* cookie。
|
||||
* `http`:标准的 HTTP 身份认证系统,包括:
|
||||
* `bearer`: 一个值为 `Bearer` 加令牌字符串的 `Authorization` 请求头。这是从 OAuth2 继承的。
|
||||
* `bearer`: 一个值为 `Bearer ` 加令牌字符串的 `Authorization` 请求头。这是从 OAuth2 继承的。
|
||||
* HTTP Basic 认证方式。
|
||||
* HTTP Digest,等等。
|
||||
* `oauth2`:所有的 OAuth2 处理安全性的方式(称为「流程」)。
|
||||
@@ -89,7 +89,7 @@ OpenAPI 定义了以下安全方案:
|
||||
* 此自动发现机制是 OpenID Connect 规范中定义的内容。
|
||||
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
集成其他身份认证/授权提供者(例如Google,Facebook,X (Twitter),GitHub等)也是可能的,而且较为容易。
|
||||
|
||||
@@ -97,10 +97,10 @@ OpenAPI 定义了以下安全方案:
|
||||
|
||||
///
|
||||
|
||||
## **FastAPI** 实用工具
|
||||
## **FastAPI** 实用工具 { #fastapi-utilities }
|
||||
|
||||
FastAPI 在 `fastapi.security` 模块中为每个安全方案提供了几种工具,这些工具简化了这些安全机制的使用方法。
|
||||
|
||||
在下一章中,你将看到如何使用 **FastAPI** 所提供的这些工具为你的 API 增加安全性。
|
||||
在接下来的章节中,你将看到如何使用 **FastAPI** 所提供的这些工具为你的 API 增加安全性。
|
||||
|
||||
而且你还将看到它如何自动地被集成到交互式文档系统中。
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
# OAuth2 实现密码哈希与 Bearer JWT 令牌验证
|
||||
# 使用密码(及哈希)的 OAuth2,基于 JWT 的 Bearer 令牌 { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens }
|
||||
|
||||
至此,我们已经编写了所有安全流,本章学习如何使用 <abbr title="JSON Web Tokens">JWT</abbr> 令牌(Token)和安全密码哈希(Hash)实现真正的安全机制。
|
||||
现在我们已经有了完整的安全流程,接下来用 <abbr title="JSON Web Tokens - JSON Web 令牌">JWT</abbr> 令牌和安全的密码哈希,让应用真正安全起来。
|
||||
|
||||
本章的示例代码真正实现了在应用的数据库中保存哈希密码等功能。
|
||||
这些代码可以直接用于你的应用,你可以把密码哈希保存到数据库中,等等。
|
||||
|
||||
接下来,我们紧接上一章,继续完善安全机制。
|
||||
我们将从上一章结束的地方继续,逐步完善。
|
||||
|
||||
## JWT 简介
|
||||
## 关于 JWT { #about-jwt }
|
||||
|
||||
JWT 即**JSON 网络令牌**(JSON Web Tokens)。
|
||||
JWT 意为 “JSON Web Tokens”。
|
||||
|
||||
JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。JWT 的内容如下所示:
|
||||
它是一种标准,把一个 JSON 对象编码成没有空格、很密集的一长串字符串。看起来像这样:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
```
|
||||
|
||||
JWT 字符串没有加密,任何人都能用它恢复原始信息。
|
||||
它不是加密的,所以任何人都可以从内容中恢复信息。
|
||||
|
||||
但 JWT 使用了签名机制。接受令牌时,可以用签名校验令牌。
|
||||
但它是“签名”的。因此,当你收到一个自己签发的令牌时,你可以验证它确实是你签发的。
|
||||
|
||||
使用 JWT 创建有效期为一周的令牌。第二天,用户持令牌再次访问时,仍为登录状态。
|
||||
这样你就可以创建一个例如有效期为 1 周的令牌。然后当用户第二天带着这个令牌回来时,你能知道该用户仍然处于登录状态。
|
||||
|
||||
令牌于一周后过期,届时,用户身份验证就会失败。只有再次登录,才能获得新的令牌。如果用户(或第三方)篡改令牌的过期时间,因为签名不匹配会导致身份验证失败。
|
||||
一周后令牌过期,用户将不再被授权,需要重新登录以获取新令牌。而如果用户(或第三方)尝试修改令牌来更改过期时间,你也能发现,因为签名将不匹配。
|
||||
|
||||
如需深入了解 JWT 令牌,了解它的工作方式,请参阅 <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>。
|
||||
如果你想动手体验 JWT 令牌并了解它的工作方式,请访问 <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>。
|
||||
|
||||
## 安装 `PyJWT`
|
||||
## 安装 `PyJWT` { #install-pyjwt }
|
||||
|
||||
安装 `PyJWT`,在 Python 中生成和校验 JWT 令牌:
|
||||
我们需要安装 `PyJWT`,以便在 Python 中生成和校验 JWT 令牌。
|
||||
|
||||
请确保创建并激活一个[虚拟环境](../../virtual-environments.md){.internal-link target=_blank},然后安装 `pyjwt`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -40,42 +42,42 @@ $ pip install pyjwt
|
||||
|
||||
</div>
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
如果您打算使用类似 RSA 或 ECDSA 的数字签名算法,您应该安装加密库依赖项 `pyjwt[crypto]`。
|
||||
如果你计划使用类似 RSA 或 ECDSA 的数字签名算法,你应该安装加密库依赖项 `pyjwt[crypto]`。
|
||||
|
||||
您可以在 <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT Installation docs</a> 获得更多信息。
|
||||
可以在 <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT 安装文档</a>中了解更多。
|
||||
|
||||
///
|
||||
|
||||
## 密码哈希
|
||||
## 密码哈希 { #password-hashing }
|
||||
|
||||
**哈希**是指把特定内容(本例中为密码)转换为乱码形式的字节序列(其实就是字符串)。
|
||||
“哈希”是指把一些内容(这里是密码)转换成看起来像乱码的一串字节(其实就是字符串)。
|
||||
|
||||
每次传入完全相同的内容时(比如,完全相同的密码),返回的都是完全相同的乱码。
|
||||
当你每次传入完全相同的内容(完全相同的密码)时,都会得到完全相同的“乱码”。
|
||||
|
||||
但这个乱码无法转换回传入的密码。
|
||||
但你无法从这个“乱码”反向还原出密码。
|
||||
|
||||
### 为什么使用密码哈希
|
||||
### 为什么使用密码哈希 { #why-use-password-hashing }
|
||||
|
||||
原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。
|
||||
如果你的数据库被盗,窃贼拿到的不会是用户的明文密码,而只是哈希值。
|
||||
|
||||
这样一来,窃贼就无法在其它应用中使用窃取的密码(要知道,很多用户在所有系统中都使用相同的密码,风险超大)。
|
||||
因此,窃贼无法把该密码拿去尝试登录另一个系统(很多用户在各处都用相同的密码,这将非常危险)。
|
||||
|
||||
## 安装 `passlib`
|
||||
## 安装 `pwdlib` { #install-pwdlib }
|
||||
|
||||
Passlib 是处理密码哈希的 Python 包。
|
||||
pwdlib 是一个用于处理密码哈希的优秀 Python 包。
|
||||
|
||||
它支持很多安全哈希算法及配套工具。
|
||||
它支持多种安全的哈希算法以及相关工具。
|
||||
|
||||
本教程推荐的算法是 **Bcrypt**。
|
||||
推荐的算法是 “Argon2”。
|
||||
|
||||
因此,请先安装附带 Bcrypt 的 PassLib:
|
||||
请确保创建并激活一个[虚拟环境](../../virtual-environments.md){.internal-link target=_blank},然后安装带 Argon2 的 pwdlib:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pip install passlib[bcrypt]
|
||||
$ pip install "pwdlib[argon2]"
|
||||
|
||||
---> 100%
|
||||
```
|
||||
@@ -84,51 +86,51 @@ $ pip install passlib[bcrypt]
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
`passlib` 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。
|
||||
使用 `pwdlib`,你甚至可以把它配置为能够读取由 **Django**、**Flask** 安全插件或其他许多工具创建的密码。
|
||||
|
||||
例如,把 Django 应用的数据共享给 FastAPI 应用的数据库。或利用同一个数据库,可以逐步把应用从 Django 迁移到 FastAPI。
|
||||
例如,你可以在数据库中让一个 Django 应用和一个 FastAPI 应用共享同一份数据。或者在使用同一个数据库的前提下,逐步迁移一个 Django 应用到 FastAPI。
|
||||
|
||||
并且,用户可以同时从 Django 应用或 FastAPI 应用登录。
|
||||
同时,你的用户既可以从 Django 应用登录,也可以从 **FastAPI** 应用登录。
|
||||
|
||||
///
|
||||
|
||||
## 密码哈希与校验
|
||||
## 哈希并校验密码 { #hash-and-verify-the-passwords }
|
||||
|
||||
从 `passlib` 导入所需工具。
|
||||
从 `pwdlib` 导入所需工具。
|
||||
|
||||
创建用于密码哈希和身份校验的 PassLib **上下文**。
|
||||
用推荐设置创建一个 PasswordHash 实例——它将用于哈希与校验密码。
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验的已弃用旧算法等。
|
||||
pwdlib 也支持 bcrypt 哈希算法,但不包含遗留算法——如果需要处理过时的哈希,建议使用 passlib 库。
|
||||
|
||||
例如,用它读取和校验其它系统(如 Django)生成的密码,但要使用其它算法,如 Bcrypt,生成新的哈希密码。
|
||||
例如,你可以用它读取并校验其他系统(如 Django)生成的密码,但对任何新密码使用不同的算法(如 Argon2 或 Bcrypt)进行哈希。
|
||||
|
||||
同时,这些功能都是兼容的。
|
||||
并且能够同时与它们全部兼容。
|
||||
|
||||
///
|
||||
|
||||
接下来,创建三个工具函数,其中一个函数用于哈希用户的密码。
|
||||
创建一个工具函数来哈希用户传入的密码。
|
||||
|
||||
第一个函数用于校验接收的密码是否匹配存储的哈希值。
|
||||
再创建一个工具函数来校验接收的密码是否匹配已存储的哈希。
|
||||
|
||||
第三个函数用于身份验证,并返回用户。
|
||||
再创建一个工具函数来进行身份验证并返回用户。
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
查看新的(伪)数据库 `fake_users_db`,就能看到哈希后的密码:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`。
|
||||
如果你查看新的(伪)数据库 `fake_users_db`,现在你会看到哈希后的密码类似这样:`"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`。
|
||||
|
||||
///
|
||||
|
||||
## 处理 JWT 令牌
|
||||
## 处理 JWT 令牌 { #handle-jwt-tokens }
|
||||
|
||||
导入已安装的模块。
|
||||
|
||||
创建用于 JWT 令牌签名的随机密钥。
|
||||
创建一个用于对 JWT 令牌进行签名的随机密钥。
|
||||
|
||||
使用以下命令,生成安全的随机密钥:
|
||||
使用下列命令生成一个安全的随机密钥:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -140,81 +142,82 @@ $ openssl rand -hex 32
|
||||
|
||||
</div>
|
||||
|
||||
然后,把生成的密钥复制到变量**SECRET_KEY**,注意,不要使用本例所示的密钥。
|
||||
把输出复制到变量 `SECRET_KEY`(不要使用示例中的那个)。
|
||||
|
||||
创建指定 JWT 令牌签名算法的变量 **ALGORITHM**,本例中的值为 `"HS256"`。
|
||||
创建变量 `ALGORITHM`,设置用于签名 JWT 令牌的算法,这里设为 `"HS256"`。
|
||||
|
||||
创建设置令牌过期时间的变量。
|
||||
创建一个变量用于设置令牌的过期时间。
|
||||
|
||||
定义令牌端点响应的 Pydantic 模型。
|
||||
定义一个用于令牌端点响应的 Pydantic 模型。
|
||||
|
||||
创建生成新的访问令牌的工具函数。
|
||||
|
||||
{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *}
|
||||
|
||||
## 更新依赖项
|
||||
|
||||
更新 `get_current_user` 以接收与之前相同的令牌,但这里用的是 JWT 令牌。
|
||||
|
||||
解码并校验接收到的令牌,然后,返回当前用户。
|
||||
|
||||
如果令牌无效,则直接返回 HTTP 错误。
|
||||
创建一个生成新访问令牌的工具函数。
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
|
||||
|
||||
## 更新 `/token` *路径操作*
|
||||
## 更新依赖项 { #update-the-dependencies }
|
||||
|
||||
用令牌过期时间创建 `timedelta` 对象。
|
||||
更新 `get_current_user` 以接收与之前相同的令牌,但这次使用的是 JWT 令牌。
|
||||
|
||||
创建并返回真正的 JWT 访问令牌。
|
||||
解码接收到的令牌,进行校验,并返回当前用户。
|
||||
|
||||
如果令牌无效,立即返回一个 HTTP 错误。
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
|
||||
|
||||
## 更新 `/token` 路径操作 { #update-the-token-path-operation }
|
||||
|
||||
用令牌的过期时间创建一个 `timedelta`。
|
||||
|
||||
创建一个真正的 JWT 访问令牌并返回它。
|
||||
|
||||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
|
||||
|
||||
### JWT `sub` 的技术细节
|
||||
### 关于 JWT “主题” `sub` 的技术细节 { #technical-details-about-the-jwt-subject-sub }
|
||||
|
||||
JWT 规范还包括 `sub` 键,值是令牌的主题。
|
||||
JWT 规范中有一个 `sub` 键,表示令牌的“主题”(subject)。
|
||||
|
||||
该键是可选的,但要把用户标识放在这个键里,所以本例使用了该键。
|
||||
使用它是可选的,但通常会把用户的标识放在这里,所以本例中我们使用它。
|
||||
|
||||
除了识别用户与许可用户在 API 上直接执行操作之外,JWT 还可能用于其它事情。
|
||||
JWT 除了用于识别用户并允许其直接在你的 API 上执行操作之外,还可能用于其他场景。
|
||||
|
||||
例如,识别**汽车**或**博客**。
|
||||
例如,你可以用它来标识一辆“车”或一篇“博客文章”。
|
||||
|
||||
接着,为实体添加权限,比如**驾驶**(汽车)或**编辑**(博客)。
|
||||
然后你可以为该实体添加权限,比如“drive”(用于车)或“edit”(用于博客)。
|
||||
|
||||
然后,把 JWT 令牌交给用户(或机器人),他们就可以执行驾驶汽车,或编辑博客等操作。无需注册账户,只要有 API 生成的 JWT 令牌就可以。
|
||||
接着,你可以把这个 JWT 令牌交给一个用户(或机器人),他们就可以在没有账户的前提下,仅凭你的 API 生成的 JWT 令牌来执行这些操作(开车、编辑文章)。
|
||||
|
||||
同理,JWT 可以用于更复杂的场景。
|
||||
基于这些想法,JWT 可以用于更复杂的场景。
|
||||
|
||||
在这些情况下,多个实体的 ID 可能是相同的,以 ID `foo` 为例,用户的 ID 是 `foo`,车的 ID 是 `foo`,博客的 ID 也是 `foo`。
|
||||
在这些情况下,多个实体可能会有相同的 ID,比如都叫 `foo`(用户 `foo`、车 `foo`、博客文章 `foo`)。
|
||||
|
||||
为了避免 ID 冲突,在给用户创建 JWT 令牌时,可以为 `sub` 键的值加上前缀,例如 `username:`。因此,在本例中,`sub` 的值可以是:`username:johndoe`。
|
||||
因此,为了避免 ID 冲突,在为用户创建 JWT 令牌时,你可以给 `sub` 键的值加一个前缀,例如 `username:`。所以在这个例子中,`sub` 的值可以是:`username:johndoe`。
|
||||
|
||||
注意,划重点,`sub` 键在整个应用中应该只有一个唯一的标识符,而且应该是字符串。
|
||||
需要牢记的一点是,`sub` 键在整个应用中应该是一个唯一标识符,并且它应该是字符串。
|
||||
|
||||
## 检查
|
||||
## 检查 { #check-it }
|
||||
|
||||
运行服务器并访问文档: <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>。
|
||||
|
||||
可以看到如下用户界面:
|
||||
你会看到这样的用户界面:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image07.png">
|
||||
<img src="/img/tutorial/security/image07.png">
|
||||
|
||||
用与上一章同样的方式实现应用授权。
|
||||
像之前一样进行授权。
|
||||
|
||||
使用如下凭证:
|
||||
使用以下凭证:
|
||||
|
||||
用户名: `johndoe` 密码: `secret`
|
||||
用户名: `johndoe`
|
||||
密码: `secret`
|
||||
|
||||
/// check | 检查
|
||||
|
||||
注意,代码中没有明文密码**`secret`**,只保存了它的哈希值。
|
||||
注意,代码中的任何地方都没有明文密码 “`secret`”,我们只有它的哈希版本。
|
||||
|
||||
///
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image08.png">
|
||||
<img src="/img/tutorial/security/image08.png">
|
||||
|
||||
调用 `/users/me/` 端点,收到下面的响应:
|
||||
调用 `/users/me/` 端点,你将得到如下响应:
|
||||
|
||||
```JSON
|
||||
{
|
||||
@@ -225,46 +228,46 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。
|
||||
}
|
||||
```
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image09.png">
|
||||
<img src="/img/tutorial/security/image09.png">
|
||||
|
||||
打开浏览器的开发者工具,查看数据是怎么发送的,而且数据里只包含了令牌,只有验证用户的第一个请求才发送密码,并获取访问令牌,但之后不会再发送密码:
|
||||
如果你打开开发者工具,你会看到发送的数据只包含令牌。密码只会在第一个请求中用于认证用户并获取访问令牌,之后就不会再发送密码了:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image10.png">
|
||||
<img src="/img/tutorial/security/image10.png">
|
||||
|
||||
/// note | 笔记
|
||||
/// note | 注意
|
||||
|
||||
注意,请求中 `Authorization` 响应头的值以 `Bearer` 开头。
|
||||
注意 `Authorization` 请求头,其值以 `Bearer ` 开头。
|
||||
|
||||
///
|
||||
|
||||
## `scopes` 高级用法
|
||||
## 使用 `scopes` 的高级用法 { #advanced-usage-with-scopes }
|
||||
|
||||
OAuth2 支持**`scopes`**(作用域)。
|
||||
OAuth2 支持 “scopes”(作用域)。
|
||||
|
||||
**`scopes`**为 JWT 令牌添加指定权限。
|
||||
你可以用它们为 JWT 令牌添加一组特定的权限。
|
||||
|
||||
让持有令牌的用户或第三方在指定限制条件下与 API 交互。
|
||||
然后你可以把这个令牌直接交给用户或第三方,在一组限制条件下与 API 交互。
|
||||
|
||||
**高级用户指南**中将介绍如何使用 `scopes`,及如何把 `scopes` 集成至 **FastAPI**。
|
||||
在**高级用户指南**中你将学习如何使用它们,以及它们如何集成进 **FastAPI**。
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
至此,您可以使用 OAuth2 和 JWT 等标准配置安全的 **FastAPI** 应用。
|
||||
通过目前所学内容,你可以使用 OAuth2 和 JWT 等标准来搭建一个安全的 **FastAPI** 应用。
|
||||
|
||||
几乎在所有框架中,处理安全问题很快都会变得非常复杂。
|
||||
在几乎任何框架中,处理安全问题都会很快变得相当复杂。
|
||||
|
||||
有些包为了简化安全流,不得不在数据模型、数据库和功能上做出妥协。而有些过于简化的软件包其实存在了安全隐患。
|
||||
许多把安全流程大幅简化的包,往往要在数据模型、数据库和可用特性上做出大量妥协。而有些过度简化的包实际上在底层存在安全隐患。
|
||||
|
||||
---
|
||||
|
||||
**FastAPI** 不向任何数据库、数据模型或工具做妥协。
|
||||
**FastAPI** 不会在任何数据库、数据模型或工具上做妥协。
|
||||
|
||||
开发者可以灵活选择最适合项目的安全机制。
|
||||
它给予你完全的灵活性,选择最适合你项目的方案。
|
||||
|
||||
还可以直接使用 `passlib` 和 `PyJWT` 等维护良好、使用广泛的包,这是因为 **FastAPI** 不需要任何复杂机制,就能集成外部的包。
|
||||
而且你可以直接使用许多维护良好、广泛使用的包,比如 `pwdlib` 和 `PyJWT`,因为 **FastAPI** 不需要复杂机制来集成外部包。
|
||||
|
||||
而且,**FastAPI** 还提供了一些工具,在不影响灵活、稳定和安全的前提下,尽可能地简化安全机制。
|
||||
同时它也为你提供尽可能简化流程的工具,而不牺牲灵活性、健壮性或安全性。
|
||||
|
||||
**FastAPI** 还支持以相对简单的方式,使用 OAuth2 等安全、标准的协议。
|
||||
你可以以相对简单的方式使用和实现像 OAuth2 这样的安全、标准协议。
|
||||
|
||||
**高级用户指南**中详细介绍了 OAuth2**`scopes`**的内容,遵循同样的标准,实现更精密的权限系统。OAuth2 的作用域是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制,让用户授权第三方应用与 API 交互。
|
||||
在**高级用户指南**中,你可以进一步了解如何使用 OAuth2 的 “scopes”,以遵循相同标准实现更细粒度的权限系统。带作用域的 OAuth2 是许多大型身份认证提供商(如 Facebook、Google、GitHub、Microsoft、X(Twitter)等)用来授权第三方应用代表其用户与其 API 交互的机制。
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# OAuth2 实现简单的 Password 和 Bearer 验证
|
||||
# OAuth2 实现简单的 Password 和 Bearer 验证 { #simple-oauth2-with-password-and-bearer }
|
||||
|
||||
本章添加上一章示例中欠缺的部分,实现完整的安全流。
|
||||
|
||||
## 获取 `username` 和 `password`
|
||||
## 获取 `username` 和 `password` { #get-the-username-and-password }
|
||||
|
||||
首先,使用 **FastAPI** 安全工具获取 `username` 和 `password`。
|
||||
|
||||
@@ -18,7 +18,7 @@ OAuth2 规范要求使用**密码流**时,客户端或用户必须以表单数
|
||||
|
||||
该规范要求必须以表单数据形式发送 `username` 和 `password`,因此,不能使用 JSON 对象。
|
||||
|
||||
### `Scope`(作用域)
|
||||
### `scope` { #scope }
|
||||
|
||||
OAuth2 还支持客户端发送**`scope`**表单字段。
|
||||
|
||||
@@ -32,7 +32,7 @@ OAuth2 还支持客户端发送**`scope`**表单字段。
|
||||
* 脸书和 Instagram 使用 `instagram_basic`
|
||||
* 谷歌使用 `https://www.googleapis.com/auth/drive`
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
|
||||
@@ -44,15 +44,15 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
|
||||
///
|
||||
|
||||
## 获取 `username` 和 `password` 的代码
|
||||
## 获取 `username` 和 `password` 的代码 { #code-to-get-the-username-and-password }
|
||||
|
||||
接下来,使用 **FastAPI** 工具获取用户名与密码。
|
||||
|
||||
### `OAuth2PasswordRequestForm`
|
||||
### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform }
|
||||
|
||||
首先,导入 `OAuth2PasswordRequestForm`,然后,在 `/token` *路径操作* 中,用 `Depends` 把该类作为依赖项。
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[4,76] *}
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *}
|
||||
|
||||
`OAuth2PasswordRequestForm` 是用以下几项内容声明表单请求体的类依赖项:
|
||||
|
||||
@@ -72,7 +72,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
* 可选的 `client_id`(本例未使用)
|
||||
* 可选的 `client_secret`(本例未使用)
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
`OAuth2PasswordRequestForm` 与 `OAuth2PasswordBearer` 一样,都不是 FastAPI 的特殊类。
|
||||
|
||||
@@ -84,7 +84,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
|
||||
///
|
||||
|
||||
### 使用表单数据
|
||||
### 使用表单数据 { #use-the-form-data }
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -100,9 +100,9 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
|
||||
本例使用 `HTTPException` 异常显示此错误:
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[3,77:79] *}
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *}
|
||||
|
||||
### 校验密码
|
||||
### 校验密码 { #check-the-password }
|
||||
|
||||
至此,我们已经从数据库中获取了用户数据,但尚未校验密码。
|
||||
|
||||
@@ -112,7 +112,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
|
||||
如果密码不匹配,则返回与上面相同的错误。
|
||||
|
||||
#### 密码哈希
|
||||
#### 密码哈希 { #password-hashing }
|
||||
|
||||
**哈希**是指,将指定内容(本例中为密码)转换为形似乱码的字节序列(其实就是字符串)。
|
||||
|
||||
@@ -120,15 +120,15 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。
|
||||
|
||||
但这个乱码无法转换回传入的密码。
|
||||
|
||||
##### 为什么使用密码哈希
|
||||
##### 为什么使用密码哈希 { #why-use-password-hashing }
|
||||
|
||||
原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。
|
||||
|
||||
这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大。
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[80:83] *}
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *}
|
||||
|
||||
#### 关于 `**user_dict`
|
||||
#### 关于 `**user_dict` { #about-user-dict }
|
||||
|
||||
`UserInDB(**user_dict)` 是指:
|
||||
|
||||
@@ -144,13 +144,13 @@ UserInDB(
|
||||
)
|
||||
```
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
`user_dict` 的说明,详见[**更多模型**一章](../extra-models.md#user_indict){.internal-link target=_blank}。
|
||||
`user_dict` 的说明,详见[**更多模型**一章](../extra-models.md#about-user-in-dict){.internal-link target=_blank}。
|
||||
|
||||
///
|
||||
|
||||
## 返回 Token
|
||||
## 返回 Token { #return-the-token }
|
||||
|
||||
`token` 端点的响应必须是 JSON 对象。
|
||||
|
||||
@@ -162,13 +162,13 @@ UserInDB(
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
下一章介绍使用哈希密码和 <abbr title="JSON Web Tokens">JWT</abbr> Token 的真正安全机制。
|
||||
下一章介绍使用哈希密码和 <abbr title="JSON Web Tokens - JSON Web 令牌">JWT</abbr> Token 的真正安全机制。
|
||||
|
||||
但现在,仅关注所需的特定细节。
|
||||
|
||||
///
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[85] *}
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *}
|
||||
|
||||
/// tip | 提示
|
||||
|
||||
@@ -182,7 +182,7 @@ UserInDB(
|
||||
|
||||
///
|
||||
|
||||
## 更新依赖项
|
||||
## 更新依赖项 { #update-the-dependencies }
|
||||
|
||||
接下来,更新依赖项。
|
||||
|
||||
@@ -194,9 +194,9 @@ UserInDB(
|
||||
|
||||
因此,在端点中,只有当用户存在、通过身份验证、且状态为激活时,才能获得该用户:
|
||||
|
||||
{* ../../docs_src/security/tutorial003.py hl[58:67,69:72,90] *}
|
||||
{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *}
|
||||
|
||||
/// info | 说明
|
||||
/// info | 信息
|
||||
|
||||
此处返回值为 `Bearer` 的响应头 `WWW-Authenticate` 也是规范的一部分。
|
||||
|
||||
@@ -210,15 +210,15 @@ UserInDB(
|
||||
|
||||
说不定什么时候,就有工具用得上它,而且,开发者或用户也可能用得上。
|
||||
|
||||
这就是遵循标准的好处……
|
||||
这就是遵循标准的好处...
|
||||
|
||||
///
|
||||
|
||||
## 实际效果
|
||||
## 实际效果 { #see-it-in-action }
|
||||
|
||||
打开 API 文档:<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
|
||||
|
||||
### 身份验证
|
||||
### 身份验证 { #authenticate }
|
||||
|
||||
点击**Authorize**按钮。
|
||||
|
||||
@@ -228,13 +228,13 @@ UserInDB(
|
||||
|
||||
密码:`secret`
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image04.png">
|
||||
<img src="/img/tutorial/security/image04.png">
|
||||
|
||||
通过身份验证后,显示下图所示的内容:
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image05.png">
|
||||
<img src="/img/tutorial/security/image05.png">
|
||||
|
||||
### 获取当前用户数据
|
||||
### 获取当前用户数据 { #get-your-own-user-data }
|
||||
|
||||
使用 `/users/me` 路径的 `GET` 操作。
|
||||
|
||||
@@ -250,7 +250,7 @@ UserInDB(
|
||||
}
|
||||
```
|
||||
|
||||
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image06.png">
|
||||
<img src="/img/tutorial/security/image06.png">
|
||||
|
||||
点击小锁图标,注销后,再执行同样的操作,则会得到 HTTP 401 错误:
|
||||
|
||||
@@ -260,7 +260,7 @@ UserInDB(
|
||||
}
|
||||
```
|
||||
|
||||
### 未激活用户
|
||||
### 未激活用户 { #inactive-user }
|
||||
|
||||
测试未激活用户,输入以下信息,进行身份验证:
|
||||
|
||||
@@ -278,7 +278,7 @@ UserInDB(
|
||||
}
|
||||
```
|
||||
|
||||
## 小结
|
||||
## 小结 { #recap }
|
||||
|
||||
使用本章的工具实现基于 `username` 和 `password` 的完整 API 安全系统。
|
||||
|
||||
@@ -286,4 +286,4 @@ UserInDB(
|
||||
|
||||
唯一欠缺的是,它仍然不是真的**安全**。
|
||||
|
||||
下一章,介绍使用密码哈希支持库与 <abbr title="JSON Web Tokens">JWT</abbr> 令牌实现真正的安全机制。
|
||||
下一章,介绍使用密码哈希支持库与 <abbr title="JSON Web Tokens - JSON Web 令牌">JWT</abbr> 令牌实现真正的安全机制。
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
# SQL(关系型)数据库
|
||||
# SQL(关系型)数据库 { #sql-relational-databases }
|
||||
|
||||
**FastAPI** 并不要求您使用 SQL(关系型)数据库。您可以使用**任何**想用的数据库。
|
||||
**FastAPI** 并不要求你使用 SQL(关系型)数据库。你可以使用你想用的**任何数据库**。
|
||||
|
||||
这里,我们来看一个使用 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a> 的示例。
|
||||
|
||||
**SQLModel** 是基于 <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> 和 Pydantic 构建的。它由 **FastAPI** 的同一作者制作,旨在完美匹配需要使用 **SQL 数据库**的 FastAPI 应用程序。
|
||||
**SQLModel** 基于 <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> 和 Pydantic 构建。它由 **FastAPI** 的同一作者制作,旨在完美匹配需要使用**SQL 数据库**的 FastAPI 应用程序。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
您可以使用任何其他您想要的 SQL 或 NoSQL 数据库(在某些情况下称为 <abbr title="对象关系映射器(Object Relational Mapper,ORM),一个术语,用来指代一种库,其中某些类对应于 SQL 数据表,这些类的实例则对应于表中的行。">“ORM”</abbr>),FastAPI 不会强迫您使用任何东西。😎
|
||||
你可以使用任意其他你想要的 SQL 或 NoSQL 数据库库(在某些情况下称为 <abbr title="Object Relational Mapper - 对象关系映射器: 一个术语,用来指代一种库,其中某些类表示 SQL 表,这些类的实例表示这些表中的行">"ORMs"</abbr>),FastAPI 不会强迫你使用任何东西。😎
|
||||
|
||||
///
|
||||
|
||||
由于 SQLModel 基于 SQLAlchemy,因此您可以轻松使用任何由 SQLAlchemy **支持的数据库**(这也让它们被 SQLModel 支持),例如:
|
||||
由于 SQLModel 基于 SQLAlchemy,因此你可以轻松使用任何由 SQLAlchemy **支持的数据库**(这也让它们被 SQLModel 支持),例如:
|
||||
|
||||
* PostgreSQL
|
||||
* MySQL
|
||||
* SQLite
|
||||
* Oracle
|
||||
* Microsoft SQL Server 等.
|
||||
* Microsoft SQL Server 等
|
||||
|
||||
在这个例子中,我们将使用 **SQLite**,因为它使用单个文件,并且 Python 对其有集成支持。因此,您可以直接复制这个例子并运行。
|
||||
在这个示例中,我们将使用 **SQLite**,因为它使用单个文件,并且 Python 对其有集成支持。因此,你可以直接复制这个示例并运行。
|
||||
|
||||
之后,对于您的生产应用程序,您可能会想要使用像 PostgreSQL 这样的数据库服务器。
|
||||
之后,对于你的生产应用程序,你可能会想要使用像 **PostgreSQL** 这样的数据库服务器。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
有一个使用 **FastAPI** 和 **PostgreSQL** 的官方的项目生成器,其中包括了前端和更多工具: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
有一个使用 **FastAPI** 和 **PostgreSQL** 的官方项目生成器,其中包括了前端和更多工具: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
|
||||
|
||||
///
|
||||
|
||||
这是一个非常简单和简短的教程。如果您想了解一般的数据库、SQL 或更高级的功能,请查看 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel 文档</a>。
|
||||
这是一个非常简单和简短的教程。如果你想了解一般的数据库、SQL 或更高级的功能,请查看 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel 文档</a>。
|
||||
|
||||
## 安装 `SQLModel`
|
||||
## 安装 `SQLModel` { #install-sqlmodel }
|
||||
|
||||
首先,确保您创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装了 `sqlmodel` :
|
||||
首先,确保你创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装 `sqlmodel`:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -45,61 +45,61 @@ $ pip install sqlmodel
|
||||
|
||||
</div>
|
||||
|
||||
## 创建含有单一模型的应用程序
|
||||
## 创建含有单一模型的应用 { #create-the-app-with-a-single-model }
|
||||
|
||||
我们首先创建应用程序的最简单的第一个版本,只有一个 **SQLModel** 模型。
|
||||
我们先创建应用的最简单的第一个版本,只有一个 **SQLModel** 模型。
|
||||
|
||||
稍后我们将通过下面的**多个模型**提高其安全性和多功能性。🤓
|
||||
|
||||
### 创建模型
|
||||
### 创建模型 { #create-models }
|
||||
|
||||
导入 `SQLModel` 并创建一个数据库模型:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
|
||||
|
||||
`Hero` 类与 Pydantic 模型非常相似(实际上,从底层来看,它确实*就是一个 Pydantic 模型*)。
|
||||
`Hero` 类与 Pydantic 模型非常相似(实际上,从底层来看,它确实就是一个 Pydantic 模型)。
|
||||
|
||||
有一些区别:
|
||||
|
||||
* `table=True` 会告诉 SQLModel 这是一个*表模型*,它应该表示 SQL 数据库中的一个*表*,而不仅仅是一个*数据模型*(就像其他常规的 Pydantic 类一样)。
|
||||
* `table=True` 会告诉 SQLModel 这是一个*表模型*,它应该表示 SQL 数据库中的一个**表**,而不仅仅是一个*数据模型*(就像其他常规的 Pydantic 类一样)。
|
||||
|
||||
* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**(您可以在 SQLModel 文档中了解更多关于 SQL 主键的信息)。
|
||||
* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**(你可以在 SQLModel 文档中了解更多关于 SQL 主键的信息)。
|
||||
|
||||
把类型设置为 `int | None` ,SQLModel 就能知道该列在 SQL 数据库中应该是 `INTEGER` 类型,并且应该是 `NULLABLE` 。
|
||||
**注意:** 我们为主键字段使用 `int | None`,这样在 Python 代码中我们可以在没有 `id`(`id=None`)的情况下创建对象,并假定数据库在保存时会生成它。SQLModel 会理解数据库会提供 `id`,并在数据库模式中将该列定义为非空的 `INTEGER`。详见 <a href="https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id" class="external-link" target="_blank">SQLModel 关于主键的文档</a>。
|
||||
|
||||
* `Field(index=True)` 会告诉 SQLModel 应该为此列创建一个 **SQL 索引**,这样在读取按此列过滤的数据时,程序能在数据库中进行更快的查找。
|
||||
|
||||
SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT` (或 `VARCHAR` ,具体取决于数据库)的 SQL 列。
|
||||
SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT`(或 `VARCHAR`,具体取决于数据库)的 SQL 列。
|
||||
|
||||
### 创建引擎(Engine)
|
||||
### 创建引擎(Engine) { #create-an-engine }
|
||||
|
||||
SQLModel 的引擎 `engine`(实际上它是一个 SQLAlchemy `engine` )是用来与数据库**保持连接**的。
|
||||
SQLModel 的 `engine`(实际上它是一个 SQLAlchemy 的 `engine`)是用来与数据库**保持连接**的。
|
||||
|
||||
您只需构建**一个 `engine`**,来让您的所有代码连接到同一个数据库。
|
||||
你只需构建**一个 `engine` 对象**,让你的所有代码连接到同一个数据库。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
|
||||
|
||||
使用 `check_same_thread=False` 可以让 FastAPI 在不同线程中使用同一个 SQLite 数据库。这很有必要,因为**单个请求**可能会使用**多个线程**(例如在依赖项中)。
|
||||
|
||||
不用担心,我们会按照代码结构确保**每个请求使用一个单独的 SQLModel *会话***,这实际上就是 `check_same_thread` 想要实现的。
|
||||
不用担心,我们会按照代码结构确保**每个请求使用一个单独的 SQLModel 会话(session)**,这实际上就是 `check_same_thread` 想要实现的。
|
||||
|
||||
### 创建表
|
||||
### 创建表 { #create-the-tables }
|
||||
|
||||
然后,我们来添加一个函数,使用 `SQLModel.metadata.create_all(engine)` 为所有*表模型***创建表**。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
|
||||
|
||||
### 创建会话(Session)依赖项
|
||||
### 创建会话(Session)依赖项 { #create-a-session-dependency }
|
||||
|
||||
**`Session`** 会存储**内存中的对象**并跟踪数据中所需更改的内容,然后它**使用 `engine`** 与数据库进行通信。
|
||||
|
||||
我们会使用 `yield` 创建一个 FastAPI **依赖项**,为每个请求提供一个新的 `Session` 。这确保我们每个请求使用一个单独的会话。🤓
|
||||
我们会使用 `yield` 创建一个 FastAPI **依赖项**,为每个请求提供一个新的 `Session`。这确保我们每个请求使用一个单独的会话。🤓
|
||||
|
||||
然后我们创建一个 `Annotated` 的依赖项 `SessionDep` 来简化其他也会用到此依赖的代码。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
|
||||
|
||||
### 在启动时创建数据库表
|
||||
### 在启动时创建数据库表 { #create-database-tables-on-startup }
|
||||
|
||||
我们会在应用程序启动时创建数据库表。
|
||||
|
||||
@@ -107,49 +107,47 @@ SQLModel 的引擎 `engine`(实际上它是一个 SQLAlchemy `engine` )是
|
||||
|
||||
此处,在应用程序启动事件中,我们创建了表。
|
||||
|
||||
而对于生产环境,您可能会用一个能够在启动应用程序之前运行的迁移脚本。🤓
|
||||
在生产环境中,你可能会使用一个在启动应用程序之前运行的迁移脚本。🤓
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
SQLModel 将会拥有封装 Alembic 的迁移工具,但目前您可以直接使用 <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a>。
|
||||
SQLModel 将会拥有封装 Alembic 的迁移工具,但目前你可以直接使用 <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a>。
|
||||
|
||||
///
|
||||
|
||||
### 创建 Hero 类
|
||||
### 创建 Hero { #create-a-hero }
|
||||
|
||||
因为每个 SQLModel 模型同时也是一个 Pydantic 模型,所以您可以在与 Pydantic 模型相同的**类型注释**中使用它。
|
||||
因为每个 SQLModel 模型同时也是一个 Pydantic 模型,所以你可以在与 Pydantic 模型相同的**类型注解**中使用它。
|
||||
|
||||
例如,如果您声明一个类型为 `Hero` 的参数,它将从 **JSON 主体**中读取数据。
|
||||
例如,如果你声明一个类型为 `Hero` 的参数,它将从 **JSON 主体**中读取数据。
|
||||
|
||||
同样,您可以将其声明为函数的**返回类型**,然后数据的结构就会显示在自动生成的 API 文档界面中。
|
||||
同样,你可以将其声明为函数的**返回类型**,然后数据的结构就会显示在自动生成的 API 文档界面中。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
|
||||
|
||||
</details>
|
||||
这里,我们使用 `SessionDep` 依赖项(一个 `Session`)将新的 `Hero` 添加到 `Session` 实例中,提交更改到数据库,刷新 `hero` 中的数据,并返回它。
|
||||
|
||||
这里,我们使用 `SessionDep` 依赖项(一个 `Session` )将新的 `Hero` 添加到 `Session` 实例中,提交更改到数据库,刷新 hero 中的数据,并返回它。
|
||||
### 读取 Hero { #read-heroes }
|
||||
|
||||
### 读取 Hero 类
|
||||
|
||||
我们可以使用 `select()` 从数据库中**读取** `Hero` 类,并利用 `limit` 和 `offset` 来对结果进行分页。
|
||||
我们可以使用 `select()` 从数据库中**读取** `Hero`,并利用 `limit` 和 `offset` 来对结果进行分页。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
|
||||
|
||||
### 读取单个 Hero
|
||||
### 读取单个 Hero { #read-one-hero }
|
||||
|
||||
我们可以**读取**单个 `Hero` 。
|
||||
我们可以**读取**单个 `Hero`。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
|
||||
|
||||
### 删除单个 Hero
|
||||
### 删除单个 Hero { #delete-a-hero }
|
||||
|
||||
我们也可以**删除**单个 `Hero` 。
|
||||
我们也可以**删除**一个 `Hero`。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
|
||||
|
||||
### 运行应用程序
|
||||
### 运行应用 { #run-the-app }
|
||||
|
||||
您可以运行这个应用程序:
|
||||
你可以运行这个应用:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -161,25 +159,25 @@ $ fastapi dev main.py
|
||||
|
||||
</div>
|
||||
|
||||
然后在 `/docs` UI 中,您能够看到 **FastAPI** 会用这些**模型**来**记录** API,并且还会用它们来**序列化**和**验证**数据。
|
||||
然后在 `/docs` UI 中,你能够看到 **FastAPI** 会用这些**模型**来**记录** API,并且还会用它们来**序列化**和**验证**数据。
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image01.png">
|
||||
</div>
|
||||
|
||||
## 更新应用程序以支持多个模型
|
||||
## 使用多个模型更新应用 { #update-the-app-with-multiple-models }
|
||||
|
||||
现在让我们稍微**重构**一下这个应用,以提高**安全性**和**多功能性**。
|
||||
|
||||
如果您查看之前的应用程序,您可以在 UI 界面中看到,到目前为止,由客户端决定要创建的 `Hero` 的 `id` 值。😱
|
||||
如果你查看之前的应用程序,你可以在 UI 界面中看到,到目前为止,它允许客户端决定要创建的 `Hero` 的 `id`。😱
|
||||
|
||||
我们不应该允许这样做,因为他们可能会覆盖我们在数据库中已经分配的 `id` 。决定 `id` 的行为应该由**后端**或**数据库**来完成,**而非客户端**。
|
||||
我们不应该允许这样做,因为他们可能会覆盖我们在数据库中已经分配的 `id`。决定 `id` 的行为应该由**后端**或**数据库**来完成,**而非客户端**。
|
||||
|
||||
此外,我们为 hero 创建了一个 `secret_name` ,但到目前为止,我们在各处都返回了它,这就不太**秘密**了……😅
|
||||
此外,我们为 hero 创建了一个 `secret_name`,但到目前为止,我们在各处都返回了它,这就不太**秘密**了……😅
|
||||
|
||||
我们将通过添加一些**额外的模型**来解决这些问题,而 SQLModel 将在这里大放异彩。✨
|
||||
|
||||
### 创建多个模型
|
||||
### 创建多个模型 { #create-multiple-models }
|
||||
|
||||
在 **SQLModel** 中,任何含有 `table=True` 属性的模型类都是一个**表模型**。
|
||||
|
||||
@@ -187,7 +185,7 @@ $ fastapi dev main.py
|
||||
|
||||
有了 SQLModel,我们就可以利用**继承**来在所有情况下**避免重复**所有字段。
|
||||
|
||||
#### `HeroBase` - 基类
|
||||
#### `HeroBase` - 基类 { #herobase-the-base-class }
|
||||
|
||||
我们从一个 `HeroBase` 模型开始,该模型具有所有模型**共享的字段**:
|
||||
|
||||
@@ -196,14 +194,14 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
|
||||
|
||||
#### `Hero` - *表模型*
|
||||
#### `Hero` - *表模型* { #hero-the-table-model }
|
||||
|
||||
接下来,我们创建 `Hero` ,实际的*表模型*,并添加那些不总是在其他模型中的**额外字段**:
|
||||
接下来,我们创建 `Hero`,实际的*表模型*,并添加那些不总是在其他模型中的**额外字段**:
|
||||
|
||||
* `id`
|
||||
* `secret_name`
|
||||
|
||||
因为 `Hero` 继承自 HeroBase ,所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为:
|
||||
因为 `Hero` 继承自 `HeroBase`,所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为:
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
@@ -212,46 +210,45 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
|
||||
|
||||
#### `HeroPublic` - 公共*数据模型*
|
||||
#### `HeroPublic` - 公共*数据模型* { #heropublic-the-public-data-model }
|
||||
|
||||
接下来,我们创建一个 `HeroPublic` 模型,这是将**返回**给 API 客户端的模型。
|
||||
|
||||
它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name` 。
|
||||
它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name`。
|
||||
|
||||
终于,我们英雄(hero)的身份得到了保护! 🥷
|
||||
终于,我们英雄(hero)的身份得到了保护!🥷
|
||||
|
||||
它还重新声明了 `id: int` 。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None` )。
|
||||
它还重新声明了 `id: int`。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None`)。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
确保返回模型始终提供一个值并且始终是 `int` (而不是 `None` )对 API 客户端非常有用,他们可以在这种确定性下编写更简单的代码。
|
||||
确保返回模型始终提供一个值并且始终是 `int`(而不是 `None`)对 API 客户端非常有用,他们可以在这种确定性下编写更简单的代码。
|
||||
|
||||
此外,**自动生成的客户端**将拥有更简洁的接口,这样与您的 API 交互的开发者就能更轻松地使用您的 API。😎
|
||||
此外,**自动生成的客户端**将拥有更简洁的接口,这样与你的 API 交互的开发者就能更轻松地使用你的 API。😎
|
||||
|
||||
///
|
||||
|
||||
`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同,其中 `id` 声明为 `int` (不是 `None` ):
|
||||
`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同,其中 `id` 声明为 `int`(不是 `None`):
|
||||
|
||||
* `id`
|
||||
* `name`
|
||||
* `age`
|
||||
* `secret_name`
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
|
||||
|
||||
#### `HeroCreate` - 用于创建 hero 的*数据模型*
|
||||
#### `HeroCreate` - 用于创建 hero 的*数据模型* { #herocreate-the-data-model-to-create-a-hero }
|
||||
|
||||
现在我们创建一个 `HeroCreate` 模型,这是用于**验证**客户数据的模型。
|
||||
现在我们创建一个 `HeroCreate` 模型,这是用于**验证**客户端数据的模型。
|
||||
|
||||
它不仅拥有与 `HeroBase` 相同的字段,还有 `secret_name` 。
|
||||
它不仅拥有与 `HeroBase` 相同的字段,还有 `secret_name`。
|
||||
|
||||
现在,当客户端**创建一个新的 hero** 时,他们会发送 `secret_name` ,它会被存储到数据库中,但这些 `secret_name` 不会通过 API 返回给客户端。
|
||||
现在,当客户端**创建一个新的 hero** 时,他们会发送 `secret_name`,它会被存储到数据库中,但这些 `secret_name` 不会通过 API 返回给客户端。
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
这应当是**密码**被处理的方式:接收密码,但不要通过 API 返回它们。
|
||||
|
||||
在存储密码之前,您还应该对密码的值进行**哈希**处理,**绝不要以明文形式存储它们**。
|
||||
在存储密码之前,你还应该对密码的值进行**哈希**处理,**绝不要以明文形式存储它们**。
|
||||
|
||||
///
|
||||
|
||||
@@ -263,17 +260,17 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
|
||||
|
||||
#### `HeroUpdate` - 用于更新 hero 的*数据模型*
|
||||
#### `HeroUpdate` - 用于更新 hero 的*数据模型* { #heroupdate-the-data-model-to-update-a-hero }
|
||||
|
||||
在之前的应用程序中,我们没有办法**更新 hero**,但现在有了**多个模型**,我们便能做到这一点了。🎉
|
||||
|
||||
`HeroUpdate` *数据模型*有些特殊,它包含创建新 hero 所需的**所有相同字段**,但所有字段都是**可选的**(它们都有默认值)。这样,当您更新一个 hero 时,您可以只发送您想要更新的字段。
|
||||
`HeroUpdate` *数据模型*有些特殊,它包含创建新 hero 所需的**所有相同字段**,但所有字段都是**可选的**(它们都有默认值)。这样,当你更新一个 hero 时,你可以只发送你想要更新的字段。
|
||||
|
||||
因为所有**字段实际上**都发生了**变化**(类型现在包括 `None` ,并且它们现在有一个默认值 `None` ),我们需要**重新声明**它们。
|
||||
因为所有**字段实际上**都发生了**变化**(类型现在包括 `None`,并且它们现在有一个默认值 `None`),我们需要**重新声明**它们。
|
||||
|
||||
我们会重新声明所有字段,因此我们并不是真的需要从 `HeroBase` 继承。我会让它继承只是为了保持一致,但这并不必要。这更多是个人喜好的问题。🤷
|
||||
我们并不真的需要从 `HeroBase` 继承,因为我们会重新声明所有字段。我会让它继承只是为了保持一致,但这并不必要。这更多是个人喜好的问题。🤷
|
||||
|
||||
`HeroUpdate` 的字段包括:
|
||||
`HeroUpdate` 的字段包括:
|
||||
|
||||
* `name`
|
||||
* `age`
|
||||
@@ -281,51 +278,51 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
|
||||
|
||||
### 使用 `HeroCreate` 创建并返回 `HeroPublic`
|
||||
### 使用 `HeroCreate` 创建并返回 `HeroPublic` { #create-with-herocreate-and-return-a-heropublic }
|
||||
|
||||
既然我们有了**多个模型**,我们就可以对使用它们的应用程序部分进行更新。
|
||||
|
||||
我们在请求中接收到一个 `HeroCreate` *数据模型*,然后从中创建一个 `Hero` *表模型*。
|
||||
|
||||
这个新的*表模型* `Hero` 会包含客户端发送的字段,以及一个由数据库生成的 `id` 。
|
||||
这个新的*表模型* `Hero` 会包含客户端发送的字段,以及一个由数据库生成的 `id`。
|
||||
|
||||
然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model` ,**FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。
|
||||
然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model`,**FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
|
||||
|
||||
/// tip
|
||||
/// tip | 提示
|
||||
|
||||
现在我们使用 `response_model=HeroPublic` 来代替**返回类型注释** `-> HeroPublic` ,因为我们返回的值实际上**并不是** `HeroPublic` 类型。
|
||||
现在我们使用 `response_model=HeroPublic` 来代替**返回类型注解** `-> HeroPublic`,因为我们返回的值实际上并不是 `HeroPublic`。
|
||||
|
||||
如果我们声明了 `-> HeroPublic` ,您的编辑器和代码检查工具会抱怨(但也确实理所应当)您返回了一个 `Hero` 而不是一个 `HeroPublic` 。
|
||||
如果我们声明了 `-> HeroPublic`,你的编辑器和代码检查工具会(理所应当地)抱怨你返回了一个 `Hero` 而不是一个 `HeroPublic`。
|
||||
|
||||
通过 `response_model` 的声明,我们让 **FastAPI** 按照它自己的方式处理,而不会干扰类型注解以及编辑器和其他工具提供的帮助。
|
||||
|
||||
///
|
||||
|
||||
### 用 `HeroPublic` 读取 Hero
|
||||
### 使用 `HeroPublic` 读取 Hero { #read-heroes-with-heropublic }
|
||||
|
||||
我们可以像之前一样**读取** `Hero` 。同样,使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。
|
||||
我们可以像之前一样**读取** `Hero`,同样,使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
|
||||
|
||||
### 用 `HeroPublic` 读取单个 Hero
|
||||
### 使用 `HeroPublic` 读取单个 Hero { #read-one-hero-with-heropublic }
|
||||
|
||||
我们可以**读取**单个 `hero` 。
|
||||
我们可以**读取**单个 hero:
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
|
||||
|
||||
### 用 `HeroUpdate` 更新单个 Hero
|
||||
### 使用 `HeroUpdate` 更新单个 Hero { #update-a-hero-with-heroupdate }
|
||||
|
||||
我们可以**更新**单个 `hero` 。为此,我们会使用 HTTP 的 `PATCH` 操作。
|
||||
我们可以**更新**单个 hero。为此,我们会使用 HTTP 的 `PATCH` 操作。
|
||||
|
||||
在代码中,我们会得到一个 `dict` ,其中包含客户端发送的所有数据,**只有客户端发送的数据**,并排除了任何一个仅仅作为默认值存在的值。为此,我们使用 `exclude_unset=True` 。这是最主要的技巧。🪄
|
||||
在代码中,我们会得到一个 `dict`,其中包含客户端发送的所有数据,**只有客户端发送的数据**,并排除了任何一个仅仅作为默认值存在的值。为此,我们使用 `exclude_unset=True`。这是最主要的技巧。🪄
|
||||
|
||||
然后我们会使用 `hero_db.sqlmodel_update(hero_data)` ,来利用 `hero_data` 的数据更新 `hero_db` 。
|
||||
然后我们会使用 `hero_db.sqlmodel_update(hero_data)`,来利用 `hero_data` 的数据更新 `hero_db`。
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
|
||||
|
||||
### (又一次)删除单个 Hero
|
||||
### (再次)删除单个 Hero { #delete-a-hero-again }
|
||||
|
||||
**删除**一个 hero 基本保持不变。
|
||||
|
||||
@@ -333,9 +330,9 @@ $ fastapi dev main.py
|
||||
|
||||
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
|
||||
|
||||
### (又一次)运行应用程序
|
||||
### (再次)运行应用 { #run-the-app-again }
|
||||
|
||||
您可以再运行一次应用程序:
|
||||
你可以再运行一次应用程序:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
@@ -347,14 +344,14 @@ $ fastapi dev main.py
|
||||
|
||||
</div>
|
||||
|
||||
您会在 `/docs` API UI 看到它现在已经更新,并且在进行创建 hero 等操作时,它不会再期望从客户端接收 `id` 数据。
|
||||
如果你进入 `/docs` API UI,你会看到它现在已经更新,并且在创建 hero 时,它不会再期望从客户端接收 `id` 数据等。
|
||||
|
||||
<div class="screenshot">
|
||||
<img src="/img/tutorial/sql-databases/image02.png">
|
||||
</div>
|
||||
|
||||
## 总结
|
||||
## 总结 { #recap }
|
||||
|
||||
您可以使用 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> 与 SQL 数据库进行交互,并通过*数据模型*和*表模型*简化代码。
|
||||
你可以使用 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> 与 SQL 数据库进行交互,并通过*数据模型*和*表模型*简化代码。
|
||||
|
||||
您可以在 SQLModel 的文档中学习到更多内容,其中有一个更详细的关于<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">如何将 SQLModel 与 FastAPI 一起使用的教程</a>。🚀
|
||||
你可以在 **SQLModel** 文档中学习到更多内容,其中有一个更详细的<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">将 SQLModel 与 **FastAPI** 一起使用的迷你教程</a>。🚀
|
||||
|
||||
@@ -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>。
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user