committed by
GitHub
913 changed files with 38689 additions and 39376 deletions
@ -0,0 +1,61 @@ |
|||||
|
# Advanced Python Types { #advanced-python-types } |
||||
|
|
||||
|
Here are some additional ideas that might be useful when working with Python types. |
||||
|
|
||||
|
## Using `Union` or `Optional` { #using-union-or-optional } |
||||
|
|
||||
|
If your code for some reason can't use `|`, for example if it's not in a type annotation but in something like `response_model=`, instead of using the vertical bar (`|`) you can use `Union` from `typing`. |
||||
|
|
||||
|
For example, you could declare that something could be a `str` or `None`: |
||||
|
|
||||
|
```python |
||||
|
from typing import Union |
||||
|
|
||||
|
|
||||
|
def say_hi(name: Union[str, None]): |
||||
|
print(f"Hi {name}!") |
||||
|
``` |
||||
|
|
||||
|
`typing` also has a shortcut to declare that something could be `None`, with `Optional`. |
||||
|
|
||||
|
Here's a tip from my very **subjective** point of view: |
||||
|
|
||||
|
* 🚨 Avoid using `Optional[SomeType]` |
||||
|
* Instead ✨ **use `Union[SomeType, None]`** ✨. |
||||
|
|
||||
|
Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required. |
||||
|
|
||||
|
I think `Union[SomeType, None]` is more explicit about what it means. |
||||
|
|
||||
|
It's just about the words and names. But those words can affect how you and your teammates think about the code. |
||||
|
|
||||
|
As an example, let's take this function: |
||||
|
|
||||
|
```python |
||||
|
from typing import Optional |
||||
|
|
||||
|
|
||||
|
def say_hi(name: Optional[str]): |
||||
|
print(f"Hey {name}!") |
||||
|
``` |
||||
|
|
||||
|
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter: |
||||
|
|
||||
|
```Python |
||||
|
say_hi() # Oh, no, this throws an error! 😱 |
||||
|
``` |
||||
|
|
||||
|
The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value: |
||||
|
|
||||
|
```Python |
||||
|
say_hi(name=None) # This works, None is valid 🎉 |
||||
|
``` |
||||
|
|
||||
|
The good news is, in most cases, you will be able to simply use `|` to define unions of types: |
||||
|
|
||||
|
```python |
||||
|
def say_hi(name: str | None): |
||||
|
print(f"Hey {name}!") |
||||
|
``` |
||||
|
|
||||
|
So, normally you don't have to worry about names like `Optional` and `Union`. 😎 |
||||
@ -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 |
```Python |
||||
checker(q="somequery") |
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 | 提示 |
/// 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"> |
<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_py310.py hl[1,4,7:10,13:16,22:24,27] *} |
||||
{!../../docs_src/dataclasses_/tutorial003.py!} |
|
||||
``` |
|
||||
|
|
||||
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 版本 `0.67.0` 起可用。🔖 |
||||
|
|
||||
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` 版起生效。🔖 |
|
||||
|
|||||
@ -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] *} |
FastAPI 会自动生成 **OpenAPI 3.1** 规范,因此你使用的任何工具都必须支持该版本。 |
||||
|
|
||||
请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item` 和 `ResponseMessage`。 |
|
||||
|
|
||||
### API 文档 |
|
||||
|
|
||||
如果您访问API文档,您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**: |
|
||||
|
|
||||
<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 |
先从一个简单的 FastAPI 应用开始: |
||||
$ npm install @hey-api/openapi-ts --save-dev |
|
||||
|
|
||||
---> 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" |
这些信息会包含在应用的 **OpenAPI 模式** 中,并显示在 API 文档里。 |
||||
{ |
|
||||
"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` 脚本后,您可以使用以下命令运行它: |
OpenAPI 中包含的这些模型信息就是用于**生成客户端代码**的基础。 |
||||
|
|
||||
<div class="termy"> |
### Hey API { #hey-api } |
||||
|
|
||||
```console |
当我们有了带模型的 FastAPI 应用后,可以使用 Hey API 来生成 TypeScript 客户端。最快的方式是通过 npx: |
||||
$ npm run generate-client |
|
||||
|
|
||||
[email protected] generate-client /home/user/code/frontend-app |
```sh |
||||
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios |
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/image02.png"> |
||||
|
|
||||
您还将自动补全要发送的数据: |
要发送的载荷也会有自动补全: |
||||
|
|
||||
<img src="/img/tutorial/generate-clients/image03.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"> |
<img src="/img/tutorial/generate-clients/image04.png"> |
||||
|
|
||||
响应(response)对象也拥有自动补全: |
响应对象同样有自动补全: |
||||
|
|
||||
<img src="/img/tutorial/generate-clients/image05.png"> |
<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] *} |
{* ../../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"> |
<img src="/img/tutorial/generate-clients/image06.png"> |
||||
|
|
||||
在这个案例中,您有: |
在这个例子中,你会有: |
||||
|
|
||||
* `ItemsService` |
* `ItemsService` |
||||
* `UsersService` |
* `UsersService` |
||||
|
|
||||
### 客户端方法名称 |
### 客户端方法名 { #client-method-names } |
||||
|
|
||||
现在生成的方法名像 `createItemItemsPost` 看起来不太简洁: |
现在,像 `createItemItemsPost` 这样的生成方法名看起来不太简洁: |
||||
|
|
||||
```TypeScript |
```TypeScript |
||||
ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) |
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] *} |
{* ../../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"> |
<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" |
```sh |
||||
{ |
npx @hey-api/openapi-ts -i ./openapi.json -o src/client |
||||
"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" |
|
||||
} |
|
||||
} |
|
||||
``` |
``` |
||||
|
|
||||
生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能: |
生成新客户端后,你将拥有**简洁的方法名**,并具备**自动补全**、**内联错误**等功能: |
||||
|
|
||||
<img src="/img/tutorial/generate-clients/image08.png"> |
<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},并且假设你已经知晓其中主要思想。 |
接下来的章节默认你已经读过它,并理解其中的核心概念。 |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue