56 changed files with 2904 additions and 2144 deletions
@ -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,97 +1,87 @@ |
|||
# 使用数据类 |
|||
# 使用数据类 { #using-dataclasses } |
|||
|
|||
FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 模型声明请求与响应。 |
|||
FastAPI 基于 **Pydantic** 构建,我已经向你展示过如何使用 Pydantic 模型声明请求与响应。 |
|||
|
|||
但 FastAPI 还可以使用数据类(<a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>): |
|||
但 FastAPI 也支持以相同方式使用 <a href="https://docs.python.org/3/library/dataclasses.html" class="external-link" target="_blank">`dataclasses`</a>: |
|||
|
|||
{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *} |
|||
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} |
|||
|
|||
这还是借助于 **Pydantic** 及其<a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">内置的 `dataclasses`</a>。 |
|||
这仍然得益于 **Pydantic**,因为它对 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">`dataclasses` 的内置支持</a>。 |
|||
|
|||
因此,即便上述代码没有显式使用 Pydantic,FastAPI 仍会使用 Pydantic 把标准数据类转换为 Pydantic 数据类(`dataclasses`)。 |
|||
因此,即便上面的代码没有显式使用 Pydantic,FastAPI 也会使用 Pydantic 将那些标准数据类转换为 Pydantic 风格的 dataclasses。 |
|||
|
|||
并且,它仍然支持以下功能: |
|||
|
|||
* 数据验证 |
|||
* 数据序列化 |
|||
* 数据存档等 |
|||
* 数据文档等 |
|||
|
|||
数据类的和运作方式与 Pydantic 模型相同。实际上,它的底层使用的也是 Pydantic。 |
|||
这与使用 Pydantic 模型时的工作方式相同。而且底层实际上也是借助 Pydantic 实现的。 |
|||
|
|||
/// info | 说明 |
|||
/// info | 信息 |
|||
|
|||
注意,数据类不支持 Pydantic 模型的所有功能。 |
|||
请注意,数据类不能完成 Pydantic 模型能做的所有事情。 |
|||
|
|||
因此,开发时仍需要使用 Pydantic 模型。 |
|||
因此,你可能仍然需要使用 Pydantic 模型。 |
|||
|
|||
但如果数据类很多,这一技巧能给 FastAPI 开发 Web API 增添不少助力。🤓 |
|||
但如果你已有一堆数据类,这个技巧可以让它们很好地为使用 FastAPI 的 Web API 所用。🤓 |
|||
|
|||
/// |
|||
|
|||
## `response_model` 使用数据类 |
|||
## 在 `response_model` 中使用数据类 { #dataclasses-in-response-model } |
|||
|
|||
在 `response_model` 参数中使用 `dataclasses`: |
|||
你也可以在 `response_model` 参数中使用 `dataclasses`: |
|||
|
|||
{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *} |
|||
{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} |
|||
|
|||
本例把数据类自动转换为 Pydantic 数据类。 |
|||
该数据类会被自动转换为 Pydantic 的数据类。 |
|||
|
|||
API 文档中也会显示相关概图: |
|||
这样,它的模式会显示在 API 文档界面中: |
|||
|
|||
<img src="/img/tutorial/dataclasses/image01.png"> |
|||
|
|||
## 在嵌套数据结构中使用数据类 |
|||
## 在嵌套数据结构中使用数据类 { #dataclasses-in-nested-data-structures } |
|||
|
|||
您还可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。 |
|||
你也可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。 |
|||
|
|||
还有一些情况也可以使用 Pydantic 的 `dataclasses`。例如,在 API 文档中显示错误。 |
|||
在某些情况下,你可能仍然需要使用 Pydantic 的 `dataclasses` 版本。例如,如果自动生成的 API 文档出现错误。 |
|||
|
|||
本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`: |
|||
在这种情况下,你可以直接把标准的 `dataclasses` 替换为 `pydantic.dataclasses`,它是一个可直接替换的实现: |
|||
|
|||
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } |
|||
{!../../docs_src/dataclasses_/tutorial003.py!} |
|||
``` |
|||
{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} |
|||
|
|||
1. 本例依然要从标准的 `dataclasses` 中导入 `field`; |
|||
1. 我们仍然从标准库的 `dataclasses` 导入 `field`。 |
|||
2. `pydantic.dataclasses` 是 `dataclasses` 的可直接替换版本。 |
|||
3. `Author` 数据类包含一个由 `Item` 数据类组成的列表。 |
|||
4. `Author` 数据类被用作 `response_model` 参数。 |
|||
5. 你可以将其它标准类型注解与数据类一起用作请求体。 |
|||
|
|||
在本例中,它是一个 `Item` 数据类列表。 |
|||
6. 这里我们返回一个字典,里面的 `items` 是一个数据类列表。 |
|||
|
|||
FastAPI 仍然能够将数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON。 |
|||
7. 这里的 `response_model` 使用了 “`Author` 数据类列表” 的类型注解。 |
|||
|
|||
同样,你可以将 `dataclasses` 与标准类型注解组合使用。 |
|||
8. 注意,这个 *路径操作函数* 使用的是常规的 `def` 而不是 `async def`。 |
|||
|
|||
一如既往,在 FastAPI 中你可以按需组合 `def` 和 `async def`。 |
|||
|
|||
如果需要回顾何时用哪一个,请查看关于 [`async` 和 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 的文档中的 _“急不可待?”_ 一节。 |
|||
9. 这个 *路径操作函数* 返回的不是数据类(当然也可以返回数据类),而是包含内部数据的字典列表。 |
|||
|
|||
FastAPI 会使用(包含数据类的)`response_model` 参数来转换响应。 |
|||
|
|||
2. 使用 `pydantic.dataclasses` 直接替换 `dataclasses`; |
|||
你可以将 `dataclasses` 与其它类型注解以多种不同方式组合,来构建复杂的数据结构。 |
|||
|
|||
3. `Author` 数据类包含 `Item` 数据类列表; |
|||
更多细节请参考上面代码中的内联注释提示。 |
|||
|
|||
4. `Author` 数据类用于 `response_model` 参数; |
|||
## 深入学习 { #learn-more } |
|||
|
|||
5. 其它带有数据类的标准类型注解也可以作为请求体; |
|||
你还可以把 `dataclasses` 与其它 Pydantic 模型组合、从它们继承、把它们包含到你自己的模型中等。 |
|||
|
|||
本例使用的是 `Item` 数据类列表; |
|||
想了解更多,请查看 <a href="https://docs.pydantic.dev/latest/concepts/dataclasses/" class="external-link" target="_blank">Pydantic 关于 dataclasses 的文档</a>。 |
|||
|
|||
6. 这行代码返回的是包含 `items` 的字典,`items` 是数据类列表; |
|||
## 版本 { #version } |
|||
|
|||
FastAPI 仍能把数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON; |
|||
|
|||
7. 这行代码中,`response_model` 的类型注解是 `Author` 数据类列表; |
|||
|
|||
再一次,可以把 `dataclasses` 与标准类型注解一起使用; |
|||
|
|||
8. 注意,*路径操作函数*使用的是普通函数,不是异步函数; |
|||
|
|||
与往常一样,在 FastAPI 中,可以按需组合普通函数与异步函数; |
|||
|
|||
如果不清楚何时使用异步函数或普通函数,请参阅**急不可待?**一节中对 <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank" class="internal-link">`async` 与 `await`</a> 的说明; |
|||
|
|||
9. *路径操作函数*返回的不是数据类(虽然它可以返回数据类),而是返回内含数据的字典列表; |
|||
|
|||
FastAPI 使用(包含数据类的) `response_model` 参数转换响应。 |
|||
|
|||
把 `dataclasses` 与其它类型注解组合在一起,可以组成不同形式的复杂数据结构。 |
|||
|
|||
更多内容详见上述代码内的注释。 |
|||
|
|||
## 深入学习 |
|||
|
|||
您还可以把 `dataclasses` 与其它 Pydantic 模型组合在一起,继承合并的模型,把它们包含在您自己的模型里。 |
|||
|
|||
详见 <a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/" class="external-link" target="_blank">Pydantic 官档 - 数据类</a>。 |
|||
|
|||
## 版本 |
|||
|
|||
本章内容自 FastAPI `0.67.0` 版起生效。🔖 |
|||
自 FastAPI 版本 `0.67.0` 起可用。🔖 |
|||
|
|||
@ -1,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)**: |
|||
FastAPI 会自动生成 **OpenAPI 3.1** 规范,因此你使用的任何工具都必须支持该版本。 |
|||
|
|||
<img src="/img/tutorial/generate-clients/image01.png"> |
|||
/// |
|||
|
|||
您可以看到这些模式,因为它们是用程序中的模型声明的。 |
|||
## 来自 FastAPI 赞助商的 SDK 生成器 { #sdk-generators-from-fastapi-sponsors } |
|||
|
|||
那些信息可以在应用的 **OpenAPI模式** 被找到,然后显示在API文档中(通过Swagger UI)。 |
|||
本节介绍的是由赞助 FastAPI 的公司提供的、具备**风险投资背景**或**公司支持**的方案。这些产品在高质量生成的 SDK 之上,提供了**更多特性**和**集成**。 |
|||
|
|||
OpenAPI中所包含的模型里有相同的信息可以用于 **生成客户端代码**。 |
|||
通过 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这些公司帮助确保框架及其**生态**保持健康并且**可持续**。 |
|||
|
|||
### 生成一个TypeScript 客户端 |
|||
他们的赞助也体现了对 FastAPI **社区**(也就是你)的高度承诺,不仅关注提供**优秀的服务**,也支持一个**健壮且繁荣的框架**——FastAPI。🙇 |
|||
|
|||
现在我们有了带有模型的应用,我们可以为前端生成客户端代码。 |
|||
例如,你可以尝试: |
|||
|
|||
#### 安装 `openapi-ts` |
|||
* <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> |
|||
|
|||
您可以使用以下工具在前端代码中安装 `openapi-ts`: |
|||
其中一些方案也可能是开源的或提供免费层级,你可以不花钱就先试用。其他商业 SDK 生成器也可在网上找到。🤓 |
|||
|
|||
<div class="termy"> |
|||
## 创建一个 TypeScript SDK { #create-a-typescript-sdk } |
|||
|
|||
```console |
|||
$ npm install @hey-api/openapi-ts --save-dev |
|||
先从一个简单的 FastAPI 应用开始: |
|||
|
|||
---> 100% |
|||
``` |
|||
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} |
|||
|
|||
</div> |
|||
请注意,这些*路径操作*使用 `Item` 和 `ResponseMessage` 模型来定义它们的请求载荷和响应载荷。 |
|||
|
|||
#### 生成客户端代码 |
|||
### API 文档 { #api-docs } |
|||
|
|||
要生成客户端代码,您可以使用现在将要安装的命令行应用程序 `openapi-ts`。 |
|||
访问 `/docs` 时,你会看到有用于请求发送和响应接收数据的**模式**: |
|||
|
|||
因为它安装在本地项目中,所以您可能无法直接使用此命令,但您可以将其放在 `package.json` 文件中。 |
|||
<img src="/img/tutorial/generate-clients/image01.png"> |
|||
|
|||
它可能看起来是这样的: |
|||
之所以能看到这些模式,是因为它们在应用中用模型声明了。 |
|||
|
|||
```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" |
|||
} |
|||
} |
|||
``` |
|||
这些信息会包含在应用的 **OpenAPI 模式** 中,并显示在 API 文档里。 |
|||
|
|||
在这里添加 NPM `generate-client` 脚本后,您可以使用以下命令运行它: |
|||
OpenAPI 中包含的这些模型信息就是用于**生成客户端代码**的基础。 |
|||
|
|||
<div class="termy"> |
|||
### Hey API { #hey-api } |
|||
|
|||
```console |
|||
$ npm run generate-client |
|||
当我们有了带模型的 FastAPI 应用后,可以使用 Hey API 来生成 TypeScript 客户端。最快的方式是通过 npx: |
|||
|
|||
[email protected] generate-client /home/user/code/frontend-app |
|||
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios |
|||
```sh |
|||
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client |
|||
``` |
|||
|
|||
</div> |
|||
这会在 `./src/client` 生成一个 TypeScript SDK。 |
|||
|
|||
此命令将在 `./src/client` 中生成代码,并将在其内部使用 `axios`(前端HTTP库)。 |
|||
你可以在其官网了解如何<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 |
|||
/// tip | 提示 |
|||
|
|||
请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。 |
|||
请注意 `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 规范 { #preprocess-the-openapi-specification-for-the-client-generator } |
|||
|
|||
### 预处理用于客户端生成器的OpenAPI规范 |
|||
生成的代码中仍有一些**重复信息**。 |
|||
|
|||
生成的代码仍然存在一些**重复的信息**。 |
|||
我们已经知道这个方法与 **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_py39.py *} |
|||
|
|||
//// tab | Node.js |
|||
|
|||
```Javascript |
|||
{!> ../../docs_src/generate_clients/tutorial004.js!} |
|||
``` |
|||
|
|||
{* ../../docs_src/generate_clients/tutorial004.py *} |
|||
//// |
|||
|
|||
通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。 |
|||
这样,操作 ID 会从 `items-get_items` 之类的名字重命名为 `get_items`,从而让客户端生成器生成更简洁的方法名。 |
|||
|
|||
### 使用预处理的OpenAPI生成TypeScript客户端 |
|||
### 使用预处理后的 OpenAPI 生成 TypeScript 客户端 { #generate-a-typescript-client-with-the-preprocessed-openapi } |
|||
|
|||
现在,由于最终结果保存在文件openapi.json中,你可以修改 package.json 文件以使用此本地文件,例如: |
|||
因为最终结果现在保存在 `openapi.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" |
|||
} |
|||
} |
|||
```sh |
|||
npx @hey-api/openapi-ts -i ./openapi.json -o src/client |
|||
``` |
|||
|
|||
生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能: |
|||
生成新客户端后,你将拥有**简洁的方法名**,并具备**自动补全**、**内联错误**等功能: |
|||
|
|||
<img src="/img/tutorial/generate-clients/image08.png"> |
|||
|
|||
## 优点 |
|||
## 优点 { #benefits } |
|||
|
|||
当使用自动生成的客户端时,你将获得以下的自动补全功能: |
|||
使用自动生成的客户端时,你会获得以下内容的**自动补全**: |
|||
|
|||
* 方法。 |
|||
* 请求体中的数据、查询参数等。 |
|||
* 响应数据。 |
|||
* 方法 |
|||
* 请求体中的数据、查询参数等 |
|||
* 响应数据 |
|||
|
|||
你还将获得针对所有内容的错误提示。 |
|||
你还会为所有内容获得**内联错误**。 |
|||
|
|||
每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*将作为方法可用,旧的方法将被删除,并且其他任何更改将反映在生成的代码中。 🤓 |
|||
每当你更新后端代码并**重新生成**前端时,新的*路径操作*会作为方法可用,旧的方法会被移除,其他任何更改都会反映到生成的代码中。🤓 |
|||
|
|||
这也意味着如果有任何更改,它将自动**反映**在客户端代码中。如果你**构建**客户端,在使用的数据上存在**不匹配**时,它将报错。 |
|||
这也意味着如果有任何变更,它会自动**反映**到客户端代码中。而当你**构建**客户端时,如果所用数据存在任何**不匹配**,它会直接报错。 |
|||
|
|||
因此,你将在开发周期的早期**检测到许多错误**,而不必等待错误在生产环境中向最终用户展示,然后尝试调试问题所在。 ✨ |
|||
因此,你可以在开发周期的早期就**发现许多错误**,而不必等到错误在生产环境中暴露给最终用户后再去调试问题所在。✨ |
|||
|
|||
@ -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-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: |
|||
## 直接返回 `Response` { #return-a-response-directly } |
|||
|
|||
你也可以在直接返回 `Response` 时添加头部。 |
|||
|
|||
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} |
|||
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: |
|||
|
|||
{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} |
|||
|
|||
/// note | 技术细节 |
|||
|
|||
你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 |
|||
你也可以使用 `from starlette.responses import Response` 或 `from starlette.responses import JSONResponse`。 |
|||
|
|||
**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。 |
|||
**FastAPI** 提供了与 `fastapi.responses` 相同的 `starlette.responses`,只是为了方便你(开发者)。但是,大多数可用的响应都直接来自 Starlette。 |
|||
|
|||
由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。 |
|||
由于 `Response` 经常用于设置头部和 cookies,**FastAPI** 还在 `fastapi.Response` 中提供了它。 |
|||
|
|||
/// |
|||
|
|||
## 自定义头部 |
|||
## 自定义头部 { #custom-headers } |
|||
|
|||
请注意,可以使用'X-'前缀添加自定义专有头部。 |
|||
请注意,可以通过<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">使用 `X-` 前缀</a>添加自定义专有头部。 |
|||
|
|||
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette的CORS文档</a>中记录的`expose_headers`参数。 |
|||
但是,如果你有自定义头部,并希望浏览器中的客户端能够看到它们,你需要将它们添加到你的 CORS 配置中(在 [CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank} 中阅读更多),使用在 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette 的 CORS 文档</a>中记录的 `expose_headers` 参数。 |
|||
|
|||
@ -1,274 +1,274 @@ |
|||
# OAuth2 作用域 |
|||
# OAuth2 作用域 { #oauth2-scopes } |
|||
|
|||
**FastAPI** 无缝集成 OAuth2 作用域(`Scopes`),可以直接使用。 |
|||
你可以在 **FastAPI** 中直接使用 OAuth2 作用域(Scopes),它们已无缝集成。 |
|||
|
|||
作用域是更精密的权限系统,遵循 OAuth2 标准,与 OpenAPI 应用(和 API 自动文档)集成。 |
|||
这样你就可以按照 OAuth2 标准,构建更精细的权限系统,并将其集成进你的 OpenAPI 应用(以及 API 文档)中。 |
|||
|
|||
OAuth2 也是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制。这些身份验证应用在用户登录应用时使用 OAuth2 提供指定权限。 |
|||
带作用域的 OAuth2 是很多大型身份验证提供商使用的机制,例如 Facebook、Google、GitHub、Microsoft、X (Twitter) 等。它们用它来为用户和应用授予特定权限。 |
|||
|
|||
脸书、谷歌、GitHub、微软、推特就是 OAuth2 作用域登录。 |
|||
每次你“使用” Facebook、Google、GitHub、Microsoft、X (Twitter) “登录”时,该应用就在使用带作用域的 OAuth2。 |
|||
|
|||
本章介绍如何在 **FastAPI** 应用中使用 OAuth2 作用域管理验证与授权。 |
|||
本节将介绍如何在你的 **FastAPI** 应用中,使用相同的带作用域的 OAuth2 管理认证与授权。 |
|||
|
|||
/// warning | 警告 |
|||
|
|||
本章内容较难,刚接触 FastAPI 的新手可以跳过。 |
|||
本节内容相对进阶,如果你刚开始,可以先跳过。 |
|||
|
|||
OAuth2 作用域不是必需的,没有它,您也可以处理身份验证与授权。 |
|||
你并不一定需要 OAuth2 作用域,你也可以用你自己的方式处理认证与授权。 |
|||
|
|||
但 OAuth2 作用域与 API(通过 OpenAPI)及 API 文档集成地更好。 |
|||
但带作用域的 OAuth2 能很好地集成进你的 API(通过 OpenAPI)和 API 文档。 |
|||
|
|||
不管怎么说,**FastAPI** 支持在代码中使用作用域或其它安全/授权需求项。 |
|||
不过,无论如何,你都可以在代码中按需强制这些作用域,或任何其它安全/授权需求。 |
|||
|
|||
很多情况下,OAuth2 作用域就像一把牛刀。 |
|||
很多情况下,带作用域的 OAuth2 可能有点“大材小用”。 |
|||
|
|||
但如果您确定要使用作用域,或对它有兴趣,请继续阅读。 |
|||
但如果你确实需要它,或者只是好奇,请继续阅读。 |
|||
|
|||
/// |
|||
|
|||
## OAuth2 作用域与 OpenAPI |
|||
## OAuth2 作用域与 OpenAPI { #oauth2-scopes-and-openapi } |
|||
|
|||
OAuth2 规范的**作用域**是由空格分割的字符串组成的列表。 |
|||
OAuth2 规范将“作用域”定义为由空格分隔的字符串列表。 |
|||
|
|||
这些字符串支持任何格式,但不能包含空格。 |
|||
这些字符串的内容可以是任意格式,但不应包含空格。 |
|||
|
|||
作用域表示的是**权限**。 |
|||
这些作用域表示“权限”。 |
|||
|
|||
OpenAPI 中(例如 API 文档)可以定义**安全方案**。 |
|||
在 OpenAPI(例如 API 文档)中,你可以定义“安全方案”(security schemes)。 |
|||
|
|||
这些安全方案在使用 OAuth2 时,还可以声明和使用作用域。 |
|||
当这些安全方案使用 OAuth2 时,你还可以声明并使用作用域。 |
|||
|
|||
**作用域**只是(不带空格的)字符串。 |
|||
每个“作用域”只是一个(不带空格的)字符串。 |
|||
|
|||
常用于声明特定安全权限,例如: |
|||
它们通常用于声明特定的安全权限,例如: |
|||
|
|||
* 常见用例为,`users:read` 或 `users:write` |
|||
* 脸书和 Instagram 使用 `instagram_basic` |
|||
* 谷歌使用 `https://www.googleapis.com/auth/drive` |
|||
* 常见示例:`users:read` 或 `users:write` |
|||
* Facebook / Instagram 使用 `instagram_basic` |
|||
* Google 使用 `https://www.googleapis.com/auth/drive` |
|||
|
|||
/// info | 说明 |
|||
/// info | 信息 |
|||
|
|||
OAuth2 中,**作用域**只是声明特定权限的字符串。 |
|||
在 OAuth2 中,“作用域”只是一个声明所需特定权限的字符串。 |
|||
|
|||
是否使用冒号 `:` 等符号,或是不是 URL 并不重要。 |
|||
是否包含像 `:` 这样的字符,或者是不是一个 URL,并不重要。 |
|||
|
|||
这些细节只是特定的实现方式。 |
|||
这些细节取决于具体实现。 |
|||
|
|||
对 OAuth2 来说,它们都只是字符串而已。 |
|||
对 OAuth2 而言,它们都只是字符串。 |
|||
|
|||
/// |
|||
|
|||
## 全局纵览 |
|||
## 全局纵览 { #global-view } |
|||
|
|||
首先,快速浏览一下以下代码与**用户指南**中 [OAuth2 实现密码哈希与 Bearer JWT 令牌验证](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}一章中代码的区别。以下代码使用 OAuth2 作用域: |
|||
首先,让我们快速看看与**用户指南**中 [OAuth2 实现密码(含哈希)、Bearer + JWT 令牌](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 示例相比有哪些变化。现在开始使用 OAuth2 作用域: |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:124,128:134,139,153] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} |
|||
|
|||
下面,我们逐步说明修改的代码内容。 |
|||
下面我们逐步回顾这些更改。 |
|||
|
|||
## OAuth2 安全方案 |
|||
## OAuth2 安全方案 { #oauth2-security-scheme } |
|||
|
|||
第一个修改的地方是,使用两个作用域 `me` 和 `items ` 声明 OAuth2 安全方案。 |
|||
第一个变化是:我们在声明 OAuth2 安全方案时,添加了两个可用的作用域 `me` 和 `items`。 |
|||
|
|||
`scopes` 参数接收**字典**,键是作用域、值是作用域的描述: |
|||
参数 `scopes` 接收一个 `dict`,以作用域为键、描述为值: |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[62:65] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} |
|||
|
|||
因为声明了作用域,所以登录或授权时会在 API 文档中显示。 |
|||
因为我们现在声明了这些作用域,所以当你登录/授权时,它们会显示在 API 文档里。 |
|||
|
|||
此处,选择给予访问权限的作用域: `me` 和 `items`。 |
|||
你可以选择要授予访问权限的作用域:`me` 和 `items`。 |
|||
|
|||
这也是使用脸书、谷歌、GitHub 登录时的授权机制。 |
|||
这与使用 Facebook、Google、GitHub 等登录时授予权限的机制相同: |
|||
|
|||
<img src="/img/tutorial/security/image11.png"> |
|||
|
|||
## JWT 令牌作用域 |
|||
## 带作用域的 JWT 令牌 { #jwt-token-with-scopes } |
|||
|
|||
现在,修改令牌*路径操作*,返回请求的作用域。 |
|||
现在,修改令牌的*路径操作*以返回请求的作用域。 |
|||
|
|||
此处仍然使用 `OAuth2PasswordRequestForm`。它包含类型为**字符串列表**的 `scopes` 属性,且`scopes` 属性中包含要在请求里接收的每个作用域。 |
|||
我们仍然使用 `OAuth2PasswordRequestForm`。它包含 `scopes` 属性,其值是 `list[str]`,包含请求中接收到的每个作用域。 |
|||
|
|||
这样,返回的 JWT 令牌中就包含了作用域。 |
|||
我们把这些作用域作为 JWT 令牌的一部分返回。 |
|||
|
|||
/// danger | 危险 |
|||
|
|||
为了简明起见,本例把接收的作用域直接添加到了令牌里。 |
|||
为简单起见,此处我们只是把接收到的作用域直接添加到了令牌中。 |
|||
|
|||
但在您的应用中,为了安全,应该只把作用域添加到确实需要作用域的用户,或预定义的用户。 |
|||
但在你的应用里,为了安全起见,你应该只添加该用户实际能够拥有的作用域,或你预先定义的作用域。 |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[153] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} |
|||
|
|||
## 在*路径操作*与依赖项中声明作用域 |
|||
## 在*路径操作*与依赖项中声明作用域 { #declare-scopes-in-path-operations-and-dependencies } |
|||
|
|||
接下来,为*路径操作* `/users/me/items/` 声明作用域 `items`。 |
|||
现在我们声明,路径操作 `/users/me/items/` 需要作用域 `items`。 |
|||
|
|||
为此,要从 `fastapi` 中导入并使用 `Security` 。 |
|||
为此,从 `fastapi` 导入并使用 `Security`。 |
|||
|
|||
`Security` 声明依赖项的方式和 `Depends` 一样,但 `Security` 还能接收作用域(字符串)列表类型的参数 `scopes`。 |
|||
你可以用 `Security` 来声明依赖(就像 `Depends` 一样),但 `Security` 还接收一个 `scopes` 参数,其值是作用域(字符串)列表。 |
|||
|
|||
此处使用与 `Depends` 相同的方式,把依赖项函数 `get_current_active_user` 传递给 `Security`。 |
|||
在这里,我们把依赖函数 `get_current_active_user` 传给 `Security`(就像用 `Depends` 一样)。 |
|||
|
|||
同时,还传递了作用域**列表**,本例中只传递了一个作用域:`items`(此处支持传递更多作用域)。 |
|||
同时还传入一个作用域 `list`,此处仅包含一个作用域:`items`(也可以包含更多)。 |
|||
|
|||
依赖项函数 `get_current_active_user` 还能声明子依赖项,不仅可以使用 `Depends`,也可以使用 `Security`。声明子依赖项函数(`get_current_user`)及更多作用域。 |
|||
依赖函数 `get_current_active_user` 也可以声明子依赖,不仅可以用 `Depends`,也可以用 `Security`。它声明了自己的子依赖函数(`get_current_user`),并添加了更多的作用域需求。 |
|||
|
|||
本例要求使用作用域 `me`(还可以使用更多作用域)。 |
|||
在这个例子里,它需要作用域 `me`(也可以需要多个作用域)。 |
|||
|
|||
/// note | 笔记 |
|||
/// note | 注意 |
|||
|
|||
不必在不同位置添加不同的作用域。 |
|||
|
|||
本例使用的这种方式只是为了展示 **FastAPI** 如何处理在不同层级声明的作用域。 |
|||
这里这样做,是为了演示 **FastAPI** 如何处理在不同层级声明的作用域。 |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[4,139,166] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} |
|||
|
|||
/// info | 技术细节 |
|||
|
|||
`Security` 实际上是 `Depends` 的子类,而且只比 `Depends` 多一个参数。 |
|||
`Security` 实际上是 `Depends` 的子类,它只多了一个我们稍后会看到的参数。 |
|||
|
|||
但使用 `Security` 代替 `Depends`,**FastAPI** 可以声明安全作用域,并在内部使用这些作用域,同时,使用 OpenAPI 存档 API。 |
|||
但当你使用 `Security` 而不是 `Depends` 时,**FastAPI** 会知道它可以声明安全作用域,在内部使用它们,并用 OpenAPI 文档化 API。 |
|||
|
|||
但实际上,从 `fastapi` 导入的 `Query`、`Path`、`Depends`、`Security` 等对象,只是返回特殊类的函数。 |
|||
另外,从 `fastapi` 导入的 `Query`、`Path`、`Depends`、`Security` 等,实际上都是返回特殊类的函数。 |
|||
|
|||
/// |
|||
|
|||
## 使用 `SecurityScopes` |
|||
## 使用 `SecurityScopes` { #use-securityscopes } |
|||
|
|||
修改依赖项 `get_current_user`。 |
|||
现在更新依赖项 `get_current_user`。 |
|||
|
|||
这是上面的依赖项使用的依赖项。 |
|||
上面那些依赖会用到它。 |
|||
|
|||
这里使用的也是之前创建的 OAuth2 方案,并把它声明为依赖项:`oauth2_scheme`。 |
|||
这里我们使用之前创建的同一个 OAuth2 方案,并把它声明为依赖:`oauth2_scheme`。 |
|||
|
|||
该依赖项函数本身不需要作用域,因此,可以使用 `Depends` 和 `oauth2_scheme`。不需要指定安全作用域时,不必使用 `Security`。 |
|||
因为这个依赖函数本身没有任何作用域需求,所以我们可以用 `Depends(oauth2_scheme)`,当不需要指定安全作用域时,不必使用 `Security`。 |
|||
|
|||
此处还声明了从 `fastapi.security` 导入的 `SecurityScopes` 类型的特殊参数。 |
|||
我们还声明了一个从 `fastapi.security` 导入的特殊参数 `SecurityScopes` 类型。 |
|||
|
|||
`SecuriScopes` 类与 `Request` 类似(`Request` 用于直接提取请求对象)。 |
|||
这个 `SecurityScopes` 类类似于 `Request`(`Request` 用来直接获取请求对象)。 |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[8,105] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} |
|||
|
|||
## 使用 `scopes` |
|||
## 使用 `scopes` { #use-the-scopes } |
|||
|
|||
参数 `security_scopes` 的类型是 `SecurityScopes`。 |
|||
|
|||
它的属性 `scopes` 是作用域列表,所有依赖项都把它作为子依赖项。也就是说所有**依赖**……这听起来有些绕,后文会有解释。 |
|||
它会有一个 `scopes` 属性,包含一个列表,里面是它自身以及所有把它作为子依赖的依赖项所需要的所有作用域。也就是说,所有“依赖者”……这可能有点绕,下面会再次解释。 |
|||
|
|||
(类 `SecurityScopes` 的)`security_scopes` 对象还提供了单字符串类型的属性 `scope_str`,该属性是(要在本例中使用的)用空格分割的作用域。 |
|||
`security_scopes` 对象(类型为 `SecurityScopes`)还提供了一个 `scope_str` 属性,它是一个用空格分隔这些作用域的单个字符串(我们将会用到它)。 |
|||
|
|||
此处还创建了后续代码中要复用(`raise`)的 `HTTPException` 。 |
|||
我们创建一个 `HTTPException`,后面可以在多个位置复用(`raise`)它。 |
|||
|
|||
该异常包含了作用域所需的(如有),以空格分割的字符串(使用 `scope_str`)。该字符串要放到包含作用域的 `WWW-Authenticate` 请求头中(这也是规范的要求)。 |
|||
在这个异常中,我们包含所需的作用域(如果有的话),以空格分隔的字符串(使用 `scope_str`)。我们把这个包含作用域的字符串放在 `WWW-Authenticate` 响应头中(这是规范要求的一部分)。 |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[105,107:115] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} |
|||
|
|||
## 校验 `username` 与数据形状 |
|||
## 校验 `username` 与数据形状 { #verify-the-username-and-data-shape } |
|||
|
|||
我们可以校验是否获取了 `username`,并抽取作用域。 |
|||
我们校验是否获取到了 `username`,并提取作用域。 |
|||
|
|||
然后,使用 Pydantic 模型校验数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或使用 Pydantic 模型验证数据时出错,就会触发之前创建的 `HTTPException` 异常。 |
|||
然后使用 Pydantic 模型验证这些数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或用 Pydantic 验证数据时出错,就抛出我们之前创建的 `HTTPException`。 |
|||
|
|||
对此,要使用新的属性 `scopes` 更新 Pydantic 模型 `TokenData`。 |
|||
为此,我们给 Pydantic 模型 `TokenData` 添加了一个新属性 `scopes`。 |
|||
|
|||
使用 Pydantic 验证数据可以确保数据中含有由作用域组成的**字符串列表**,以及 `username` 字符串等内容。 |
|||
通过用 Pydantic 验证数据,我们可以确保确实得到了例如一个由作用域组成的 `list[str]`,以及一个 `str` 类型的 `username`。 |
|||
|
|||
反之,如果使用**字典**或其它数据结构,就有可能在后面某些位置破坏应用,形成安全隐患。 |
|||
而不是,例如得到一个 `dict` 或其它什么,这可能会在后续某个时刻破坏应用,形成安全风险。 |
|||
|
|||
还可以使用用户名验证用户,如果没有用户,也会触发之前创建的异常。 |
|||
我们还验证是否存在该用户名的用户,如果没有,就抛出前面创建的同一个异常。 |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[46,116:127] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} |
|||
|
|||
## 校验 `scopes` |
|||
## 校验 `scopes` { #verify-the-scopes } |
|||
|
|||
接下来,校验所有依赖项和依赖要素(包括*路径操作*)所需的作用域。这些作用域包含在令牌的 `scopes` 里,如果不在其中就会触发 `HTTPException` 异常。 |
|||
现在我们要验证,这个依赖以及所有依赖者(包括*路径操作*)所需的所有作用域,是否都包含在接收到的令牌里的作用域中,否则就抛出 `HTTPException`。 |
|||
|
|||
为此,要使用包含所有作用域**字符串列表**的 `security_scopes.scopes`, 。 |
|||
为此,我们使用 `security_scopes.scopes`,它包含一个由这些作用域组成的 `list[str]`。 |
|||
|
|||
{* ../../docs_src/security/tutorial005.py hl[128:134] *} |
|||
{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} |
|||
|
|||
## 依赖项树与作用域 |
|||
## 依赖树与作用域 { #dependency-tree-and-scopes } |
|||
|
|||
再次查看这个依赖项树与作用域。 |
|||
再次回顾这个依赖树与作用域。 |
|||
|
|||
`get_current_active_user` 依赖项包含子依赖项 `get_current_user`,并在 `get_current_active_user`中声明了作用域 `"me"` 包含所需作用域列表 ,在 `security_scopes.scopes` 中传递给 `get_current_user`。 |
|||
由于 `get_current_active_user` 依赖把 `get_current_user` 作为子依赖,因此在 `get_current_active_user` 中声明的作用域 `"me"` 会被包含在传给 `get_current_user` 的 `security_scopes.scopes` 所需作用域列表中。 |
|||
|
|||
*路径操作*自身也声明了作用域,`"items"`,这也是 `security_scopes.scopes` 列表传递给 `get_current_user` 的。 |
|||
*路径操作*本身也声明了一个作用域 `"items"`,它也会包含在传给 `get_current_user` 的 `security_scopes.scopes` 列表中。 |
|||
|
|||
依赖项与作用域的层级架构如下: |
|||
依赖与作用域的层级结构如下: |
|||
|
|||
* *路径操作* `read_own_items` 包含: |
|||
* 依赖项所需的作用域 `["items"]`: |
|||
* `get_current_active_user`: |
|||
* 依赖项函数 `get_current_active_user` 包含: |
|||
* 所需的作用域 `"me"` 包含依赖项: |
|||
* `get_current_user`: |
|||
* 依赖项函数 `get_current_user` 包含: |
|||
* 没有作用域需求其自身 |
|||
* 依赖项使用 `oauth2_scheme` |
|||
* `security_scopes` 参数的类型是 `SecurityScopes`: |
|||
* `security_scopes` 参数的属性 `scopes` 是包含上述声明的所有作用域的**列表**,因此: |
|||
* `security_scopes.scopes` 包含用于*路径操作*的 `["me", "items"]` |
|||
* `security_scopes.scopes` 包含*路径操作* `read_users_me` 的 `["me"]`,因为它在依赖项里被声明 |
|||
* `security_scopes.scopes` 包含用于*路径操作* `read_system_status` 的 `[]`(空列表),并且它的依赖项 `get_current_user` 也没有声明任何 `scope` |
|||
* 带有依赖的必需作用域 `["items"]`: |
|||
* `get_current_active_user`: |
|||
* 依赖函数 `get_current_active_user` 包含: |
|||
* 带有依赖的必需作用域 `["me"]`: |
|||
* `get_current_user`: |
|||
* 依赖函数 `get_current_user` 包含: |
|||
* 自身不需要任何作用域。 |
|||
* 一个使用 `oauth2_scheme` 的依赖。 |
|||
* 一个类型为 `SecurityScopes` 的 `security_scopes` 参数: |
|||
* 该 `security_scopes` 参数有一个 `scopes` 属性,它是一个包含上面所有已声明作用域的 `list`,因此: |
|||
* 对于*路径操作* `read_own_items`,`security_scopes.scopes` 将包含 `["me", "items"]`。 |
|||
* 对于*路径操作* `read_users_me`,`security_scopes.scopes` 将包含 `["me"]`,因为它在依赖 `get_current_active_user` 中被声明。 |
|||
* 对于*路径操作* `read_system_status`,`security_scopes.scopes` 将包含 `[]`(空列表),因为它既没有声明任何带 `scopes` 的 `Security`,其依赖 `get_current_user` 也没有声明任何 `scopes`。 |
|||
|
|||
/// tip | 提示 |
|||
|
|||
此处重要且**神奇**的事情是,`get_current_user` 检查每个*路径操作*时可以使用不同的 `scopes` 列表。 |
|||
这里重要且“神奇”的地方是,`get_current_user` 在检查每个*路径操作*时会得到不同的 `scopes` 列表。 |
|||
|
|||
所有这些都依赖于在每个*路径操作*和指定*路径操作*的依赖树中的每个依赖项。 |
|||
这一切都取决于为该特定*路径操作*在其自身以及依赖树中的每个依赖里声明的 `scopes`。 |
|||
|
|||
/// |
|||
|
|||
## `SecurityScopes` 的更多细节 |
|||
## 关于 `SecurityScopes` 的更多细节 { #more-details-about-securityscopes } |
|||
|
|||
您可以任何位置或多个位置使用 `SecurityScopes`,不一定非得在**根**依赖项中使用。 |
|||
你可以在任意位置、多个位置使用 `SecurityScopes`,不一定非得在“根”依赖里。 |
|||
|
|||
它总是在当前 `Security` 依赖项中和所有依赖因子对于**特定** *路径操作*和**特定**依赖树中安全作用域 |
|||
它总会包含当前 `Security` 依赖中以及所有依赖者在“该特定”*路径操作*和“该特定”依赖树里声明的安全作用域。 |
|||
|
|||
因为 `SecurityScopes` 包含所有由依赖项声明的作用域,可以在核心依赖函数中用它验证所需作用域的令牌,然后再在不同的*路径操作*中声明不同作用域需求。 |
|||
因为 `SecurityScopes` 会包含依赖者声明的所有作用域,你可以在一个核心依赖函数里用它验证令牌是否具有所需作用域,然后在不同的*路径操作*里声明不同的作用域需求。 |
|||
|
|||
它们会为每个*路径操作*进行单独检查。 |
|||
它们会针对每个*路径操作*分别检查。 |
|||
|
|||
## 查看文档 |
|||
## 查看文档 { #check-it } |
|||
|
|||
打开 API 文档,进行身份验证,并指定要授权的作用域。 |
|||
打开 API 文档,你可以进行身份验证,并指定要授权的作用域。 |
|||
|
|||
<img src="/img/tutorial/security/image11.png"> |
|||
|
|||
没有选择任何作用域,也可以进行**身份验证**,但访问 `/uses/me` 或 `/users/me/items` 时,会显示没有足够的权限。但仍可以访问 `/status/`。 |
|||
如果你不选择任何作用域,你依然会“通过认证”,但当你访问 `/users/me/` 或 `/users/me/items/` 时,会收到一个错误,提示你没有足够的权限。你仍然可以访问 `/status/`。 |
|||
|
|||
如果选择了作用域 `me`,但没有选择作用域 `items`,则可以访问 `/users/me/`,但不能访问 `/users/me/items`。 |
|||
如果你选择了作用域 `me`,但没有选择作用域 `items`,你可以访问 `/users/me/`,但不能访问 `/users/me/items/`。 |
|||
|
|||
这就是通过用户提供的令牌使用第三方应用访问这些*路径操作*时会发生的情况,具体怎样取决于用户授予第三方应用的权限。 |
|||
当第三方应用使用用户提供的令牌访问这些*路径操作*时,也会发生同样的情况,取决于用户授予该应用了多少权限。 |
|||
|
|||
## 关于第三方集成 |
|||
## 关于第三方集成 { #about-third-party-integrations } |
|||
|
|||
本例使用 OAuth2 **密码**流。 |
|||
在这个示例中我们使用的是 OAuth2 的“password”流。 |
|||
|
|||
这种方式适用于登录我们自己的应用,最好使用我们自己的前端。 |
|||
当我们登录自己的应用(很可能还有我们自己的前端)时,这是合适的。 |
|||
|
|||
因为我们能控制自己的前端应用,可以信任它接收 `username` 与 `password`。 |
|||
因为我们可以信任它来接收 `username` 和 `password`,毕竟我们掌控它。 |
|||
|
|||
但如果构建的是连接其它应用的 OAuth2 应用,比如具有与脸书、谷歌、GitHub 相同功能的第三方身份验证应用。那您就应该使用其它安全流。 |
|||
但如果你在构建一个 OAuth2 应用,让其它应用来连接(也就是说,你在构建等同于 Facebook、Google、GitHub 等的身份验证提供商),你应该使用其它的流。 |
|||
|
|||
最常用的是隐式流。 |
|||
最常见的是隐式流(implicit flow)。 |
|||
|
|||
最安全的是代码流,但实现起来更复杂,而且需要更多步骤。因为它更复杂,很多第三方身份验证应用最终建议使用隐式流。 |
|||
最安全的是代码流(authorization code flow),但实现更复杂,需要更多步骤。也因为更复杂,很多提供商最终会建议使用隐式流。 |
|||
|
|||
/// note | 笔记 |
|||
/// note | 注意 |
|||
|
|||
每个身份验证应用都会采用不同方式会命名流,以便融合入自己的品牌。 |
|||
每个身份验证提供商常常会用不同的方式给它们的流命名,以融入自己的品牌。 |
|||
|
|||
但归根结底,它们使用的都是 OAuth2 标准。 |
|||
但归根结底,它们实现的都是同一个 OAuth2 标准。 |
|||
|
|||
/// |
|||
|
|||
**FastAPI** 的 `fastapi.security.oauth2` 里包含了所有 OAuth2 身份验证流工具。 |
|||
**FastAPI** 在 `fastapi.security.oauth2` 中为所有这些 OAuth2 身份验证流提供了工具。 |
|||
|
|||
## 装饰器 `dependencies` 中的 `Security` |
|||
## 装饰器 `dependencies` 中的 `Security` { #security-in-decorator-dependencies } |
|||
|
|||
同样,您可以在装饰器的 `dependencies` 参数中定义 `Depends` 列表,(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank})),也可以把 `scopes` 与 `Security` 一起使用。 |
|||
就像你可以在装饰器的 `dependencies` 参数中定义 `Depends` 的 `list`(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}),你也可以在那儿配合 `Security` 使用 `scopes`。 |
|||
|
|||
@ -1,190 +1,94 @@ |
|||
# 设置和环境变量 |
|||
# 设置和环境变量 { #settings-and-environment-variables } |
|||
|
|||
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。 |
|||
在许多情况下,你的应用可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等。 |
|||
|
|||
这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。 |
|||
这些设置中的大多数是可变的(可能会改变),例如数据库 URL。并且很多可能是敏感的,比如密钥。 |
|||
|
|||
因此,通常会将它们提供为由应用程序读取的环境变量。 |
|||
|
|||
## 环境变量 |
|||
/// tip | 提示 |
|||
|
|||
/// tip |
|||
|
|||
如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。 |
|||
要理解环境变量,你可以阅读[环境变量](../environment-variables.md){.internal-link target=_blank}。 |
|||
|
|||
/// |
|||
|
|||
环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。 |
|||
|
|||
您可以在 shell 中创建和使用环境变量,而无需使用 Python: |
|||
|
|||
//// tab | Linux、macOS、Windows Bash |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// 您可以创建一个名为 MY_NAME 的环境变量 |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// 然后您可以与其他程序一起使用它,例如 |
|||
$ echo "Hello $MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
//// tab | Windows PowerShell |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// 创建一个名为 MY_NAME 的环境变量 |
|||
$ $Env:MY_NAME = "Wade Wilson" |
|||
|
|||
// 与其他程序一起使用它,例如 |
|||
$ echo "Hello $Env:MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
//// |
|||
|
|||
### 在 Python 中读取环境变量 |
|||
|
|||
您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。 |
|||
|
|||
例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容: |
|||
|
|||
```Python hl_lines="3" |
|||
import os |
|||
## 类型和验证 { #types-and-validation } |
|||
|
|||
name = os.getenv("MY_NAME", "World") |
|||
print(f"Hello {name} from Python") |
|||
``` |
|||
这些环境变量只能处理文本字符串,因为它们在 Python 之外,并且必须与其他程序及系统的其余部分兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 |
|||
|
|||
/// tip |
|||
这意味着,在 Python 中从环境变量读取的任何值都是 `str` 类型,任何到不同类型的转换或任何验证都必须在代码中完成。 |
|||
|
|||
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的第二个参数是要返回的默认值。 |
|||
## Pydantic 的 `Settings` { #pydantic-settings } |
|||
|
|||
如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。 |
|||
幸运的是,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 } |
|||
|
|||
然后,您可以调用该 Python 程序: |
|||
首先,确保你创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装 `pydantic-settings` 包: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// 这里我们还没有设置环境变量 |
|||
$ python main.py |
|||
|
|||
// 因为我们没有设置环境变量,所以我们得到默认值 |
|||
|
|||
Hello World from Python |
|||
|
|||
// 但是如果我们先创建一个环境变量 |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// 然后再次调用程序 |
|||
$ python main.py |
|||
|
|||
// 现在它可以读取环境变量 |
|||
|
|||
Hello Wade Wilson from Python |
|||
$ pip install pydantic-settings |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。 |
|||
|
|||
|
|||
|
|||
您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。 |
|||
|
|||
要做到这一点,在程序本身之前的同一行创建它: |
|||
当你用以下方式安装 `all` 扩展时,它也会被一并安装: |
|||
|
|||
<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 |
|||
$ pip install "fastapi[all]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip |
|||
|
|||
您可以在 <a href="https://12factor.net/config" class="external-link" target="_blank">Twelve-Factor App: Config</a> 中阅读更多相关信息。 |
|||
|
|||
/// |
|||
|
|||
### 类型和验证 |
|||
|
|||
这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 |
|||
### 创建 `Settings` 对象 { #create-the-settings-object } |
|||
|
|||
这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。 |
|||
从 Pydantic 导入 `BaseSettings` 并创建一个子类,这与创建 Pydantic 模型非常相似。 |
|||
|
|||
## Pydantic 的 `Settings` |
|||
与 Pydantic 模型一样,用类型注解声明类属性,也可以指定默认值。 |
|||
|
|||
幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>。 |
|||
你可以使用与 Pydantic 模型相同的验证功能和工具,例如不同的数据类型,以及使用 `Field()` 进行附加验证。 |
|||
|
|||
### 创建 `Settings` 对象 |
|||
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} |
|||
|
|||
从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。 |
|||
/// tip | 提示 |
|||
|
|||
与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。 |
|||
|
|||
您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 |
|||
|
|||
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} |
|||
|
|||
/// tip |
|||
|
|||
如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。 |
|||
如果你想要一个可以快速复制粘贴的示例,请不要使用这个示例,使用下面最后一个示例。 |
|||
|
|||
/// |
|||
|
|||
然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。 |
|||
当你创建该 `Settings` 类的实例(此处是 `settings` 对象)时,Pydantic 会以不区分大小写的方式读取环境变量,因此,大写变量 `APP_NAME` 仍会用于属性 `app_name`。 |
|||
|
|||
然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 |
|||
接着它会转换并验证数据。因此,当你使用该 `settings` 对象时,你将获得你声明的类型的数据(例如 `items_per_user` 将是 `int`)。 |
|||
|
|||
### 使用 `settings` |
|||
### 使用 `settings` { #use-the-settings } |
|||
|
|||
然后,您可以在应用程序中使用新的 `settings` 对象: |
|||
然后你可以在应用中使用新的 `settings` 对象: |
|||
|
|||
{* ../../docs_src/settings/tutorial001.py hl[18:20] *} |
|||
{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} |
|||
|
|||
### 运行服务器 |
|||
### 运行服务器 { #run-the-server } |
|||
|
|||
接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示: |
|||
接下来,运行服务器,并把配置作为环境变量传入,例如你可以设置 `ADMIN_EMAIL` 和 `APP_NAME`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp"uvicorn main:app |
|||
$ ADMIN_EMAIL="[email protected]" 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="[email protected]" 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="[email protected]" |
|||
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-technical-details } |
|||
|
|||
#### `lru_cache` 技术细节 |
|||
`@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,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,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,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 |
|||
|
|||
这里我们使用 <a href="https://docs.python.org/3/library/time.html#time.perf_counter" class="external-link" target="_blank">`time.perf_counter()`</a> 而不是 `time.time()`,因为在这类场景中它可能更精确。🤓 |
|||
|
|||
/// |
|||
|
|||
## 多个中间件的执行顺序 { #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 User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}阅读更多关于中间件的教程. |
|||
你可以稍后在[高级用户指南:高级中间件](../advanced/middleware.md){.internal-link target=_blank}中阅读更多关于其他中间件的内容。 |
|||
|
|||
你将在下一节中学习如何使用中间件处理 <abbr title="Cross-Origin Resource Sharing">CORS</abbr> . |
|||
你将在下一节中了解如何使用中间件处理 <abbr title="Cross-Origin Resource Sharing - 跨域资源共享">CORS</abbr>。 |
|||
|
|||
@ -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,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,添加你自己的自定义额外信息。 |
|||
|
|||
例如,你可以用它为前端用户界面添加元数据等。 |
|||
|
|||
/// |
|||
|
|||
/// info | 信息 |
|||
|
|||
/// warning |
|||
OpenAPI 3.1.0(自 FastAPI 0.99.0 起使用)增加了对 `examples` 的支持,它是 JSON Schema 标准的一部分。 |
|||
|
|||
请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。 |
|||
在此之前,只支持使用单个示例的关键字 `example`。OpenAPI 3.1.0 仍然支持它,但它已被弃用,并不属于 JSON Schema 标准。因此,建议你把 `example` 迁移到 `examples`。🤓 |
|||
|
|||
你可以在本页末尾阅读更多内容。 |
|||
|
|||
/// |
|||
|
|||
## `Body` 额外参数 |
|||
## `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()` |
|||
|
|||
你可以通过传递额外信息给 `Field` 同样的方式操作`Path`, `Query`, `Body`等。 |
|||
你也可以声明一组 `examples`,这些带有附加信息的示例将被添加到它们在 OpenAPI 中的 JSON Schema 里。 |
|||
|
|||
比如,你可以将请求体的一个 `example` 传递给 `Body`: |
|||
### 带有 `examples` 的 `Body` { #body-with-examples } |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *} |
|||
这里我们向 `Body()` 传入 `examples`,其中包含一个期望的数据示例: |
|||
|
|||
## 文档 UI 中的例子 |
|||
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} |
|||
|
|||
使用上面的任何方法,它在 `/docs` 中看起来都是这样的: |
|||
### 文档 UI 中的示例 { #example-in-the-docs-ui } |
|||
|
|||
使用上述任一方法,在 `/docs` 中看起来会是这样: |
|||
|
|||
<img src="/img/tutorial/body-fields/image01.png"> |
|||
|
|||
## 技术细节 |
|||
### 带有多个 `examples` 的 `Body` { #body-with-multiple-examples } |
|||
|
|||
当然,你也可以传入多个 `examples`: |
|||
|
|||
{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} |
|||
|
|||
这样做时,这些示例会成为该请求体数据内部 JSON Schema 的一部分。 |
|||
|
|||
不过,在<abbr title="2023-08-26">撰写本文时</abbr>,用于展示文档 UI 的 Swagger UI 并不支持显示 JSON Schema 中数据的多个示例。但请继续阅读,下面有一种变通方法。 |
|||
|
|||
### OpenAPI 特定的 `examples` { #openapi-specific-examples } |
|||
|
|||
在 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 中。 |
|||
|
|||
关于 `example` 和 `examples`... |
|||
### Swagger UI 与 OpenAPI 特定的 `examples` { #swagger-ui-and-openapi-specific-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`. |
|||
此前,由于 Swagger UI 不支持多个 JSON Schema 示例(截至 2023-08-26),用户无法在文档中展示多个示例。 |
|||
|
|||
所以 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). |
|||
为了解决这个问题,FastAPI `0.103.0` 通过新增参数 `openapi_examples`,为声明同样的旧式 OpenAPI 特定 `examples` 字段提供了支持。🤓 |
|||
|
|||
所以,虽然 `example` 不是JSON Schema的一部分,但它是OpenAPI的一部分,这将被文档UI使用。 |
|||
### 总结 { #summary } |
|||
|
|||
## 其他信息 |
|||
我曾经说我不太喜欢历史……结果现在在这儿上“技术史”课。😅 |
|||
|
|||
同样的方法,你可以添加你自己的额外信息,这些信息将被添加到每个模型的JSON模式中,例如定制前端用户界面,等等。 |
|||
简而言之,升级到 FastAPI 0.99.0 或更高版本,一切会更简单、一致、直观,你也不必了解这些历史细节。😎 |
|||
|
|||
@ -1,40 +1,40 @@ |
|||
# 静态文件 |
|||
# 静态文件 { #static-files } |
|||
|
|||
您可以使用 `StaticFiles`从目录中自动提供静态文件。 |
|||
你可以使用 `StaticFiles` 从目录中自动提供静态文件。 |
|||
|
|||
## 使用`StaticFiles` |
|||
## 使用 `StaticFiles` { #use-staticfiles } |
|||
|
|||
* 导入`StaticFiles`。 |
|||
* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。 |
|||
* 导入 `StaticFiles`。 |
|||
* 将一个 `StaticFiles()` 实例“挂载”(Mount)到指定路径。 |
|||
|
|||
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} |
|||
{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} |
|||
|
|||
/// note | 技术细节 |
|||
/// note | 注意 |
|||
|
|||
你也可以用 `from starlette.staticfiles import StaticFiles`。 |
|||
|
|||
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。 |
|||
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles`,只是为了方便你这个开发者。但它确实直接来自 Starlette。 |
|||
|
|||
/// |
|||
|
|||
### 什么是"挂载"(Mounting) |
|||
### 什么是“挂载”(Mounting) { #what-is-mounting } |
|||
|
|||
"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。 |
|||
“挂载”表示在特定路径添加一个完全“独立”的应用,然后负责处理所有子路径。 |
|||
|
|||
这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。 |
|||
这与使用 `APIRouter` 不同,因为挂载的应用是完全独立的。主应用的 OpenAPI 和文档不会包含已挂载应用的任何内容,等等。 |
|||
|
|||
你可以在**高级用户指南**中了解更多。 |
|||
你可以在[高级用户指南](../advanced/index.md){.internal-link target=_blank}中了解更多。 |
|||
|
|||
## 细节 |
|||
## 细节 { #details } |
|||
|
|||
这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。 |
|||
第一个 `"/static"` 指的是这个“子应用”将被“挂载”到的子路径。因此,任何以 `"/static"` 开头的路径都会由它处理。 |
|||
|
|||
`directory="static"` 指向包含你的静态文件的目录名字。 |
|||
`directory="static"` 指的是包含你的静态文件的目录名称。 |
|||
|
|||
`name="static"` 提供了一个能被**FastAPI**内部使用的名字。 |
|||
`name="static"` 为它提供了一个可被 **FastAPI** 内部使用的名称。 |
|||
|
|||
所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。 |
|||
这些参数都可以不是“`static`”,请根据你的应用需求和具体细节进行调整。 |
|||
|
|||
## 更多信息 |
|||
## 更多信息 { #more-info } |
|||
|
|||
更多细节和选择查阅 <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette's docs about Static Files</a>. |
|||
更多细节和选项请查阅 <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette 的静态文件文档</a>。 |
|||
|
|||
Loading…
Reference in new issue