Browse Source
* Update all * 🎨 Auto format * Add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>pull/14922/head
committed by
GitHub
96 changed files with 2504 additions and 515 deletions
@ -0,0 +1,755 @@ |
|||
# LLM 测试文件 { #llm-test-file } |
|||
|
|||
本文用于测试用于翻译文档的 <abbr title="Large Language Model - 大型语言模型">LLM</abbr> 是否理解 `scripts/translate.py` 中的 `general_prompt` 以及 `docs/{language code}/llm-prompt.md` 中的语言特定提示。语言特定提示会追加到 `general_prompt` 之后。 |
|||
|
|||
这里添加的测试会被所有语言特定提示的设计者看到。 |
|||
|
|||
用法如下: |
|||
|
|||
* 准备语言特定提示——`docs/{language code}/llm-prompt.md`。 |
|||
* 将本文重新翻译为你的目标语言(例如使用 `translate.py` 的 `translate-page` 命令)。这会在 `docs/{language code}/docs/_llm-test.md` 下创建翻译。 |
|||
* 检查翻译是否正确。 |
|||
* 如有需要,改进你的语言特定提示、通用提示,或英文文档。 |
|||
* 然后手动修正翻译中剩余的问题,确保这是一个优秀的译文。 |
|||
* 重新翻译,在已有的优秀译文基础上进行。理想情况是 LLM 不再对译文做任何更改。这意味着通用提示和你的语言特定提示已经尽可能完善(有时它仍会做一些看似随机的改动,原因是<a href="https://doublespeak.chat/#/handbook#deterministic-output" class="external-link" target="_blank">LLM 不是确定性算法</a>)。 |
|||
|
|||
测试如下: |
|||
|
|||
## 代码片段 { #code-snippets } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
这是一个代码片段:`foo`。这是另一个代码片段:`bar`。还有一个:`baz quux`。 |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
代码片段的内容应保持不变。 |
|||
|
|||
参见 `scripts/translate.py` 中通用提示的 `### Content of code snippets` 部分。 |
|||
|
|||
//// |
|||
|
|||
## 引号 { #quotes } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
昨天,我的朋友写道:"如果你把 incorrectly 拼对了,你就把它拼错了"。我回答:"没错,但 'incorrectly' 错的不是 '"incorrectly"'"。 |
|||
|
|||
/// note | 注意 |
|||
|
|||
LLM 很可能会把这段翻错。我们只关心在重新翻译时它是否能保持修正后的译文。 |
|||
|
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
提示词设计者可以选择是否将中性引号转换为排版引号。也可以保持不变。 |
|||
|
|||
例如参见 `docs/de/llm-prompt.md` 中的 `### Quotes` 部分。 |
|||
|
|||
//// |
|||
|
|||
## 代码片段中的引号 { #quotes-in-code-snippets } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
`pip install "foo[bar]"` |
|||
|
|||
代码片段中的字符串字面量示例:`"this"`,`'that'`。 |
|||
|
|||
一个较难的字符串字面量示例:`f"I like {'oranges' if orange else "apples"}"` |
|||
|
|||
硬核:`Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
... 但是,代码片段内的引号必须保持不变。 |
|||
|
|||
//// |
|||
|
|||
## 代码块 { #code-blocks } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
一个 Bash 代码示例... |
|||
|
|||
```bash |
|||
# 向宇宙打印问候 |
|||
echo "Hello universe" |
|||
``` |
|||
|
|||
...以及一个控制台代码示例... |
|||
|
|||
```console |
|||
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u> |
|||
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting server |
|||
Searching for package file structure |
|||
``` |
|||
|
|||
...以及另一个控制台代码示例... |
|||
|
|||
```console |
|||
// 创建目录 "code" |
|||
$ mkdir code |
|||
// 切换到该目录 |
|||
$ cd code |
|||
``` |
|||
|
|||
...以及一个 Python 代码示例... |
|||
|
|||
```Python |
|||
wont_work() # 这不会起作用 😱 |
|||
works(foo="bar") # 这可行 🎉 |
|||
``` |
|||
|
|||
...就这些。 |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
代码块中的代码不应被修改,注释除外。 |
|||
|
|||
参见 `scripts/translate.py` 中通用提示的 `### Content of code blocks` 部分。 |
|||
|
|||
//// |
|||
|
|||
## 选项卡与彩色提示框 { #tabs-and-colored-boxes } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
/// info | 信息 |
|||
Some text |
|||
/// |
|||
|
|||
/// note | 注意 |
|||
Some text |
|||
/// |
|||
|
|||
/// note | 技术细节 |
|||
Some text |
|||
/// |
|||
|
|||
/// check | 检查 |
|||
Some text |
|||
/// |
|||
|
|||
/// tip | 提示 |
|||
Some text |
|||
/// |
|||
|
|||
/// warning | 警告 |
|||
Some text |
|||
/// |
|||
|
|||
/// danger | 危险 |
|||
Some text |
|||
/// |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
选项卡以及 `Info`/`Note`/`Warning`/等提示块,应在竖线(`|`)后添加其标题的翻译。 |
|||
|
|||
参见 `scripts/translate.py` 中通用提示的 `### Special blocks` 与 `### Tab blocks` 部分。 |
|||
|
|||
//// |
|||
|
|||
## Web 与内部链接 { #web-and-internal-links } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
链接文本应被翻译,链接地址应保持不变: |
|||
|
|||
* [链接到上面的标题](#code-snippets) |
|||
* [内部链接](index.md#installation){.internal-link target=_blank} |
|||
* <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">外部链接</a> |
|||
* <a href="https://fastapi.tiangolo.com/css/styles.css" class="external-link" target="_blank">样式链接</a> |
|||
* <a href="https://fastapi.tiangolo.com/js/logic.js" class="external-link" target="_blank">脚本链接</a> |
|||
* <a href="https://fastapi.tiangolo.com/img/foo.jpg" class="external-link" target="_blank">图片链接</a> |
|||
|
|||
链接文本应被翻译,且链接地址应指向对应的译文页面: |
|||
|
|||
* <a href="https://fastapi.tiangolo.com/zh/" class="external-link" target="_blank">FastAPI 链接</a> |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
链接的文本应被翻译,但地址保持不变。唯一的例外是指向 FastAPI 文档页面的绝对链接,此时应指向对应语言的译文。 |
|||
|
|||
参见 `scripts/translate.py` 中通用提示的 `### Links` 部分。 |
|||
|
|||
//// |
|||
|
|||
## HTML "abbr" 元素 { #html-abbr-elements } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
这里有一些包裹在 HTML "abbr" 元素中的内容(有些是虚构的): |
|||
|
|||
### abbr 提供了完整短语 { #the-abbr-gives-a-full-phrase } |
|||
|
|||
* <abbr title="Getting Things Done - 尽管去做">GTD</abbr> |
|||
* <abbr title="less than - 小于"><code>lt</code></abbr> |
|||
* <abbr title="XML Web Token - XML Web 令牌">XWT</abbr> |
|||
* <abbr title="Parallel Server Gateway Interface - 并行服务器网关接口">PSGI</abbr> |
|||
|
|||
### abbr 提供了完整短语与解释 { #the-abbr-gives-a-full-phrase-and-an-explanation } |
|||
|
|||
* <abbr title="Mozilla Developer Network - Mozilla 开发者网络: 为开发者编写的文档,由 Firefox 团队撰写">MDN</abbr> |
|||
* <abbr title="Input/Output - 输入/输出: 磁盘读写,网络通信。">I/O</abbr>. |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
"abbr" 元素的 "title" 属性需要按照特定规则进行翻译。 |
|||
|
|||
译文可以自行添加 "abbr" 元素以解释英语单词,LLM 不应删除这些元素。 |
|||
|
|||
参见 `scripts/translate.py` 中通用提示的 `### HTML abbr elements` 部分。 |
|||
|
|||
//// |
|||
|
|||
## HTML "dfn" 元素 { #html-dfn-elements } |
|||
|
|||
* <dfn title="配置为以某种方式连接并协同工作的机器组">集群</dfn> |
|||
* <dfn title="一种使用具有多个隐藏层的人工神经网络的机器学习方法,从输入层到输出层构建了完整的内部结构">深度学习</dfn> |
|||
|
|||
## 标题 { #headings } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
### 开发 Web 应用——教程 { #develop-a-webapp-a-tutorial } |
|||
|
|||
Hello. |
|||
|
|||
### 类型提示与注解 { #type-hints-and-annotations } |
|||
|
|||
Hello again. |
|||
|
|||
### 超类与子类 { #super-and-subclasses } |
|||
|
|||
Hello again. |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
关于标题的唯一硬性规则是:LLM 必须保持花括号内的哈希部分不变,以确保链接不会失效。 |
|||
|
|||
参见 `scripts/translate.py` 中通用提示的 `### Headings` 部分。 |
|||
|
|||
语言特定的说明可参见例如 `docs/de/llm-prompt.md` 中的 `### Headings` 部分。 |
|||
|
|||
//// |
|||
|
|||
## 文档中使用的术语 { #terms-used-in-the-docs } |
|||
|
|||
//// tab | 测试 |
|||
|
|||
* you |
|||
* your |
|||
|
|||
* e.g. |
|||
* etc. |
|||
|
|||
* `foo` as an `int` |
|||
* `bar` as a `str` |
|||
* `baz` as a `list` |
|||
|
|||
* the Tutorial - User guide |
|||
* the Advanced User Guide |
|||
* the SQLModel docs |
|||
* the API docs |
|||
* the automatic docs |
|||
|
|||
* Data Science |
|||
* Deep Learning |
|||
* Machine Learning |
|||
* Dependency Injection |
|||
* HTTP Basic authentication |
|||
* HTTP Digest |
|||
* ISO format |
|||
* the JSON Schema standard |
|||
* the JSON schema |
|||
* the schema definition |
|||
* Password Flow |
|||
* Mobile |
|||
|
|||
* deprecated |
|||
* designed |
|||
* invalid |
|||
* on the fly |
|||
* standard |
|||
* default |
|||
* case-sensitive |
|||
* case-insensitive |
|||
|
|||
* to serve the application |
|||
* to serve the page |
|||
|
|||
* the app |
|||
* the application |
|||
|
|||
* the request |
|||
* the response |
|||
* the error response |
|||
|
|||
* the path operation |
|||
* the path operation decorator |
|||
* the path operation function |
|||
|
|||
* the body |
|||
* the request body |
|||
* the response body |
|||
* the JSON body |
|||
* the form body |
|||
* the file body |
|||
* the function body |
|||
|
|||
* the parameter |
|||
* the body parameter |
|||
* the path parameter |
|||
* the query parameter |
|||
* the cookie parameter |
|||
* the header parameter |
|||
* the form parameter |
|||
* the function parameter |
|||
|
|||
* the event |
|||
* the startup event |
|||
* the startup of the server |
|||
* the shutdown event |
|||
* the lifespan event |
|||
|
|||
* the handler |
|||
* the event handler |
|||
* the exception handler |
|||
* to handle |
|||
|
|||
* the model |
|||
* the Pydantic model |
|||
* the data model |
|||
* the database model |
|||
* the form model |
|||
* the model object |
|||
|
|||
* the class |
|||
* the base class |
|||
* the parent class |
|||
* the subclass |
|||
* the child class |
|||
* the sibling class |
|||
* the class method |
|||
|
|||
* the header |
|||
* the headers |
|||
* the authorization header |
|||
* the `Authorization` header |
|||
* the forwarded header |
|||
|
|||
* the dependency injection system |
|||
* the dependency |
|||
* the dependable |
|||
* the dependant |
|||
|
|||
* I/O bound |
|||
* CPU bound |
|||
* concurrency |
|||
* parallelism |
|||
* multiprocessing |
|||
|
|||
* the env var |
|||
* the environment variable |
|||
* the `PATH` |
|||
* the `PATH` variable |
|||
|
|||
* the authentication |
|||
* the authentication provider |
|||
* the authorization |
|||
* the authorization form |
|||
* the authorization provider |
|||
* the user authenticates |
|||
* the system authenticates the user |
|||
|
|||
* the CLI |
|||
* the command line interface |
|||
|
|||
* the server |
|||
* the client |
|||
|
|||
* the cloud provider |
|||
* the cloud service |
|||
|
|||
* the development |
|||
* the development stages |
|||
|
|||
* the dict |
|||
* the dictionary |
|||
* the enumeration |
|||
* the enum |
|||
* the enum member |
|||
|
|||
* the encoder |
|||
* the decoder |
|||
* to encode |
|||
* to decode |
|||
|
|||
* the exception |
|||
* to raise |
|||
|
|||
* the expression |
|||
* the statement |
|||
|
|||
* the frontend |
|||
* the backend |
|||
|
|||
* the GitHub discussion |
|||
* the GitHub issue |
|||
|
|||
* the performance |
|||
* the performance optimization |
|||
|
|||
* the return type |
|||
* the return value |
|||
|
|||
* the security |
|||
* the security scheme |
|||
|
|||
* the task |
|||
* the background task |
|||
* the task function |
|||
|
|||
* the template |
|||
* the template engine |
|||
|
|||
* the type annotation |
|||
* the type hint |
|||
|
|||
* the server worker |
|||
* the Uvicorn worker |
|||
* the Gunicorn Worker |
|||
* the worker process |
|||
* the worker class |
|||
* the workload |
|||
|
|||
* the deployment |
|||
* to deploy |
|||
|
|||
* the SDK |
|||
* the software development kit |
|||
|
|||
* the `APIRouter` |
|||
* the `requirements.txt` |
|||
* the Bearer Token |
|||
* the breaking change |
|||
* the bug |
|||
* the button |
|||
* the callable |
|||
* the code |
|||
* the commit |
|||
* the context manager |
|||
* the coroutine |
|||
* the database session |
|||
* the disk |
|||
* the domain |
|||
* the engine |
|||
* the fake X |
|||
* the HTTP GET method |
|||
* the item |
|||
* the library |
|||
* the lifespan |
|||
* the lock |
|||
* the middleware |
|||
* the mobile application |
|||
* the module |
|||
* the mounting |
|||
* the network |
|||
* the origin |
|||
* the override |
|||
* the payload |
|||
* the processor |
|||
* the property |
|||
* the proxy |
|||
* the pull request |
|||
* the query |
|||
* the RAM |
|||
* the remote machine |
|||
* the status code |
|||
* the string |
|||
* the tag |
|||
* the web framework |
|||
* the wildcard |
|||
* to return |
|||
* to validate |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息 |
|||
|
|||
这是一份不完整且非规范性的(主要是)技术术语清单,取自文档中常见的词汇。它可能有助于提示词设计者判断哪些术语需要对 LLM 提供额外指引。例如当它总是把一个好的译法改回次优译法,或在你的语言中对某个术语的词形变化有困难时。 |
|||
|
|||
参见例如 `docs/de/llm-prompt.md` 中的 `### List of English terms and their preferred German translations` 部分。 |
|||
|
|||
//// |
|||
|
|||
//// |
|||
|
|||
翻译(术语)对照: |
|||
|
|||
//// tab | 测试(译文) |
|||
|
|||
* 你 |
|||
* 你的 |
|||
|
|||
* 例如 |
|||
* 等等 |
|||
|
|||
* 将 `foo` 作为 `int` |
|||
* 将 `bar` 作为 `str` |
|||
* 将 `baz` 作为 `list` |
|||
|
|||
* 教程 - 用户指南 |
|||
* 高级用户指南 |
|||
* SQLModel 文档 |
|||
* API 文档 |
|||
* 自动文档 |
|||
|
|||
* 数据科学 |
|||
* 深度学习 |
|||
* 机器学习 |
|||
* 依赖注入 |
|||
* HTTP 基本认证 |
|||
* HTTP 摘要认证 |
|||
* ISO 格式 |
|||
* JSON Schema 标准 |
|||
* JSON 模式 |
|||
* 模式定义 |
|||
* 密码流 |
|||
* 移动端 |
|||
|
|||
* 已弃用 |
|||
* 设计的 |
|||
* 无效 |
|||
* 即时 |
|||
* 标准的 |
|||
* 默认的 |
|||
* 区分大小写 |
|||
* 不区分大小写 |
|||
|
|||
* 为应用提供服务 |
|||
* 为页面提供服务 |
|||
|
|||
* 应用 |
|||
* 应用程序 |
|||
|
|||
* 请求 |
|||
* 响应 |
|||
* 错误响应 |
|||
|
|||
* 路径操作 |
|||
* 路径操作装饰器 |
|||
* 路径操作函数 |
|||
|
|||
* 主体 |
|||
* 请求体 |
|||
* 响应体 |
|||
* JSON 体 |
|||
* 表单体 |
|||
* 文件体 |
|||
* 函数体 |
|||
|
|||
* 参数 |
|||
* 请求体参数 |
|||
* 路径参数 |
|||
* 查询参数 |
|||
* Cookie 参数 |
|||
* Header 参数 |
|||
* 表单参数 |
|||
* 函数参数 |
|||
|
|||
* 事件 |
|||
* 启动事件 |
|||
* 服务器的启动 |
|||
* 关闭事件 |
|||
* 生命周期事件 |
|||
|
|||
* 处理器 |
|||
* 事件处理器 |
|||
* 异常处理器 |
|||
* 处理 |
|||
|
|||
* 模型 |
|||
* Pydantic 模型 |
|||
* 数据模型 |
|||
* 数据库模型 |
|||
* 表单模型 |
|||
* 模型对象 |
|||
|
|||
* 类 |
|||
* 基类 |
|||
* 父类 |
|||
* 子类 |
|||
* 子类 |
|||
* 兄弟类 |
|||
* 类方法 |
|||
|
|||
* 请求头 |
|||
* 请求头 |
|||
* 授权头 |
|||
* `Authorization` 头 |
|||
* 转发头 |
|||
|
|||
* 依赖注入系统 |
|||
* 依赖 |
|||
* 可依赖对象 |
|||
* 依赖项 |
|||
|
|||
* I/O 受限 |
|||
* CPU 受限 |
|||
* 并发 |
|||
* 并行 |
|||
* 多进程 |
|||
|
|||
* 环境变量 |
|||
* 环境变量 |
|||
* `PATH` |
|||
* `PATH` 变量 |
|||
|
|||
* 认证 |
|||
* 认证提供方 |
|||
* 授权 |
|||
* 授权表单 |
|||
* 授权提供方 |
|||
* 用户进行认证 |
|||
* 系统对用户进行认证 |
|||
|
|||
* CLI |
|||
* 命令行界面 |
|||
|
|||
* 服务器 |
|||
* 客户端 |
|||
|
|||
* 云服务提供商 |
|||
* 云服务 |
|||
|
|||
* 开发 |
|||
* 开发阶段 |
|||
|
|||
* dict |
|||
* 字典 |
|||
* 枚举 |
|||
* 枚举 |
|||
* 枚举成员 |
|||
|
|||
* 编码器 |
|||
* 解码器 |
|||
* 编码 |
|||
* 解码 |
|||
|
|||
* 异常 |
|||
* 抛出 |
|||
|
|||
* 表达式 |
|||
* 语句 |
|||
|
|||
* 前端 |
|||
* 后端 |
|||
|
|||
* GitHub 讨论 |
|||
* GitHub Issue |
|||
|
|||
* 性能 |
|||
* 性能优化 |
|||
|
|||
* 返回类型 |
|||
* 返回值 |
|||
|
|||
* 安全 |
|||
* 安全方案 |
|||
|
|||
* 任务 |
|||
* 后台任务 |
|||
* 任务函数 |
|||
|
|||
* 模板 |
|||
* 模板引擎 |
|||
|
|||
* 类型注解 |
|||
* 类型提示 |
|||
|
|||
* 服务器 worker |
|||
* Uvicorn worker |
|||
* Gunicorn worker |
|||
* worker 进程 |
|||
* worker 类 |
|||
* 工作负载 |
|||
|
|||
* 部署 |
|||
* 部署 |
|||
|
|||
* SDK |
|||
* 软件开发工具包 |
|||
|
|||
* `APIRouter` |
|||
* `requirements.txt` |
|||
* Bearer Token |
|||
* 破坏性变更 |
|||
* Bug |
|||
* 按钮 |
|||
* 可调用对象 |
|||
* 代码 |
|||
* 提交 |
|||
* 上下文管理器 |
|||
* 协程 |
|||
* 数据库会话 |
|||
* 磁盘 |
|||
* 域名 |
|||
* 引擎 |
|||
* 假 X |
|||
* HTTP GET 方法 |
|||
* 项 |
|||
* 库 |
|||
* 生命周期 |
|||
* 锁 |
|||
* 中间件 |
|||
* 移动应用 |
|||
* 模块 |
|||
* 挂载 |
|||
* 网络 |
|||
* 源 |
|||
* 覆盖 |
|||
* 负载 |
|||
* 处理器 |
|||
* 属性 |
|||
* 代理 |
|||
* Pull Request |
|||
* 查询 |
|||
* RAM |
|||
* 远程机器 |
|||
* 状态码 |
|||
* 字符串 |
|||
* 标签 |
|||
* Web 框架 |
|||
* 通配符 |
|||
* 返回 |
|||
* 校验 |
|||
|
|||
//// |
|||
|
|||
//// tab | 信息(译文) |
|||
|
|||
此清单是不完整且非规范性的,列出(主要是)文档中出现的技术术语。它有助于提示词设计者确定哪些术语需要额外的指引。例如当 LLM 总是把更好的译法改回次优译法,或在你的语言中难以正确变形时。 |
|||
|
|||
也可参见 `docs/de/llm-prompt.md` 中的 `### List of English terms and their preferred German translations` 部分。 |
|||
|
|||
//// |
|||
@ -0,0 +1,3 @@ |
|||
# 关于 { #about } |
|||
|
|||
关于 FastAPI、其设计、灵感等。🤓 |
|||
@ -0,0 +1,61 @@ |
|||
# 高级 Python 类型 { #advanced-python-types } |
|||
|
|||
这里有一些在使用 Python 类型时可能有用的额外想法。 |
|||
|
|||
## 使用 `Union` 或 `Optional` { #using-union-or-optional } |
|||
|
|||
如果你的代码因为某些原因不能使用 `|`,例如它不是在类型注解里,而是在 `response_model=` 之类的参数中,那么你可以使用 `typing` 中的 `Union` 来代替竖线(`|`)。 |
|||
|
|||
例如,你可以声明某个值可以是 `str` 或 `None`: |
|||
|
|||
```python |
|||
from typing import Union |
|||
|
|||
|
|||
def say_hi(name: Union[str, None]): |
|||
print(f"Hi {name}!") |
|||
``` |
|||
|
|||
`typing` 也提供了一个声明“可能为 `None`”的快捷方式:`Optional`。 |
|||
|
|||
从我非常主观的角度给个小建议: |
|||
|
|||
- 🚨 避免使用 `Optional[SomeType]` |
|||
- 改用 ✨`Union[SomeType, None]`✨。 |
|||
|
|||
两者是等价的,底层其实也是一样的。但我更推荐使用 `Union` 而不是 `Optional`,因为单词“optional”(可选)看起来会暗示该值是可选的,而它真正的含义是“它可以是 `None`”,即使它并不是可选的,仍然是必填的。 |
|||
|
|||
我认为 `Union[SomeType, None]` 更能明确表达其含义。 |
|||
|
|||
这只是关于词语和命名的问题,但这些词语会影响你和你的队友如何看待代码。 |
|||
|
|||
举个例子,看这段函数: |
|||
|
|||
```python |
|||
from typing import Optional |
|||
|
|||
|
|||
def say_hi(name: Optional[str]): |
|||
print(f"Hey {name}!") |
|||
``` |
|||
|
|||
参数 `name` 被定义为 `Optional[str]`,但它并不是“可选”的,你不能不传这个参数就调用函数: |
|||
|
|||
```Python |
|||
say_hi() # 哎呀,这会报错!😱 |
|||
``` |
|||
|
|||
参数 `name` 仍然是必填的(不是“可选”),因为它没有默认值。不过,`name` 接受 `None` 作为取值: |
|||
|
|||
```Python |
|||
say_hi(name=None) # 这样可以,None 是有效的 🎉 |
|||
``` |
|||
|
|||
好消息是,在大多数情况下,你可以直接使用 `|` 来定义类型联合: |
|||
|
|||
```python |
|||
def say_hi(name: str | None): |
|||
print(f"Hey {name}!") |
|||
``` |
|||
|
|||
因此,通常你不必为像 `Optional` 和 `Union` 这样的名字而操心。😎 |
|||
@ -54,7 +54,7 @@ $ pip install "fastapi[all]" |
|||
|
|||
你可以使用与 Pydantic 模型相同的验证功能和工具,例如不同的数据类型,以及使用 `Field()` 进行附加验证。 |
|||
|
|||
{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} |
|||
{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
@ -70,7 +70,7 @@ $ pip install "fastapi[all]" |
|||
|
|||
然后你可以在应用中使用新的 `settings` 对象: |
|||
|
|||
{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} |
|||
{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} |
|||
|
|||
### 运行服务器 { #run-the-server } |
|||
|
|||
@ -100,19 +100,19 @@ $ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.p |
|||
|
|||
## 在另一个模块中放置设置 { #settings-in-another-module } |
|||
|
|||
你可以把这些设置放在另一个模块文件中,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 |
|||
你可以把这些设置放在另一个模块文件中,就像你在[更大的应用 - 多个文件](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 |
|||
|
|||
例如,可以有一个 `config.py` 文件: |
|||
|
|||
{* ../../docs_src/settings/app01_py39/config.py *} |
|||
{* ../../docs_src/settings/app01_py310/config.py *} |
|||
|
|||
然后在 `main.py` 文件中使用它: |
|||
|
|||
{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} |
|||
{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
你还需要一个 `__init__.py` 文件,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 |
|||
你还需要一个 `__init__.py` 文件,就像你在[更大的应用 - 多个文件](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 |
|||
|
|||
/// |
|||
|
|||
@ -126,7 +126,7 @@ $ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.p |
|||
|
|||
延续上一个示例,你的 `config.py` 文件可能如下所示: |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} |
|||
{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} |
|||
|
|||
注意,现在我们不再创建默认实例 `settings = Settings()`。 |
|||
|
|||
@ -134,7 +134,7 @@ $ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.p |
|||
|
|||
现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} |
|||
{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
@ -144,15 +144,15 @@ $ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.p |
|||
|
|||
/// |
|||
|
|||
然后我们可以在“路径操作函数”中将其作为依赖项引入,并在需要的任何地方使用它。 |
|||
然后我们可以在路径操作函数中将其作为依赖项引入,并在需要的任何地方使用它。 |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} |
|||
{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} |
|||
|
|||
### 设置与测试 { #settings-and-testing } |
|||
|
|||
接着,在测试期间,通过为 `get_settings` 创建依赖项覆盖,就可以很容易地提供一个不同的设置对象: |
|||
|
|||
{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} |
|||
{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} |
|||
|
|||
在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 |
|||
|
|||
@ -193,7 +193,7 @@ APP_NAME="ChimichangApp" |
|||
|
|||
然后更新 `config.py`: |
|||
|
|||
{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} |
|||
{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
@ -226,7 +226,7 @@ def get_settings(): |
|||
|
|||
但由于我们在顶部使用了 `@lru_cache` 装饰器,`Settings` 对象只会在第一次调用时创建一次。 ✔️ |
|||
|
|||
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} |
|||
{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} |
|||
|
|||
接着,对于后续请求中依赖项里对 `get_settings()` 的任何调用,它不会再次执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是会一遍又一遍地返回第一次调用时返回的那个相同对象。 |
|||
|
|||
|
|||
@ -0,0 +1,482 @@ |
|||
# 替代方案、灵感与对比 { #alternatives-inspiration-and-comparisons } |
|||
|
|||
是什么启发了 **FastAPI**,它与替代方案的比较,以及它从中学到的东西。 |
|||
|
|||
## 介绍 { #intro } |
|||
|
|||
没有前人的工作,就不会有 **FastAPI**。 |
|||
|
|||
在它诞生之前,已经有许多工具为其提供了灵感。 |
|||
|
|||
我曾经多年避免创建一个新框架。起初,我尝试用许多不同的框架、插件和工具来解决 **FastAPI** 所覆盖的全部功能。 |
|||
|
|||
但在某个时刻,除了创造一个能提供所有这些功能的东西之外,别无选择;它要吸收以往工具的最佳理念,并以尽可能好的方式组合起来,利用之前都不存在的语言特性(Python 3.6+ 类型提示)。 |
|||
|
|||
## 先前的工具 { #previous-tools } |
|||
|
|||
### <a href="https://www.djangoproject.com/" class="external-link" target="_blank">Django</a> { #django } |
|||
|
|||
它是最流行且被广泛信任的 Python 框架。被用于构建 Instagram 等系统。 |
|||
|
|||
它与关系型数据库(如 MySQL、PostgreSQL)耦合相对紧密,因此若要以 NoSQL 数据库(如 Couchbase、MongoDB、Cassandra 等)作为主要存储引擎并不容易。 |
|||
|
|||
它最初用于在后端生成 HTML,而不是创建由现代前端(如 React、Vue.js、Angular)或与之通信的其他系统(如 <abbr title="Internet of Things - 物联网">IoT</abbr> 设备)使用的 API。 |
|||
|
|||
### <a href="https://www.django-rest-framework.org/" class="external-link" target="_blank">Django REST Framework</a> { #django-rest-framework } |
|||
|
|||
Django REST framework 作为一个灵活工具箱而创建,用于在底层使用 Django 构建 Web API,从而增强其 API 能力。 |
|||
|
|||
它被包括 Mozilla、Red Hat、Eventbrite 在内的许多公司使用。 |
|||
|
|||
它是最早的“自动 API 文档”的范例之一,这正是启发“寻找” **FastAPI** 的最初想法之一。 |
|||
|
|||
/// note | 注意 |
|||
|
|||
Django REST Framework 由 Tom Christie 创建。他也是 Starlette 和 Uvicorn 的作者,**FastAPI** 就是基于它们构建的。 |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
提供自动化的 API 文档 Web 界面。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://flask.palletsprojects.com" class="external-link" target="_blank">Flask</a> { #flask } |
|||
|
|||
Flask 是一个“微框架”,它不包含数据库集成,也没有像 Django 那样的许多默认内建功能。 |
|||
|
|||
这种简单与灵活使得可以将 NoSQL 数据库作为主要的数据存储系统。 |
|||
|
|||
由于非常简单,它相对直观易学,尽管文档在某些部分略显偏技术。 |
|||
|
|||
它也常用于不一定需要数据库、用户管理,或任何 Django 预构建功能的应用;当然,许多这类功能可以通过插件添加。 |
|||
|
|||
这种组件解耦、可按需扩展的“微框架”特性,是我想保留的关键点。 |
|||
|
|||
鉴于 Flask 的简洁,它似乎非常适合构建 API。接下来要找的,就是 Flask 版的 “Django REST Framework”。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
- 成为微框架,便于按需组合所需的工具与组件。 |
|||
- 提供简单易用的路由系统。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://requests.readthedocs.io" class="external-link" target="_blank">Requests</a> { #requests } |
|||
|
|||
**FastAPI** 实际上不是 **Requests** 的替代品。它们的作用范围完全不同。 |
|||
|
|||
在 FastAPI 应用程序内部使用 Requests 其实非常常见。 |
|||
|
|||
尽管如此,FastAPI 依然从 Requests 中获得了不少灵感。 |
|||
|
|||
**Requests** 是一个用于与 API 交互(作为客户端)的库,而 **FastAPI** 是一个用于构建 API(作为服务端)的库。 |
|||
|
|||
它们处在某种意义上的“对立端”,彼此互补。 |
|||
|
|||
Requests 设计非常简单直观,易于使用,且有合理的默认值。同时它也非常强大、可定制。 |
|||
|
|||
这就是为什么,正如其官网所说: |
|||
|
|||
> Requests 是有史以来下载量最高的 Python 包之一 |
|||
|
|||
它的用法非常简单。例如,进行一次 `GET` 请求,你会这样写: |
|||
|
|||
```Python |
|||
response = requests.get("http://example.com/some/url") |
|||
``` |
|||
|
|||
对应地,FastAPI 的 API 路径操作可能看起来是这样的: |
|||
|
|||
```Python hl_lines="1" |
|||
@app.get("/some/url") |
|||
def read_url(): |
|||
return {"message": "Hello World"} |
|||
``` |
|||
|
|||
可以看到 `requests.get(...)` 与 `@app.get(...)` 的相似之处。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
- 提供简单直观的 API。 |
|||
- 直接、自然地使用 HTTP 方法名(操作)。 |
|||
- 具备合理默认值,同时支持强大定制能力。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://swagger.io/" class="external-link" target="_blank">Swagger</a> / <a href="https://github.com/OAI/OpenAPI-Specification/" class="external-link" target="_blank">OpenAPI</a> { #swagger-openapi } |
|||
|
|||
我想从 Django REST Framework 得到的主要特性之一是自动 API 文档。 |
|||
|
|||
随后我发现有一个用于用 JSON(或 YAML,JSON 的扩展)来描述 API 的标准,称为 Swagger。 |
|||
|
|||
并且已经有了用于 Swagger API 的 Web 用户界面。因此,只要能为 API 生成 Swagger 文档,就能自动使用这个 Web 界面。 |
|||
|
|||
后来,Swagger 交由 Linux 基金会管理,并更名为 OpenAPI。 |
|||
|
|||
因此,在谈到 2.0 版本时人们常说 “Swagger”,而 3+ 版本则称为 “OpenAPI”。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
采用并使用开放的 API 规范标准,而非自定义模式。 |
|||
|
|||
并集成基于标准的用户界面工具: |
|||
|
|||
- <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> |
|||
- <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> |
|||
|
|||
选择这两者是因为它们相当流行且稳定;但稍作搜索,你就能找到数十种 OpenAPI 的替代用户界面(都可以与 **FastAPI** 搭配使用)。 |
|||
|
|||
/// |
|||
|
|||
### Flask REST 框架 { #flask-rest-frameworks } |
|||
|
|||
有若干基于 Flask 的 REST 框架,但在投入时间精力深入调研后,我发现许多已停止维护或被弃用,并存在多处未解决问题,不太适合采用。 |
|||
|
|||
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow } |
|||
|
|||
API 系统所需的主要特性之一是数据“<dfn title="也称为:编组、转换">序列化</dfn>”,即将代码(Python)中的数据转换为可通过网络发送的形式。例如,将包含数据库数据的对象转换为 JSON 对象、将 `datetime` 对象转换为字符串等。 |
|||
|
|||
API 的另一个重要特性是数据校验,确保数据在给定约束下是有效的。例如,某个字段必须是 `int` 而不是任意字符串。这对传入数据尤其有用。 |
|||
|
|||
没有数据校验系统的话,你就得在代码里手写所有检查。 |
|||
|
|||
这些正是 Marshmallow 要提供的功能。它是个很棒的库,我之前大量使用过。 |
|||
|
|||
但它诞生于 Python 类型提示出现之前。因此,定义每个<dfn title="数据应如何构造的定义">模式</dfn>都需要使用 Marshmallow 提供的特定工具和类。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
使用代码定义“模式”,自动提供数据类型与校验。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs } |
|||
|
|||
API 的另一个重要需求是从传入请求中<dfn title="读取并转换为 Python 数据">解析</dfn>数据。 |
|||
|
|||
Webargs 是一个在多个框架(包括 Flask)之上提供该功能的工具。 |
|||
|
|||
它在底层使用 Marshmallow 进行数据校验,并且由相同的开发者创建。 |
|||
|
|||
在拥有 **FastAPI** 之前,我也大量使用过它,这是个很棒的工具。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
Webargs 由与 Marshmallow 相同的开发者创建。 |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
对传入请求数据进行自动校验。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://apispec.readthedocs.io/en/stable/" class="external-link" target="_blank">APISpec</a> { #apispec } |
|||
|
|||
Marshmallow 与 Webargs 通过插件提供了校验、解析与序列化。 |
|||
|
|||
但文档仍然缺失,于是出现了 APISpec。 |
|||
|
|||
它为许多框架提供插件(Starlette 也有插件)。 |
|||
|
|||
它的工作方式是:你在处理路由的每个函数的文档字符串里,用 YAML 格式编写模式定义。 |
|||
|
|||
然后它会生成 OpenAPI 模式。 |
|||
|
|||
这正是它在 Flask、Starlette、Responder 等框架里的工作方式。 |
|||
|
|||
但这样我们又回到了在 Python 字符串中维护一套“微语法”(一大段 YAML)的问题上。 |
|||
|
|||
编辑器很难为此提供帮助;而且如果我们修改了参数或 Marshmallow 模式,却忘了同步更新那个 YAML 文档字符串,生成的模式就会过时。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
APISpec 由与 Marshmallow 相同的开发者创建。 |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
支持开放的 API 标准 OpenAPI。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://flask-apispec.readthedocs.io/en/latest/" class="external-link" target="_blank">Flask-apispec</a> { #flask-apispec } |
|||
|
|||
这是一个 Flask 插件,将 Webargs、Marshmallow 与 APISpec 结合在一起。 |
|||
|
|||
它利用 Webargs 与 Marshmallow 的信息,通过 APISpec 自动生成 OpenAPI 模式。 |
|||
|
|||
这是个很棒却被低估的工具;它理应比许多 Flask 插件更流行。或许是因为它的文档过于简洁与抽象。 |
|||
|
|||
这解决了在 Python 文档字符串里书写 YAML(另一套语法)的问题。 |
|||
|
|||
在构建 **FastAPI** 之前,Flask + Flask-apispec + Marshmallow + Webargs 的组合是我最喜欢的后端技术栈。 |
|||
|
|||
使用它促成了若干 Flask 全栈脚手架的诞生。以下是我(以及若干外部团队)至今使用的主要技术栈: |
|||
|
|||
* <a href="https://github.com/tiangolo/full-stack" class="external-link" target="_blank">https://github.com/tiangolo/full-stack</a> |
|||
* <a href="https://github.com/tiangolo/full-stack-flask-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchbase</a> |
|||
* <a href="https://github.com/tiangolo/full-stack-flask-couchdb" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-flask-couchdb</a> |
|||
|
|||
这些全栈脚手架也成为了[**FastAPI** 项目脚手架](project-generation.md){.internal-link target=_blank}的基础。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
Flask-apispec 由与 Marshmallow 相同的开发者创建。 |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
从定义序列化与校验的同一份代码自动生成 OpenAPI 模式。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://nestjs.com/" class="external-link" target="_blank">NestJS</a>(以及 <a href="https://angular.io/" class="external-link" target="_blank">Angular</a>) { #nestjs-and-angular } |
|||
|
|||
这甚至不是 Python。NestJS 是一个 JavaScript(TypeScript)的 NodeJS 框架,受 Angular 启发。 |
|||
|
|||
它实现了与 Flask-apispec 有些类似的效果。 |
|||
|
|||
它集成了受 Angular 2 启发的依赖注入系统。与我所知的其他依赖注入系统一样,需要预先注册“可注入项”,因此会增加冗长与重复。 |
|||
|
|||
由于参数用 TypeScript 类型描述(类似 Python 类型提示),编辑器支持相当好。 |
|||
|
|||
但由于 TypeScript 的类型在编译为 JavaScript 后不会保留,无法只依赖这些类型同时定义校验、序列化与文档。受此以及一些设计决策影响,为了获得校验、序列化与自动 schema 生成,需要在许多位置添加装饰器,因此代码会相当冗长。 |
|||
|
|||
它对嵌套模型的支持并不好。如果请求的 JSON 体是包含嵌套 JSON 对象的 JSON 对象,则无法被正确文档化和校验。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
使用 Python 类型以获得出色的编辑器支持。 |
|||
|
|||
拥有强大的依赖注入系统,并设法尽量减少代码重复。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://sanic.readthedocs.io/en/latest/" class="external-link" target="_blank">Sanic</a> { #sanic } |
|||
|
|||
它是最早的一批基于 `asyncio` 的极速 Python 框架之一,且做得与 Flask 很相似。 |
|||
|
|||
/// note | 技术细节 |
|||
|
|||
它使用了 <a href="https://github.com/MagicStack/uvloop" class="external-link" target="_blank">`uvloop`</a> 来替代 Python 默认的 `asyncio` 循环。这正是它如此之快的原因。 |
|||
|
|||
它显然启发了 Uvicorn 和 Starlette;在公开的基准测试中,它们目前比 Sanic 更快。 |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
找到实现疯狂性能的路径。 |
|||
|
|||
这就是 **FastAPI** 基于 Starlette 的原因,因为它是目前可用的最快框架(由第三方基准测试验证)。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://falconframework.org/" class="external-link" target="_blank">Falcon</a> { #falcon } |
|||
|
|||
Falcon 是另一个高性能 Python 框架,它被设计为精简且可作为 Hug 等其他框架的基础。 |
|||
|
|||
它设计为接收两个参数的函数:一个“request”和一个“response”。然后从 request 中“读取”,向 response 中“写入”。由于这种设计,无法用标准的 Python 类型提示将请求参数和请求体声明为函数形参。 |
|||
|
|||
因此,数据校验、序列化与文档要么需要手写完成,无法自动化;要么需要在 Falcon 之上实现一个框架,例如 Hug。其他受 Falcon 设计启发、采用“一个 request 对象 + 一个 response 对象作为参数”的框架也有同样的区别。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
寻找获得卓越性能的方法。 |
|||
|
|||
与 Hug(Hug 基于 Falcon)一起,启发 **FastAPI** 在函数中声明一个 `response` 参数。尽管在 FastAPI 中它是可选的,主要用于设置 headers、cookies 和可选的状态码。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://moltenframework.com/" class="external-link" target="_blank">Molten</a> { #molten } |
|||
|
|||
我在构建 **FastAPI** 的早期阶段发现了 Molten。它有不少相似的想法: |
|||
|
|||
* 基于 Python 类型提示。 |
|||
* 从这些类型获得校验与文档。 |
|||
* 依赖注入系统。 |
|||
|
|||
它没有使用像 Pydantic 这样的第三方数据校验、序列化与文档库,而是有自己的实现。因此这些数据类型定义不太容易在其他地方复用。 |
|||
|
|||
它需要稍微冗长一些的配置。并且由于基于 WSGI(而非 ASGI),它并未设计为充分利用 Uvicorn、Starlette、Sanic 等工具所提供的高性能。 |
|||
|
|||
其依赖注入系统需要预先注册依赖,且依赖根据声明的类型来解析。因此无法为同一类型声明多于一个“组件”。 |
|||
|
|||
路由在一个地方集中声明,使用在其他地方声明的函数(而不是使用可以直接放在处理端点函数之上的装饰器)。这更接近 Django 的做法,而不是 Flask(和 Starlette)。它在代码中割裂了相对紧耦合的内容。 |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
通过模型属性的“默认值”为数据类型定义额外校验。这提升了编辑器支持,而这在当时的 Pydantic 中尚不可用。 |
|||
|
|||
这实际上促成了对 Pydantic 的部分更新,以支持这种校验声明风格(这些功能现已在 Pydantic 中可用)。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://github.com/hugapi/hug" class="external-link" target="_blank">Hug</a> { #hug } |
|||
|
|||
Hug 是最早使用 Python 类型提示来声明 API 参数类型的框架之一。这一绝妙想法也启发了其他工具。 |
|||
|
|||
它在声明中使用自定义类型而不是标准的 Python 类型,但这依然是巨大的进步。 |
|||
|
|||
它也是最早生成一个自定义 JSON 模式来声明整个 API 的框架之一。 |
|||
|
|||
它并不基于 OpenAPI 与 JSON Schema 这类标准。因此与其他工具(如 Swagger UI)的集成并非一帆风顺。但它仍是非常有创新性的想法。 |
|||
|
|||
它有一个有趣且少见的特性:使用同一框架,可以同时创建 API 与 CLI。 |
|||
|
|||
由于基于同步 Python Web 框架的上一代标准(WSGI),它无法处理 WebSocket 等,尽管它的性能仍然很高。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
Hug 由 Timothy Crosley 创建,他也是 <a href="https://github.com/timothycrosley/isort" class="external-link" target="_blank">`isort`</a> 的作者,这是一个能自动排序 Python 文件中导入的优秀工具。 |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI** 的想法: |
|||
|
|||
Hug 启发了 APIStar 的部分设计,也是我当时最看好的工具之一,与 APIStar 并列。 |
|||
|
|||
Hug 促使 **FastAPI** 使用 Python 类型提示来声明参数,并自动生成定义整个 API 的模式。 |
|||
|
|||
Hug 启发 **FastAPI** 在函数中声明 `response` 参数,用于设置 headers 与 cookies。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://github.com/encode/apistar" class="external-link" target="_blank">APIStar</a> (<= 0.5) { #apistar-0-5 } |
|||
|
|||
就在决定动手构建 **FastAPI** 之前,我找到了 **APIStar** 服务器。它几乎具备我想要的一切,设计也很出色。 |
|||
|
|||
在我见过的框架中,它是最早使用 Python 类型提示来声明参数和请求的实现之一(早于 NestJS 与 Molten)。我与 Hug 几乎同时发现了它。但 APIStar 使用了 OpenAPI 标准。 |
|||
|
|||
它基于相同的类型提示,在多处自动进行数据校验、序列化并生成 OpenAPI 模式。 |
|||
|
|||
请求体模式定义并未使用与 Pydantic 相同的 Python 类型提示,它更接近 Marshmallow,因此编辑器支持不如 Pydantic 好,但即便如此,APIStar 仍是当时可用的最佳选择。 |
|||
|
|||
它在当时拥有最好的性能基准(仅被 Starlette 超越)。 |
|||
|
|||
起初它没有自动 API 文档 Web 界面,但我知道我可以把 Swagger UI 加进去。 |
|||
|
|||
它有一个依赖注入系统。与上文提到的其他工具一样,需要预先注册组件。但这依然是很棒的特性。 |
|||
|
|||
我从未在完整项目中使用过它,因为它没有安全集成,因此我无法用它替代基于 Flask-apispec 的全栈脚手架所具备的全部功能。我曾把“提交一个增加该功能的 PR”放在了待办里。 |
|||
|
|||
但随后,项目的重心发生了变化。 |
|||
|
|||
它不再是一个 API Web 框架,因为作者需要专注于 Starlette。 |
|||
|
|||
现在 APIStar 是一组用于校验 OpenAPI 规范的工具,而不是 Web 框架。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
APIStar 由 Tom Christie 创建。他还创建了: |
|||
|
|||
* Django REST Framework |
|||
* Starlette(**FastAPI** 基于其之上) |
|||
* Uvicorn(被 Starlette 与 **FastAPI** 使用) |
|||
|
|||
/// |
|||
|
|||
/// check | 启发 **FastAPI**: |
|||
|
|||
诞生。 |
|||
|
|||
用同一套 Python 类型同时声明多件事(数据校验、序列化与文档),并且还能提供出色的编辑器支持——我认为这是个极其巧妙的想法。 |
|||
|
|||
在长时间寻找与测试多种替代之后,APIStar 是当时最好的选择。 |
|||
|
|||
随后 APIStar 不再作为服务器存在,而 Starlette 出现,成为实现该体系的更佳基础。这成为构建 **FastAPI** 的最终灵感来源。 |
|||
|
|||
我把 **FastAPI** 视为 APIStar 的“精神续作”,并在此基础上,结合前述工具的经验,改进并增强了功能、类型系统及其他各方面。 |
|||
|
|||
/// |
|||
|
|||
## **FastAPI** 所使用的组件 { #used-by-fastapi } |
|||
|
|||
### <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> { #pydantic } |
|||
|
|||
Pydantic 是一个基于 Python 类型提示来定义数据校验、序列化与文档(使用 JSON Schema)的库。 |
|||
|
|||
这使得它极其直观。 |
|||
|
|||
它可与 Marshmallow 类比。尽管在基准测试中它比 Marshmallow 更快。并且由于同样基于 Python 类型提示,编辑器支持优秀。 |
|||
|
|||
/// check | **FastAPI** 用它来: |
|||
|
|||
处理所有数据校验、数据序列化与自动模型文档(基于 JSON Schema)。 |
|||
|
|||
随后 **FastAPI** 会把这些 JSON Schema 数据纳入 OpenAPI(以及完成其他所有工作)。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette } |
|||
|
|||
Starlette 是一个轻量级的 <dfn title="构建异步 Python Web 应用的新标准">ASGI</dfn> 框架/工具集,非常适合构建高性能的 asyncio 服务。 |
|||
|
|||
它非常简单直观。被设计为易于扩展,且具有模块化组件。 |
|||
|
|||
它具备: |
|||
|
|||
* 性能极其出色。 |
|||
* 支持 WebSocket。 |
|||
* 进程内后台任务。 |
|||
* 启动与停止事件。 |
|||
* 基于 HTTPX 的测试客户端。 |
|||
* CORS、GZip、静态文件、流式响应。 |
|||
* 会话与 Cookie 支持。 |
|||
* 100% 测试覆盖率。 |
|||
* 100% 类型注解的代码库。 |
|||
* 极少的强依赖。 |
|||
|
|||
Starlette 目前是测试中最快的 Python 框架。仅次于 Uvicorn,它不是框架,而是服务器。 |
|||
|
|||
Starlette 提供了 Web 微框架的全部基础能力。 |
|||
|
|||
但它不提供自动的数据校验、序列化或文档。 |
|||
|
|||
这正是 **FastAPI** 在其之上增加的主要内容之一,全部基于 Python 类型提示(通过 Pydantic)。此外还有依赖注入系统、安全工具、OpenAPI 模式生成等。 |
|||
|
|||
/// note | 技术细节 |
|||
|
|||
ASGI 是由 Django 核心团队成员推动的新“标准”。它尚不是正式的“Python 标准”(PEP),尽管正朝此方向推进。 |
|||
|
|||
尽管如此,已有多种工具将其作为“标准”使用。这极大提升了互操作性:你可以把 Uvicorn 换成其他 ASGI 服务器(如 Daphne 或 Hypercorn),或添加 ASGI 兼容的工具,如 `python-socketio`。 |
|||
|
|||
/// |
|||
|
|||
/// check | **FastAPI** 用它来: |
|||
|
|||
处理所有核心 Web 部分,并在其之上扩展功能。 |
|||
|
|||
`FastAPI` 类本身直接继承自 `Starlette`。 |
|||
|
|||
因此,凡是你能用 Starlette 完成的事,也能直接用 **FastAPI** 完成;可以把它看作“加速版”的 Starlette。 |
|||
|
|||
/// |
|||
|
|||
### <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a> { #uvicorn } |
|||
|
|||
Uvicorn 是一个基于 uvloop 与 httptools 构建的极速 ASGI 服务器。 |
|||
|
|||
它不是 Web 框架,而是服务器。例如它不提供按路径路由的工具——这是 Starlette(或 **FastAPI**)这类框架在其之上提供的功能。 |
|||
|
|||
它是 Starlette 与 **FastAPI** 推荐的服务器。 |
|||
|
|||
/// check | **FastAPI** 推荐将其作为: |
|||
|
|||
运行 **FastAPI** 应用的主要 Web 服务器。 |
|||
|
|||
你也可以使用 `--workers` 命令行选项以获得异步的多进程服务器。 |
|||
|
|||
更多细节见[部署](deployment/index.md){.internal-link target=_blank}一节。 |
|||
|
|||
/// |
|||
|
|||
## 基准与速度 { #benchmarks-and-speed } |
|||
|
|||
要理解、比较并查看 Uvicorn、Starlette 与 FastAPI 之间的差异,请查看[基准](benchmarks.md){.internal-link target=_blank}一节。 |
|||
@ -0,0 +1,65 @@ |
|||
# FastAPI Cloud { #fastapi-cloud } |
|||
|
|||
你可以用**一条命令**将你的 FastAPI 应用部署到 <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>,如果还没有,去加入候补名单吧。🚀 |
|||
|
|||
## 登录 { #login } |
|||
|
|||
请确保你已有 **FastAPI Cloud** 账号(我们已从候补名单向你发出邀请 😉)。 |
|||
|
|||
然后登录: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi login |
|||
|
|||
You are logged in to FastAPI Cloud 🚀 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
## 部署 { #deploy } |
|||
|
|||
现在用**一条命令**部署你的应用: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ fastapi deploy |
|||
|
|||
Deploying to FastAPI Cloud... |
|||
|
|||
✅ Deployment successful! |
|||
|
|||
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
就这样!现在你可以通过该 URL 访问你的应用。✨ |
|||
|
|||
## 关于 FastAPI Cloud { #about-fastapi-cloud } |
|||
|
|||
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** 由 **FastAPI** 背后的作者与团队打造。 |
|||
|
|||
它让你以最小的投入完成 API 的**构建**、**部署**与**访问**。 |
|||
|
|||
它把使用 FastAPI 构建应用时的**开发者体验**,同样带到将应用**部署**到云上的过程。🎉 |
|||
|
|||
它还会替你处理部署应用时大多数需要的事项,例如: |
|||
|
|||
* HTTPS |
|||
* 副本、基于请求的自动伸缩 |
|||
* 等等 |
|||
|
|||
FastAPI Cloud 是 *FastAPI and friends* 开源项目的主要赞助方与资金提供者。✨ |
|||
|
|||
## 部署到其他云服务商 { #deploy-to-other-cloud-providers } |
|||
|
|||
FastAPI 是开源的,并基于标准。你可以将 FastAPI 应用部署到你选择的任意云服务商。 |
|||
|
|||
按照你所选云服务商的指南部署 FastAPI 应用即可。🤓 |
|||
|
|||
## 部署到你自己的服务器 { #deploy-your-own-server } |
|||
|
|||
在后面的**部署**指南中,我也会讲解所有细节,帮助你理解幕后发生了什么、需要做什么,以及如何自行部署 FastAPI 应用,包括部署到你自己的服务器。🤓 |
|||
@ -0,0 +1,17 @@ |
|||
# 使用旧的 403 认证错误状态码 { #use-old-403-authentication-error-status-codes } |
|||
|
|||
在 FastAPI `0.122.0` 版本之前,当内置的安全工具在认证失败后向客户端返回错误时,会使用 HTTP 状态码 `403 Forbidden`。 |
|||
|
|||
从 FastAPI `0.122.0` 版本开始,它们改用更合适的 HTTP 状态码 `401 Unauthorized`,并在响应中返回合理的 `WWW-Authenticate` 头,遵循 HTTP 规范,<a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>、<a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>。 |
|||
|
|||
但如果由于某些原因你的客户端依赖旧行为,你可以在你的安全类中重写方法 `make_not_authenticated_error` 来回退到旧行为。 |
|||
|
|||
例如,你可以创建一个 `HTTPBearer` 的子类,使其返回 `403 Forbidden` 错误,而不是默认的 `401 Unauthorized` 错误: |
|||
|
|||
{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
注意该函数返回的是异常实例,而不是直接抛出它。抛出操作由其余的内部代码完成。 |
|||
|
|||
/// |
|||
@ -0,0 +1,56 @@ |
|||
# 按条件配置 OpenAPI { #conditional-openapi } |
|||
|
|||
如果需要,你可以使用设置和环境变量,按环境有条件地配置 OpenAPI,甚至完全禁用它。 |
|||
|
|||
## 关于安全、API 和文档 { #about-security-apis-and-docs } |
|||
|
|||
在生产环境隐藏文档界面并不应该成为保护 API 的方式。 |
|||
|
|||
这并不会给你的 API 增加任何额外的安全性,*路径操作* 仍然会在原来的位置可用。 |
|||
|
|||
如果你的代码里有安全漏洞,它仍然存在。 |
|||
|
|||
隐藏文档只会让理解如何与 API 交互变得更困难,也可能让你在生产环境中调试更困难。这大体上可以被视为一种 <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">通过隐藏实现安全</a> 的做法。 |
|||
|
|||
如果你想保护你的 API,有很多更好的措施,例如: |
|||
|
|||
- 确保为请求体和响应定义完善的 Pydantic 模型。 |
|||
- 使用依赖配置所需的权限和角色。 |
|||
- 绝不要存储明文密码,只存储密码哈希。 |
|||
- 实现并使用成熟的密码学工具,比如 pwdlib 和 JWT 令牌等。 |
|||
- 在需要的地方使用 OAuth2 作用域添加更细粒度的权限控制。 |
|||
- ...等。 |
|||
|
|||
尽管如此,你可能确实有非常特定的用例,需要在某些环境(例如生产环境)禁用 API 文档,或根据环境变量的配置来决定。 |
|||
|
|||
## 基于设置和环境变量的条件式 OpenAPI { #conditional-openapi-from-settings-and-env-vars } |
|||
|
|||
你可以很容易地使用相同的 Pydantic 设置来配置生成的 OpenAPI 和文档 UI。 |
|||
|
|||
例如: |
|||
|
|||
{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} |
|||
|
|||
这里我们声明了设置项 `openapi_url`,其默认值同样是 `"/openapi.json"`。 |
|||
|
|||
然后在创建 `FastAPI` 应用时使用它。 |
|||
|
|||
接着,你可以通过把环境变量 `OPENAPI_URL` 设为空字符串来禁用 OpenAPI(包括文档 UI),例如: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ OPENAPI_URL= uvicorn main:app |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
然后如果你访问 `/openapi.json`、`/docs` 或 `/redoc`,就会得到一个 `404 Not Found` 错误,例如: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": "Not Found" |
|||
} |
|||
``` |
|||
@ -0,0 +1,185 @@ |
|||
# 自托管自定义文档 UI 静态资源 { #custom-docs-ui-static-assets-self-hosting } |
|||
|
|||
API 文档使用 Swagger UI 和 ReDoc,它们各自需要一些 JavaScript 和 CSS 文件。 |
|||
|
|||
默认情况下,这些文件从一个 <abbr title="Content Delivery Network - 内容分发网络: 一种服务,通常由多台服务器组成,用于提供静态文件,如 JavaScript 和 CSS。它常用于从更接近客户端的服务器提供这些文件,从而提升性能。">CDN</abbr> 提供。 |
|||
|
|||
不过你可以自定义:可以指定特定的 CDN,或自行提供这些文件。 |
|||
|
|||
## 为 JavaScript 和 CSS 自定义 CDN { #custom-cdn-for-javascript-and-css } |
|||
|
|||
假设你想使用不同的 <abbr title="Content Delivery Network - 内容分发网络">CDN</abbr>,例如使用 `https://unpkg.com/`。 |
|||
|
|||
如果你所在的国家/地区屏蔽了某些 URL,这会很有用。 |
|||
|
|||
### 关闭自动文档 { #disable-the-automatic-docs } |
|||
|
|||
第一步是关闭自动文档,因为默认它们会使用默认的 CDN。 |
|||
|
|||
要关闭它们,在创建 `FastAPI` 应用时将其 URL 设为 `None`: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} |
|||
|
|||
### 包含自定义文档 { #include-the-custom-docs } |
|||
|
|||
现在你可以为自定义文档创建*路径操作*。 |
|||
|
|||
你可以复用 FastAPI 的内部函数来创建文档的 HTML 页面,并传入所需参数: |
|||
|
|||
- `openapi_url`:文档 HTML 页面获取你的 API 的 OpenAPI 模式的 URL。这里可以使用 `app.openapi_url` 属性。 |
|||
- `title`:你的 API 标题。 |
|||
- `oauth2_redirect_url`:这里可以使用 `app.swagger_ui_oauth2_redirect_url` 来使用默认值。 |
|||
- `swagger_js_url`:你的 Swagger UI 文档 HTML 获取**JavaScript** 文件的 URL。这里是自定义的 CDN URL。 |
|||
- `swagger_css_url`:你的 Swagger UI 文档 HTML 获取**CSS** 文件的 URL。这里是自定义的 CDN URL。 |
|||
|
|||
ReDoc 也类似... |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
`swagger_ui_redirect` 的*路径操作*是在你使用 OAuth2 时的一个辅助。 |
|||
|
|||
如果你把 API 与某个 OAuth2 提供方集成,你就可以完成认证并带着获取到的凭据回到 API 文档里。然后使用真实的 OAuth2 认证与之交互。 |
|||
|
|||
Swagger UI 会在幕后为你处理这些,但它需要这个“重定向”辅助路径。 |
|||
|
|||
/// |
|||
|
|||
### 创建一个路径操作进行测试 { #create-a-path-operation-to-test-it } |
|||
|
|||
现在,为了测试一切是否正常,创建一个*路径操作*: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} |
|||
|
|||
### 测试 { #test-it } |
|||
|
|||
现在,你应该可以访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>,并刷新页面,页面会从新的 CDN 加载这些资源。 |
|||
|
|||
## 为文档自托管 JavaScript 和 CSS { #self-hosting-javascript-and-css-for-docs } |
|||
|
|||
如果你需要在离线、无法访问互联网或仅在局域网内时,应用仍能工作,那么自托管 JavaScript 和 CSS 会很有用。 |
|||
|
|||
这里你将看到如何在同一个 FastAPI 应用中自行提供这些文件,并配置文档使用它们。 |
|||
|
|||
### 项目文件结构 { #project-file-structure } |
|||
|
|||
假设你的项目文件结构如下: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
``` |
|||
|
|||
现在创建一个目录来存放这些静态文件。 |
|||
|
|||
你的新文件结构可能如下: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
└── static/ |
|||
``` |
|||
|
|||
### 下载文件 { #download-the-files } |
|||
|
|||
下载文档需要的静态文件,并将它们放到 `static/` 目录中。 |
|||
|
|||
你通常可以右键点击每个链接,选择类似“将链接另存为...”的选项。 |
|||
|
|||
Swagger UI 使用以下文件: |
|||
|
|||
- <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" class="external-link" target="_blank">`swagger-ui-bundle.js`</a> |
|||
- <a href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" class="external-link" target="_blank">`swagger-ui.css`</a> |
|||
|
|||
而 ReDoc 使用以下文件: |
|||
|
|||
- <a href="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js" class="external-link" target="_blank">`redoc.standalone.js`</a> |
|||
|
|||
之后,你的文件结构可能如下: |
|||
|
|||
``` |
|||
. |
|||
├── app |
|||
│ ├── __init__.py |
|||
│ ├── main.py |
|||
└── static |
|||
├── redoc.standalone.js |
|||
├── swagger-ui-bundle.js |
|||
└── swagger-ui.css |
|||
``` |
|||
|
|||
### 提供静态文件 { #serve-the-static-files } |
|||
|
|||
- 导入 `StaticFiles`。 |
|||
- 在特定路径上“挂载”一个 `StaticFiles()` 实例。 |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} |
|||
|
|||
### 测试静态文件 { #test-the-static-files } |
|||
|
|||
启动你的应用,并访问 <a href="http://127.0.0.1:8000/static/redoc.standalone.js" class="external-link" target="_blank">http://127.0.0.1:8000/static/redoc.standalone.js</a>。 |
|||
|
|||
你应该会看到一个非常长的 **ReDoc** 的 JavaScript 文件。 |
|||
|
|||
它可能以如下内容开头: |
|||
|
|||
```JavaScript |
|||
/*! For license information please see redoc.standalone.js.LICENSE.txt */ |
|||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): |
|||
... |
|||
``` |
|||
|
|||
这就确认了你的应用能够提供静态文件,并且你把文档所需的静态文件放在了正确的位置。 |
|||
|
|||
现在我们可以配置应用,让文档使用这些静态文件。 |
|||
|
|||
### 为静态文件关闭自动文档 { #disable-the-automatic-docs-for-static-files } |
|||
|
|||
和使用自定义 CDN 一样,第一步是关闭自动文档,因为默认情况下它们会使用 CDN。 |
|||
|
|||
要关闭它们,在创建 `FastAPI` 应用时将其 URL 设为 `None`: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} |
|||
|
|||
### 为静态文件包含自定义文档 { #include-the-custom-docs-for-static-files } |
|||
|
|||
同样地,现在你可以为自定义文档创建*路径操作*。 |
|||
|
|||
你可以再次复用 FastAPI 的内部函数来创建文档的 HTML 页面,并传入所需参数: |
|||
|
|||
- `openapi_url`:文档 HTML 页面获取你的 API 的 OpenAPI 模式的 URL。这里可以使用 `app.openapi_url` 属性。 |
|||
- `title`:你的 API 标题。 |
|||
- `oauth2_redirect_url`:这里可以使用 `app.swagger_ui_oauth2_redirect_url` 来使用默认值。 |
|||
- `swagger_js_url`:你的 Swagger UI 文档 HTML 获取**JavaScript** 文件的 URL。**这是现在由你的应用自己提供的那个**。 |
|||
- `swagger_css_url`:你的 Swagger UI 文档 HTML 获取**CSS** 文件的 URL。**这是现在由你的应用自己提供的那个**。 |
|||
|
|||
ReDoc 也类似... |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} |
|||
|
|||
/// tip | 提示 |
|||
|
|||
`swagger_ui_redirect` 的*路径操作*是在你使用 OAuth2 时的一个辅助。 |
|||
|
|||
如果你把 API 与某个 OAuth2 提供方集成,你就可以完成认证并带着获取到的凭据回到 API 文档里。然后使用真实的 OAuth2 认证与之交互。 |
|||
|
|||
Swagger UI 会在幕后为你处理这些,但它需要这个“重定向”辅助路径。 |
|||
|
|||
/// |
|||
|
|||
### 创建一个路径操作测试静态文件 { #create-a-path-operation-to-test-static-files } |
|||
|
|||
现在,为了测试一切是否正常,创建一个*路径操作*: |
|||
|
|||
{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} |
|||
|
|||
### 测试静态文件 UI { #test-static-files-ui } |
|||
|
|||
现在,你可以断开 WiFi,访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>,并刷新页面。 |
|||
|
|||
即使没有互联网,你也能看到 API 的文档并与之交互。 |
|||
@ -0,0 +1,109 @@ |
|||
# 自定义 Request 和 APIRoute 类 { #custom-request-and-apiroute-class } |
|||
|
|||
在某些情况下,你可能想要重写 `Request` 和 `APIRoute` 类使用的逻辑。 |
|||
|
|||
尤其是,当你本来会把这些逻辑放到中间件里时,这是一个不错的替代方案。 |
|||
|
|||
例如,如果你想在应用处理之前读取或操作请求体。 |
|||
|
|||
/// danger | 危险 |
|||
|
|||
这是一个“高级”特性。 |
|||
|
|||
如果你刚开始使用 **FastAPI**,可以先跳过本节。 |
|||
|
|||
/// |
|||
|
|||
## 使用场景 { #use-cases } |
|||
|
|||
一些使用场景包括: |
|||
|
|||
* 将非 JSON 的请求体转换为 JSON(例如 <a href="https://msgpack.org/index.html" class="external-link" target="_blank">`msgpack`</a>)。 |
|||
* 解压缩使用 gzip 压缩的请求体。 |
|||
* 自动记录所有请求体日志。 |
|||
|
|||
## 处理自定义请求体编码 { #handling-custom-request-body-encodings } |
|||
|
|||
来看如何用自定义的 `Request` 子类来解压 gzip 请求。 |
|||
|
|||
以及一个 `APIRoute` 子类来使用该自定义请求类。 |
|||
|
|||
### 创建自定义 `GzipRequest` 类 { #create-a-custom-gziprequest-class } |
|||
|
|||
/// tip | 提示 |
|||
|
|||
这是一个演示工作原理的示例。如果你需要 Gzip 支持,可以直接使用提供的 [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}。 |
|||
|
|||
/// |
|||
|
|||
首先,我们创建一个 `GzipRequest` 类,它会重写 `Request.body()` 方法:当请求头中存在相应标记时对请求体进行解压。 |
|||
|
|||
如果请求头中没有 `gzip`,则不会尝试解压。 |
|||
|
|||
这样,同一个路由类即可同时处理 gzip 压缩和未压缩的请求。 |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} |
|||
|
|||
### 创建自定义 `GzipRoute` 类 { #create-a-custom-gziproute-class } |
|||
|
|||
接着,我们创建 `fastapi.routing.APIRoute` 的自定义子类来使用 `GzipRequest`。 |
|||
|
|||
这次,我们会重写 `APIRoute.get_route_handler()` 方法。 |
|||
|
|||
该方法返回一个函数,这个函数负责接收请求并返回响应。 |
|||
|
|||
这里我们用它把原始请求包装为 `GzipRequest`。 |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} |
|||
|
|||
/// note | 技术细节 |
|||
|
|||
`Request` 拥有 `request.scope` 属性,它就是一个 Python `dict`,包含与请求相关的元数据。 |
|||
|
|||
`Request` 还包含 `request.receive`,它是一个用于“接收”请求体的函数。 |
|||
|
|||
`scope` 字典和 `receive` 函数都是 ASGI 规范的一部分。 |
|||
|
|||
创建一个新的 `Request` 实例需要这两样:`scope` 和 `receive`。 |
|||
|
|||
想了解更多关于 `Request` 的信息,请查看 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">Starlette 的 Request 文档</a>。 |
|||
|
|||
/// |
|||
|
|||
由 `GzipRequest.get_route_handler` 返回的函数唯一不同之处是把 `Request` 转换为 `GzipRequest`。 |
|||
|
|||
这样,在传给我们的路径操作之前,`GzipRequest` 会(在需要时)负责解压数据。 |
|||
|
|||
之后,其余处理逻辑完全相同。 |
|||
|
|||
但由于我们修改了 `GzipRequest.body`,在 **FastAPI** 需要读取时,请求体会被自动解压。 |
|||
|
|||
## 在异常处理器中访问请求体 { #accessing-the-request-body-in-an-exception-handler } |
|||
|
|||
/// tip | 提示 |
|||
|
|||
要解决类似问题,使用 `RequestValidationError` 的自定义处理器中的 `body` 往往更简单([处理错误](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank})。 |
|||
|
|||
但本示例同样有效,并展示了如何与内部组件交互。 |
|||
|
|||
/// |
|||
|
|||
我们也可以用相同的方法在异常处理器中访问请求体。 |
|||
|
|||
所需仅是在 `try`/`except` 块中处理请求: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} |
|||
|
|||
如果发生异常,`Request` 实例仍在作用域内,因此我们可以在处理错误时读取并使用请求体: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} |
|||
|
|||
## 在路由器中自定义 `APIRoute` 类 { #custom-apiroute-class-in-a-router } |
|||
|
|||
你也可以设置 `APIRouter` 的 `route_class` 参数: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} |
|||
|
|||
在此示例中,`router` 下的路径操作将使用自定义的 `TimedRoute` 类,响应中会多一个 `X-Response-Time` 头,包含生成响应所用的时间: |
|||
|
|||
{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} |
|||
@ -0,0 +1,80 @@ |
|||
# 扩展 OpenAPI { #extending-openapi } |
|||
|
|||
在某些情况下,你可能需要修改生成的 OpenAPI 架构(schema)。 |
|||
|
|||
本节将介绍如何实现。 |
|||
|
|||
## 常规流程 { #the-normal-process } |
|||
|
|||
常规(默认)流程如下。 |
|||
|
|||
`FastAPI` 应用(实例)有一个 `.openapi()` 方法,预期返回 OpenAPI 架构。 |
|||
|
|||
在创建应用对象时,会注册一个用于 `/openapi.json`(或你在 `openapi_url` 中设置的路径)的路径操作。 |
|||
|
|||
它只会返回一个 JSON 响应,内容是应用 `.openapi()` 方法的结果。 |
|||
|
|||
默认情况下,`.openapi()` 方法会检查属性 `.openapi_schema` 是否已有内容,若有则直接返回。 |
|||
|
|||
如果没有,则使用 `fastapi.openapi.utils.get_openapi` 工具函数生成。 |
|||
|
|||
该 `get_openapi()` 函数接收以下参数: |
|||
|
|||
- `title`:OpenAPI 标题,显示在文档中。 |
|||
- `version`:你的 API 版本,例如 `2.5.0`。 |
|||
- `openapi_version`:使用的 OpenAPI 规范版本。默认是最新的 `3.1.0`。 |
|||
- `summary`:API 的简短摘要。 |
|||
- `description`:API 的描述,可包含 Markdown,并会展示在文档中。 |
|||
- `routes`:路由列表,即已注册的每个路径操作。来自 `app.routes`。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
参数 `summary` 仅在 OpenAPI 3.1.0 及更高版本中可用,FastAPI 0.99.0 及以上版本支持。 |
|||
|
|||
/// |
|||
|
|||
## 覆盖默认值 { #overriding-the-defaults } |
|||
|
|||
基于以上信息,你可以用同一个工具函数生成 OpenAPI 架构,并按需覆盖其中的各个部分。 |
|||
|
|||
例如,让我们添加 <a href="https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo" class="external-link" target="_blank">ReDoc 的 OpenAPI 扩展以包含自定义 Logo</a>。 |
|||
|
|||
### 常规 **FastAPI** { #normal-fastapi } |
|||
|
|||
首先,像平常一样编写你的 **FastAPI** 应用: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} |
|||
|
|||
### 生成 OpenAPI 架构 { #generate-the-openapi-schema } |
|||
|
|||
然后,在一个 `custom_openapi()` 函数中使用同一个工具函数生成 OpenAPI 架构: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} |
|||
|
|||
### 修改 OpenAPI 架构 { #modify-the-openapi-schema } |
|||
|
|||
现在你可以添加 ReDoc 扩展,在 OpenAPI 架构的 `info` “对象”中加入自定义 `x-logo`: |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} |
|||
|
|||
### 缓存 OpenAPI 架构 { #cache-the-openapi-schema } |
|||
|
|||
你可以把 `.openapi_schema` 属性当作“缓存”,用来存储已生成的架构。 |
|||
|
|||
这样一来,用户每次打开 API 文档时,应用就不必重新生成架构。 |
|||
|
|||
它只会生成一次,后续请求都会使用同一份缓存的架构。 |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} |
|||
|
|||
### 覆盖方法 { #override-the-method } |
|||
|
|||
现在你可以用你的新函数替换 `.openapi()` 方法。 |
|||
|
|||
{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} |
|||
|
|||
### 验证 { #check-it } |
|||
|
|||
当你访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a> 时,你会看到已使用你的自定义 Logo(本例中为 **FastAPI** 的 Logo): |
|||
|
|||
<img src="/img/tutorial/extending-openapi/image01.png"> |
|||
@ -0,0 +1,60 @@ |
|||
# GraphQL { #graphql } |
|||
|
|||
由于 **FastAPI** 基于 **ASGI** 标准,因此很容易集成任何也兼容 ASGI 的 **GraphQL** 库。 |
|||
|
|||
你可以在同一个应用中将常规的 FastAPI 路径操作与 GraphQL 结合使用。 |
|||
|
|||
/// tip | 提示 |
|||
|
|||
**GraphQL** 解决一些非常特定的用例。 |
|||
|
|||
与常见的 **Web API** 相比,它有各自的**优点**和**缺点**。 |
|||
|
|||
请确保评估在你的用例中,这些**好处**是否足以弥补这些**缺点**。 🤓 |
|||
|
|||
/// |
|||
|
|||
## GraphQL 库 { #graphql-libraries } |
|||
|
|||
以下是一些支持 **ASGI** 的 **GraphQL** 库。你可以将它们与 **FastAPI** 一起使用: |
|||
|
|||
* <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a> 🍓 |
|||
* 提供 <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">面向 FastAPI 的文档</a> |
|||
* <a href="https://ariadnegraphql.org/" class="external-link" target="_blank">Ariadne</a> |
|||
* 提供 <a href="https://ariadnegraphql.org/docs/fastapi-integration" class="external-link" target="_blank">面向 FastAPI 的文档</a> |
|||
* <a href="https://tartiflette.io/" class="external-link" target="_blank">Tartiflette</a> |
|||
* 提供用于 ASGI 集成的 <a href="https://tartiflette.github.io/tartiflette-asgi/" class="external-link" target="_blank">Tartiflette ASGI</a> |
|||
* <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a> |
|||
* 可配合 <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a> 使用 |
|||
|
|||
## 使用 Strawberry 的 GraphQL { #graphql-with-strawberry } |
|||
|
|||
如果你需要或想要使用 **GraphQL**,<a href="https://strawberry.rocks/" class="external-link" target="_blank">**Strawberry**</a> 是**推荐**的库,因为它的设计与 **FastAPI** 最为接近,全部基于**类型注解**。 |
|||
|
|||
根据你的用例,你可能会更喜欢其他库,但如果你问我,我大概率会建议你先试试 **Strawberry**。 |
|||
|
|||
下面是一个将 Strawberry 与 FastAPI 集成的小预览: |
|||
|
|||
{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} |
|||
|
|||
你可以在 <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry 文档</a>中了解更多信息。 |
|||
|
|||
还有关于 <a href="https://strawberry.rocks/docs/integrations/fastapi" class="external-link" target="_blank">将 Strawberry 与 FastAPI 结合使用</a>的文档。 |
|||
|
|||
## Starlette 中较早的 `GraphQLApp` { #older-graphqlapp-from-starlette } |
|||
|
|||
早期版本的 Starlette 包含一个 `GraphQLApp` 类,用于与 <a href="https://graphene-python.org/" class="external-link" target="_blank">Graphene</a> 集成。 |
|||
|
|||
它已在 Starlette 中被弃用,但如果你的代码使用了它,你可以轻松**迁移**到 <a href="https://github.com/ciscorn/starlette-graphene3" class="external-link" target="_blank">starlette-graphene3</a>,它覆盖相同的用例,且接口**几乎完全一致**。 |
|||
|
|||
/// tip | 提示 |
|||
|
|||
如果你需要 GraphQL,我仍然建议看看 <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry</a>,因为它基于类型注解而不是自定义类和类型。 |
|||
|
|||
/// |
|||
|
|||
## 了解更多 { #learn-more } |
|||
|
|||
你可以在 <a href="https://graphql.org/" class="external-link" target="_blank">GraphQL 官方文档</a>中了解更多关于 **GraphQL** 的内容。 |
|||
|
|||
你也可以通过上面的链接阅读各个库的更多信息。 |
|||
@ -0,0 +1,135 @@ |
|||
# 从 Pydantic v1 迁移到 Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } |
|||
|
|||
如果你有一个较旧的 FastAPI 应用,可能在使用 Pydantic v1。 |
|||
|
|||
FastAPI 0.100.0 同时支持 Pydantic v1 和 v2,会使用你已安装的任一版本。 |
|||
|
|||
FastAPI 0.119.0 引入了在 Pydantic v2 内部以 `pydantic.v1` 形式对 Pydantic v1 的部分支持,以便于迁移到 v2。 |
|||
|
|||
FastAPI 0.126.0 移除了对 Pydantic v1 的支持,但在一段时间内仍支持 `pydantic.v1`。 |
|||
|
|||
/// warning | 警告 |
|||
|
|||
从 Python 3.14 开始,Pydantic 团队不再为最新的 Python 版本提供 Pydantic v1 的支持。 |
|||
|
|||
这也包括 `pydantic.v1`,在 Python 3.14 及更高版本中不再受支持。 |
|||
|
|||
如果你想使用 Python 的最新特性,需要确保使用 Pydantic v2。 |
|||
|
|||
/// |
|||
|
|||
如果你的旧 FastAPI 应用在用 Pydantic v1,这里将向你展示如何迁移到 Pydantic v2,以及 FastAPI 0.119.0 中可帮助你渐进式迁移的功能。 |
|||
|
|||
## 官方指南 { #official-guide } |
|||
|
|||
Pydantic 有一份从 v1 迁移到 v2 的官方 <a href="https://docs.pydantic.dev/latest/migration/" class="external-link" target="_blank">迁移指南</a>。 |
|||
|
|||
其中包含变更内容、校验如何更准确更严格、可能的注意事项等。 |
|||
|
|||
你可以阅读以更好地了解变更。 |
|||
|
|||
## 测试 { #tests } |
|||
|
|||
请确保你的应用有[测试](../tutorial/testing.md){.internal-link target=_blank},并在持续集成(CI)中运行它们。 |
|||
|
|||
这样你就可以升级并确保一切仍按预期工作。 |
|||
|
|||
## `bump-pydantic` { #bump-pydantic } |
|||
|
|||
在很多情况下,如果你使用的是未做自定义的常规 Pydantic 模型,可以将从 Pydantic v1 迁移到 v2 的大部分过程自动化。 |
|||
|
|||
你可以使用同一 Pydantic 团队提供的 <a href="https://github.com/pydantic/bump-pydantic" class="external-link" target="_blank">`bump-pydantic`</a>。 |
|||
|
|||
该工具会帮助你自动修改大部分需要变更的代码。 |
|||
|
|||
之后运行测试检查是否一切正常。如果正常,你就完成了。😎 |
|||
|
|||
## v2 中的 Pydantic v1 { #pydantic-v1-in-v2 } |
|||
|
|||
Pydantic v2 以子模块 `pydantic.v1` 的形式包含了 Pydantic v1 的全部内容。但在 Python 3.13 以上的版本中不再受支持。 |
|||
|
|||
这意味着你可以安装最新的 Pydantic v2,并从该子模块导入并使用旧的 Pydantic v1 组件,就像安装了旧版 Pydantic v1 一样。 |
|||
|
|||
{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} |
|||
|
|||
### FastAPI 对 v2 中 Pydantic v1 的支持 { #fastapi-support-for-pydantic-v1-in-v2 } |
|||
|
|||
自 FastAPI 0.119.0 起,FastAPI 也对 Pydantic v2 内的 Pydantic v1 提供了部分支持,以便迁移到 v2。 |
|||
|
|||
因此,你可以将 Pydantic 升级到最新的 v2,并将导入改为使用 `pydantic.v1` 子模块,在很多情况下就能直接工作。 |
|||
|
|||
{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} |
|||
|
|||
/// warning | 警告 |
|||
|
|||
请注意,由于 Pydantic 团队自 Python 3.14 起不再在较新的 Python 版本中支持 Pydantic v1,使用 `pydantic.v1` 在 Python 3.14 及更高版本中也不受支持。 |
|||
|
|||
/// |
|||
|
|||
### 同一应用中同时使用 Pydantic v1 与 v2 { #pydantic-v1-and-v2-on-the-same-app } |
|||
|
|||
Pydantic 不支持在一个 Pydantic v2 模型的字段中定义 Pydantic v1 模型,反之亦然。 |
|||
|
|||
```mermaid |
|||
graph TB |
|||
subgraph "❌ Not Supported" |
|||
direction TB |
|||
subgraph V2["Pydantic v2 Model"] |
|||
V1Field["Pydantic v1 Model"] |
|||
end |
|||
subgraph V1["Pydantic v1 Model"] |
|||
V2Field["Pydantic v2 Model"] |
|||
end |
|||
end |
|||
|
|||
style V2 fill:#f9fff3 |
|||
style V1 fill:#fff6f0 |
|||
style V1Field fill:#fff6f0 |
|||
style V2Field fill:#f9fff3 |
|||
``` |
|||
|
|||
...但是,你可以在同一个应用中分别使用 Pydantic v1 和 v2 的独立模型。 |
|||
|
|||
```mermaid |
|||
graph TB |
|||
subgraph "✅ Supported" |
|||
direction TB |
|||
subgraph V2["Pydantic v2 Model"] |
|||
V2Field["Pydantic v2 Model"] |
|||
end |
|||
subgraph V1["Pydantic v1 Model"] |
|||
V1Field["Pydantic v1 Model"] |
|||
end |
|||
end |
|||
|
|||
style V2 fill:#f9fff3 |
|||
style V1 fill:#fff6f0 |
|||
style V1Field fill:#fff6f0 |
|||
style V2Field fill:#f9fff3 |
|||
``` |
|||
|
|||
在某些情况下,甚至可以在 FastAPI 应用的同一个路径操作中同时使用 Pydantic v1 和 v2 模型: |
|||
|
|||
{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} |
|||
|
|||
在上面的示例中,输入模型是 Pydantic v1 模型,输出模型(在 `response_model=ItemV2` 中定义)是 Pydantic v2 模型。 |
|||
|
|||
### Pydantic v1 参数 { #pydantic-v1-parameters } |
|||
|
|||
如果你需要在 Pydantic v1 模型中使用 FastAPI 特有的参数工具,如 `Body`、`Query`、`Form` 等,在完成向 Pydantic v2 的迁移前,可以从 `fastapi.temp_pydantic_v1_params` 导入它们: |
|||
|
|||
{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} |
|||
|
|||
### 分步迁移 { #migrate-in-steps } |
|||
|
|||
/// tip | 提示 |
|||
|
|||
优先尝试 `bump-pydantic`,如果测试通过且可行,那么你就用一个命令完成了。✨ |
|||
|
|||
/// |
|||
|
|||
如果 `bump-pydantic` 不适用于你的场景,你可以在同一应用中同时支持 Pydantic v1 和 v2 模型,逐步迁移到 Pydantic v2。 |
|||
|
|||
你可以首先将 Pydantic 升级到最新的 v2,并将所有模型的导入改为使用 `pydantic.v1`。 |
|||
|
|||
然后按模块或分组,逐步把模型从 Pydantic v1 迁移到 v2。🚶 |
|||
@ -0,0 +1,102 @@ |
|||
# 是否为输入和输出分别生成 OpenAPI JSON Schema { #separate-openapi-schemas-for-input-and-output-or-not } |
|||
|
|||
自从发布了 **Pydantic v2**,生成的 OpenAPI 比之前更精确、更**正确**了。😎 |
|||
|
|||
事实上,在某些情况下,对于同一个 Pydantic 模型,OpenAPI 中会根据是否带有**默认值**,为输入和输出分别生成**两个 JSON Schema**。 |
|||
|
|||
我们来看看它如何工作,以及在需要时如何修改。 |
|||
|
|||
## 用于输入和输出的 Pydantic 模型 { #pydantic-models-for-input-and-output } |
|||
|
|||
假设你有一个带有默认值的 Pydantic 模型,例如: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} |
|||
|
|||
### 输入用的模型 { #model-for-input } |
|||
|
|||
如果你像下面这样把该模型用作输入: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} |
|||
|
|||
...那么 `description` 字段将**不是必填项**,因为它的默认值是 `None`。 |
|||
|
|||
### 文档中的输入模型 { #input-model-in-docs } |
|||
|
|||
你可以在文档中确认,`description` 字段没有**红色星号**,也就是未被标记为必填: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image01.png"> |
|||
</div> |
|||
|
|||
### 输出用的模型 { #model-for-output } |
|||
|
|||
但如果你把同一个模型用作输出,例如: |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} |
|||
|
|||
...那么因为 `description` 有默认值,即使你**不返回该字段**,它仍然会有这个**默认值**。 |
|||
|
|||
### 输出响应数据的模型 { #model-for-output-response-data } |
|||
|
|||
如果你在文档中交互并查看响应,即使代码没有给某个 `description` 字段赋值,JSON 响应中仍包含默认值(`null`): |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image02.png"> |
|||
</div> |
|||
|
|||
这意味着它**总会有值**,只是有时该值可能为 `None`(在 JSON 中是 `null`)。 |
|||
|
|||
这也意味着,使用你的 API 的客户端无需检查该值是否存在,他们可以**假设该字段总会存在**,只是有时它会是默认值 `None`。 |
|||
|
|||
在 OpenAPI 中描述这一点的方式,是把该字段标记为**必填**,因为它总会存在。 |
|||
|
|||
因此,一个模型的 JSON Schema 会根据它用于**输入还是输出**而有所不同: |
|||
|
|||
- 用于**输入**时,`description` **不是必填** |
|||
- 用于**输出**时,它是**必填**(并且可能为 `None`,在 JSON 中为 `null`) |
|||
|
|||
### 文档中的输出模型 { #model-for-output-in-docs } |
|||
|
|||
你也可以在文档中查看输出模型,`name` 和 `description` **都**被**红色星号**标记为**必填**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image03.png"> |
|||
</div> |
|||
|
|||
### 文档中的输入/输出模型 { #model-for-input-and-output-in-docs } |
|||
|
|||
如果你查看 OpenAPI 中可用的所有 Schema(JSON Schema),你会看到有两个,一个是 `Item-Input`,一个是 `Item-Output`。 |
|||
|
|||
对于 `Item-Input`,`description` **不是必填**,没有红色星号。 |
|||
|
|||
但对于 `Item-Output`,`description` 是**必填**,带有红色星号。 |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image04.png"> |
|||
</div> |
|||
|
|||
借助 **Pydantic v2** 的这个特性,你的 API 文档会更**精确**,如果你有自动生成的客户端和 SDK,它们也会更精确,带来更好的**开发者体验**和一致性。🎉 |
|||
|
|||
## 不要分离 Schema { #do-not-separate-schemas } |
|||
|
|||
当然,在某些情况下,你可能希望**输入和输出使用同一个 schema**。 |
|||
|
|||
最常见的情形是:你已经有一些自动生成的客户端代码/SDK,你暂时不想更新所有这些自动生成的客户端代码/SDK(也许未来会,但不是现在)。 |
|||
|
|||
这种情况下,你可以在 **FastAPI** 中通过参数 `separate_input_output_schemas=False` 禁用该特性。 |
|||
|
|||
/// info | 信息 |
|||
|
|||
对 `separate_input_output_schemas` 的支持是在 FastAPI `0.102.0` 中添加的。🤓 |
|||
|
|||
/// |
|||
|
|||
{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} |
|||
|
|||
### 文档中输入/输出使用同一 Schema 的模型 { #same-schema-for-input-and-output-models-in-docs } |
|||
|
|||
现在该模型的输入和输出将只使用一个 schema,即 `Item`,并且其中的 `description` **不是必填**: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/separate-openapi-schemas/image05.png"> |
|||
</div> |
|||
@ -0,0 +1,7 @@ |
|||
# 测试数据库 { #testing-a-database } |
|||
|
|||
你可以在 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel 文档</a> 中学习数据库、SQL 和 SQLModel。🤓 |
|||
|
|||
这里有一个关于在 FastAPI 中使用 SQLModel 的小教程:<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">使用 SQLModel 搭配 FastAPI 的教程</a>。✨ |
|||
|
|||
该教程包含一个关于 <a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/tests/" class="external-link" target="_blank">测试 SQL 数据库</a> 的章节。😎 |
|||
@ -0,0 +1,3 @@ |
|||
# 资源 { #resources } |
|||
|
|||
更多资源、外部链接等。✈️ |
|||
@ -0,0 +1,11 @@ |
|||
/// details | 🌐 由 AI 与人类协作翻译 |
|||
|
|||
本翻译由人类引导的 AI 生成。🤝 |
|||
|
|||
可能存在误解原意或不够自然等问题。🤖 |
|||
|
|||
你可以通过[帮助我们更好地引导 AI LLM](https://fastapi.tiangolo.com/zh/contributing/#translations)来改进此翻译。 |
|||
|
|||
[英文版本](ENGLISH_VERSION_URL) |
|||
|
|||
/// |
|||
Loading…
Reference in new issue