Sync fastapi docs from b5ca1324 on 2025-12-07
Issue Manager / issue-manager (push) Has been cancelled
Build Docs / changes (push) Has been cancelled
Build Docs / langs (push) Has been cancelled
Build Docs / build-docs (push) Has been cancelled
Build Docs / docs-all-green (push) Has been cancelled
Conflict detector / main (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi) (push) Has been cancelled
Test Redistribute / test-redistribute (fastapi-slim) (push) Has been cancelled
Test Redistribute / test-redistribute-alls-green (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (pydantic-v1, 3.10) (push) Has been cancelled
Test / test (pydantic-v1, 3.11) (push) Has been cancelled
Test / test (pydantic-v1, 3.13) (push) Has been cancelled
Test / test (pydantic-v1, 3.8) (push) Has been cancelled
Test / test (pydantic-v1, 3.9) (push) Has been cancelled
Test / test (pydantic-v2, 3.10) (push) Has been cancelled
Test / test (pydantic-v2, 3.11) (push) Has been cancelled
Test / test (pydantic-v2, 3.12) (push) Has been cancelled
Test / test (pydantic-v2, 3.13) (push) Has been cancelled
Test / test (pydantic-v2, 3.14) (push) Has been cancelled
Test / test (pydantic-v2, 3.8) (push) Has been cancelled
Test / test (pydantic-v2, 3.9) (push) Has been cancelled
Test / coverage-combine (push) Has been cancelled
Test / check (push) Has been cancelled
Label Approved / label-approved (push) Has been cancelled
FastAPI People Contributors / job (push) Has been cancelled
FastAPI People Sponsors / job (push) Has been cancelled
Update Topic Repos / topic-repos (push) Has been cancelled
FastAPI People / job (push) Has been cancelled
Test / test (pydantic-v1, 3.12) (push) Has been cancelled

This commit is contained in:
The Scheduler
2025-12-07 21:35:20 +00:00
commit a42b1ff04e
2600 changed files with 301870 additions and 0 deletions
@@ -0,0 +1,221 @@
# OPENAPI 中的其他响应
您可以声明附加响应,包括附加状态代码、媒体类型、描述等。
这些额外的响应将包含在OpenAPI模式中,因此它们也将出现在API文档中。
但是对于那些额外的响应,你必须确保你直接返回一个像 `JSONResponse` 一样的 `Response` ,并包含你的状态代码和内容。
## `model`附加响应
您可以向路径操作装饰器传递参数 `responses`
它接收一个 `dict`,键是每个响应的状态代码(如`200`),值是包含每个响应信息的其他 `dict`
每个响应字典都可以有一个关键模型,其中包含一个 `Pydantic` 模型,就像 `response_model` 一样。
**FastAPI**将采用该模型,生成其`JSON Schema`并将其包含在`OpenAPI`中的正确位置。
例如,要声明另一个具有状态码 `404``Pydantic`模型 `Message` 的响应,可以写:
{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *}
/// note
请记住,您必须直接返回 `JSONResponse`
///
/// info
`model` 密钥不是OpenAPI的一部分。
**FastAPI**将从那里获取`Pydantic`模型,生成` JSON Schema` ,并将其放在正确的位置。
- 正确的位置是:
- 在键 `content` 中,其具有另一个`JSON`对象( `dict` )作为值,该`JSON`对象包含:
- 媒体类型的密钥,例如 `application/json` ,它包含另一个`JSON`对象作为值,该对象包含:
- 一个键` schema` ,它的值是来自模型的`JSON Schema`,正确的位置在这里。
- **FastAPI**在这里添加了对OpenAPI中另一个地方的全局JSON模式的引用,而不是直接包含它。这样,其他应用程序和客户端可以直接使用这些JSON模式,提供更好的代码生成工具等。
///
**在OpenAPI中为该路径操作生成的响应将是:**
```json hl_lines="3-12"
{
"responses": {
"404": {
"description": "Additional Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
```
**模式被引用到OpenAPI模式中的另一个位置:**
```json hl_lines="4-16"
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"required": [
"message"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"required": [
"id",
"value"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"required": [
"loc",
"msg",
"type"
],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
```
## 主响应的其他媒体类型
您可以使用相同的 `responses` 参数为相同的主响应添加不同的媒体类型。
例如,您可以添加一个额外的媒体类型` image/png` ,声明您的路径操作可以返回JSON对象(媒体类型 `application/json` )或PNG图像:
{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *}
/// note
- 请注意,您必须直接使用 `FileResponse` 返回图像。
///
/// info
- 除非在 `responses` 参数中明确指定不同的媒体类型,否则**FastAPI**将假定响应与主响应类具有相同的媒体类型(默认为` application/json` )。
- 但是如果您指定了一个自定义响应类,并将 `None `作为其媒体类型,**FastAPI**将使用 `application/json` 作为具有关联模型的任何其他响应。
///
## 组合信息
您还可以联合接收来自多个位置的响应信息,包括 `response_model `、 `status_code` 和 `responses `参数。
您可以使用默认的状态码 `200` (或者您需要的自定义状态码)声明一个 `response_model `,然后直接在OpenAPI模式中在 `responses` 中声明相同响应的其他信息。
**FastAPI**将保留来自 `responses` 的附加信息,并将其与模型中的JSON Schema结合起来。
例如,您可以使用状态码 `404` 声明响应,该响应使用`Pydantic`模型并具有自定义的` description` 。
以及一个状态码为 `200` 的响应,它使用您的 `response_model` ,但包含自定义的 `example`
{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *}
所有这些都将被合并并包含在您的OpenAPI中,并在API文档中显示:
## 联合预定义响应和自定义响应
您可能希望有一些应用于许多路径操作的预定义响应,但是你想将不同的路径和自定义的相应组合在一块。
对于这些情况,你可以使用Python的技术,将 `dict` 与 `**dict_to_unpack` 解包:
```Python
old_dict = {
"old key": "old value",
"second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}
```
这里, new_dict 将包含来自 old_dict 的所有键值对加上新的键值对:
```python
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
```
您可以使用该技术在路径操作中重用一些预定义的响应,并将它们与其他自定义响应相结合。
**例如:**
{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *}
## 有关OpenAPI响应的更多信息
要了解您可以在响应中包含哪些内容,您可以查看OpenAPI规范中的以下部分:
+ [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responsesObject),它包括 Response Object 。
+ [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responseObject),您可以直接在 `responses` 参数中的每个响应中包含任何内容。包括 `description` 、 `headers` 、 `content` (其中是声明不同的媒体类型和JSON Schemas)和 `links` 。
@@ -0,0 +1,41 @@
# 额外的状态码
**FastAPI** 默认使用 `JSONResponse` 返回一个响应,将你的 *路径操作* 中的返回内容放到该 `JSONResponse` 中。
**FastAPI** 会自动使用默认的状态码或者使用你在 *路径操作* 中设置的状态码。
## 额外的状态码
如果你想要返回主要状态码之外的状态码,你可以通过直接返回一个 `Response` 来实现,比如 `JSONResponse`,然后直接设置额外的状态码。
例如,假设你想有一个 *路径操作* 能够更新条目,并且更新成功时返回 200 「成功」 的 HTTP 状态码。
但是你也希望它能够接受新的条目。并且当这些条目不存在时,会自动创建并返回 201 「创建」的 HTTP 状态码。
要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。
{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *}
/// warning | 警告
当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。
FastAPI 不会用模型等对该响应进行序列化。
确保其中有你想要的数据,且返回的值为合法的 JSON(如果你使用 `JSONResponse` 的话)。
///
/// note | 技术细节
你也可以使用 `from starlette.responses import JSONResponse`。 
出于方便,**FastAPI** 为开发者提供同 `starlette.responses` 一样的 `fastapi.responses`。但是大多数可用的响应都是直接来自 Starlette。`status` 也是一样。
///
## OpenAPI 和 API 文档
如果你直接返回额外的状态码和响应,它们不会包含在 OpenAPI 方案(API 文档)中,因为 FastAPI 没办法预先知道你要返回什么。
但是你可以使用 [额外的响应](additional-responses.md){.internal-link target=_blank} 在代码中记录这些内容。
@@ -0,0 +1,65 @@
# 高级依赖项
## 参数化的依赖项
我们之前看到的所有依赖项都是写死的函数或类。
但也可以为依赖项设置参数,避免声明多个不同的函数或类。
假设要创建校验查询参数 `q` 是否包含固定内容的依赖项。
但此处要把待检验的固定内容定义为参数。
## **可调用**实例
Python 可以把类实例变为**可调用项**。
这里说的不是类本身(类本就是可调用项),而是类实例。
为此,需要声明 `__call__` 方法:
{* ../../docs_src/dependencies/tutorial011.py hl[10] *}
本例中,**FastAPI** 使用 `__call__` 检查附加参数及子依赖项,稍后,还要调用它向*路径操作函数*传递值。
## 参数化实例
接下来,使用 `__init__` 声明用于**参数化**依赖项的实例参数:
{* ../../docs_src/dependencies/tutorial011.py hl[7] *}
本例中,**FastAPI** 不使用 `__init__`,我们要直接在代码中使用。
## 创建实例
使用以下代码创建类实例:
{* ../../docs_src/dependencies/tutorial011.py hl[16] *}
这样就可以**参数化**依赖项,它包含 `checker.fixed_content` 的属性 - `"bar"`
## 把实例作为依赖项
然后,不要再在 `Depends(checker)` 中使用 `Depends(FixedContentQueryChecker)` 而是要使用 `checker`,因为依赖项是类实例 - `checker`,不是类。
处理依赖项时,**FastAPI** 以如下方式调用 `checker`
```Python
checker(q="somequery")
```
……并用*路径操作函数*的参数 `fixed_content_included` 返回依赖项的值:
{* ../../docs_src/dependencies/tutorial011.py hl[20] *}
/// tip | 提示
本章示例有些刻意,也看不出有什么用处。
这个简例只是为了说明高级依赖项的运作机制。
在有关安全的章节中,工具函数将以这种方式实现。
只要能理解本章内容,就能理解安全工具背后的运行机制。
///
+99
View File
@@ -0,0 +1,99 @@
# 异步测试
您已经了解了如何使用 `TestClient` 测试 **FastAPI** 应用程序。但是到目前为止,您只了解了如何编写同步测试,而没有使用 `async` 异步函数。
在测试中能够使用异步函数可能会很有用,比如当您需要异步查询数据库的时候。想象一下,您想要测试向 FastAPI 应用程序发送请求,然后验证您的后端是否成功在数据库中写入了正确的数据,与此同时您使用了异步的数据库的库。
让我们看看如何才能实现这一点。
## pytest.mark.anyio
如果我们想在测试中调用异步函数,那么我们的测试函数必须是异步的。 AnyIO 为此提供了一个简洁的插件,它允许我们指定一些测试函数要异步调用。
## HTTPX
即使您的 **FastAPI** 应用程序使用普通的 `def` 函数而不是 `async def` ,它本质上仍是一个 `async` 异步应用程序。
`TestClient` 在内部通过一些“魔法”操作,使得您可以在普通的 `def` 测试函数中调用异步的 FastAPI 应用程序,并使用标准的 pytest。但当我们在异步函数中使用它时,这种“魔法”就不再生效了。由于测试以异步方式运行,我们无法在测试函数中继续使用 `TestClient`
`TestClient` 是基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> 的。幸运的是,我们可以直接使用它来测试API。
## 示例
举个简单的例子,让我们来看一个[更大的应用](../tutorial/bigger-applications.md){.internal-link target=_blank}和[测试](../tutorial/testing.md){.internal-link target=_blank}中描述的类似文件结构:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
文件 `main.py` 将包含:
{* ../../docs_src/async_tests/main.py *}
文件 `test_main.py` 将包含针对 `main.py` 的测试,现在它可能看起来如下:
{* ../../docs_src/async_tests/test_main.py *}
## 运行测试
您可以通过以下方式照常运行测试:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## 详细说明
这个标记 `@pytest.mark.anyio` 会告诉 pytest 该测试函数应该被异步调用:
{* ../../docs_src/async_tests/test_main.py hl[7] *}
/// tip
请注意,测试函数现在用的是 `async def`,而不是像以前使用 `TestClient` 时那样只是 `def`
///
我们现在可以使用应用程序创建一个 `AsyncClient` ,并使用 `await` 向其发送异步请求。
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
这相当于:
```Python
response = client.get('/')
```
我们曾经通过它向 `TestClient` 发出请求。
/// tip
请注意,我们正在将 async/await 与新的 `AsyncClient` 一起使用——请求是异步的。
///
/// warning
如果您的应用程序依赖于生命周期事件, `AsyncClient` 将不会触发这些事件。为了确保它们被触发,请使用 <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a> 中的 `LifespanManager`
///
## 其他异步函数调用
由于测试函数现在是异步的,因此除了在测试中向 FastAPI 应用程序发送请求之外,您现在还可以调用(和使用 `await` 等待)其他 `async` 异步函数,就和您在代码中的其他任何地方调用它们的方法一样。
/// tip
如果您在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB 的 MotorClient</a> 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如通过 `'@app.on_event("startup")` 回调函数进行初始化。
///
+357
View File
@@ -0,0 +1,357 @@
# 使用代理
有些情况下,您可能要使用 Traefik 或 Nginx 等**代理**服务器,并添加应用不能识别的附加路径前缀配置。
此时,要使用 `root_path` 配置应用。
`root_path` 是 ASGI 规范提供的机制,FastAPI 就是基于此规范开发的(通过 Starlette)。
`root_path` 用于处理这些特定情况。
在挂载子应用时,也可以在内部使用。
## 移除路径前缀的代理
本例中,移除路径前缀的代理是指在代码中声明路径 `/app`,然后在应用顶层添加代理,把 **FastAPI** 应用放在 `/api/v1` 路径下。
本例的原始路径 `/app` 实际上是在 `/api/v1/app` 提供服务。
哪怕所有代码都假设只有 `/app`
代理只在把请求传送给 Uvicorn 之前才会**移除路径前缀**,让应用以为它是在 `/app` 提供服务,因此不必在代码中加入前缀 `/api/v1`
但之后,在(前端)打开 API 文档时,代理会要求在 `/openapi.json`,而不是 `/api/v1/openapi.json` 中提取 OpenAPI 概图。
因此, (运行在浏览器中的)前端会尝试访问 `/openapi.json`,但没有办法获取 OpenAPI 概图。
这是因为应用使用了以 `/api/v1` 为路径前缀的代理,前端要从 `/api/v1/openapi.json` 中提取 OpenAPI 概图。
```mermaid
graph LR
browser("Browser")
proxy["Proxy on http://0.0.0.0:9999/api/v1/app"]
server["Server on http://127.0.0.1:8000/app"]
browser --> proxy
proxy --> server
```
/// tip | 提示
IP `0.0.0.0` 常用于指程序监听本机或服务器上的所有有效 IP。
///
API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`(使用代理时的 URL)。例如:
```JSON hl_lines="4-8"
{
"openapi": "3.0.2",
// More stuff here
"servers": [
{
"url": "/api/v1"
}
],
"paths": {
// More stuff here
}
}
```
本例中的 `Proxy` 是 **Traefik**`server` 是运行 FastAPI 应用的 **Uvicorn**。
### 提供 `root_path`
为此,要以如下方式使用命令行选项 `--root-path`
<div class="termy">
```console
$ uvicorn main:app --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Hypercorn 也支持 `--root-path `选项。
/// note | 技术细节
ASGI 规范定义的 `root_path` 就是为了这种用例。
并且 `--root-path` 命令行选项支持 `root_path`。
///
### 查看当前的 `root_path`
获取应用为每个请求使用的当前 `root_path`,这是 `scope` 字典的内容(也是 ASGI 规范的内容)。
我们在这里的信息里包含 `roo_path` 只是为了演示。
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
然后,用以下命令启动 Uvicorn:
<div class="termy">
```console
$ uvicorn main:app --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
返回的响应如下:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
### 在 FastAPI 应用里设置 `root_path`
还有一种方案,如果不能提供 `--root-path` 或等效的命令行选项,则在创建 FastAPI 应用时要设置 `root_path` 参数。
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *}
传递 `root_path` 给 `FastAPI` 与传递 `--root-path` 命令行选项给 Uvicorn 或 Hypercorn 一样。
### 关于 `root_path`
注意,服务器(Uvicorn)只是把 `root_path` 传递给应用。
在浏览器中输入 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app 时能看到标准响应:</a>
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
它不要求访问 `http://127.0.0.1:800/api/v1/app`。
Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶部添加 `/api/v1` 前缀是代理要做的事情。
## 关于移除路径前缀的代理
注意,移除路径前缀的代理只是配置代理的方式之一。
大部分情况下,代理默认都不会移除路径前缀。
(未移除路径前缀时)代理监听 `https://myawesomeapp.com` 等对象,如果浏览器跳转到 `https://myawesomeapp.com/api/v1/app`,且服务器(例如 Uvicorn)监听 `http://127.0.0.1:8000` 代理(未移除路径前缀) 会在同样的路径:`http://127.0.0.1:8000/api/v1/app` 访问 Uvicorn。
## 本地测试 Traefik
您可以轻易地在本地使用 <a href="https://docs.traefik.io/" class="external-link" target="_blank">Traefik</a> 运行移除路径前缀的试验。
<a href="https://github.com/containous/traefik/releases" class="external-link" target="_blank">下载 Traefik</a>,这是一个二进制文件,需要解压文件,并在 Terminal 中直接运行。
然后创建包含如下内容的 `traefik.toml` 文件:
```TOML hl_lines="3"
[entryPoints]
[entryPoints.http]
address = ":9999"
[providers]
[providers.file]
filename = "routes.toml"
```
这个文件把 Traefik 监听端口设置为 `9999`,并设置要使用另一个文件 `routes.toml`。
/// tip | 提示
使用端口 9999 代替标准的 HTTP 端口 80,这样就不必使用管理员权限运行(`sudo`)。
///
接下来,创建 `routes.toml`
```TOML hl_lines="5 12 20"
[http]
[http.middlewares]
[http.middlewares.api-stripprefix.stripPrefix]
prefixes = ["/api/v1"]
[http.routers]
[http.routers.app-http]
entryPoints = ["http"]
service = "app"
rule = "PathPrefix(`/api/v1`)"
middlewares = ["api-stripprefix"]
[http.services]
[http.services.app]
[http.services.app.loadBalancer]
[[http.services.app.loadBalancer.servers]]
url = "http://127.0.0.1:8000"
```
这个文件配置 Traefik 使用路径前缀 `/api/v1`。
然后,它把请求重定位到运行在 `http://127.0.0.1:8000` 上的 Uvicorn。
现在,启动 Traefik
<div class="termy">
```console
$ ./traefik --configFile=traefik.toml
INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
```
</div>
接下来,使用 Uvicorn 启动应用,并使用 `--root-path` 选项:
<div class="termy">
```console
$ uvicorn main:app --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### 查看响应
访问含 Uvicorn 端口的 URL<a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app,就能看到标准响应:</a>
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
/// tip | 提示
注意,就算访问 `http://127.0.0.1:8000/app`,也显示从选项 `--root-path` 中提取的 `/api/v1`,这是 `root_path` 的值。
///
打开含 Traefik 端口的 URL,包含路径前缀:<a href="http://127.0.0.1:9999/api/v1/app" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/app。</a>
得到同样的响应:
```JSON
{
"message": "Hello World",
"root_path": "/api/v1"
}
```
但这一次 URL 包含了代理提供的路径前缀:`/api/v1`。
当然,这是通过代理访问应用的方式,因此,路径前缀 `/app/v1` 版本才是**正确**的。
而不带路径前缀的版本(`http://127.0.0.1:8000/app`),则由 Uvicorn 直接提供,专供*代理*Traefik)访问。
这演示了代理(Traefik)如何使用路径前缀,以及服务器(Uvicorn)如何使用选项 `--root-path` 中的 `root_path`。
### 查看文档
但这才是有趣的地方 ✨
访问应用的**官方**方式是通过含路径前缀的代理。因此,不出所料,如果没有在 URL 中添加路径前缀,直接访问通过 Uvicorn 运行的 API 文档,不能正常访问,因为需要通过代理才能访问。
输入 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs 查看 API 文档:</a>
<img src="/img/tutorial/behind-a-proxy/image01.png">
但输入**官方**链接 `/api/v1/docs`,并使用端口 `9999` 访问 API 文档,就能正常运行了!🎉
输入 <a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs 查看文档:</a>
<img src="/img/tutorial/behind-a-proxy/image02.png">
一切正常。 ✔️
这是因为 FastAPI 在 OpenAPI 里使用 `root_path` 提供的 URL 创建默认 `server`。
## 附加的服务器
/// warning | 警告
此用例较难,可以跳过。
///
默认情况下,**FastAPI** 使用 `root_path` 的链接在 OpenAPI 概图中创建 `server`。
但也可以使用其它备选 `servers`,例如,需要同一个 API 文档与 staging 和生产环境交互。
如果传递自定义 `servers` 列表,并有 `root_path`( 因为 API 使用了代理),**FastAPI** 会在列表开头使用这个 `root_path` 插入**服务器**。
例如:
{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *}
这段代码生产如下 OpenAPI 概图:
```JSON hl_lines="5-7"
{
"openapi": "3.0.2",
// More stuff here
"servers": [
{
"url": "/api/v1"
},
{
"url": "https://stag.example.com",
"description": "Staging environment"
},
{
"url": "https://prod.example.com",
"description": "Production environment"
}
],
"paths": {
// More stuff here
}
}
```
/// tip | 提示
注意,自动生成服务器时,`url` 的值 `/api/v1` 提取自 `roog_path`。
///
<a href="http://127.0.0.1:9999/api/v1/docs" class="external-link" target="_blank">http://127.0.0.1:9999/api/v1/docs 的 API 文档所示如下:</a>
<img src="/img/tutorial/behind-a-proxy/image03.png">
/// tip | 提示
API 文档与所选的服务器进行交互。
///
### 从 `root_path` 禁用自动服务器
如果不想让 **FastAPI** 包含使用 `root_path` 的自动服务器,则要使用参数 `root_path_in_servers=False`
{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *}
这样,就不会在 OpenAPI 概图中包含服务器了。
## 挂载子应用
如需挂载子应用(详见 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}),也要通过 `root_path` 使用代理,这与正常应用一样,别无二致。
FastAPI 在内部使用 `root_path`,因此子应用也可以正常运行。✨
+218
View File
@@ -0,0 +1,218 @@
# 自定义响应 - HTML,流,文件和其他
**FastAPI** 默认会使用 `JSONResponse` 返回响应。
你可以通过直接返回 `Response` 来重载它,参见 [直接返回响应](response-directly.md){.internal-link target=_blank}。
但如果你直接返回 `Response`,返回数据不会自动转换,也不会自动生成文档(例如,在 HTTP 头 `Content-Type` 中包含特定的「媒体类型」作为生成的 OpenAPI 的一部分)。
你还可以在 *路径操作装饰器* 中声明你想用的 `Response`
你从 *路径操作函数* 中返回的内容将被放在该 `Response` 中。
并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或者 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。
/// note | 说明
如果你使用不带有任何媒体类型的响应类,FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。
///
## 使用 `ORJSONResponse`
例如,如果你需要压榨性能,你可以安装并使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 并将响应设置为 `ORJSONResponse`
导入你想要使用的 `Response` 类(子类)然后在 *路径操作装饰器* 中声明它。
{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *}
/// info | 提示
参数 `response_class` 也会用来定义响应的「媒体类型」。
在这个例子中,HTTP 头的 `Content-Type` 会被设置成 `application/json`
并且在 OpenAPI 文档中也会这样记录。
///
/// tip | 小贴士
`ORJSONResponse` 目前只在 FastAPI 中可用,而在 Starlette 中不可用。
///
## HTML 响应
使用 `HTMLResponse` 来从 **FastAPI** 中直接返回一个 HTML 响应。
* 导入 `HTMLResponse`
*`HTMLResponse` 作为你的 *路径操作*`response_class` 参数传入。
{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *}
/// info | 提示
参数 `response_class` 也会用来定义响应的「媒体类型」。
在这个例子中,HTTP 头的 `Content-Type` 会被设置成 `text/html`
并且在 OpenAPI 文档中也会这样记录。
///
### 返回一个 `Response`
正如你在 [直接返回响应](response-directly.md){.internal-link target=_blank} 中了解到的,你也可以通过直接返回响应在 *路径操作* 中直接重载响应。
和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样:
{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *}
/// warning | 警告
*路径操作函数* 直接返回的 `Response` 不会被 OpenAPI 的文档记录(比如,`Content-Type` 不会被文档记录),并且在自动化交互文档中也是不可见的。
///
/// info | 提示
当然,实际的 `Content-Type` 头,状态码等等,将来自于你返回的 `Response` 对象。
///
### OpenAPI 中的文档和重载 `Response`
如果你想要在函数内重载响应,但是同时在 OpenAPI 中文档化「媒体类型」,你可以使用 `response_class` 参数并返回一个 `Response` 对象。
接着 `response_class` 参数只会被用来文档化 OpenAPI 的 *路径操作*,你的 `Response` 用来返回响应。
### 直接返回 `HTMLResponse`
比如像这样:
{* ../../docs_src/custom_response/tutorial004.py hl[7,23,21] *}
在这个例子中,函数 `generate_html_response()` 已经生成并返回 `Response` 对象而不是在 `str` 中返回 HTML。
通过返回函数 `generate_html_response()` 的调用结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response` 对象,
但如果你在 `response_class` 中也传入了 `HTMLResponse`**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML。
<img src="/img/tutorial/custom-response/image01.png">
## 可用响应
这里有一些可用的响应。
要记得你可以使用 `Response` 来返回任何其他东西,甚至创建一个自定义的子类。
/// note | 技术细节
你也可以使用 `from starlette.responses import HTMLResponse`
**FastAPI** 提供了同 `fastapi.responses` 相同的 `starlette.responses` 只是为了方便开发者。但大多数可用的响应都直接来自 Starlette。
///
### `Response`
其他全部的响应都继承自主类 `Response`
你可以直接返回它。
`Response` 类接受如下参数:
* `content` - 一个 `str` 或者 `bytes`
* `status_code` - 一个 `int` 类型的 HTTP 状态码。
* `headers` - 一个由字符串组成的 `dict`
* `media_type` - 一个给出媒体类型的 `str`,比如 `"text/html"`
FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
### `HTMLResponse`
如上文所述,接受文本或字节并返回 HTML 响应。
### `PlainTextResponse`
接受文本或字节并返回纯文本响应。
{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *}
### `JSONResponse`
接受数据并返回一个 `application/json` 编码的响应。
如上文所述,这是 **FastAPI** 中使用的默认响应。
### `ORJSONResponse`
如上文所述,`ORJSONResponse` 是一个使用 <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a> 的快速的可选 JSON 响应。
### `UJSONResponse`
`UJSONResponse` 是一个使用 <a href="https://github.com/ultrajson/ultrajson" class="external-link" target="_blank">`ujson`</a> 的可选 JSON 响应。
/// warning | 警告
在处理某些边缘情况时,`ujson` 不如 Python 的内置实现那么谨慎。
///
{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *}
/// tip | 小贴士
`ORJSONResponse` 可能是一个更快的选择。
///
### `RedirectResponse`
返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)。
{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *}
### `StreamingResponse`
采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。
{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *}
#### 对类似文件的对象使用 `StreamingResponse`
如果您有类似文件的对象(例如,由 `open()` 返回的对象),则可以在 `StreamingResponse` 中将其返回。
包括许多与云存储,视频处理等交互的库。
{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *}
/// tip | 小贴士
注意在这里,因为我们使用的是不支持 `async``await` 的标准 `open()`,我们使用普通的 `def` 声明了路径操作。
///
### `FileResponse`
异步传输文件作为响应。
与其他响应类型相比,接受不同的参数集进行实例化:
* `path` - 要流式传输的文件的文件路径。
* `headers` - 任何自定义响应头,传入字典类型。
* `media_type` - 给出媒体类型的字符串。如果未设置,则文件名或路径将用于推断媒体类型。
* `filename` - 如果给出,它将包含在响应的 `Content-Disposition` 中。
文件响应将包含适当的 `Content-Length``Last-Modified``ETag` 的响应头。
{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *}
## 额外文档
您还可以使用 `response` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。
+97
View File
@@ -0,0 +1,97 @@
# 使用数据类
FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 模型声明请求与响应。
但 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] *}
这还是借助于 **Pydantic** 及其<a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/#use-of-stdlib-dataclasses-with-basemodel" class="external-link" target="_blank">内置的 `dataclasses`</a>。
因此,即便上述代码没有显式使用 PydanticFastAPI 仍会使用 Pydantic 把标准数据类转换为 Pydantic 数据类(`dataclasses`)。
并且,它仍然支持以下功能:
* 数据验证
* 数据序列化
* 数据存档等
数据类的和运作方式与 Pydantic 模型相同。实际上,它的底层使用的也是 Pydantic。
/// info | 说明
注意,数据类不支持 Pydantic 模型的所有功能。
因此,开发时仍需要使用 Pydantic 模型。
但如果数据类很多,这一技巧能给 FastAPI 开发 Web API 增添不少助力。🤓
///
## `response_model` 使用数据类
`response_model` 参数中使用 `dataclasses`
{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *}
本例把数据类自动转换为 Pydantic 数据类。
API 文档中也会显示相关概图:
<img src="/img/tutorial/dataclasses/image01.png">
## 在嵌套数据结构中使用数据类
您还可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。
还有一些情况也可以使用 Pydantic 的 `dataclasses`。例如,在 API 文档中显示错误。
本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`
```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" }
{!../../docs_src/dataclasses/tutorial003.py!}
```
1. 本例依然要从标准的 `dataclasses` 中导入 `field`
2. 使用 `pydantic.dataclasses` 直接替换 `dataclasses`
3. `Author` 数据类包含 `Item` 数据类列表;
4. `Author` 数据类用于 `response_model` 参数;
5. 其它带有数据类的标准类型注解也可以作为请求体;
本例使用的是 `Item` 数据类列表;
6. 这行代码返回的是包含 `items` 的字典,`items` 是数据类列表;
FastAPI 仍能把数据<abbr title="把数据转换为可以传输的格式">序列化</abbr>为 JSON
7. 这行代码中,`response_model` 的类型注解是 `Author` 数据类列表;
再一次,可以把 `dataclasses` 与标准类型注解一起使用;
8. 注意,*路径操作函数*使用的是普通函数,不是异步函数;
与往常一样,在 FastAPI 中,可以按需组合普通函数与异步函数;
如果不清楚何时使用异步函数或普通函数,请参阅**急不可待?**一节中对 <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank" class="internal-link">`async` 与 `await`</a> 的说明;
9. *路径操作函数*返回的不是数据类(虽然它可以返回数据类),而是返回内含数据的字典列表;
FastAPI 使用(包含数据类的) `response_model` 参数转换响应。
把 `dataclasses` 与其它类型注解组合在一起,可以组成不同形式的复杂数据结构。
更多内容详见上述代码内的注释。
## 深入学习
您还可以把 `dataclasses` 与其它 Pydantic 模型组合在一起,继承合并的模型,把它们包含在您自己的模型里。
详见 <a href="https://pydantic-docs.helpmanual.io/usage/dataclasses/" class="external-link" target="_blank">Pydantic 官档 - 数据类</a>。
## 版本
本章内容自 FastAPI `0.67.0` 版起生效。🔖
+173
View File
@@ -0,0 +1,173 @@
# 生命周期事件
你可以定义在应用**启动**前执行的逻辑(代码)。这意味着在应用**开始接收请求**之前,这些代码只会被执行**一次**。
同样地,你可以定义在应用**关闭**时应执行的逻辑。在这种情况下,这段代码将在**处理可能的多次请求后**执行**一次**。
因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**("生命周期"这个词很重要😉)。
这对于设置你需要在整个应用中使用的**资源**非常有用,这些资源在请求之间**共享**,你可能需要在之后进行**释放**。例如,数据库连接池,或加载一个共享的机器学习模型。
## 用例
让我们从一个示例用例开始,看看如何解决它。
假设你有几个**机器学习的模型**,你想要用它们来处理请求。
相同的模型在请求之间是共享的,因此并非每个请求或每个用户各自拥有一个模型。
假设加载模型可能**需要相当长的时间**,因为它必须从**磁盘**读取大量数据。因此你不希望每个请求都加载它。
你可以在模块/文件的顶部加载它,但这也意味着即使你只是在运行一个简单的自动化测试,它也会**加载模型**,这样测试将**变慢**,因为它必须在能够独立运行代码的其他部分之前等待模型加载完成。
这就是我们要解决的问题——在处理请求前加载模型,但只是在应用开始接收请求前,而不是代码执行时。
## 生命周期 lifespan
你可以使用`FastAPI()`应用的`lifespan`参数和一个上下文管理器(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。
让我们从一个例子开始,然后详细介绍。
我们使用`yield`创建了一个异步函数`lifespan()`像这样:
```Python hl_lines="16 19"
{!../../docs_src/events/tutorial003.py!}
```
在这里,我们在 `yield` 之前将(虚拟的)模型函数放入机器学习模型的字典中,以此模拟加载模型的耗时**启动**操作。这段代码将在应用程序**开始处理请求之前**执行,即**启动**期间。
然后,在 `yield` 之后,我们卸载模型。这段代码将会在应用程序**完成处理请求后**执行,即在**关闭**之前。这可以释放诸如内存或 GPU 之类的资源。
/// tip | 提示
**关闭**事件只会在你停止应用时触发。
可能你需要启动一个新版本,或者你只是你厌倦了运行它。 🤷
///
## 生命周期函数
首先要注意的是,我们定义了一个带有 `yield` 的异步函数。这与带有 `yield` 的依赖项非常相似。
```Python hl_lines="14-19"
{!../../docs_src/events/tutorial003.py!}
```
这个函数在 `yield`之前的部分,会在应用启动前执行。
剩下的部分在 `yield` 之后,会在应用完成后执行。
## 异步上下文管理器
如你所见,这个函数有一个装饰器 `@asynccontextmanager` 。
它将函数转化为所谓的“**异步上下文管理器**”。
```Python hl_lines="1 13"
{!../../docs_src/events/tutorial003.py!}
```
在 Python 中, **上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。
```Python
with open("file.txt") as file:
file.read()
```
Python 的最近几个版本也有了一个**异步上下文管理器**,你可以通过 `async with` 来使用:
```Python
async with lifespan(app):
await do_stuff()
```
你可以像上面一样创建了一个上下文管理器或者异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。
但在我们上面的例子里,我们并不是直接使用,而是传递给 FastAPI 来供其使用。
`FastAPI()` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的上下文管理器 `lifespan` 传给它。
```Python hl_lines="22"
{!../../docs_src/events/tutorial003.py!}
```
## 替代事件(弃用)
/// warning | 警告
配置**启动**和**关闭**事件的推荐方法是使用 `FastAPI()` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`)事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。
你可以跳过这一部分。
///
有一种替代方法可以定义在**启动**和**关闭**期间执行的逻辑。
**FastAPI** 支持定义在应用启动前,或应用关闭时执行的事件处理器(函数)。
事件函数既可以声明为异步函数(`async def`),也可以声明为普通函数(`def`)。
### `startup` 事件
使用 `startup` 事件声明 `app` 启动前运行的函数:
{* ../../docs_src/events/tutorial001.py hl[8] *}
本例中,`startup` 事件处理器函数为项目数据库(只是**字典**)提供了一些初始值。
**FastAPI** 支持多个事件处理器函数。
只有所有 `startup` 事件处理器运行完毕,**FastAPI** 应用才开始接收请求。
### `shutdown` 事件
使用 `shutdown` 事件声明 `app` 关闭时运行的函数:
{* ../../docs_src/events/tutorial002.py hl[6] *}
此处,`shutdown` 事件处理器函数在 `log.txt` 中写入一行文本 `Application shutdown`。
/// info | 说明
`open()` 函数中,`mode="a"` 指的是**追加**。因此这行文本会添加在文件已有内容之后,不会覆盖之前的内容。
///
/// tip | 提示
注意,本例使用 Python `open()` 标准函数与文件交互。
这个函数执行 I/O(输入/输出)操作,需要等待内容写进磁盘。
但 `open()` 函数不支持使用 `async` 与 `await`。
因此,声明事件处理函数要使用 `def`,不能使用 `asnyc def`。
///
### `startup` 和 `shutdown` 一起使用
启动和关闭的逻辑很可能是连接在一起的,你可能希望启动某个东西然后结束它,获取一个资源然后释放它等等。
在不共享逻辑或变量的不同函数中处理这些逻辑比较困难,因为你需要在全局变量中存储值或使用类似的方式。
因此,推荐使用 `lifespan` 。
## 技术细节
只是为好奇者提供的技术细节。🤓
在底层,这部分是<a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">生命周期协议</a>的一部分,参见 ASGI 技术规范,定义了称为启动(`startup`)和关闭(`shutdown`)的事件。
/// info | 说明
有关事件处理器的详情,请参阅 <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">Starlette 官档 - 事件</a>。
包括如何处理生命周期状态,这可以用于程序的其他部分。
///
## 子应用
🚨 **FastAPI** 只会触发主应用中的生命周期事件,不包括[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}中的。
+237
View File
@@ -0,0 +1,237 @@
# 生成客户端
因为 **FastAPI** 是基于OpenAPI规范的,自然您可以使用许多相匹配的工具,包括自动生成API文档 (由 Swagger UI 提供)。
一个不太明显而又特别的优势是,你可以为你的API针对不同的**编程语言**来**生成客户端**(有时候被叫做 <abbr title="Software Development Kits">**SDKs**</abbr> )。
## OpenAPI 客户端生成
有许多工具可以从**OpenAPI**生成客户端。
一个常见的工具是 <a href="https://openapi-generator.tech/" class="external-link" target="_blank">OpenAPI Generator</a>。
如果您正在开发**前端**,一个非常有趣的替代方案是 <a href="https://github.com/hey-api/openapi-ts" class="external-link" target="_blank">openapi-ts</a>。
## 生成一个 TypeScript 前端客户端
让我们从一个简单的 FastAPI 应用开始:
{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *}
请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item``ResponseMessage`
### API 文档
如果您访问API文档,您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**
<img src="/img/tutorial/generate-clients/image01.png">
您可以看到这些模式,因为它们是用程序中的模型声明的。
那些信息可以在应用的 **OpenAPI模式** 被找到,然后显示在API文档中(通过Swagger UI)。
OpenAPI中所包含的模型里有相同的信息可以用于 **生成客户端代码**
### 生成一个TypeScript 客户端
现在我们有了带有模型的应用,我们可以为前端生成客户端代码。
#### 安装 `openapi-ts`
您可以使用以下工具在前端代码中安装 `openapi-ts`:
<div class="termy">
```console
$ npm install @hey-api/openapi-ts --save-dev
---> 100%
```
</div>
#### 生成客户端代码
要生成客户端代码,您可以使用现在将要安装的命令行应用程序 `openapi-ts`
因为它安装在本地项目中,所以您可能无法直接使用此命令,但您可以将其放在 `package.json` 文件中。
它可能看起来是这样的:
```JSON hl_lines="7"
{
"name": "frontend-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios"
},
"author": "",
"license": "",
"devDependencies": {
"@hey-api/openapi-ts": "^0.27.38",
"typescript": "^4.6.2"
}
}
```
在这里添加 NPM `generate-client` 脚本后,您可以使用以下命令运行它:
<div class="termy">
```console
$ npm run generate-client
frontend-app@1.0.0 generate-client /home/user/code/frontend-app
> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios
```
</div>
此命令将在 `./src/client` 中生成代码,并将在其内部使用 `axios`(前端HTTP库)。
### 尝试客户端代码
现在您可以导入并使用客户端代码,它可能看起来像这样,请注意,您可以为这些方法使用自动补全:
<img src="/img/tutorial/generate-clients/image02.png">
您还将自动补全要发送的数据:
<img src="/img/tutorial/generate-clients/image03.png">
/// tip
请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。
///
如果发送的数据字段不符,你也会看到编辑器的错误提示:
<img src="/img/tutorial/generate-clients/image04.png">
响应(response)对象也拥有自动补全:
<img src="/img/tutorial/generate-clients/image05.png">
## 带有标签的 FastAPI 应用
在许多情况下,你的FastAPI应用程序会更复杂,你可能会使用标签来分隔不同组的*路径操作(path operations)*。
例如,您可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔:
{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *}
### 生成带有标签的 TypeScript 客户端
如果您使用标签为FastAPI应用生成客户端,它通常也会根据标签分割客户端代码。
通过这种方式,您将能够为客户端代码进行正确地排序和分组:
<img src="/img/tutorial/generate-clients/image06.png">
在这个案例中,您有:
* `ItemsService`
* `UsersService`
### 客户端方法名称
现在生成的方法名像 `createItemItemsPost` 看起来不太简洁:
```TypeScript
ItemsService.createItemItemsPost({name: "Plumbus", price: 5})
```
...这是因为客户端生成器为每个 *路径操作* 使用OpenAPI的内部 **操作 ID(operation ID)**。
OpenAPI要求每个操作 ID 在所有 *路径操作* 中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP方法/操作**来生成此操作ID,因为这样可以确保这些操作 ID 是唯一的。
但接下来我会告诉你如何改进。 🤓
## 自定义操作ID和更好的方法名
您可以**修改**这些操作ID的**生成**方式,以使其更简洁,并在客户端中具有**更简洁的方法名称**。
在这种情况下,您必须确保每个操作ID在其他方面是**唯一**的。
例如,您可以确保每个*路径操作*都有一个标签,然后根据**标签**和*路径操作***名称**(函数名)来生成操作ID。
### 自定义生成唯一ID函数
FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID**,也用于任何所需自定义模型的名称,用于请求或响应。
你可以自定义该函数。它接受一个 `APIRoute` 对象作为输入,并输出一个字符串。
例如,以下是一个示例,它使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。
然后,你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**:
{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *}
### 使用自定义操作ID生成TypeScript客户端
现在,如果你再次生成客户端,你会发现它具有改善的方法名称:
<img src="/img/tutorial/generate-clients/image07.png">
正如你所见,现在方法名称中只包含标签和函数名,不再包含URL路径和HTTP操作的信息。
### 预处理用于客户端生成器的OpenAPI规范
生成的代码仍然存在一些**重复的信息**。
我们已经知道该方法与 **items** 相关,因为它在 `ItemsService` 中(从标签中获取),但方法名中仍然有标签名作为前缀。😕
一般情况下对于OpenAPI,我们可能仍然希望保留它,因为这将确保操作ID是**唯一的**。
但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI 操作ID,以使方法名称更加美观和**简洁**。
我们可以将 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**:
{* ../../docs_src/generate_clients/tutorial004.py *}
通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。
### 使用预处理的OpenAPI生成TypeScript客户端
现在,由于最终结果保存在文件openapi.json中,你可以修改 package.json 文件以使用此本地文件,例如:
```JSON hl_lines="7"
{
"name": "frontend-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios"
},
"author": "",
"license": "",
"devDependencies": {
"@hey-api/openapi-ts": "^0.27.38",
"typescript": "^4.6.2"
}
}
```
生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能:
<img src="/img/tutorial/generate-clients/image08.png">
## 优点
当使用自动生成的客户端时,你将获得以下的自动补全功能:
* 方法。
* 请求体中的数据、查询参数等。
* 响应数据。
你还将获得针对所有内容的错误提示。
每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*将作为方法可用,旧的方法将被删除,并且其他任何更改将反映在生成的代码中。 🤓
这也意味着如果有任何更改,它将自动**反映**在客户端代码中。如果你**构建**客户端,在使用的数据上存在**不匹配**时,它将报错。
因此,你将在开发周期的早期**检测到许多错误**,而不必等待错误在生产环境中向最终用户展示,然后尝试调试问题所在。 ✨
+21
View File
@@ -0,0 +1,21 @@
# 高级用户指南
## 额外特性
主要的教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 应该足以让你了解 **FastAPI** 的所有主要特性。
你会在接下来的章节中了解到其他的选项、配置以及额外的特性。
/// tip
接下来的章节**并不一定是**「高级的」。
而且对于你的使用场景来说,解决方案很可能就在其中。
///
## 先阅读教程
你可能仍会用到 **FastAPI** 主教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 中的大多数特性。
接下来的章节我们认为你已经读过 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank},并且假设你已经知晓其中主要思想。
+95
View File
@@ -0,0 +1,95 @@
# 高级中间件
用户指南介绍了如何为应用添加[自定义中间件](../tutorial/middleware.md){.internal-link target=_blank} 。
以及如何[使用 `CORSMiddleware` 处理 CORS](../tutorial/cors.md){.internal-link target=_blank}。
本章学习如何使用其它中间件。
## 添加 ASGI 中间件
因为 **FastAPI** 基于 Starlette,且执行 <abbr title="Asynchronous Server Gateway Interface,异步服务器网关界面">ASGI</abbr> 规范,所以可以使用任意 ASGI 中间件。
中间件不必是专为 FastAPI 或 Starlette 定制的,只要遵循 ASGI 规范即可。
总之,ASGI 中间件是类,并把 ASGI 应用作为第一个参数。
因此,有些第三方 ASGI 中间件的文档推荐以如下方式使用中间件:
```Python
from unicorn import UnicornMiddleware
app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")
```
但 FastAPI(实际上是 Starlette)提供了一种更简单的方式,能让内部中间件在处理服务器错误的同时,还能让自定义异常处理器正常运作。
为此,要使用 `app.add_middleware()` (与 CORS 中的示例一样)。
```Python
from fastapi import FastAPI
from unicorn import UnicornMiddleware
app = FastAPI()
app.add_middleware(UnicornMiddleware, some_config="rainbow")
```
`app.add_middleware()` 的第一个参数是中间件的类,其它参数则是要传递给中间件的参数。
## 集成中间件
**FastAPI** 为常见用例提供了一些中间件,下面介绍怎么使用这些中间件。
/// note | 技术细节
以下几个示例中也可以使用 `from starlette.middleware.something import SomethingMiddleware`
**FastAPI**`fastapi.middleware` 中提供的中间件只是为了方便开发者使用,但绝大多数可用的中间件都直接继承自 Starlette。
///
## `HTTPSRedirectMiddleware`
强制所有传入请求必须是 `https``wss`
任何传向 `http``ws` 的请求都会被重定向至安全方案。
{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *}
## `TrustedHostMiddleware`
强制所有传入请求都必须正确设置 `Host` 请求头,以防 HTTP 主机头攻击。
{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *}
支持以下参数:
* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名,或使用 `allowed_hosts=["*"]` 允许任意主机名,或省略中间件。
如果传入的请求没有通过验证,则发送 `400` 响应。
## `GZipMiddleware`
处理 `Accept-Encoding` 请求头中包含 `gzip` 请求的 GZip 响应。
中间件会处理标准响应与流响应。
{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *}
支持以下参数:
* `minimum_size` - 小于最小字节的响应不使用 GZip。 默认值是 `500`
## 其它中间件
除了上述中间件外,FastAPI 还支持其它ASGI 中间件。
例如:
* <a href="https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py" class="external-link" target="_blank">Uvicorn 的 `ProxyHeadersMiddleware`</a>
* <a href="https://github.com/florimondmanca/msgpack-asgi" class="external-link" target="_blank">MessagePack</a>
其它可用中间件详见 <a href="https://www.starlette.dev/middleware/" class="external-link" target="_blank">Starlette 官档 -  中间件</a> 及 <a href="https://github.com/florimondmanca/awesome-asgi" class="external-link" target="_blank">ASGI Awesome 列表</a>。
+186
View File
@@ -0,0 +1,186 @@
# OpenAPI 回调
您可以创建触发外部 API 请求的*路径操作* API,这个外部 API 可以是别人创建的,也可以是由您自己创建的。
API 应用调用外部 API 时的流程叫做**回调**。因为外部开发者编写的软件发送请求至您的 API,然后您的 API 要进行回调,并把请求发送至外部 API。
此时,我们需要存档外部 API 的*信息*,比如应该有哪些*路径操作*,返回什么样的请求体,应该返回哪种响应等。
## 使用回调的应用
示例如下。
假设要开发一个创建发票的应用。
发票包括 `id``title`(可选)、`customer``total` 等属性。
API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建一条发票记录。
(假设)您的 API 将:
* 把发票发送至外部开发者的消费者
* 归集现金
* 把通知发送至 API 的用户(外部开发者)
* 通过(从您的 API)发送 POST 请求至外部 API (即**回调**)来完成
## 常规 **FastAPI** 应用
添加回调前,首先看下常规 API 应用是什么样子。
常规 API 应用包含接收 `Invoice` 请求体的*路径操作*,还有包含回调 URL 的查询参数 `callback_url`
这部分代码很常规,您对绝大多数代码应该都比较熟悉了:
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[10:14,37:54] *}
/// tip | 提示
`callback_url` 查询参数使用 Pydantic 的 <a href="https://pydantic-docs.helpmanual.io/usage/types/#urls" class="external-link" target="_blank">URL</a> 类型。
///
此处唯一比较新的内容是*路径操作装饰器*中的 `callbacks=invoices_callback_router.routes` 参数,下文介绍。
## 存档回调
实际的回调代码高度依赖于您自己的 API 应用。
并且可能每个应用都各不相同。
回调代码可能只有一两行,比如:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
但回调最重要的部分可能是,根据 API 要发送给回调请求体的数据等内容,确保您的 API 用户(外部开发者)正确地实现*外部 API*。
因此,我们下一步要做的就是添加代码,为从 API 接收回调的*外部 API*存档。
这部分文档在 `/docs` 下的 Swagger API 文档中显示,并且会告诉外部开发者如何构建*外部 API*。
本例没有实现回调本身(只是一行代码),只有文档部分。
/// tip | 提示
实际的回调只是 HTTP 请求。
实现回调时,要使用 <a href="https://www.encode.io/httpx/" class="external-link" target="_blank">HTTPX</a> 或 <a href="https://requests.readthedocs.io/" class="external-link" target="_blank">Requests</a>。
///
## 编写回调文档代码
应用不执行这部分代码,只是用它来*记录 外部 API* 。
但,您已经知道用 **FastAPI** 创建自动 API 文档有多简单了。
我们要使用与存档*外部 API* 相同的知识……通过创建外部 API 要实现的*路径操作*(您的 API 要调用的)。
/// tip | 提示
编写存档回调的代码时,假设您是*外部开发者*可能会用的上。并且您当前正在实现的是*外部 API*,不是*您自己的 API*。
临时改变(为外部开发者的)视角能让您更清楚该如何放置*外部 API* 响应和请求体的参数与 Pydantic 模型等。
///
### 创建回调的 `APIRouter`
首先,新建包含一些用于回调的 `APIRouter`
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[5,26] *}
### 创建回调*路径操作*
创建回调*路径操作*也使用之前创建的 `APIRouter`
它看起来和常规 FastAPI *路径操作*差不多:
* 声明要接收的请求体,例如,`body: InvoiceEvent`
* 还要声明要返回的响应,例如,`response_model=InvoiceEventReceived`
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[17:19,22:23,29:33] *}
回调*路径操作*与常规*路径操作*有两点主要区别:
* 它不需要任何实际的代码,因为应用不会调用这段代码。它只是用于存档*外部 API*。因此,函数的内容只需要 `pass` 就可以了
* *路径*可以包含 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>(详见下文),可以使用带参数的变量,以及发送至您的 API 的原始请求的部分
### 回调路径表达式
回调*路径*支持包含发送给您的 API 的原始请求的部分的 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#key-expression" class="external-link" target="_blank">OpenAPI 3 表达式</a>。
本例中是**字符串**
```Python
"{$callback_url}/invoices/{$request.body.id}"
```
因此,如果您的 API 用户(外部开发者)发送请求到您的 API:
```
https://yourapi.com/invoices/?callback_url=https://www.external.org/events
```
使用如下 JSON 请求体:
```JSON
{
"id": "2expen51ve",
"customer": "Mr. Richie Rich",
"total": "9999"
}
```
然后,您的 API 就会处理发票,并在某个点之后,发送回调请求至 `callback_url`(外部 API):
```
https://www.external.org/events/invoices/2expen51ve
```
JSON 请求体包含如下内容:
```JSON
{
"description": "Payment celebration",
"paid": true
}
```
它会预期*外部 API* 的响应包含如下 JSON 请求体:
```JSON
{
"ok": true
}
```
/// tip | 提示
注意,回调 URL包含 `callback_url` `https://www.external.org/events`)中的查询参数,还有 JSON 请求体内部的发票 ID(`2expen51ve`)。
///
### 添加回调路由
至此,在上文创建的回调路由里就包含了*回调路径操作*(外部开发者要在外部 API 中实现)。
现在使用 API *路径操作装饰器*的参数 `callbacks`,从回调路由传递属性 `.routes`(实际上只是路由/路径操作的**列表**):
{* ../../docs_src/openapi_callbacks/tutorial001.py hl[36] *}
/// tip | 提示
注意,不能把路由本身(`invoices_callback_router`)传递给 `callback=`,要传递 `invoices_callback_router.routes` 中的 `.routes` 属性。
///
### 查看文档
现在,使用 Uvicorn 启动应用,打开 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs。</a>
就能看到文档的*路径操作*已经包含了**回调**的内容以及*外部 API*:
<img src="/img/tutorial/openapi-callbacks/image01.png">
+55
View File
@@ -0,0 +1,55 @@
# OpenAPI 网络钩子
有些情况下,您可能想告诉您的 API **用户**,您的应用程序可以携带一些数据调用*他们的*应用程序(给它们发送请求),通常是为了**通知**某种**事件**。
这意味着,除了您的用户向您的 API 发送请求的一般情况,**您的 API**(或您的应用)也可以向**他们的系统**(他们的 API、他们的应用)**发送请求**。
这通常被称为**网络钩子**(Webhook)。
## 使用网络钩子的步骤
通常的过程是**您**在代码中**定义**要发送的消息,即**请求的主体**。
您还需要以某种方式定义您的应用程序将在**何时**发送这些请求或事件。
**用户**会以某种方式(例如在某个网页仪表板上)定义您的应用程序发送这些请求应该使用的 **URL**
所有关于注册网络钩子的 URL 的**逻辑**以及发送这些请求的实际代码都由您决定。您可以在**自己的代码**中以任何想要的方式来编写它。
## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子
使用 **FastAPI**,您可以利用 OpenAPI 来自定义这些网络钩子的名称、您的应用可以发送的 HTTP 操作类型(例如 `POST``PUT` 等)以及您的应用将发送的**请求体**。
这能让您的用户更轻松地**实现他们的 API** 来接收您的**网络钩子**请求,他们甚至可能能够自动生成一些自己的 API 代码。
/// info
网络钩子在 OpenAPI 3.1.0 及以上版本中可用,FastAPI `0.99.0` 及以上版本支持。
///
## 带有网络钩子的应用程序
当您创建一个 **FastAPI** 应用程序时,有一个 `webhooks` 属性可以用来定义网络钩子,方式与您定义*路径操作*的时候相同,例如使用 `@app.webhooks.post()`
{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *}
您定义的网络钩子将被包含在 `OpenAPI` 的架构中,并出现在自动生成的**文档 UI** 中。
/// info
`app.webhooks` 对象实际上只是一个 `APIRouter` ,与您在使用多个文件来构建应用程序时所使用的类型相同。
///
请注意,使用网络钩子时,您实际上并没有声明一个*路径*(比如 `/items/` ),您传递的文本只是这个网络钩子的**标识符**(事件的名称)。例如在 `@app.webhooks.post("new-subscription")` 中,网络钩子的名称是 `new-subscription`
这是因为我们预计**您的用户**会以其他方式(例如通过网页仪表板)来定义他们希望接收网络钩子的请求的实际 **URL 路径**
### 查看文档
现在您可以启动您的应用程序并访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
您会看到您的文档不仅有正常的*路径操作*显示,现在还多了一些**网络钩子**:
<img src="/img/tutorial/openapi-webhooks/image01.png">
@@ -0,0 +1,54 @@
# 路径操作的高级配置
## OpenAPI 的 operationId
/// warning
如果你并非 OpenAPI 的「专家」,你可能不需要这部分内容。
///
你可以在路径操作中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`
务必确保每个操作路径的 `operation_id` 都是唯一的。
{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *}
### 使用 *路径操作函数* 的函数名作为 operationId
如果你想用你的 API 的函数名作为 `operationId` 的名字,你可以遍历一遍 API 的函数名,然后使用他们的 `APIRoute.name` 重写每个 *路径操作*`operation_id`
你应该在添加了所有 *路径操作* 之后执行此操作。
{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12,13,14,15,16,17,18,19,20,21,24] *}
/// tip
如果你手动调用 `app.openapi()`,你应该在此之前更新 `operationId`
///
/// warning
如果你这样做,务必确保你的每个 *路径操作函数* 的名字唯一。
即使它们在不同的模块中(Python 文件)。
///
## 从 OpenAPI 中排除
使用参数 `include_in_schema` 并将其设置为 `False` ,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了)。
{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *}
## docstring 的高级描述
你可以限制 *路径操作函数*`docstring` 中用于 OpenAPI 的行数。
添加一个 `\f` (一个「换页」的转义字符)可以使 **FastAPI** 在那一位置截断用于 OpenAPI 的输出。
剩余部分不会出现在文档中,但是其他工具(比如 Sphinx)可以使用剩余部分。
{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19,20,21,22,23,24,25,26,27,28,29] *}
@@ -0,0 +1,29 @@
# 响应 - 更改状态码
你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。
但在某些情况下,你需要返回一个不同于默认值的状态码。
## 使用场景
例如,假设你想默认返回一个HTTP状态码为“OK”`200`
但如果数据不存在,你想创建它,并返回一个HTTP状态码为“CREATED”`201`
但你仍然希望能够使用`response_model`过滤和转换你返回的数据。
对于这些情况,你可以使用一个`Response`参数。
## 使用 `Response` 参数
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。
然后你可以在这个*临时*响应对象中设置`status_code`
{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *}
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。
+49
View File
@@ -0,0 +1,49 @@
# 响应Cookies
## 使用 `Response` 参数
你可以在 *路径函数* 中定义一个类型为 `Response`的参数,这样你就可以在这个临时响应对象中设置cookie了。
{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *}
而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`,数据库model等。
如果你定义了 `response_model`,程序会自动根据`response_model`来过滤和转换你响应的对象。
**FastAPI** 会使用这个 *临时* 响应对象去装在这些cookies信息 (同样还有headers和状态码等信息), 最终会将这些信息和通过`response_model`转化过的数据合并到最终的响应里。
你也可以在depend中定义`Response`参数,并设置cookie和header。
## 直接响应 `Response`
你还可以在直接响应`Response`时直接创建cookies。
你可以参考[Return a Response Directly](response-directly.md){.internal-link target=_blank}来创建response
然后设置Cookies,并返回:
{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *}
/// tip
需要注意,如果你直接反馈一个response对象,而不是使用`Response`入参,FastAPI则会直接反馈你封装的response对象。
所以你需要确保你响应数据类型的正确性,如:你可以使用`JSONResponse`来兼容JSON的场景。
同时,你也应当仅反馈通过`response_model`过滤过的数据。
///
### 更多信息
/// note | 技术细节
你也可以使用`from starlette.responses import Response` 或者 `from starlette.responses import JSONResponse`
为了方便开发者,**FastAPI** 封装了相同数据类型,如`starlette.responses``fastapi.responses`。不过大部分response对象都是直接引用自Starlette。
因为`Response`对象可以非常便捷的设置headers和cookies,所以 **FastAPI** 同时也封装了`fastapi.Response`
///
如果你想查看所有可用的参数和选项,可以参考 <a href="https://www.starlette.dev/responses/#set-cookie" class="external-link" target="_blank">Starlette帮助文档</a>
@@ -0,0 +1,66 @@
# 直接返回响应
当你创建一个 **FastAPI** *路径操作* 时,你可以正常返回以下任意一种数据:`dict``list`,Pydantic 模型,数据库模型等等。
**FastAPI** 默认会使用 `jsonable_encoder` 将这些类型的返回值转换成 JSON 格式,`jsonable_encoder` 在 [JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank} 中有阐述。
然后,**FastAPI** 会在后台将这些兼容 JSON 的数据(比如字典)放到一个 `JSONResponse` 中,该 `JSONResponse` 会用来发送响应给客户端。
但是你可以在你的 *路径操作* 中直接返回一个 `JSONResponse`
直接返回响应可能会有用处,比如返回自定义的响应头和 cookies。
## 返回 `Response`
事实上,你可以返回任意 `Response` 或者任意 `Response` 的子类。
/// tip | 小贴士
`JSONResponse` 本身是一个 `Response` 的子类。
///
当你返回一个 `Response` 时,**FastAPI** 会直接传递它。
**FastAPI** 不会用 Pydantic 模型做任何数据转换,不会将响应内容转换成任何类型,等等。
这种特性给你极大的可扩展性。你可以返回任何数据类型,重写任何数据声明或者校验,等等。
## 在 `Response` 中使用 `jsonable_encoder`
由于 **FastAPI** 并未对你返回的 `Response` 做任何改变,你必须确保你已经准备好响应内容。
例如,如果不首先将 Pydantic 模型转换为 `dict`,并将所有数据类型(如 `datetime``UUID` 等)转换为兼容 JSON 的类型,则不能将其放入JSONResponse中。
对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据。
{* ../../docs_src/response_directly/tutorial001.py hl[4,6,20,21] *}
/// note | 技术细节
你也可以使用 `from starlette.responses import JSONResponse`
出于方便,**FastAPI** 会提供与 `starlette.responses` 相同的 `fastapi.responses` 给开发者。但是大多数可用的响应都直接来自 Starlette。
///
## 返回自定义 `Response`
上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而**FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict`等等。
现在,让我们看看你如何才能返回一个自定义的响应。
假设你想要返回一个 <a href="https://en.wikipedia.org/wiki/XML" class="external-link" target="_blank">XML</a> 响应。
你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回。
{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *}
## 说明
当你直接返回 `Response` 时,它的数据既没有校验,又不会进行转换(序列化),也不会自动生成文档。
但是你仍可以参考 [OpenApI 中的额外响应](additional-responses.md){.internal-link target=_blank} 给响应编写文档。
在后续的章节中你可以了解到如何使用/声明这些自定义的 `Response` 的同时还保留自动化的数据转换和文档等。
+39
View File
@@ -0,0 +1,39 @@
# 响应头
## 使用 `Response` 参数
你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。
然后你可以在这个*临时*响应对象中设置头部。
{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *}
然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。
**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。
你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。
## 直接返回 `Response`
你也可以在直接返回`Response`时添加头部。
按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递:
{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *}
/// note | 技术细节
你也可以使用`from starlette.responses import Response``from starlette.responses import JSONResponse`
**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。
由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。
///
## 自定义头部
请注意,可以使用'X-'前缀添加自定义专有头部。
但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在<a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette的CORS文档</a>中记录的`expose_headers`参数。
@@ -0,0 +1,107 @@
# HTTP 基础授权
最简单的用例是使用 HTTP 基础授权(HTTP Basic Auth)。
在 HTTP 基础授权中,应用需要请求头包含用户名与密码。
如果没有接收到 HTTP 基础授权,就返回 HTTP 401 `"Unauthorized"` 错误。
并返回含 `Basic` 值的请求头 `WWW-Authenticate`以及可选的 `realm` 参数。
HTTP 基础授权让浏览器显示内置的用户名与密码提示。
输入用户名与密码后,浏览器会把它们自动发送至请求头。
## 简单的 HTTP 基础授权
* 导入 `HTTPBasic``HTTPBasicCredentials`
* 使用 `HTTPBasic` 创建**安全概图**
* 在*路径操作*的依赖项中使用 `security`
* 返回类型为 `HTTPBasicCredentials` 的对象:
* 包含发送的 `username``password`
{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *}
第一次打开 URL(或在 API 文档中点击 **Execute** 按钮)时,浏览器要求输入用户名与密码:
<img src="/img/tutorial/security/image12.png">
## 检查用户名
以下是更完整的示例。
使用依赖项检查用户名与密码是否正确。
为此要使用 Python 标准模块 <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> 检查用户名与密码。
`secrets.compare_digest()` 需要仅包含 ASCII 字符(英语字符)的 `bytes``str`,这意味着它不适用于像`á`一样的字符,如 `Sebastián`
为了解决这个问题,我们首先将 `username``password` 转换为使用 UTF-8 编码的 `bytes`
然后我们可以使用 `secrets.compare_digest()` 来确保 `credentials.username``"stanleyjobson"`,且 `credentials.password``"swordfish"`
{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *}
这类似于:
```Python
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Return some error
...
```
但使用 `secrets.compare_digest()`,可以防御**时差攻击**,更加安全。
### 时差攻击
什么是**时差攻击**
假设攻击者试图猜出用户名与密码。
他们发送用户名为 `johndoe`,密码为 `love123` 的请求。
然后,Python 代码执行如下操作:
```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```
但就在 Python 比较完 `johndoe` 的第一个字母 `j``stanleyjobson``s` 时,Python 就已经知道这两个字符串不相同了,它会这么想,**没必要浪费更多时间执行剩余字母的对比计算了**。应用立刻就会返回**错误的用户或密码**。
但接下来,攻击者继续尝试 `stanleyjobsox` 和 密码 `love123`
应用代码会执行类似下面的操作:
```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```
此时,Python 要对比 `stanleyjobsox``stanleyjobson` 中的 `stanleyjobso`,才能知道这两个字符串不一样。因此会多花费几微秒来返回**错误的用户或密码**。
#### 反应时间对攻击者的帮助
通过服务器花费了更多微秒才发送**错误的用户或密码**响应,攻击者会知道猜对了一些内容,起码开头字母是正确的。
然后,他们就可以放弃 `johndoe`,再用类似 `stanleyjobsox` 的内容进行尝试。
#### **专业**攻击
当然,攻击者不用手动操作,而是编写每秒能执行成千上万次测试的攻击程序,每次都会找到更多正确字符。
但是,在您的应用的**帮助**下,攻击者利用时间差,就能在几分钟或几小时内,以这种方式猜出正确的用户名和密码。
#### 使用 `secrets.compare_digest()` 修补
在此,代码中使用了 `secrets.compare_digest()`
简单的说,它使用相同的时间对比 `stanleyjobsox``stanleyjobson`,还有 `johndoe``stanleyjobson`。对比密码时也一样。
在代码中使用 `secrets.compare_digest()` ,就可以安全地防御全面攻击了。
### 返回错误
检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加请求头 `WWW-Authenticate`,让浏览器再次显示登录提示:
{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *}
+19
View File
@@ -0,0 +1,19 @@
# 高级安全
## 附加特性
除 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性.
/// tip | 小贴士
接下来的章节 **并不一定是 "高级的"**.
而且对于你的使用场景来说,解决方案很可能就在其中。
///
## 先阅读教程
接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank}.
它们都基于相同的概念,但支持一些额外的功能.
@@ -0,0 +1,274 @@
# OAuth2 作用域
**FastAPI** 无缝集成 OAuth2 作用域(`Scopes`),可以直接使用。
作用域是更精密的权限系统,遵循 OAuth2 标准,与 OpenAPI 应用(和 API 自动文档)集成。
OAuth2 也是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制。这些身份验证应用在用户登录应用时使用 OAuth2 提供指定权限。
脸书、谷歌、GitHub、微软、推特就是 OAuth2 作用域登录。
本章介绍如何在 **FastAPI** 应用中使用 OAuth2 作用域管理验证与授权。
/// warning | 警告
本章内容较难,刚接触 FastAPI 的新手可以跳过。
OAuth2 作用域不是必需的,没有它,您也可以处理身份验证与授权。
但 OAuth2 作用域与 API(通过 OpenAPI)及 API 文档集成地更好。
不管怎么说,**FastAPI** 支持在代码中使用作用域或其它安全/授权需求项。
很多情况下,OAuth2 作用域就像一把牛刀。
但如果您确定要使用作用域,或对它有兴趣,请继续阅读。
///
## OAuth2 作用域与 OpenAPI
OAuth2 规范的**作用域**是由空格分割的字符串组成的列表。
这些字符串支持任何格式,但不能包含空格。
作用域表示的是**权限**。
OpenAPI 中(例如 API 文档)可以定义**安全方案**。
这些安全方案在使用 OAuth2 时,还可以声明和使用作用域。
**作用域**只是(不带空格的)字符串。
常用于声明特定安全权限,例如:
* 常见用例为,`users:read``users:write`
* 脸书和 Instagram 使用 `instagram_basic`
* 谷歌使用 `https://www.googleapis.com/auth/drive`
/// info | 说明
OAuth2 中,**作用域**只是声明特定权限的字符串。
是否使用冒号 `:` 等符号,或是不是 URL 并不重要。
这些细节只是特定的实现方式。
对 OAuth2 来说,它们都只是字符串而已。
///
## 全局纵览
首先,快速浏览一下以下代码与**用户指南**中 [OAuth2 实现密码哈希与 Bearer JWT 令牌验证](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}一章中代码的区别。以下代码使用 OAuth2 作用域:
{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:124,128:134,139,153] *}
下面,我们逐步说明修改的代码内容。
## OAuth2 安全方案
第一个修改的地方是,使用两个作用域 `me``items ` 声明 OAuth2 安全方案。
`scopes` 参数接收**字典**,键是作用域、值是作用域的描述:
{* ../../docs_src/security/tutorial005.py hl[62:65] *}
因为声明了作用域,所以登录或授权时会在 API 文档中显示。
此处,选择给予访问权限的作用域: `me``items`
这也是使用脸书、谷歌、GitHub 登录时的授权机制。
<img src="/img/tutorial/security/image11.png">
## JWT 令牌作用域
现在,修改令牌*路径操作*,返回请求的作用域。
此处仍然使用 `OAuth2PasswordRequestForm`。它包含类型为**字符串列表**的 `scopes` 属性,且`scopes` 属性中包含要在请求里接收的每个作用域。
这样,返回的 JWT 令牌中就包含了作用域。
/// danger | 危险
为了简明起见,本例把接收的作用域直接添加到了令牌里。
但在您的应用中,为了安全,应该只把作用域添加到确实需要作用域的用户,或预定义的用户。
///
{* ../../docs_src/security/tutorial005.py hl[153] *}
## 在*路径操作*与依赖项中声明作用域
接下来,为*路径操作* `/users/me/items/` 声明作用域 `items`
为此,要从 `fastapi` 中导入并使用 `Security`
`Security` 声明依赖项的方式和 `Depends` 一样,但 `Security` 还能接收作用域(字符串)列表类型的参数 `scopes`
此处使用与 `Depends` 相同的方式,把依赖项函数 `get_current_active_user` 传递给 `Security`
同时,还传递了作用域**列表**,本例中只传递了一个作用域:`items`(此处支持传递更多作用域)。
依赖项函数 `get_current_active_user` 还能声明子依赖项,不仅可以使用 `Depends`,也可以使用 `Security`。声明子依赖项函数(`get_current_user`)及更多作用域。
本例要求使用作用域 `me`(还可以使用更多作用域)。
/// note | 笔记
不必在不同位置添加不同的作用域。
本例使用的这种方式只是为了展示 **FastAPI** 如何处理在不同层级声明的作用域。
///
{* ../../docs_src/security/tutorial005.py hl[4,139,166] *}
/// info | 技术细节
`Security` 实际上是 `Depends` 的子类,而且只比 `Depends` 多一个参数。
但使用 `Security` 代替 `Depends`**FastAPI** 可以声明安全作用域,并在内部使用这些作用域,同时,使用 OpenAPI 存档 API。
但实际上,从 `fastapi` 导入的 `Query``Path``Depends``Security` 等对象,只是返回特殊类的函数。
///
## 使用 `SecurityScopes`
修改依赖项 `get_current_user`
这是上面的依赖项使用的依赖项。
这里使用的也是之前创建的 OAuth2 方案,并把它声明为依赖项:`oauth2_scheme`
该依赖项函数本身不需要作用域,因此,可以使用 `Depends``oauth2_scheme`。不需要指定安全作用域时,不必使用 `Security`
此处还声明了从 `fastapi.security` 导入的 `SecurityScopes` 类型的特殊参数。
`SecuriScopes` 类与 `Request` 类似(`Request` 用于直接提取请求对象)。
{* ../../docs_src/security/tutorial005.py hl[8,105] *}
## 使用 `scopes`
参数 `security_scopes` 的类型是 `SecurityScopes`
它的属性 `scopes` 是作用域列表,所有依赖项都把它作为子依赖项。也就是说所有**依赖**……这听起来有些绕,后文会有解释。
(类 `SecurityScopes` 的)`security_scopes` 对象还提供了单字符串类型的属性 `scope_str`,该属性是(要在本例中使用的)用空格分割的作用域。
此处还创建了后续代码中要复用(`raise`)的 `HTTPException`
该异常包含了作用域所需的(如有),以空格分割的字符串(使用 `scope_str`)。该字符串要放到包含作用域的 `WWW-Authenticate` 请求头中(这也是规范的要求)。
{* ../../docs_src/security/tutorial005.py hl[105,107:115] *}
## 校验 `username` 与数据形状
我们可以校验是否获取了 `username`,并抽取作用域。
然后,使用 Pydantic 模型校验数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或使用 Pydantic 模型验证数据时出错,就会触发之前创建的 `HTTPException` 异常。
对此,要使用新的属性 `scopes` 更新 Pydantic 模型 `TokenData`
使用 Pydantic 验证数据可以确保数据中含有由作用域组成的**字符串列表**,以及 `username` 字符串等内容。
反之,如果使用**字典**或其它数据结构,就有可能在后面某些位置破坏应用,形成安全隐患。
还可以使用用户名验证用户,如果没有用户,也会触发之前创建的异常。
{* ../../docs_src/security/tutorial005.py hl[46,116:127] *}
## 校验 `scopes`
接下来,校验所有依赖项和依赖要素(包括*路径操作*)所需的作用域。这些作用域包含在令牌的 `scopes` 里,如果不在其中就会触发 `HTTPException` 异常。
为此,要使用包含所有作用域**字符串列表**的 `security_scopes.scopes`
{* ../../docs_src/security/tutorial005.py hl[128:134] *}
## 依赖项树与作用域
再次查看这个依赖项树与作用域。
`get_current_active_user` 依赖项包含子依赖项 `get_current_user`,并在 `get_current_active_user`中声明了作用域 `"me"` 包含所需作用域列表 ,在 `security_scopes.scopes` 中传递给 `get_current_user`
*路径操作*自身也声明了作用域,`"items"`,这也是 `security_scopes.scopes` 列表传递给 `get_current_user` 的。
依赖项与作用域的层级架构如下:
* *路径操作* `read_own_items` 包含:
* 依赖项所需的作用域 `["items"]`
* `get_current_active_user`:
* 依赖项函数 `get_current_active_user` 包含:
* 所需的作用域 `"me"` 包含依赖项:
* `get_current_user`:
* 依赖项函数 `get_current_user` 包含:
* 没有作用域需求其自身
* 依赖项使用 `oauth2_scheme`
* `security_scopes` 参数的类型是 `SecurityScopes`
* `security_scopes` 参数的属性 `scopes` 是包含上述声明的所有作用域的**列表**,因此:
* `security_scopes.scopes` 包含用于*路径操作*的 `["me", "items"]`
* `security_scopes.scopes` 包含*路径操作* `read_users_me``["me"]`,因为它在依赖项里被声明
* `security_scopes.scopes` 包含用于*路径操作* `read_system_status``[]`(空列表),并且它的依赖项 `get_current_user` 也没有声明任何 `scope`
/// tip | 提示
此处重要且**神奇**的事情是,`get_current_user` 检查每个*路径操作*时可以使用不同的 `scopes` 列表。
所有这些都依赖于在每个*路径操作*和指定*路径操作*的依赖树中的每个依赖项。
///
## `SecurityScopes` 的更多细节
您可以任何位置或多个位置使用 `SecurityScopes`,不一定非得在**根**依赖项中使用。
它总是在当前 `Security` 依赖项中和所有依赖因子对于**特定** *路径操作*和**特定**依赖树中安全作用域
因为 `SecurityScopes` 包含所有由依赖项声明的作用域,可以在核心依赖函数中用它验证所需作用域的令牌,然后再在不同的*路径操作*中声明不同作用域需求。
它们会为每个*路径操作*进行单独检查。
## 查看文档
打开 API 文档,进行身份验证,并指定要授权的作用域。
<img src="/img/tutorial/security/image11.png">
没有选择任何作用域,也可以进行**身份验证**,但访问 `/uses/me``/users/me/items` 时,会显示没有足够的权限。但仍可以访问 `/status/`
如果选择了作用域 `me`,但没有选择作用域 `items`,则可以访问 `/users/me/`,但不能访问 `/users/me/items`
这就是通过用户提供的令牌使用第三方应用访问这些*路径操作*时会发生的情况,具体怎样取决于用户授予第三方应用的权限。
## 关于第三方集成
本例使用 OAuth2 **密码**流。
这种方式适用于登录我们自己的应用,最好使用我们自己的前端。
因为我们能控制自己的前端应用,可以信任它接收 `username``password`
但如果构建的是连接其它应用的 OAuth2 应用,比如具有与脸书、谷歌、GitHub 相同功能的第三方身份验证应用。那您就应该使用其它安全流。
最常用的是隐式流。
最安全的是代码流,但实现起来更复杂,而且需要更多步骤。因为它更复杂,很多第三方身份验证应用最终建议使用隐式流。
/// note | 笔记
每个身份验证应用都会采用不同方式会命名流,以便融合入自己的品牌。
但归根结底,它们使用的都是 OAuth2 标准。
///
**FastAPI**`fastapi.security.oauth2` 里包含了所有 OAuth2 身份验证流工具。
## 装饰器 `dependencies` 中的 `Security`
同样,您可以在装饰器的 `dependencies` 参数中定义 `Depends` 列表,(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank})),也可以把 `scopes``Security` 一起使用。
+397
View File
@@ -0,0 +1,397 @@
# 设置和环境变量
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。
这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。
因此,通常会将它们提供为由应用程序读取的环境变量。
## 环境变量
/// tip
如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。
///
环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。
您可以在 shell 中创建和使用环境变量,而无需使用 Python:
//// tab | Linux、macOS、Windows Bash
<div class="termy">
```console
// 您可以创建一个名为 MY_NAME 的环境变量
$ export MY_NAME="Wade Wilson"
// 然后您可以与其他程序一起使用它,例如
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// 创建一个名为 MY_NAME 的环境变量
$ $Env:MY_NAME = "Wade Wilson"
// 与其他程序一起使用它,例如
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
### 在 Python 中读取环境变量
您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。
例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的第二个参数是要返回的默认值。
如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。
///
然后,您可以调用该 Python 程序:
<div class="termy">
```console
// 这里我们还没有设置环境变量
$ python main.py
// 因为我们没有设置环境变量,所以我们得到默认值
Hello World from Python
// 但是如果我们先创建一个环境变量
$ export MY_NAME="Wade Wilson"
// 然后再次调用程序
$ python main.py
// 现在它可以读取环境变量
Hello Wade Wilson from Python
```
</div>
由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。
您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。
要做到这一点,在程序本身之前的同一行创建它:
<div class="termy">
```console
// 在此程序调用行中创建一个名为 MY_NAME 的环境变量
$ MY_NAME="Wade Wilson" python main.py
// 现在它可以读取环境变量
Hello Wade Wilson from Python
// 之后环境变量不再存在
$ python main.py
Hello World from Python
```
</div>
/// tip
您可以在 <a href="https://12factor.net/config" class="external-link" target="_blank">Twelve-Factor App: Config</a> 中阅读更多相关信息。
///
### 类型和验证
这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。
这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。
## Pydantic 的 `Settings`
幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>。
### 创建 `Settings` 对象
从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。
与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。
您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *}
/// tip
如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。
///
然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。
然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。
### 使用 `settings`
然后,您可以在应用程序中使用新的 `settings` 对象:
{* ../../docs_src/settings/tutorial001.py hl[18:20] *}
### 运行服务器
接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示:
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
/// tip
要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。
///
然后,`admin_email` 设置将为 `"deadpool@example.com"`。
`app_name` 将为 `"ChimichangApp"`。
而 `items_per_user` 将保持其默认值为 `50`。
## 在另一个模块中设置
您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。
例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容:
{* ../../docs_src/settings/app01/config.py *}
然后在一个名为 `main.py` 的文件中使用它:
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
/// tip
您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。
///
## 在依赖项中使用设置
在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。
这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。
### 配置文件
根据前面的示例,您的 `config.py` 文件可能如下所示:
{* ../../docs_src/settings/app02/config.py hl[10] *}
请注意,现在我们不创建默认实例 `settings = Settings()`。
### 主应用程序文件
现在我们创建一个依赖项,返回一个新的 `config.Settings()`。
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// tip
我们稍后会讨论 `@lru_cache`。
目前,您可以将 `get_settings()` 视为普通函数。
///
然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *}
### 设置和测试
然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象:
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *}
在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。
然后,我们可以测试它是否被使用。
## 从 `.env` 文件中读取设置
如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。
这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。
/// tip
以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。
但是,dotenv 文件实际上不一定要具有确切的文件名。
///
Pydantic 支持使用外部库从这些类型的文件中读取。您可以在<a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic 设置: Dotenv (.env) 支持</a>中阅读更多相关信息。
/// tip
要使其工作,您需要执行 `pip install python-dotenv`。
///
### `.env` 文件
您可以使用以下内容创建一个名为 `.env` 的文件:
```bash
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
```
### 从 `.env` 文件中读取设置
然后,您可以使用以下方式更新您的 `config.py`
{* ../../docs_src/settings/app03/config.py hl[9:10] *}
在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。
/// tip
`Config` 类仅用于 Pydantic 配置。您可以在<a href="https://docs.pydantic.dev/latest/api/config/" class="external-link" target="_blank">Pydantic Model Config</a>中阅读更多相关信息。
///
### 使用 `lru_cache` 仅创建一次 `Settings`
从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。
但是,每次执行以下操作:
```Python
Settings()
```
都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。
如果依赖项函数只是这样的:
```Python
def get_settings():
return Settings()
```
我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️
但是,由于我们在顶部使用了 `@lru_cache` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *}
然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。
#### `lru_cache` 技术细节
`@lru_cache` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。
因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。
例如,如果您有一个函数:
```Python
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
```
您的程序可以像这样执行:
```mermaid
sequenceDiagram
participant code as Code
participant function as say_hi()
participant execute as Execute function
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: 执行函数代码
execute ->> code: 返回结果
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: 返回存储的结果
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: 执行函数代码
execute ->> code: 返回结果
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: 执行函数代码
execute ->> code: 返回结果
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: 返回存储的结果
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: 返回存储的结果
end
```
对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。
这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。
`@lru_cache` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在<a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python 文档中了解有关 `@lru_cache` 的更多信息</a>。
## 小结
您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。
* 通过使用依赖项,您可以简化测试。
* 您可以使用 `.env` 文件。
* 使用 `@lru_cache` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。
+67
View File
@@ -0,0 +1,67 @@
# 子应用 - 挂载
如果需要两个独立的 FastAPI 应用,拥有各自独立的 OpenAPI 与文档,则需设置一个主应用,并**挂载**一个(或多个)子应用。
## 挂载 **FastAPI** 应用
**挂载**是指在特定路径中添加完全**独立**的应用,然后在该路径下使用*路径操作*声明的子应用处理所有事务。
### 顶层应用
首先,创建主(顶层)**FastAPI** 应用及其*路径操作*
{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *}
### 子应用
接下来,创建子应用及其*路径操作*。
子应用只是另一个标准 FastAPI 应用,但这个应用是被**挂载**的应用:
{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *}
### 挂载子应用
在顶层应用 `app` 中,挂载子应用 `subapi`
本例的子应用挂载在 `/subapi` 路径下:
{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *}
### 查看文档
如果主文件是 `main.py`,则用以下 `uvicorn` 命令运行主应用:
<div class="termy">
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
查看文档 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs。</a>
下图显示的是主应用 API 文档,只包括其自有的*路径操作*。
<img src="/img/tutorial/sub-applications/image01.png">
然后查看子应用文档 <a href="http://127.0.0.1:8000/subapi/docs" class="external-link" target="_blank">http://127.0.0.1:8000/subapi/docs。</a>
下图显示的是子应用的 API 文档,也是只包括其自有的*路径操作*,所有这些路径操作都在 `/subapi` 子路径前缀下。
<img src="/img/tutorial/sub-applications/image02.png">
两个用户界面都可以正常运行,因为浏览器能够与每个指定的应用或子应用会话。
### 技术细节:`root_path`
以上述方式挂载子应用时,FastAPI 使用 ASGI 规范中的 `root_path` 机制处理挂载子应用路径之间的通信。
这样,子应用就可以为自动文档使用路径前缀。
并且子应用还可以再挂载子应用,一切都会正常运行,FastAPI 可以自动处理所有 `root_path`
关于 `root_path` 及如何显式使用 `root_path` 的内容,详见[使用代理](behind-a-proxy.md){.internal-link target=_blank}一章。
+125
View File
@@ -0,0 +1,125 @@
# 模板
**FastAPI** 支持多种模板引擎。
Flask 等工具使用的 Jinja2 是最用的模板引擎。
在 Starlette 的支持下,**FastAPI** 应用可以直接使用工具轻易地配置 Jinja2。
## 安装依赖项
安装 `jinja2`
<div class="termy">
```console
$ pip install jinja2
---> 100%
```
</div>
## 使用 `Jinja2Templates`
* 导入 `Jinja2Templates`
* 创建可复用的 `templates` 对象
* 在返回模板的*路径操作*中声明 `Request` 参数
* 使用 `templates` 渲染并返回 `TemplateResponse`, 传递模板的名称、request对象以及一个包含多个键值对(用于Jinja2模板)的"context"字典,
{* ../../docs_src/templates/tutorial001.py hl[4,11,15:16] *}
/// note | 笔记
在FastAPI 0.108.0Starlette 0.29.0之前,`name`是第一个参数。
并且,在此之前,`request`对象是作为context的一部分以键值对的形式传递的。
///
/// tip | 提示
通过声明 `response_class=HTMLResponse`,API 文档就能识别响应的对象是 HTML。
///
/// note | 技术细节
您还可以使用 `from starlette.templating import Jinja2Templates`
**FastAPI**`fastapi.templating` 只是为开发者提供的快捷方式。实际上,绝大多数可用响应都直接继承自 Starlette。 `Request``StaticFiles` 也一样。
///
## 编写模板
编写模板 `templates/item.html`,代码如下:
```jinja hl_lines="7"
{!../../docs_src/templates/templates/item.html!}
```
### 模板上下文
在包含如下语句的html中:
{% raw %}
```jinja
Item ID: {{ id }}
```
{% endraw %}
...这将显示你从"context"字典传递的 `id`:
```Python
{"id": id}
```
例如。当ID为 `42`时, 会渲染成:
```html
Item ID: 42
```
### 模板 `url_for` 参数
你还可以在模板内使用 `url_for()`,其参数与*路径操作函数*的参数相同.
所以,该部分:
{% raw %}
```jinja
<a href="{{ url_for('read_item', id=id) }}">
```
{% endraw %}
...将生成一个与处理*路径操作函数* `read_item(id=id)`的URL相同的链接
例如。当ID为 `42`时, 会渲染成:
```html
<a href="/items/42">
```
## 模板与静态文件
你还可以在模板内部将 `url_for()`用于静态文件,例如你挂载的 `name="static"`的 `StaticFiles`。
```jinja hl_lines="4"
{!../../docs_src/templates/templates/item.html!}
```
本例中,它将链接到 `static/styles.css`中的CSS文件:
```CSS hl_lines="4"
{!../../docs_src/templates/static/styles.css!}
```
因为使用了 `StaticFiles` **FastAPI** 应用会自动提供位于 URL `/static/styles.css`的 CSS 文件。
## 更多说明
包括测试模板等更多详情,请参阅 <a href="https://www.starlette.dev/templates/" class="external-link" target="_blank">Starlette 官方文档 - 模板</a>。
@@ -0,0 +1,53 @@
# 测试依赖项
## 测试时覆盖依赖项
有些场景下,您可能需要在测试时覆盖依赖项。
即不希望运行原有依赖项(及其子依赖项)。
反之,要在测试期间(或只是为某些特定测试)提供只用于测试的依赖项,并使用此依赖项的值替换原有依赖项的值。
### 用例:外部服务
常见实例是调用外部第三方身份验证应用。
向第三方应用发送令牌,然后返回经验证的用户。
但第三方服务商处理每次请求都可能会收费,并且耗时通常也比调用写死的模拟测试用户更长。
一般只要测试一次外部验证应用就够了,不必每次测试都去调用。
此时,最好覆盖调用外部验证应用的依赖项,使用返回模拟测试用户的自定义依赖项就可以了。
### 使用 `app.dependency_overrides` 属性
对于这些用例,**FastAPI** 应用支持 `app.dependency_overrides` 属性,该属性就是**字典**。
要在测试时覆盖原有依赖项,这个字典的键应当是原依赖项(函数),值是覆盖依赖项(另一个函数)。
这样一来,**FastAPI** 就会调用覆盖依赖项,不再调用原依赖项。
{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *}
/// tip | 提示
**FastAPI** 应用中的任何位置都可以实现覆盖依赖项。
原依赖项可用于*路径操作函数*、*路径操作装饰器*(不需要返回值时)、`.include_router()` 调用等。
FastAPI 可以覆盖这些位置的依赖项。
///
然后,使用 `app.dependency_overrides` 把覆盖依赖项重置为空**字典**
```Python
app.dependency_overrides = {}
```
/// tip | 提示
如果只在某些测试时覆盖依赖项,您可以在测试开始时(在测试函数内)设置覆盖依赖项,并在结束时(在测试函数结尾)重置覆盖依赖项。
///
+5
View File
@@ -0,0 +1,5 @@
# 测试事件:启动 - 关闭
使用 `TestClient``with` 语句,在测试中运行事件处理器(`startup``shutdown`)。
{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *}
@@ -0,0 +1,13 @@
# 测试 WebSockets
测试 WebSockets 也使用 `TestClient`
为此,要在 `with` 语句中使用 `TestClient` 连接 WebSocket。
{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *}
/// note | 笔记
更多细节详见 <a href="https://www.starlette.dev/testclient/#testing-websocket-sessions" class="external-link" target="_blank">Starlette 官档 - 测试 WebSockets</a>。
///
@@ -0,0 +1,56 @@
# 直接使用请求
至此,我们已经使用多种类型声明了请求的各种组件。
并从以下对象中提取数据:
* 路径参数
* 请求头
* Cookies
*
**FastAPI** 使用这种方式验证数据、转换数据,并自动生成 API 文档。
但有时,我们也需要直接访问 `Request` 对象。
## `Request` 对象的细节
实际上,**FastAPI** 的底层是 **Starlette****FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">`Request`</a> 对象。
但直接从 `Request` 对象提取数据时(例如,读取请求体),**FastAPI** 不会验证、转换和存档数据(为 API 文档使用 OpenAPI)。
不过,仍可以验证、转换与注释(使用 Pydantic 模型的请求体等)其它正常声明的参数。
但在某些特定情况下,还是需要提取 `Request` 对象。
## 直接使用 `Request` 对象
假设要在*路径操作函数*中获取客户端 IP 地址和主机。
此时,需要直接访问请求。
{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *}
把*路径操作函数*的参数类型声明为 `Request`**FastAPI** 就能把 `Request` 传递到参数里。
/// tip | 提示
注意,本例除了声明请求参数之外,还声明了路径参数。
因此,能够提取、验证路径参数、并转换为指定类型,还可以用 OpenAPI 注释。
同样,您也可以正常声明其它参数,而且还可以提取 `Request`
///
## `Request` 文档
更多细节详见 <a href="https://www.starlette.dev/requests/" class="external-link" target="_blank">Starlette 官档 - `Request` 对象</a>。
/// note | 技术细节
您也可以使用 `from starlette.requests import Request`
**FastAPI**`from fastapi import Request` 只是为开发者提供的快捷方式,但其实它直接继承自 Starlette。
///
+176
View File
@@ -0,0 +1,176 @@
# WebSockets
您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。
## 安装 `WebSockets`
首先,您需要安装 `WebSockets`
```console
$ pip install websockets
---> 100%
```
## WebSockets 客户端
### 在生产环境中
在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。
要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。
或者,您可能有一个原生移动应用程序,直接使用原生代码与 WebSocket 后端通信。
或者,您可能有其他与 WebSocket 终端通信的方式。
---
但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。
当然,这并不是最优的做法,您不应该在生产环境中使用它。
在生产环境中,您应该选择上述任一选项。
但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式:
{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *}
## 创建 `websocket`
在您的 **FastAPI** 应用程序中,创建一个 `websocket`
{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *}
/// note | 技术细节
您也可以使用 `from starlette.websockets import WebSocket`
**FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。但它直接来自 Starlette。
///
## 等待消息并发送消息
在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。
{* ../../docs_src/websockets/tutorial001.py hl[48:52] *}
您可以接收和发送二进制、文本和 JSON 数据。
## 尝试一下
如果您的文件名为 `main.py`,请使用以下命令运行应用程序:
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
您将看到一个简单的页面,如下所示:
<img src="/img/tutorial/websockets/image01.png">
您可以在输入框中输入消息并发送:
<img src="/img/tutorial/websockets/image02.png">
您的 **FastAPI** 应用程序将回复:
<img src="/img/tutorial/websockets/image03.png">
您可以发送(和接收)多条消息:
<img src="/img/tutorial/websockets/image04.png">
所有这些消息都将使用同一个 WebSocket 连
接。
## 使用 `Depends` 和其他依赖项
在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容:
* `Depends`
* `Security`
* `Cookie`
* `Header`
* `Path`
* `Query`
它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同:
{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *}
/// info
由于这是一个 WebSocket,抛出 `HTTPException` 并不是很合理,而是抛出 `WebSocketException`
您可以使用<a href="https://tools.ietf.org/html/rfc6455#section-7.4.1" class="external-link" target="_blank">规范中定义的有效代码</a>。
///
### 尝试带有依赖项的 WebSockets
如果您的文件名为 `main.py`,请使用以下命令运行应用程序:
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
在浏览器中打开 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
在页面中,您可以设置:
* "Item ID",用于路径。
* "Token",作为查询参数。
/// tip
注意,查询参数 `token` 将由依赖项处理。
///
通过这样,您可以连接 WebSocket,然后发送和接收消息:
<img src="/img/tutorial/websockets/image05.png">
## 处理断开连接和多个客户端
当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。
{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *}
尝试以下操作:
* 使用多个浏览器选项卡打开应用程序。
* 从这些选项卡中发送消息。
* 然后关闭其中一个选项卡。
这将引发 `WebSocketDisconnect` 异常,并且所有其他客户端都会收到类似以下的消息:
```
Client #1596980209979 left the chat
```
/// tip
上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。
但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。
如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。
///
## 更多信息
要了解更多选项,请查看 Starlette 的文档:
* [WebSocket 类](https://www.starlette.dev/websockets/)
* [基于类的 WebSocket 处理](https://www.starlette.dev/endpoints/#websocketendpoint)。
+35
View File
@@ -0,0 +1,35 @@
# 包含 WSGI - FlaskDjango,其它
您可以挂载多个 WSGI 应用,正如您在 [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。
为此, 您可以使用 `WSGIMiddleware` 来包装你的 WSGI 应用,如:FlaskDjango,等等。
## 使用 `WSGIMiddleware`
您需要导入 `WSGIMiddleware`
然后使用该中间件包装 WSGI 应用(例如 Flask)。
之后将其挂载到某一个路径下。
{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *}
## 检查
现在,所有定义在 `/v1/` 路径下的请求将会被 Flask 应用处理。
其余的请求则会被 **FastAPI** 处理。
如果您使用 Uvicorn 运行应用实例并且访问 <a href="http://localhost:8000/v1/" class="external-link" target="_blank">http://localhost:8000/v1/</a>,您将会看到由 Flask 返回的响应:
```txt
Hello, World from Flask!
```
并且如果您访问 <a href="http://localhost:8000/v2" class="external-link" target="_blank">http://localhost:8000/v2</a>,您将会看到由 FastAPI 返回的响应:
```JSON
{
"message": "Hello World"
}
```
+444
View File
@@ -0,0 +1,444 @@
# 并发 async / await
有关路径操作函数的 `async def` 语法以及异步代码、并发和并行的一些背景知识。
## 赶时间吗?
<abbr title="too long; didn't read(长文警告)"><strong>TL;DR:</strong></abbr>
如果你正在使用第三方库,它们会告诉你使用 `await` 关键字来调用它们,就像这样:
```Python
results = await some_library()
```
然后,通过 `async def` 声明你的 *路径操作函数*
```Python hl_lines="2"
@app.get('/')
async def read_results():
results = await some_library()
return results
```
/// note
你只能在被 `async def` 创建的函数内使用 `await`
///
---
如果你正在使用一个第三方库和某些组件(比如:数据库、API、文件系统...)进行通信,第三方库又不支持使用 `await` (目前大多数数据库三方库都是这样),这种情况你可以像平常那样使用 `def` 声明一个路径操作函数,就像这样:
```Python hl_lines="2"
@app.get('/')
def results():
results = some_library()
return results
```
---
如果你的应用程序不需要与其他任何东西通信而等待其响应,请使用 `async def`。
---
如果你不清楚,使用 `def` 就好.
---
**注意**:你可以根据需要在路径操作函数中混合使用 `def` 和 `async def`,并使用最适合你的方式去定义每个函数。FastAPI 将为他们做正确的事情。
无论如何,在上述任何情况下,FastAPI 仍将异步工作,速度也非常快。
但是,通过遵循上述步骤,它将能够进行一些性能优化。
## 技术细节
Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 `await` 语法的东西来写**”异步代码“**。
让我们在下面的部分中逐一介绍:
* **异步代码**
* **`async` 和 `await`**
* **协程**
## 异步代码
异步代码仅仅意味着编程语言 💬 有办法告诉计算机/程序 🤖 在代码中的某个点,它 🤖 将不得不等待在某些地方完成一些事情。让我们假设一些事情被称为 "慢文件"📝.
所以,在等待"慢文件"📝完成的这段时间,计算机可以做一些其他工作。
然后计算机/程序 🤖 每次有机会都会回来,因为它又在等待,或者它 🤖 完成了当前所有的工作。而且它 🤖 将查看它等待的所有任务中是否有已经完成的,做它必须做的任何事情。
接下来,它 🤖 完成第一个任务(比如是我们的"慢文件"📝) 并继续与之相关的一切。
这个"等待其他事情"通常指的是一些相对较慢(与处理器和 RAM 存储器的速度相比)的 <abbr title="Input and Output">I/O</abbr> 操作,比如说:
* 通过网络发送来自客户端的数据
* 客户端接收来自网络中的数据
* 磁盘中要由系统读取并提供给程序的文件的内容
* 程序提供给系统的要写入磁盘的内容
* 一个 API 的远程调用
* 一个数据库操作,直到完成
* 一个数据库查询,直到返回结果
* 等等.
这个执行的时间大多是在等待 <abbr title="Input and Output">I/O</abbr> 操作,因此它们被叫做 "I/O 密集型" 操作。
它被称为"异步"的原因是因为计算机/程序不必与慢任务"同步",去等待任务完成的确切时刻,而在此期间不做任何事情直到能够获取任务结果才继续工作。
相反,作为一个"异步"系统,一旦完成,任务就可以排队等待一段时间(几微秒),等待计算机程序完成它要做的任何事情,然后回来获取结果并继续处理它们。
对于"同步"(与"异步"相反),他们通常也使用"顺序"一词,因为计算机程序在切换到另一个任务之前是按顺序执行所有步骤,即使这些步骤涉及到等待。
### 并发与汉堡
上述异步代码的思想有时也被称为“并发”,它不同于“并行”。
并发和并行都与“不同的事情或多或少同时发生”有关。
但是并发和并行之间的细节是完全不同的。
要了解差异,请想象以下关于汉堡的故事:
### 并发汉堡
你和你的恋人一起去快餐店,你排队在后面,收银员从你前面的人接单。😍
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
然后轮到你了,你为你的恋人和你选了两个非常豪华的汉堡。🍔🍔
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
收银员对厨房里的厨师说了一些话,让他们知道他们必须为你准备汉堡(尽管他们目前正在为之前的顾客准备汉堡)。
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
你付钱了。 💸
收银员给你轮到的号码。
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
当你在等待的时候,你和你的恋人一起去挑选一张桌子,然后你们坐下来聊了很长时间(因为汉堡很豪华,需要一些时间来准备)。
当你和你的恋人坐在桌子旁,等待汉堡的时候,你可以用这段时间来欣赏你的恋人是多么的棒、可爱和聪明✨😍✨。
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
在等待中和你的恋人交谈时,你会不时地查看柜台上显示的号码,看看是否已经轮到你了。
然后在某个时刻,终于轮到你了。你去柜台拿汉堡然后回到桌子上。
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
你们享用了汉堡,整个过程都很开心。✨
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
/// info
漂亮的插画来自 <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
///
---
在那个故事里,假设你是计算机程序 🤖 。
当你在排队时,你只是闲着😴, 轮到你前不做任何事情(仅排队)。但排队很快,因为收银员只接订单(不准备订单),所以这一切都还好。
然后,当轮到你时,需要你做一些实际性的工作,比如查看菜单,决定你想要什么,让你的恋人选择,支付,检查你是否提供了正确的账单或卡,检查你的收费是否正确,检查订单是否有正确的项目,等等。
此时,即使你仍然没有汉堡,你和收银员的工作也"暂停"了⏸, 因为你必须等待一段时间 🕙 让你的汉堡做好。
但是,当你离开柜台并坐在桌子旁,在轮到你的号码前的这段时间,你可以将焦点切换到 🔀 你的恋人上,并做一些"工作"⏯ 🤓。你可以做一些非常"有成效"的事情,比如和你的恋人调情😍.
之后,收银员 💁 把号码显示在显示屏上,并说到 "汉堡做好了",而当显示的号码是你的号码时,你不会立刻疯狂地跳起来。因为你知道没有人会偷你的汉堡,因为你有你的号码,而其他人又有他们自己的号码。
所以你要等待你的恋人完成故事(完成当前的工作⏯ /正在做的事🤓), 轻轻微笑,说你要吃汉堡⏸.
然后你去柜台🔀, 到现在初始任务已经完成⏯, 拿起汉堡,说声谢谢,然后把它们送到桌上。这就完成了与计数器交互的步骤/任务⏹. 这反过来又产生了一项新任务,即"吃汉堡"🔀 ⏯, 上一个"拿汉堡"的任务已经结束了⏹.
### 并行汉堡
现在让我们假设不是"并发汉堡",而是"并行汉堡"。
你和你的恋人一起去吃并行快餐。
你站在队伍中,同时是厨师的几个收银员(比方说8个)从前面的人那里接单。
你之前的每个人都在等待他们的汉堡准备好后才离开柜台,因为8名收银员都会在下一份订单前马上准备好汉堡。
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
然后,终于轮到你了,你为你的恋人和你订购了两个非常精美的汉堡。
你付钱了 💸。
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
收银员去厨房。
你站在柜台前 🕙等待着,这样就不会有人在你之前抢走你的汉堡,因为没有轮流的号码。
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
当你和你的恋人忙于不让任何人出现在你面前,并且在他们到来的时候拿走你的汉堡时,你无法关注到你的恋人。😞
这是"同步"的工作,你被迫与服务员/厨师 👨‍🍳"同步"。你在此必须等待 🕙 ,在收银员/厨师 👨‍🍳 完成汉堡并将它们交给你的确切时间到达之前一直等待,否则其他人可能会拿走它们。
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
你经过长时间的等待 🕙 ,收银员/厨师 👨‍🍳终于带着汉堡回到了柜台。
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
你拿着汉堡,和你的情人一起上桌。
你们仅仅是吃了它们,就结束了。⏹
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
没有太多的交谈或调情,因为大部分时间 🕙 都在柜台前等待😞。
/// info
漂亮的插画来自 <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
///
---
在这个并行汉堡的场景中,你是一个计算机程序 🤖 且有两个处理器(你和你的恋人),都在等待 🕙 ,并投入他们的注意力 ⏯ 在柜台上等待了很长一段时间。
这家快餐店有 8 个处理器(收银员/厨师)。而并发汉堡店可能只有 2 个(一个收银员和一个厨师)。
但最终的体验仍然不是最好的。😞
---
这将是与汉堡的类似故事。🍔
一种更"贴近生活"的例子,想象一家银行。
直到最近,大多数银行都有多个出纳员 👨‍💼👨‍💼👨‍💼👨‍💼 还有一条长长排队队伍🕙🕙🕙🕙🕙🕙🕙🕙。
所有收银员都是一个接一个的在客户面前做完所有的工作👨‍💼⏯.
你必须经过 🕙 较长时间排队,否则你就没机会了。
你可不会想带你的恋人 😍 和你一起去银行办事🏦.
### 汉堡结论
在"你与恋人一起吃汉堡"的这个场景中,因为有很多人在等待🕙, 使用并发系统更有意义⏸🔀⏯.
大多数 Web 应用都是这样的。
你的服务器正在等待很多很多用户通过他们不太好的网络发送来的请求。
然后再次等待 🕙 响应回来。
这个"等待" 🕙 是以微秒为单位测量的,但总的来说,最后还是等待很久。
这就是为什么使用异步对于 Web API 很有意义的原因 ⏸🔀⏯。
这种异步机制正是 NodeJS 受到欢迎的原因(尽管 NodeJS 不是并行的),以及 Go 作为编程语言的优势所在。
这与 **FastAPI** 的性能水平相同。
你可以同时拥有并行性和异步性,你可以获得比大多数经过测试的 NodeJS 框架更高的性能,并且与 Go 不相上下, Go 是一种更接近于 C 的编译语言(<a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">全部归功于 Starlette</a>)。
### 并发比并行好吗?
不!这不是故事的本意。
并发不同于并行。而是在需要大量等待的特定场景下效果更好。因此,在 Web 应用程序开发中,它通常比并行要好得多,但这并不意味着全部。
因此,为了平衡这一点,想象一下下面的短篇故事:
> 你必须打扫一个又大又脏的房子。
*是的,这就是完整的故事。*
---
在任何地方, 都不需要等待 🕙 ,只需要在房子的多个地方做着很多工作。
你可以像汉堡的例子那样轮流执行,先是客厅,然后是厨房,但因为你不需要等待 🕙 ,对于任何事情都是清洁,清洁,还是清洁,轮流不会影响任何事情。
无论是否轮流执行(并发),都需要相同的时间来完成,而你也会完成相同的工作量。
但在这种情况下,如果你能带上 8 名前收银员/厨师,现在是清洁工一起清扫,他们中的每一个人(加上你)都能占据房子的一个区域来清扫,你就可以在额外的帮助下并行的更快地完成所有工作。
在这个场景中,每个清洁工(包括你)都将是一个处理器,完成这个工作的一部分。
由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 <abbr title="Central Processing Unit">CPU</abbr> 完成的,所以他们称这些问题为"CPU 密集型"。
---
CPU 密集型操作的常见示例是需要复杂的数学处理。
例如:
* **音频**或**图像**处理;
* **计算机视觉**: 一幅图像由数百万像素组成,每个像素有3种颜色值,处理通常需要同时对这些像素进行计算;
* **机器学习**: 它通常需要大量的"矩阵"和"向量"乘法。想象一个包含数字的巨大电子表格,并同时将所有数字相乘;
* **深度学习**: 这是机器学习的一个子领域,同样适用。只是没有一个数字的电子表格可以相乘,而是一个庞大的数字集合,在很多情况下,你需要使用一个特殊的处理器来构建和使用这些模型。
### 并发 + 并行: Web + 机器学习
使用 **FastAPI**,你可以利用 Web 开发中常见的并发机制的优势(NodeJS 的主要吸引力)。
并且,你也可以利用并行和多进程(让多个进程并行运行)的优点来处理与机器学习系统中类似的 **CPU 密集型** 工作。
这一点,再加上 Python 是**数据科学**、机器学习(尤其是深度学习)的主要语言这一简单事实,使得 **FastAPI** 与数据科学/机器学习 Web API 和应用程序(以及其他许多应用程序)非常匹配。
了解如何在生产环境中实现这种并行性,可查看此文 [Deployment](deployment/index.md){.internal-link target=_blank}。
## `async` 和 `await`
现代版本的 Python 有一种非常直观的方式来定义异步代码。这使它看起来就像正常的"顺序"代码,并在适当的时候"等待"。
当有一个操作需要等待才能给出结果,且支持这个新的 Python 特性时,你可以编写如下代码:
```Python
burgers = await get_burgers(2)
```
这里的关键是 `await`。它告诉 Python 它必须等待 ⏸ `get_burgers(2)` 完成它的工作 🕙 ,然后将结果存储在 `burgers` 中。这样,Python 就会知道此时它可以去做其他事情 🔀 ⏯ (比如接收另一个请求)。
要使 `await` 工作,它必须位于支持这种异步机制的函数内。因此,只需使用 `async def` 声明它:
```Python hl_lines="1"
async def get_burgers(number: int):
# Do some asynchronous stuff to create the burgers
return burgers
```
...而不是 `def`:
```Python hl_lines="2"
# This is not asynchronous
def get_sequential_burgers(number: int):
# Do some sequential stuff to create the burgers
return burgers
```
使用 `async def`,Python 就知道在该函数中,它将遇上 `await`,并且它可以"暂停" ⏸ 执行该函数,直至执行其他操作 🔀 后回来。
当你想调用一个 `async def` 函数时,你必须"等待"它。因此,这不会起作用:
```Python
# This won't work, because get_burgers was defined with: async def
burgers = get_burgers(2)
```
---
因此,如果你使用的库告诉你可以使用 `await` 调用它,则需要使用 `async def` 创建路径操作函数 ,如:
```Python hl_lines="2-3"
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
```
### 更多技术细节
你可能已经注意到,`await` 只能在 `async def` 定义的函数内部使用。
但与此同时,必须"等待"通过 `async def` 定义的函数。因此,带 `async def` 的函数也只能在 `async def` 定义的函数内部调用。
那么,这关于先有鸡还是先有蛋的问题,如何调用第一个 `async` 函数?
如果你使用 **FastAPI**,你不必担心这一点,因为"第一个"函数将是你的路径操作函数,FastAPI 将知道如何做正确的事情。
但如果你想在没有 FastAPI 的情况下使用 `async` / `await`,则可以这样做。
### 编写自己的异步代码
Starlette (和 **FastAPI** 是基于 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 实现的,这使得它们可以兼容 Python 的标准库 <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> 和 <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>。
特别是,你可以直接使用 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 来处理高级的并发用例,这些用例需要在自己的代码中使用更高级的模式。
即使你没有使用 **FastAPI**,你也可以使用 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 编写自己的异步程序,使其拥有较高的兼容性并获得一些好处(例如, 结构化并发)。
我(指原作者 —— 译者注)基于 AnyIO 新建了一个库,作为一个轻量级的封装层,用来优化类型注解,同时提供了更好的**自动补全**、**内联错误提示**等功能。这个库还附带了一个友好的入门指南和教程,能帮助你**理解**并编写**自己的异步代码**<a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>。如果你有**结合使用异步代码和常规**(阻塞/同步)代码的需求,这个库会特别有用。
### 其他形式的异步代码
这种使用 `async` 和 `await` 的风格在语言中相对较新。
但它使处理异步代码变得容易很多。
这种相同的语法(或几乎相同)最近也包含在现代版本的 JavaScript 中(在浏览器和 NodeJS 中)。
但在此之前,处理异步代码非常复杂和困难。
在以前版本的 Python,你可以使用多线程或者 <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>。但代码的理解、调试和思考都要复杂许多。
在以前版本的 NodeJS / 浏览器 JavaScript 中,你会使用"回调",因此也可能导致“回调地狱”。
## 协程
**协程**只是 `async def` 函数返回的一个非常奇特的东西的称呼。Python 知道它有点像一个函数,它可以启动,也会在某个时刻结束,而且它可能会在内部暂停 ⏸ ,只要内部有一个 `await`。
通过使用 `async` 和 `await` 的异步代码的所有功能大多数被概括为"协程"。它可以与 Go 的主要关键特性 "Goroutines" 相媲美。
## 结论
让我们再来回顾下上文所说的:
> Python 的现代版本可以通过使用 `async` 和 `await` 语法创建**协程**,并用于支持**异步代码**。
现在应该能明白其含义了。✨
所有这些使得 FastAPI(通过 Starlette)如此强大,也是它拥有如此令人印象深刻的性能的原因。
## 非常技术性的细节
/// warning
你可以跳过这里。
这些都是 FastAPI 如何在内部工作的技术细节。
如果你有相当多的技术知识(协程、线程、阻塞等),并且对 FastAPI 如何处理 `async def` 与常规 `def` 感到好奇,请继续。
///
### 路径操作函数
当你使用 `def` 而不是 `async def` 来声明一个*路径操作函数*时,它运行在外部的线程池中并等待其结果,而不是直接调用(因为它会阻塞服务器)。
如果你使用过另一个不以上述方式工作的异步框架,并且你习惯于用普通的 `def` 定义普通的仅计算路径操作函数,以获得微小的性能增益(大约100纳秒),请注意,在 FastAPI 中,效果将完全相反。在这些情况下,最好使用 `async def`,除非路径操作函数内使用执行阻塞 <abbr title="输入/输出:磁盘读写,网络通讯.">I/O</abbr> 的代码。
在这两种情况下,与你之前的框架相比,**FastAPI** 可能[仍然很快](index.md#_11){.internal-link target=_blank}。
### 依赖
这同样适用于[依赖](tutorial/dependencies/index.md){.internal-link target=_blank}。如果一个依赖是标准的 `def` 函数而不是 `async def`,它将被运行在外部线程池中。
### 子依赖
你可以拥有多个相互依赖的依赖以及[子依赖](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} (作为函数的参数),它们中的一些可能是通过 `async def` 声明,也可能是通过 `def` 声明。它们仍然可以正常工作,这些通过 `def` 声明的函数将会在外部线程中调用(来自线程池),而不是"被等待"。
### 其他函数
你可直接调用通过 `def` 或 `async def` 创建的任何其他函数,FastAPI 不会影响你调用它们的方式。
这与 FastAPI 为你调用*路径操作函数*和依赖项的逻辑相反。
如果你的函数是通过 `def` 声明的,它将被直接调用(在代码中编写的地方),而不会在线程池中,如果这个函数通过 `async def` 声明,当在代码中调用时,你就应该使用 `await` 等待函数的结果。
---
再次提醒,这些是非常技术性的细节,如果你来搜索它可能对你有用。
否则,你最好应该遵守的指导原则<a href="#_1">赶时间吗?</a>.
+34
View File
@@ -0,0 +1,34 @@
# 基准测试
第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">可用的最快的 Python 框架之一</a>,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*)
但是在查看基准得分和对比时,请注意以下几点。
## 基准测试和速度
当你查看基准测试时,几个不同类型的工具被等效地做比较是很常见的情况。
具体来说,是将 UvicornStarlette 和 FastAPI 一起比较(在许多其它工具中)。
该工具解决的问题最简单,它将获得更好的性能。而且大多数基准测试并未测试该工具提供的其他功能。
层次结构如下:
* **Uvicorn**ASGI服务器
* **Starlette**:(使用 Uvicorn)网络微框架
* **FastAPI**:(使用 Starlette) 具有多个附加功能的API微框架,用于构建API,进行数据验证等。
* **Uvicorn**:
* 具有最佳性能,因为除了服务器本身外,它没有太多额外的代码。
* 您不会直接在 Uvicorn 中编写应用程序。这意味着您的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果您这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。
* 如果要对比与 Uvicorn 对标的服务器,请将其与 DaphneHypercornuWSGI等应用服务器进行比较。
* **Starlette**:
* 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。
* 但它为您提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。
* 如果想对比与 Starlette 对标的开发框架,请将其与 Sanic,Flask,Django 等网络框架(或微框架)进行比较。
* **FastAPI**:
* 与 Starlette 使用 Uvicorn 一样,由于 **FastAPI** 使用 Starlette,因此 FastAPI 不能比 Starlette 更快。
* FastAPI 在 Starlette 基础上提供了更多功能。例如在开发 API 时,所需的数据验证和序列化功能。FastAPI 可以帮助您自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。
* 如果您不使用 FastAPI 而直接使用 Starlette(或诸如 SanicFlaskResponder 等其它工具),您则要自己实现所有的数据验证和序列化。那么最终您的应用程序会和使用 FastAPI 构建的程序有相同的开销。一般这种数据验证和序列化的操作在您应用程序的代码中会占很大比重。
* 因此,通过使用 FastAPI 意味着您可以节省开发时间,减少编码错误,用更少的编码实现其功能,并且相比不使用 FastAPI 您很大可能会获得相同或更好的性能(因为那样您必须在代码中实现所有相同的功能)。
* 如果您想对比与 FastAPI 对标的开发框架,请与能够提供数据验证,序列化和带有自动文档生成的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证,序列化和自动化文档的 Flask-apispecNestJSMolten 等。
+13
View File
@@ -0,0 +1,13 @@
# 在云上部署 FastAPI
您几乎可以使用**任何云服务商**来部署 FastAPI 应用程序。
在大多数情况下,主要的云服务商都有部署 FastAPI 的指南。
## 云服务商 - 赞助商
一些云服务商 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这确保了FastAPI 及其**生态系统**持续健康地**发展**。
这表明了他们对 FastAPI 及其**社区**(您)的真正承诺,因为他们不仅想为您提供**良好的服务**,而且还想确保您拥有一个**良好且健康的框架**:FastAPI。 🙇
您可能想尝试他们的服务并阅读他们的指南.
+330
View File
@@ -0,0 +1,330 @@
# 部署概念
在部署 **FastAPI** 应用程序或任何类型的 Web API 时,有几个概念值得了解,通过掌握这些概念您可以找到**最合适的**方法来**部署您的应用程序**。
一些重要的概念是:
* 安全性 - HTTPS
* 启动时运行
* 重新启动
* 复制(运行的进程数)
* 内存
* 开始前的先前步骤
我们接下来了解它们将如何影响**部署**。
我们的最终目标是能够以**安全**的方式**为您的 API 客户端**提供服务,同时要**避免中断**,并且尽可能高效地利用**计算资源**( 例如服务器CPU资源)。 🚀
我将在这里告诉您更多关于这些**概念**的信息,希望能给您提供**直觉**来决定如何在非常不同的环境中部署 API,甚至在是尚不存在的**未来**的环境里。
通过考虑这些概念,您将能够**评估和设计**部署**您自己的 API**的最佳方式。
在接下来的章节中,我将为您提供更多部署 FastAPI 应用程序的**具体方法**。
但现在,让我们仔细看一下这些重要的**概念**。 这些概念也适用于任何其他类型的 Web API。 💡
## 安全性 - HTTPS
在[上一章有关 HTTPS](https.md){.internal-link target=_blank} 中,我们了解了 HTTPS 如何为您的 API 提供加密。
我们还看到,HTTPS 通常由应用程序服务器的**外部**组件(**TLS 终止代理**)提供。
并且必须有某个东西负责**更新 HTTPS 证书**,它可以是相同的组件,也可以是不同的组件。
### HTTPS 示例工具
您可以用作 TLS 终止代理的一些工具包括:
* Traefik
* 自动处理证书更新 ✨
* Caddy
* 自动处理证书更新 ✨
* Nginx
* 使用 Certbot 等外部组件进行证书更新
* HAProxy
* 使用 Certbot 等外部组件进行证书更新
* 带有 Ingress Controller(如Nginx) 的 Kubernetes
* 使用诸如 cert-manager 之类的外部组件来进行证书更新
* 由云服务商内部处理,作为其服务的一部分(请阅读下文👇)
另一种选择是您可以使用**云服务**来完成更多工作,包括设置 HTTPS。 它可能有一些限制或向您收取更多费用等。但在这种情况下,您不必自己设置 TLS 终止代理。
我将在接下来的章节中向您展示一些具体示例。
---
接下来要考虑的概念都是关于运行实际 API 的程序(例如 Uvicorn)。
## 程序和进程
我们将讨论很多关于正在运行的“**进程**”的内容,因此弄清楚它的含义以及与“**程序**”这个词有什么区别是很有用的。
### 什么是程序
**程序**这个词通常用来描述很多东西:
* 您编写的 **代码****Python 文件**。
* 操作系统可以**执行**的**文件**,例如:`python``python.exe``uvicorn`
* 在操作系统上**运行**、使用CPU 并将内容存储在内存上的特定程序。 这也被称为**进程**。
### 什么是进程
**进程** 这个词通常以更具体的方式使用,仅指在操作系统中运行的东西(如上面的最后一点):
* 在操作系统上**运行**的特定程序。
* 这不是指文件,也不是指代码,它**具体**指的是操作系统正在**执行**和管理的东西。
* 任何程序,任何代码,**只有在执行时才能做事**。 因此,是当有**进程正在运行**时。
* 该进程可以由您或操作系统**终止**(或“杀死”)。 那时,它停止运行/被执行,并且它可以**不再做事情**。
* 您计算机上运行的每个应用程序背后都有一些进程,每个正在运行的程序,每个窗口等。并且通常在计算机打开时**同时**运行许多进程。
* **同一程序**可以有**多个进程**同时运行。
如果您检查操作系统中的“任务管理器”或“系统监视器”(或类似工具),您将能够看到许多正在运行的进程。
例如,您可能会看到有多个进程运行同一个浏览器程序(Firefox、Chrome、Edge 等)。 他们通常每个tab运行一个进程,再加上一些其他额外的进程。
<img class="shadow" src="/img/deployment/concepts/image01.png">
---
现在我们知道了术语“进程”和“程序”之间的区别,让我们继续讨论部署。
## 启动时运行
在大多数情况下,当您创建 Web API 时,您希望它**始终运行**、不间断,以便您的客户端始终可以访问它。 这是当然的,除非您有特定原因希望它仅在某些情况下运行,但大多数时候您希望它不断运行并且**可用**。
### 在远程服务器中
当您设置远程服务器(云服务器、虚拟机等)时,您可以做的最简单的事情就是手动运行 Uvicorn(或类似的),就像本地开发时一样。
它将会在**开发过程中**发挥作用并发挥作用。
但是,如果您与服务器的连接丢失,**正在运行的进程**可能会终止。
如果服务器重新启动(例如更新后或从云提供商迁移后),您可能**不会注意到它**。 因此,您甚至不知道必须手动重新启动该进程。 所以,你的 API 将一直处于挂掉的状态。 😱
### 启动时自动运行
一般来说,您可能希望服务器程序(例如 Uvicorn)在服务器启动时自动启动,并且不需要任何**人为干预**,让进程始终与您的 API 一起运行(例如 Uvicorn 运行您的 FastAPI 应用程序) 。
### 单独的程序
为了实现这一点,您通常会有一个**单独的程序**来确保您的应用程序在启动时运行。 在许多情况下,它还可以确保其他组件或应用程序也运行,例如数据库。
### 启动时运行的示例工具
可以完成这项工作的工具的一些示例是:
* Docker
* Kubernetes
* Docker Compose
* Docker in Swarm Mode
* Systemd
* Supervisor
* 作为其服务的一部分由云提供商内部处理
* 其他的...
我将在接下来的章节中为您提供更具体的示例。
## 重新启动
与确保应用程序在启动时运行类似,您可能还想确保它在挂掉后**重新启动**。
### 我们会犯错误
作为人类,我们总是会犯**错误**。 软件几乎*总是*在不同的地方隐藏着**bug**。 🐛
作为开发人员,当我们发现这些bug并实现新功能(也可能添加新bug😅)时,我们会不断改进代码。
### 自动处理小错误
使用 FastAPI 构建 Web API 时,如果我们的代码中存在错误,FastAPI 通常会将其包含到触发错误的单个请求中。 🛡
对于该请求,客户端将收到 **500 内部服务器错误**,但应用程序将继续处理下一个请求,而不是完全崩溃。
### 更大的错误 - 崩溃
尽管如此,在某些情况下,我们编写的一些代码可能会导致整个应用程序崩溃,从而导致 Uvicorn 和 Python 崩溃。 💥
尽管如此,您可能不希望应用程序因为某个地方出现错误而保持死机状态,您可能希望它**继续运行**,至少对于未破坏的*路径操作*。
### 崩溃后重新启动
但在那些严重错误导致正在运行的**进程**崩溃的情况下,您需要一个外部组件来负责**重新启动**进程,至少尝试几次......
/// tip
...尽管如果整个应用程序只是**立即崩溃**,那么永远重新启动它可能没有意义。 但在这些情况下,您可能会在开发过程中注意到它,或者至少在部署后立即注意到它。
因此,让我们关注主要情况,在**未来**的某些特定情况下,它可能会完全崩溃,但重新启动它仍然有意义。
///
您可能希望让这个东西作为 **外部组件** 负责重新启动您的应用程序,因为到那时,使用 Uvicorn 和 Python 的同一应用程序已经崩溃了,因此同一应用程序的相同代码中没有东西可以对此做出什么。
### 自动重新启动的示例工具
在大多数情况下,用于**启动时运行程序**的同一工具也用于处理自动**重新启动**。
例如,可以通过以下方式处理:
* Docker
* Kubernetes
* Docker Compose
* Docker in Swarm mode
* Systemd
* Supervisor
* 作为其服务的一部分由云提供商内部处理
* 其他的...
## 复制 - 进程和内存
对于 FastAPI 应用程序,使用像 Uvicorn 这样的服务器程序,在**一个进程**中运行一次就可以同时为多个客户端提供服务。
但在许多情况下,您会希望同时运行多个工作进程。
### 多进程 - Workers
如果您的客户端数量多于单个进程可以处理的数量(例如,如果虚拟机不是太大),并且服务器的 CPU 中有 **多个核心**,那么您可以让 **多个进程** 运行 同时处理同一个应用程序,并在它们之间分发所有请求。
当您运行同一 API 程序的**多个进程**时,它们通常称为 **workers**
### 工作进程和端口
还记得文档 [About HTTPS](https.md){.internal-link target=_blank} 中只有一个进程可以侦听服务器中的端口和 IP 地址的一种组合吗?
现在仍然是对的。
因此,为了能够同时拥有**多个进程**,必须有一个**单个进程侦听端口**,然后以某种方式将通信传输到每个工作进程。
### 每个进程的内存
现在,当程序将内容加载到内存中时,例如,将机器学习模型加载到变量中,或者将大文件的内容加载到变量中,所有这些都会消耗服务器的一点内存 (RAM) 。
多个进程通常**不共享任何内存**。 这意味着每个正在运行的进程都有自己的东西、变量和内存。 如果您的代码消耗了大量内存,**每个进程**将消耗等量的内存。
### 服务器内存
例如,如果您的代码加载 **1 GB 大小**的机器学习模型,则当您使用 API 运行一个进程时,它将至少消耗 1 GB RAM。 如果您启动 **4 个进程**(4 个工作进程),每个进程将消耗 1 GB RAM。 因此,您的 API 总共将消耗 **4 GB RAM**
如果您的远程服务器或虚拟机只有 3 GB RAM,尝试加载超过 4 GB RAM 将导致问题。 🚨
### 多进程 - 一个例子
在此示例中,有一个 **Manager Process** 启动并控制两个 **Worker Processes**
该管理器进程可能是监听 IP 中的 **端口** 的进程。 它将所有通信传输到工作进程。
这些工作进程将是运行您的应用程序的进程,它们将执行主要计算以接收 **请求** 并返回 **响应**,并且它们将加载您放入 RAM 中的变量中的任何内容。
<img src="/img/deployment/concepts/process-ram.drawio.svg">
当然,除了您的应用程序之外,同一台机器可能还运行**其他进程**。
一个有趣的细节是,随着时间的推移,每个进程使用的 **CPU 百分比可能会发生很大变化,但内存 (RAM) 通常会或多或少保持稳定**
如果您有一个每次执行相当数量的计算的 API,并且您有很多客户端,那么 **CPU 利用率** 可能也会保持稳定(而不是不断快速上升和下降)。
### 复制工具和策略示例
可以通过多种方法来实现这一目标,我将在接下来的章节中向您详细介绍具体策略,例如在谈论 Docker 和容器时。
要考虑的主要限制是必须有一个**单个**组件来处理**公共IP**中的**端口**。 然后它必须有一种方法将通信**传输**到复制的**进程/worker**。
以下是一些可能的组合和策略:
* **Gunicorn** 管理 **Uvicorn workers**
* Gunicorn 将是监听 **IP****端口****进程管理器**,复制将通过 **多个 Uvicorn 工作进程** 进行
* **Uvicorn** 管理 **Uvicorn workers**
* 一个 Uvicorn **进程管理器** 将监听 **IP****端口**,并且它将启动 **多个 Uvicorn 工作进程**
* **Kubernetes** 和其他分布式 **容器系统**
* **Kubernetes** 层中的某些东西将侦听 **IP****端口**。 复制将通过拥有**多个容器**,每个容器运行**一个 Uvicorn 进程**
* **云服务** 为您处理此问题
* 云服务可能**为您处理复制**。 它可能会让您定义 **要运行的进程**,或要使用的 **容器映像**,在任何情况下,它很可能是 **单个 Uvicorn 进程**,并且云服务将负责复制它。
/// tip
如果这些关于 **容器**、Docker 或 Kubernetes 的内容还没有多大意义,请不要担心。
我将在以后的章节中向您详细介绍容器镜像、Docker、Kubernetes 等:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
///
## 启动之前的步骤
在很多情况下,您希望在**启动**应用程序之前执行一些步骤。
例如,您可能想要运行**数据库迁移**。
但在大多数情况下,您只想执行这些步骤**一次**。
因此,在启动应用程序之前,您将需要一个**单个进程**来执行这些**前面的步骤**。
而且您必须确保它是运行前面步骤的单个进程, *即使*之后您为应用程序本身启动**多个进程**(多个worker)。 如果这些步骤由**多个进程**运行,它们会通过在**并行**运行来**重复**工作,并且如果这些步骤像数据库迁移一样需要小心处理,它们可能会导致每个进程和其他进程发生冲突。
当然,也有一些情况,多次运行前面的步骤也没有问题,这样的话就好办多了。
/// tip
另外,请记住,根据您的设置,在某些情况下,您在开始应用程序之前**可能甚至不需要任何先前的步骤**。
在这种情况下,您就不必担心这些。 🤷
///
### 前面步骤策略的示例
这将在**很大程度上取决于您部署系统的方式**,并且可能与您启动程序、处理重启等的方式有关。
以下是一些可能的想法:
* Kubernetes 中的“Init Container”在应用程序容器之前运行
* 一个 bash 脚本,运行前面的步骤,然后启动您的应用程序
* 您仍然需要一种方法来启动/重新启动 bash 脚本、检测错误等。
/// tip
我将在以后的章节中为您提供使用容器执行此操作的更具体示例:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
///
## 资源利用率
您的服务器是一个**资源**,您可以通过您的程序消耗或**利用**CPU 上的计算时间以及可用的 RAM 内存。
您想要消耗/利用多少系统资源? 您可能很容易认为“不多”,但实际上,您可能希望在不崩溃的情况下**尽可能多地消耗**。
如果您支付了 3 台服务器的费用,但只使用了它们的一点点 RAM 和 CPU,那么您可能**浪费金钱** 💸,并且可能 **浪费服务器电力** 🌎,等等。
在这种情况下,最好只拥有 2 台服务器并使用更高比例的资源(CPU、内存、磁盘、网络带宽等)。
另一方面,如果您有 2 台服务器,并且正在使用 **100% 的 CPU 和 RAM**,则在某些时候,一个进程会要求更多内存,并且服务器将不得不使用磁盘作为“内存” (这可能会慢数千倍),甚至**崩溃**。 或者一个进程可能需要执行一些计算,并且必须等到 CPU 再次空闲。
在这种情况下,最好购买**一台额外的服务器**并在其上运行一些进程,以便它们都有**足够的 RAM 和 CPU 时间**。
由于某种原因,您的 API 的使用量也有可能出现**激增**。 也许它像病毒一样传播开来,或者也许其他一些服务或机器人开始使用它。 在这些情况下,您可能需要额外的资源来保证安全。
您可以将一个**任意数字**设置为目标,例如,资源利用率**在 50% 到 90%** 之间。 重点是,这些可能是您想要衡量和用来调整部署的主要内容。
您可以使用“htop”等简单工具来查看服务器中使用的 CPU 和 RAM 或每个进程使用的数量。 或者您可以使用更复杂的监控工具,这些工具可能分布在服务器等上。
## 回顾
您在这里阅读了一些在决定如何部署应用程序时可能需要牢记的主要概念:
* 安全性 - HTTPS
* 启动时运行
* 重新启动
* 复制(运行的进程数)
* 内存
* 开始前的先前步骤
了解这些想法以及如何应用它们应该会给您足够的直觉在配置和调整部署时做出任何决定。 🤓
在接下来的部分中,我将为您提供更具体的示例,说明您可以遵循的可能策略。 🚀
+760
View File
@@ -0,0 +1,760 @@
# 容器中的 FastAPI - Docker
部署 FastAPI 应用程序时,常见的方法是构建 **Linux 容器镜像**。 通常使用 <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a> 完成。 然后,你可以通过几种可能的方式之一部署该容器镜像。
使用 Linux 容器有几个优点,包括**安全性**、**可复制性**、**简单性**等。
/// tip
赶时间并且已经知道这些东西了? 跳转到下面的 [`Dockerfile` 👇](#fastapi-docker_1)。
///
<details>
<summary>Dockerfile Preview 👀</summary>
```Dockerfile
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
# If running behind a proxy like Nginx or Traefik add --proxy-headers
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
```
</details>
## 什么是容器
容器(主要是 Linux 容器)是一种非常**轻量级**的打包应用程序的方式,其包括所有依赖项和必要的文件,同时它们可以和同一系统中的其他容器(或者其他应用程序/组件)相互隔离。
Linux 容器使用宿主机(如物理服务器、虚拟机、云服务器等)的Linux 内核运行。 这意味着它们非常轻量(与模拟整个操作系统的完整虚拟机相比)。
通过这样的方式,容器消耗**很少的资源**,与直接运行进程相当(虚拟机会消耗更多)。
容器的进程(通常只有一个)、文件系统和网络都运行在隔离的环境,这简化了部署、安全、开发等。
## 什么是容器镜像
**容器**是从**容器镜像**运行的。
容器镜像是容器中文件、环境变量和默认命令/程序的**静态**版本。 **静态**这里的意思是容器**镜像**还没有运行,只是打包的文件和元数据。
与存储静态内容的“**容器镜像**”相反,“**容器**”通常指正在运行的实例,即正在**执行的**。
当**容器**启动并运行时(从**容器镜像**启动),它可以创建或更改文件、环境变量等。这些更改将仅存在于该容器中,而不会持久化到底层的容器镜像中(不会保存到磁盘)。
容器镜像相当于**程序**和文件,例如 `python`命令 和某些文件 如`main.py`
而**容器**本身(与**容器镜像**相反)是镜像的实际运行实例,相当于**进程**。 事实上,容器仅在有**进程运行**时才运行(通常它只是一个单独的进程)。 当容器中没有进程运行时,容器就会停止。
## 容器镜像
Docker 一直是创建和管理**容器镜像**和**容器**的主要工具之一。
还有一个公共 <a href="https://hub.docker.com/" class="external-link" target="_blank">Docker Hub</a> ,其中包含预制的 **官方容器镜像**, 适用于许多工具、环境、数据库和应用程序。
例如,有一个官方的 <a href="https://hub.docker.com/_/python" class="external-link" target="_blank">Python 镜像</a>。
还有许多其他镜像用于不同的需要(例如数据库),例如:
* <a href="https://hub.docker.com/_/postgres" class="external-link" target="_blank">PostgreSQL</a>
* <a href="https://hub.docker.com/_/mysql" class="external-link" target="_blank">MySQL</a>
* <a href="https://hub.docker.com/_/mongo" class="external-link" target="_blank">MongoDB</a>
* <a href="https://hub.docker.com/_/redis" class="external-link" target="_blank">Redis</a>, etc.
通过使用预制的容器镜像,可以非常轻松地**组合**并使用不同的工具。 例如,尝试一个新的数据库。 在大多数情况下,你可以使用**官方镜像**,只需为其配置环境变量即可。
这样,在许多情况下,你可以了解容器和 Docker,并通过许多不同的工具和组件重复使用这些知识。
因此,你可以运行带有不同内容的**多个容器**,例如数据库、Python 应用程序、带有 React 前端应用程序的 Web 服务器,并通过内部网络将它们连接在一起。
所有容器管理系统(如 Docker 或 Kubernetes)都集成了这些网络功能。
## 容器和进程
**容器镜像**通常在其元数据中包含启动**容器**时应运行的默认程序或命令以及要传递给该程序的参数。 与在命令行中的情况非常相似。
**容器** 启动时,它将运行该命令/程序(尽管你可以覆盖它并使其运行不同的命令/程序)。
只要**主进程**(命令或程序)在运行,容器就在运行。
容器通常有一个**单个进程**,但也可以从主进程启动子进程,这样你就可以在同一个容器中拥有**多个进程**。
但是,如果没有**至少一个正在运行的进程**,就不可能有一个正在运行的容器。 如果主进程停止,容器也会停止。
## 为 FastAPI 构建 Docker 镜像
好吧,让我们现在构建一些东西! 🚀
我将向你展示如何基于 **官方 Python** 镜像 **从头开始** 为 FastAPI 构建 **Docker 镜像**
这是你在**大多数情况**下想要做的,例如:
* 使用 **Kubernetes** 或类似工具
***Raspberry Pi** 上运行时
* 使用可为你运行容器镜像的云服务等。
### 依赖项
你通常会在某个文件中包含应用程序的**依赖项**。
具体做法取决于你**安装**这些依赖时所使用的工具。
最常见的方法是创建一个`requirements.txt`文件,其中每行包含一个包名称和它的版本。
你当然也可以使用在[关于 FastAPI 版本](versions.md){.internal-link target=_blank} 中讲到的方法来设置版本范围。
例如,你的`requirements.txt`可能如下所示:
```
fastapi>=0.68.0,<0.69.0
pydantic>=1.8.0,<2.0.0
uvicorn>=0.15.0,<0.16.0
```
你通常会使用`pip`安装这些依赖项:
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
Successfully installed fastapi pydantic uvicorn
```
</div>
/// info
还有其他文件格式和工具来定义和安装依赖项。
我将在下面的部分中向你展示一个使用 Poetry 的示例。 👇
///
### 创建 **FastAPI** 代码
* 创建`app`目录并进入。
* 创建一个空文件`__init__.py`
* 创建一个 `main.py` 文件:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
### Dockerfile
现在在相同的project目录创建一个名为`Dockerfile`的文件:
```{ .dockerfile .annotate }
# (1)
FROM python:3.9
# (2)
WORKDIR /code
# (3)
COPY ./requirements.txt /code/requirements.txt
# (4)
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (5)
COPY ./app /code/app
# (6)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. 从官方Python基础镜像开始。
2. 将当前工作目录设置为`/code`。
这是我们放置`requirements.txt`文件和`app`目录的位置。
3. 将符合要求的文件复制到`/code`目录中。
首先仅复制requirements.txt文件,而不复制其余代码。
由于此文件**不经常更改**,Docker 将检测到它并在这一步中使用**缓存**,从而为下一步启用缓存。
4. 安装需求文件中的包依赖项。
`--no-cache-dir` 选项告诉 `pip` 不要在本地保存下载的包,因为只有当 `pip` 再次运行以安装相同的包时才会这样,但在与容器一起工作时情况并非如此。
/// note | 笔记
`--no-cache-dir` 仅与 `pip` 相关,与 Docker 或容器无关。
///
`--upgrade` 选项告诉 `pip` 升级软件包(如果已经安装)。
因为上一步复制文件可以被 **Docker 缓存** 检测到,所以此步骤也将 **使用 Docker 缓存**(如果可用)。
在开发过程中一次又一次构建镜像时,在此步骤中使用缓存将为你节省大量**时间**,而不是**每次**都**下载和安装**所有依赖项。
5. 将“./app”目录复制到“/code”目录中。
由于其中包含**更改最频繁**的所有代码,因此 Docker **缓存**不会轻易用于此操作或任何**后续步骤**。
因此,将其放在`Dockerfile`**接近最后**的位置非常重要,以优化容器镜像的构建时间。
6. 设置**命令**来运行 `uvicorn` 服务器。
`CMD` 接受一个字符串列表,每个字符串都是你在命令行中输入的内容,并用空格分隔。
该命令将从 **当前工作目录** 运行,即你上面使用`WORKDIR /code`设置的同一`/code`目录。
因为程序将从`/code`启动,并且其中包含你的代码的目录`./app`,所以**Uvicorn**将能够从`app.main`中查看并**import**`app`。
/// tip
通过单击代码中的每个数字气泡来查看每行的作用。 👆
///
你现在应该具有如下目录结构:
```
.
├── app
│   ├── __init__.py
│ └── main.py
├── Dockerfile
└── requirements.txt
```
#### 在 TLS 终止代理后面
如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请添加选项 `--proxy-headers`,这将告诉 Uvicorn 信任该代理发送的标头,告诉它应用程序正在 HTTPS 后面运行等信息
```Dockerfile
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
```
#### Docker 缓存
这个`Dockerfile`中有一个重要的技巧,我们首先只单独复制**包含依赖项的文件**,而不是其余代码。 让我来告诉你这是为什么。
```Dockerfile
COPY ./requirements.txt /code/requirements.txt
```
Docker之类的构建工具是通过**增量**的方式来构建这些容器镜像的。具体做法是从`Dockerfile`顶部开始,每一条指令生成的文件都是镜像的“一层”,同过把这些“层”一层一层地叠加到基础镜像上,最后我们就得到了最终的镜像。
Docker 和类似工具在构建镜像时也会使用**内部缓存**,如果自上次构建容器镜像以来文件没有更改,那么它将**重新使用上次创建的同一层**,而不是再次复制文件并从头开始创建新层。
仅仅避免文件的复制不一定会有太多速度提升,但是如果在这一步使用了缓存,那么才可以**在下一步中使用缓存**。 例如,可以使用安装依赖项那条指令的缓存:
```Dockerfile
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
```
包含包依赖项的文件**不会频繁更改**。 只复制该文件(不复制其他的应用代码),Docker 才能在这一步**使用缓存**。
Docker 进而能**使用缓存进行下一步**,即下载并安装这些依赖项。 这才是我们**节省大量时间**的地方。 ✨ ...可以避免无聊的等待。 😪😆
下载和安装依赖项**可能需要几分钟**,但使用**缓存**最多**只需要几秒钟**。
由于你在开发过程中会一次又一次地构建容器镜像以检查代码更改是否有效,因此可以累计节省大量时间。
在`Dockerfile`末尾附近,我们再添加复制代码的指令。 由于代码是**更改最频繁的**,所以将其放在最后,因为这一步之后的内容基本上都是无法使用缓存的。
```Dockerfile
COPY ./app /code/app
```
### 构建 Docker 镜像
现在所有文件都已就位,让我们构建容器镜像。
* 转到项目目录(在`Dockerfile`所在的位置,包含`app`目录)。
* 构建你的 FastAPI 镜像:
<div class="termy">
```console
$ docker build -t myimage .
---> 100%
```
</div>
/// tip
注意最后的 `.`,它相当于`./`,它告诉 Docker 用于构建容器镜像的目录。
在本例中,它是相同的当前目录(`.`)。
///
### 启动 Docker 容器
* 根据你的镜像运行容器:
<div class="termy">
```console
$ docker run -d --name mycontainer -p 80:80 myimage
```
</div>
## 检查一下
你应该能在Docker容器的URL中检查它,例如: <a href="http://192.168.99.100/items/5?q=somequery" class="external-link" target="_blank">http://192.168.99.100/items/5?q=somequery</a> 或 <a href="http://127.0.0.1/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1/items/5?q=somequery</a> (或其他等价的,使用 Docker 主机).
你会看到类似内容:
```JSON
{"item_id": 5, "q": "somequery"}
```
## 交互式 API 文档
现在你可以转到 <a href="http://192.168.99.100/docs" class="external-link" target="_blank">http://192.168.99.100/docs</a> 或 <a href ="http://127.0.0.1/docs" class="external-link" target="_blank">http://127.0.0.1/docs</a> (或其他等价的,使用 Docker 主机)。
你将看到自动交互式 API 文档(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a 提供) >):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
## 备选的 API 文档
你还可以访问 <a href="http://192.168.99.100/redoc" class="external-link" target="_blank">http://192.168.99.100/redoc</a> 或 <a href="http://127.0.0.1/redoc" class="external-link" target="_blank">http://127.0.0.1/redoc</a> (或其他等价的,使用 Docker 主机)。
你将看到备选的自动文档(由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 提供):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## 使用单文件 FastAPI 构建 Docker 镜像
如果你的 FastAPI 是单个文件,例如没有`./app`目录的`main.py`,则你的文件结构可能如下所示:
```
.
├── Dockerfile
├── main.py
└── requirements.txt
```
然后你只需更改相应的路径即可将文件复制到`Dockerfile`中:
```{ .dockerfile .annotate hl_lines="10 13" }
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (1)
COPY ./main.py /code/
# (2)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. 直接将`main.py`文件复制到`/code`目录中(不包含任何`./app`目录)。
2. 运行 Uvicorn 并告诉它从 `main` 导入 `app` 对象(而不是从 `app.main` 导入)。
然后调整Uvicorn命令使用新模块`main`而不是`app.main`来导入FastAPI 实例`app`。
## 部署概念
我们再谈谈容器方面的一些相同的[部署概念](concepts.md){.internal-link target=_blank}。
容器主要是一种简化**构建和部署**应用程序的过程的工具,但它们并不强制执行特定的方法来处理这些**部署概念**,并且有几种可能的策略。
**好消息**是,对于每种不同的策略,都有一种方法可以涵盖所有部署概念。 🎉
让我们从容器的角度回顾一下这些**部署概念**:
* HTTPS
* 启动时运行
* 重新启动
* 复制(运行的进程数)
* 内存
* 开始前的先前步骤
## HTTPS
如果我们只关注 FastAPI 应用程序的 **容器镜像**(以及稍后运行的 **容器**),HTTPS 通常会由另一个工具在 **外部** 处理。
它可以是另一个容器,例如使用 <a href="https://traefik.io/" class="external-link" target="_blank">Traefik</a>,处理 **HTTPS** 和 **自动**获取**证书**。
/// tip
Traefik可以与 Docker、Kubernetes 等集成,因此使用它为容器设置和配置 HTTPS 非常容易。
///
或者,HTTPS 可以由云服务商作为其服务之一进行处理(同时仍在容器中运行应用程序)。
## 在启动和重新启动时运行
通常还有另一个工具负责**启动和运行**你的容器。
它可以直接是**Docker**, 或者**Docker Compose**、**Kubernetes**、**云服务**等。
在大多数(或所有)情况下,有一个简单的选项可以在启动时运行容器并在失败时重新启动。 例如,在 Docker 中,它是命令行选项 `--restart`。
如果不使用容器,让应用程序在启动时运行并重新启动可能会很麻烦且困难。 但在大多数情况下,当**使用容器**时,默认情况下会包含该功能。 ✨
## 复制 - 进程数
如果你有一个 <abbr title="一组配置为以某种方式连接并协同工作的计算机。">集群</abbr>, 比如 **Kubernetes**、Docker Swarm、Nomad 或其他类似的复杂系统来管理多台机器上的分布式容器,那么你可能希望在**集群级别**处理复制**,而不是在每个容器中使用**进程管理器**(如带有Worker的 Gunicorn
像 Kubernetes 这样的分布式容器管理系统通常有一些集成的方法来处理**容器的复制**,同时仍然支持传入请求的**负载均衡**。 全部都在**集群级别**。
在这些情况下,你可能希望从头开始构建一个 **Docker 镜像**,如[上面所解释](#dockerfile)的那样,安装依赖项并运行 **单个 Uvicorn 进程**,而不是运行 Gunicorn 和 Uvicorn workers这种。
### 负载均衡器
使用容器时,通常会有一些组件**监听主端口**。 它可能是处理 **HTTPS** 的 **TLS 终止代理** 或一些类似的工具的另一个容器。
由于该组件将接受请求的**负载**并(希望)以**平衡**的方式在worker之间分配该请求,因此它通常也称为**负载均衡器**。
/// tip
用于 HTTPS **TLS 终止代理** 的相同组件也可能是 **负载均衡器**。
///
当使用容器时,你用来启动和管理容器的同一系统已经具有内部工具来传输来自该**负载均衡器**(也可以是**TLS 终止代理**) 的**网络通信**(例如HTTP请求)到你的应用程序容器。
### 一个负载均衡器 - 多个worker容器
当使用 **Kubernetes** 或类似的分布式容器管理系统时,使用其内部网络机制将允许单个在主 **端口** 上侦听的 **负载均衡器** 将通信(请求)传输到可能的 **多个** 运行你应用程序的容器。
运行你的应用程序的每个容器通常**只有一个进程**(例如,运行 FastAPI 应用程序的 Uvicorn 进程)。 它们都是**相同的容器**,运行相同的东西,但每个容器都有自己的进程、内存等。这样你就可以在 CPU 的**不同核心**, 甚至在**不同的机器**充分利用**并行化(parallelization)**。
具有**负载均衡器**的分布式容器系统将**将请求轮流分配**给你的应用程序的每个容器。 因此,每个请求都可以由运行你的应用程序的多个**复制容器**之一来处理。
通常,这个**负载均衡器**能够处理发送到集群中的*其他*应用程序的请求(例如发送到不同的域,或在不同的 URL 路径前缀下),并正确地将该通信传输到在集群中运行的*其他*应用程序的对应容器。
### 每个容器一个进程
在这种类型的场景中,你可能希望**每个容器有一个(Uvicorn)进程**,因为你已经在集群级别处理复制。
因此,在这种情况下,你**不会**希望拥有像 Gunicorn 和 Uvicorn worker一样的进程管理器,或者 Uvicorn 使用自己的 Uvicorn worker。 你可能希望每个容器(但可能有多个容器)只有一个**单独的 Uvicorn 进程**。
在容器内拥有另一个进程管理器(就像使用 Gunicorn 或 Uvicorn 管理 Uvicorn 工作线程一样)只会增加**不必要的复杂性**,而你很可能已经在集群系统中处理这些复杂性了。
### 具有多个进程的容器
当然,在某些**特殊情况**,你可能希望拥有 **一个容器**,其中包含 **Gunicorn 进程管理器**,并在其中启动多个 **Uvicorn worker进程**。
在这些情况下,你可以使用 **官方 Docker 镜像**,其中包含 **Gunicorn** 作为运行多个 **Uvicorn 工作进程** 的进程管理器,以及一些默认设置来根据当前情况调整工作进程数量 自动CPU核心。 我将在下面的 [Gunicorn - Uvicorn 官方 Docker 镜像](#official-docker-image-with-gunicorn-uvicorn) 中告诉你更多相关信息。
下面一些什么时候这种做法有意义的示例:
#### 一个简单的应用程序
如果你的应用程序**足够简单**,你不需要(至少现在不需要)过多地微调进程数量,并且你可以使用自动默认值,那么你可能需要容器中的进程管理器 (使用官方 Docker 镜像),并且你在**单个服务器**而不是集群上运行它。
#### Docker Compose
你可以使用 **Docker Compose** 部署到**单个服务器**(而不是集群),因此你没有一种简单的方法来管理容器的复制(使用 Docker Compose),同时保留共享网络和 **负载均衡**。
然后,你可能希望拥有一个**单个容器**,其中有一个**进程管理器**,在其中启动**多个worker进程**。
#### Prometheus和其他原因
你还可能有**其他原因**,这将使你更容易拥有一个带有**多个进程**的**单个容器**,而不是拥有每个容器中都有**单个进程**的**多个容器**。
例如(取决于你的设置)你可以在同一个容器中拥有一些工具,例如 Prometheus exporter,该工具应该有权访问**每个请求**。
在这种情况下,如果你有**多个容器**,默认情况下,当 Prometheus 来**读取metrics**时,它每次都会获取**单个容器**的metrics(对于处理该特定请求的容器),而不是获取所有复制容器的**累积metrics**。
在这种情况, 这种做法会更加简单:让**一个容器**具有**多个进程**,并在同一个容器上使用本地工具(例如 Prometheus exporter)收集所有内部进程的 Prometheus 指标并公开单个容器上的这些指标。
---
要点是,这些都**不是**你必须盲目遵循的**一成不变的规则**。 你可以根据这些思路**评估你自己的场景**并决定什么方法是最适合你的的系统,考虑如何管理以下概念:
* 安全性 - HTTPS
* 启动时运行
* 重新启动
* 复制(运行的进程数)
* 内存
* 开始前的先前步骤
## 内存
如果你**每个容器运行一个进程**,那么每个容器所消耗的内存或多或少是定义明确的、稳定的且有限的(如果它们是复制的,则不止一个)。
然后,你可以在容器管理系统的配置中设置相同的内存限制和要求(例如在 **Kubernetes** 中)。 这样,它将能够在**可用机器**中**复制容器**,同时考虑容器所需的内存量以及集群中机器中的可用内存量。
如果你的应用程序很**简单**,这可能**不是问题**,并且你可能不需要指定内存限制。 但是,如果你**使用大量内存**(例如使用**机器学习**模型),则应该检查你消耗了多少内存并调整**每台机器**中运行的**容器数量**(也许可以向集群添加更多机器)。
如果你**每个容器运行多个进程**(例如使用官方 Docker 镜像),你必须确保启动的进程数量不会消耗比可用内存**更多的内存**。
## 启动之前的步骤和容器
如果你使用容器(例如 Docker、Kubernetes),那么你可以使用两种主要方法。
### 多个容器
如果你有 **多个容器**,可能每个容器都运行一个 **单个进程**(例如,在 **Kubernetes** 集群中),那么你可能希望有一个 **单独的容器** 执行以下操作: 在单个容器中运行单个进程执行**先前步骤**,即运行复制的worker容器之前。
/// info
如果你使用 Kubernetes,这可能是 <a href="https://kubernetes.io/docs/concepts/workloads/pods/init-containers/" class="external-link" target="_blank">Init Container</a>。
///
如果在你的用例中,运行前面的步骤**并行多次**没有问题(例如,如果你没有运行数据库迁移,而只是检查数据库是否已准备好),那么你也可以将它们放在开始主进程之前在每个容器中。
### 单容器
如果你有一个简单的设置,使用一个**单个容器**,然后启动多个**工作进程**(或者也只是一个进程),那么你可以在启动进程之前在应用程序同一个容器中运行先前的步骤。 官方 Docker 镜像内部支持这一点。
## 带有 Gunicorn 的官方 Docker 镜像 - Uvicorn
有一个官方 Docker 镜像,其中包含与 Uvicorn worker一起运行的 Gunicorn,如上一章所述:[服务器工作线程 - Gunicorn 与 Uvicorn](server-workers.md){.internal-link target=_blank}。
该镜像主要在上述情况下有用:[具有多个进程和特殊情况的容器](#containers-with-multiple-processes-and-special-cases)。
* <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank">tiangolo/uvicorn-gunicorn-fastapi</a>.
/// warning
你很有可能不需要此基础镜像或任何其他类似的镜像,最好从头开始构建镜像,如[上面所述:为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。
///
该镜像包含一个**自动调整**机制,用于根据可用的 CPU 核心设置**worker进程数**。
它具有**合理的默认值**,但你仍然可以使用**环境变量**或配置文件更改和更新所有配置。
它还支持通过一个脚本运行<a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#pre_start_path" class="external-link" target="_blank">**开始前的先前步骤** </a>。
/// tip
要查看所有配置和选项,请转到 Docker 镜像页面: <a href="https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker" class="external-link" target="_blank" >tiangolo/uvicorn-gunicorn-fastapi</a>。
///
### 官方 Docker 镜像上的进程数
此镜像上的**进程数**是根据可用的 CPU **核心**自动计算的。
这意味着它将尝试尽可能多地**榨取**CPU 的**性能**。
你还可以使用 **环境变量** 等配置来调整它。
但这也意味着,由于进程数量取决于容器运行的 CPU,因此**消耗的内存量**也将取决于该数量。
因此,如果你的应用程序消耗大量内存(例如机器学习模型),并且你的服务器有很多 CPU 核心**但内存很少**,那么你的容器最终可能会尝试使用比实际情况更多的内存 可用,并且性能会下降很多(甚至崩溃)。 🚨
### 创建一个`Dockerfile`
以下是如何根据此镜像创建`Dockerfile`
```Dockerfile
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app
```
### 更大的应用程序
如果你按照有关创建[具有多个文件的更大应用程序](../tutorial/bigger-applications.md){.internal-link target=_blank}的部分进行操作,你的`Dockerfile`可能看起来这样:
```Dockerfile hl_lines="7"
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9
COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
COPY ./app /app/app
```
### 何时使用
如果你使用 **Kubernetes** (或其他)并且你已经在集群级别设置 **复制**,并且具有多个 **容器**。 在这些情况下,你最好按照上面的描述 **从头开始构建镜像**:[为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。
该镜像主要在[具有多个进程的容器和特殊情况](#containers-with-multiple-processes-and-special-cases)中描述的特殊情况下有用。 例如,如果你的应用程序**足够简单**,基于 CPU 设置默认进程数效果很好,你不想在集群级别手动配置复制,并且不会运行更多进程, 或者你使用 **Docker Compose** 进行部署,在单个服务器上运行等。
## 部署容器镜像
拥有容器(Docker)镜像后,有多种方法可以部署它。
例如:
* 在单个服务器中使用 **Docker Compose**
* 使用 **Kubernetes** 集群
* 使用 Docker Swarm 模式集群
* 使用Nomad等其他工具
* 使用云服务获取容器镜像并部署它
## Docker 镜像与Poetry
如果你使用 <a href="https://python-poetry.org/" class="external-link" target="_blank">Poetry</a> 来管理项目的依赖项,你可以使用 Docker 多阶段构建:
```{ .dockerfile .annotate }
# (1)
FROM python:3.9 as requirements-stage
# (2)
WORKDIR /tmp
# (3)
RUN pip install poetry
# (4)
COPY ./pyproject.toml ./poetry.lock* /tmp/
# (5)
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
# (6)
FROM python:3.9
# (7)
WORKDIR /code
# (8)
COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt
# (9)
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# (10)
COPY ./app /code/app
# (11)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```
1. 这是第一阶段,称为`requirements-stage`。
2. 将 `/tmp` 设置为当前工作目录。
这是我们生成文件`requirements.txt`的地方
3. 在此阶段安装Poetry。
4. 将`pyproject.toml`和`poetry.lock`文件复制到`/tmp`目录。
因为它使用 `./poetry.lock*` (以 `*` 结尾),所以如果该文件尚不可用,它不会崩溃。
5. 生成`requirements.txt`文件。
6. 这是最后阶段,这里的任何内容都将保留在最终的容器镜像中。
7. 将当前工作目录设置为`/code`。
8. 将 `requirements.txt` 文件复制到 `/code` 目录。
该文件仅存在于前一个阶段,这就是为什么我们使用 `--from-requirements-stage` 来复制它。
9. 安装生成的`requirements.txt`文件中的依赖项。
10. 将`app`目录复制到`/code`目录。
11. 运行`uvicorn`命令,告诉它使用从`app.main`导入的`app`对象。
/// tip
单击气泡数字可查看每行的作用。
///
**Docker stage** 是 `Dockerfile` 的一部分,用作 **临时容器镜像**,仅用于生成一些稍后使用的文件。
第一阶段仅用于 **安装 Poetry** 并使用 Poetry 的 `pyproject.toml` 文件中的项目依赖项 **生成 `requirements.txt`**。
此`requirements.txt`文件将在**下一阶段**与`pip`一起使用。
在最终的容器镜像中**仅保留最后阶段**。 之前的阶段将被丢弃。
使用 Poetry 时,使用 **Docker 多阶段构建** 是有意义的,因为你实际上并不需要在最终的容器镜像中安装 Poetry 及其依赖项,你 **只需要** 生成用于安装项目依赖项的`requirements.txt`文件。
然后,在下一个(也是最后一个)阶段,你将或多或少地以与前面描述的相同的方式构建镜像。
### 在TLS 终止代理后面 - Poetry
同样,如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请将选项`--proxy-headers`添加到命令中:
```Dockerfile
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
```
## 回顾
使用容器系统(例如使用**Docker**和**Kubernetes**),处理所有**部署概念**变得相当简单:
* HTTPS
* 启动时运行
* 重新启动
* 复制(运行的进程数)
* 内存
* 开始前的先前步骤
在大多数情况下,你可能不想使用任何基础镜像,而是基于官方 Python Docker 镜像 **从头开始构建容器镜像** 。
处理好`Dockerfile`和 **Docker 缓存**中指令的**顺序**,你可以**最小化构建时间**,从而最大限度地提高生产力(并避免无聊)。 😎
在某些特殊情况下,你可能需要使用 FastAPI 的官方 Docker 镜像。 🤓
+201
View File
@@ -0,0 +1,201 @@
# 关于 HTTPS
人们很容易认为 HTTPS 仅仅是“启用”或“未启用”的东西。
但实际情况比这复杂得多。
/// note | 提示
如果你很赶时间或不在乎,请继续阅读下一部分,下一部分会提供一个step-by-step的教程,告诉你怎么使用不同技术来把一切都配置好。
///
要从用户的视角**了解 HTTPS 的基础知识**,请查看 <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>。
现在,从**开发人员的视角**,在了解 HTTPS 时需要记住以下几点:
* 要使用 HTTPS,**服务器**需要拥有由**第三方**生成的**"证书(certificate)"**。
* 这些证书实际上是从第三方**获取**的,而不是“生成”的。
* 证书有**生命周期**。
* 它们会**过期**。
* 然后它们需要**更新**,**再次从第三方获取**。
* 连接的加密发生在 **TCP 层**
* 这是 HTTP 协议**下面的一层**。
* 因此,**证书和加密**处理是在 **HTTP之前**完成的。
* **TCP 不知道域名**。 仅仅知道 IP 地址。
* 有关所请求的 **特定域名** 的信息位于 **HTTP 数据**中。
* **HTTPS 证书**“证明”**某个域名**,但协议和加密发生在 TCP 层,在知道正在处理哪个域名**之前**。
* **默认情况下**,这意味着你**每个 IP 地址只能拥有一个 HTTPS 证书**。
* 无论你的服务器有多大,或者服务器上的每个应用程序有多小。
* 不过,对此有一个**解决方案**。
* **TLS** 协议(在 HTTP 之下的TCP 层处理加密的协议)有一个**扩展**,称为 **<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="服务器名称指示">SNI</abbr></a>**。
* SNI 扩展允许一台服务器(具有 **单个 IP 地址**)拥有 **多个 HTTPS 证书** 并提供 **多个 HTTPS 域名/应用程序**
* 为此,服务器上会有**单独**的一个组件(程序)侦听**公共 IP 地址**,这个组件必须拥有服务器中的**所有 HTTPS 证书**。
* **获得安全连接后**,通信协议**仍然是HTTP**。
* 内容是 **加密过的**,即使它们是通过 **HTTP 协议** 发送的。
通常的做法是在服务器上运行**一个程序/HTTP 服务器**并**管理所有 HTTPS 部分**:接收**加密的 HTTPS 请求**, 将 **解密的 HTTP 请求** 发送到在同一服务器中运行的实际 HTTP 应用程序(在本例中为 **FastAPI** 应用程序),从应用程序中获取 **HTTP 响应** 使用适当的 **HTTPS 证书**对其进行加密并使用 **HTTPS** 将其发送回客户端。 此服务器通常被称为 **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">TLS 终止代理(TLS Termination Proxy)</a>**。
你可以用作 TLS 终止代理的一些选项包括:
* Traefik(也可以处理证书更新)
* Caddy(也可以处理证书更新)
* Nginx
* HAProxy
## Let's Encrypt
在 Let's Encrypt 之前,这些 **HTTPS 证书** 由受信任的第三方出售。
过去,获得这些证书的过程非常繁琐,需要大量的文书工作,而且证书非常昂贵。
但随后 **<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>** 创建了。
它是 Linux 基金会的一个项目。 它以自动方式免费提供 **HTTPS 证书**。 这些证书可以使用所有符合标准的安全加密,并且有效期很短(大约 3 个月),因此**安全性实际上更好**,因为它们的生命周期缩短了。
域可以被安全地验证并自动生成证书。 这还允许自动更新这些证书。
我们的想法是自动获取和更新这些证书,以便你可以永远免费拥有**安全的 HTTPS**。
## 面向开发人员的 HTTPS
这里有一个 HTTPS API 看起来是什么样的示例,我们会分步说明,并且主要关注对开发人员重要的部分。
### 域名
第一步我们要先**获取**一些**域名(Domain Name)**。 然后可以在 DNS 服务器(可能是你的同一家云服务商提供的)中配置它。
你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个<abbr title="That isn't Change">固定</abbr> **公共IP地址**
在 DNS 服务器中,你可以配置一条记录(“A 记录”)以将 **你的域名** 指向你服务器的公共 **IP 地址**
这个操作一般只需要在最开始执行一次。
/// tip
域名这部分发生在 HTTPS 之前,由于这一切都依赖于域名和 IP 地址,所以先在这里提一下。
///
### DNS
现在让我们关注真正的 HTTPS 部分。
首先,浏览器将通过 **DNS 服务器** 查询**域名的IP** 是什么,在本例中为 `someapp.example.com`
DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是你在 DNS 服务器中为你的服务器配置的公共 IP 地址。
<img src="/img/deployment/https/https01.drawio.svg">
### TLS 握手开始
然后,浏览器将在**端口 443**(HTTPS 端口)上与该 IP 地址进行通信。
通信的第一部分只是建立客户端和服务器之间的连接并决定它们将使用的加密密钥等。
<img src="/img/deployment/https/https02.drawio.svg">
客户端和服务器之间建立 TLS 连接的过程称为 **TLS 握手**
### 带有 SNI 扩展的 TLS
**服务器中只有一个进程**可以侦听特定 **IP 地址**的特定 **端口**。 可能有其他进程在同一 IP 地址的其他端口上侦听,但每个 IP 地址和端口组合只有一个进程。
TLS (HTTPS) 默认使用端口`443`。 这就是我们需要的端口。
由于只有一个进程可以监听此端口,因此监听端口的进程将是 **TLS 终止代理**
TLS 终止代理可以访问一个或多个 **TLS 证书**HTTPS 证书)。
使用上面讨论的 **SNI 扩展**,TLS 终止代理将检查应该用于此连接的可用 TLS (HTTPS) 证书,并使用与客户端期望的域名相匹配的证书。
在这种情况下,它将使用`someapp.example.com`的证书。
<img src="/img/deployment/https/https03.drawio.svg">
客户端已经**信任**生成该 TLS 证书的实体(在本例中为 Let's Encrypt,但我们稍后会看到),因此它可以**验证**该证书是否有效。
然后,通过使用证书,客户端和 TLS 终止代理 **决定如何加密** **TCP 通信** 的其余部分。 这就完成了 **TLS 握手** 部分。
此后,客户端和服务器就拥有了**加密的 TCP 连接**,这就是 TLS 提供的功能。 然后他们可以使用该连接来启动实际的 **HTTP 通信**
这就是 **HTTPS**,它只是 **安全 TLS 连接** 内的普通 **HTTP**,而不是纯粹的(未加密的)TCP 连接。
/// tip
请注意,通信加密发生在 **TCP 层**,而不是 HTTP 层。
///
### HTTPS 请求
现在客户端和服务器(特别是浏览器和 TLS 终止代理)具有 **加密的 TCP 连接**,它们可以开始 **HTTP 通信**
接下来,客户端发送一个 **HTTPS 请求**。 这其实只是一个通过 TLS 加密连接的 HTTP 请求。
<img src="/img/deployment/https/https04.drawio.svg">
### 解密请求
TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(解密的)HTTP 请求**传输到运行应用程序的进程(例如运行 FastAPI 应用的 Uvicorn 进程)。
<img src="/img/deployment/https/https05.drawio.svg">
### HTTP 响应
应用程序将处理请求并向 TLS 终止代理发送**(未加密)HTTP 响应**。
<img src="/img/deployment/https/https06.drawio.svg">
### HTTPS 响应
然后,TLS 终止代理将使用之前协商的加密算法(以`someapp.example.com`的证书开头)对响应进行加密,并将其发送回浏览器。
接下来,浏览器将验证响应是否有效和是否使用了正确的加密密钥等。然后它会**解密响应**并处理它。
<img src="/img/deployment/https/https07.drawio.svg">
客户端(浏览器)将知道响应来自正确的服务器,因为它使用了他们之前使用 **HTTPS 证书** 协商出的加密算法。
### 多个应用程序
在同一台(或多台)服务器中,可能存在**多个应用程序**,例如其他 API 程序或数据库。
只有一个进程可以处理特定的 IP 和端口(在我们的示例中为 TLS 终止代理),但其他应用程序/进程也可以在服务器上运行,只要它们不尝试使用相同的 **公共 IP 和端口的组合**
<img src="/img/deployment/https/https08.drawio.svg">
这样,TLS 终止代理就可以为多个应用程序处理**多个域名**的 HTTPS 和证书,然后在每种情况下将请求传输到正确的应用程序。
### 证书更新
在未来的某个时候,每个证书都会**过期**(大约在获得证书后 3 个月)。
然后,会有另一个程序(在某些情况下是另一个程序,在某些情况下可能是同一个 TLS 终止代理)与 Let's Encrypt 通信并更新证书。
<img src="/img/deployment/https/https.drawio.svg">
**TLS 证书** **与域名相关联**,而不是与 IP 地址相关联。
因此,要更新证书,更新程序需要向权威机构(Let's Encrypt**证明**它确实**“拥有”并控制该域名**。
有多种方法可以做到这一点。 一些流行的方式是:
* **修改一些DNS记录**。
* 为此,续订程序需要支持 DNS 提供商的 API,因此,要看你使用的 DNS 提供商是否提供这一功能。
* **在与域名关联的公共 IP 地址上作为服务器运行**(至少在证书获取过程中)。
* 正如我们上面所说,只有一个进程可以监听特定的 IP 和端口。
* 这就是当同一个 TLS 终止代理还负责证书续订过程时它非常有用的原因之一。
* 否则,你可能需要暂时停止 TLS 终止代理,启动续订程序以获取证书,然后使用 TLS 终止代理配置它们,然后重新启动 TLS 终止代理。 这并不理想,因为你的应用程序在 TLS 终止代理关闭期间将不可用。
通过拥有一个**单独的系统来使用 TLS 终止代理来处理 HTTPS**, 而不是直接将 TLS 证书与应用程序服务器一起使用 (例如 Uvicorn),你可以在
更新证书的过程中同时保持提供服务。
## 回顾
拥有**HTTPS** 非常重要,并且在大多数情况下相当**关键**。 作为开发人员,你围绕 HTTPS 所做的大部分努力就是**理解这些概念**以及它们的工作原理。
一旦你了解了**面向开发人员的 HTTPS** 的基础知识,你就可以轻松组合和配置不同的工具,以帮助你以简单的方式管理一切。
在接下来的一些章节中,我将向你展示几个为 **FastAPI** 应用程序设置 **HTTPS** 的具体示例。 🔒
+21
View File
@@ -0,0 +1,21 @@
# 部署
部署 **FastAPI** 应用程序相对容易。
## 部署是什么意思
**部署**应用程序意味着执行必要的步骤以使其**可供用户使用**
对于**Web API**来说,通常涉及将上传到**云服务器**中,搭配一个性能和稳定性都不错的**服务器程序**,以便你的**用户**可以高效地**访问**你的应用程序,而不会出现中断或其他问题。
这与**开发**阶段形成鲜明对比,在**开发**阶段,你不断更改代码、破坏代码、修复代码, 来回停止和重启服务器等。
## 部署策略
根据你的使用场景和使用的工具,有多种方法可以实现此目的。
你可以使用一些工具自行**部署服务器**,你也可以使用能为你完成部分工作的**云服务**,或其他可能的选项。
我将向你展示在部署 **FastAPI** 应用程序时你可能应该记住的一些主要概念(尽管其中大部分适用于任何其他类型的 Web 应用程序)。
在接下来的部分中,你将看到更多需要记住的细节以及一些技巧。 ✨
+158
View File
@@ -0,0 +1,158 @@
# 手动运行服务器
## 使用 `fastapi run` 命令
简而言之,使用 `fastapi run` 来运行您的 FastAPI 应用程序:
<div class="termy">
```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 production server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
```
</div>
这在大多数情况下都能正常运行。😎
例如,您可以使用该命令在容器、服务器等环境中启动您的 **FastAPI** 应用。
## ASGI 服务器
让我们深入了解一些细节。
FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称为 <abbr title="Asynchronous Server Gateway Interface,异步服务器网关接口">ASGI</abbr>。FastAPI 本质上是一个 ASGI Web 框架。
要在远程服务器上运行 **FastAPI** 应用(或任何其他 ASGI 应用),您需要一个 ASGI 服务器程序,例如 **Uvicorn**。它是 `fastapi` 命令默认使用的 ASGI 服务器。
除此之外,还有其他一些可选的 ASGI 服务器,例如:
* <a href="https://www.uvicorn.dev/" class="external-link" target="_blank">Uvicorn</a>:高性能 ASGI 服务器。
* <a href="https://hypercorn.readthedocs.io/" class="external-link" target="_blank">Hypercorn</a>:与 HTTP/2 和 Trio 等兼容的 ASGI 服务器。
* <a href="https://github.com/django/daphne" class="external-link" target="_blank">Daphne</a>:为 Django Channels 构建的 ASGI 服务器。
* <a href="https://github.com/emmett-framework/granian" class="external-link" target="_blank">Granian</a>:基于 Rust 的 HTTP 服务器,专为 Python 应用设计。
* <a href="https://unit.nginx.org/howto/fastapi/" class="external-link" target="_blank">NGINX Unit</a>NGINX Unit 是一个轻量级且灵活的 Web 应用运行时环境。
## 服务器主机和服务器程序
关于名称,有一个小细节需要记住。 💡
“**服务器**”一词通常用于指远程/云计算机(物理机或虚拟机)以及在该计算机上运行的程序(例如 Uvicorn)。
请记住,当您一般读到“服务器”这个名词时,它可能指的是这两者之一。
当提到远程主机时,通常将其称为**服务器**,但也称为**机器**(machine)、**VM**(虚拟机)、**节点**。 这些都是指某种类型的远程计算机,通常运行 Linux,您可以在其中运行程序。
## 安装服务器程序
当您安装 FastAPI 时,它自带一个生产环境服务器——Uvicorn,并且您可以使用 `fastapi run` 命令来启动它。
不过,您也可以手动安装 ASGI 服务器。
请确保您创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装服务器应用程序。
例如,要安装 Uvicorn,可以运行以下命令:
<div class="termy">
```console
$ pip install "uvicorn[standard]"
---> 100%
```
</div>
类似的流程也适用于任何其他 ASGI 服务器程序。
/// tip
通过添加 `standard` 选项,Uvicorn 将安装并使用一些推荐的额外依赖项。
其中包括 `uvloop`,这是 `asyncio` 的高性能替代方案,能够显著提升并发性能。
当您使用 `pip install "fastapi[standard]"` 安装 FastAPI 时,实际上也会安装 `uvicorn[standard]`
///
## 运行服务器程序
如果您手动安装了 ASGI 服务器,通常需要以特定格式传递一个导入字符串,以便服务器能够正确导入您的 FastAPI 应用:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 80
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
```
</div>
/// note
命令 `uvicorn main:app` 的含义如下:
* `main`:指的是 `main.py` 文件(即 Python “模块”)。
* `app`:指的是 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。
它等价于以下导入语句:
```Python
from main import app
```
///
每种 ASGI 服务器程序通常都会有类似的命令,您可以在它们的官方文档中找到更多信息。
/// warning
Uvicorn 和其他服务器支持 `--reload` 选项,该选项在开发过程中非常有用。
`--reload` 选项会消耗更多资源,且相对不稳定。
它对于**开发阶段**非常有帮助,但在**生产环境**中**不应该**使用。
///
## 部署概念
这些示例运行服务器程序(例如 Uvicorn),启动**单个进程**,在所有 IP(`0.0.0.0`)上监听预定义端口(例如`80`)。
这是基本思路。 但您可能需要处理一些其他事情,例如:
* 安全性 - HTTPS
* 启动时运行
* 重新启动
* 复制(运行的进程数)
* 内存
* 开始前的步骤
在接下来的章节中,我将向您详细介绍每个概念、如何思考它们,以及一些具体示例以及处理它们的策略。 🚀
+139
View File
@@ -0,0 +1,139 @@
# 服务器工作进程(Workers) - 使用 Uvicorn 的多工作进程模式
让我们回顾一下之前的部署概念:
* 安全性 - HTTPS
* 启动时运行
* 重新启动
* **复制(运行的进程数)**
* 内存
* 启动前的先前步骤
到目前为止,在文档中的所有教程中,您可能一直是在运行一个**服务器程序**,例如使用 `fastapi` 命令来启动 Uvicorn,而它默认运行的是**单进程模式**。
部署应用程序时,您可能希望进行一些**进程复制**,以利用**多核** CPU 并能够处理更多请求。
正如您在上一章有关[部署概念](concepts.md){.internal-link target=_blank}中看到的,您可以使用多种策略。
在本章节中,我将向您展示如何使用 `fastapi` 命令或直接使用 `uvicorn` 命令以**多工作进程模式**运行 **Uvicorn**
/// info
如果您正在使用容器,例如 Docker 或 Kubernetes,我将在下一章中告诉您更多相关信息:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。
比较特别的是,在 **Kubernetes** 环境中运行时,您通常**不需要**使用多个工作进程,而是**每个容器运行一个 Uvicorn 进程**。不过,我会在本章节的后续部分详细介绍这一点。
///
## 多个工作进程
您可以使用 `--workers` 命令行选项来启动多个工作进程:
//// tab | `fastapi`
如果您使用 `fastapi` 命令:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
Searching for package file structure from directories with
<font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
////
//// tab | `uvicorn`
如果您更想要直接使用 `uvicorn` 命令:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit)
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>]
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27369</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27370</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27367</font>]
<font color="#A6E22E">INFO</font>: Waiting for application startup.
<font color="#A6E22E">INFO</font>: Application startup complete.
```
</div>
////
这里唯一的新选项是 `--workers` 告诉 Uvicorn 启动 4 个工作进程。
您还可以看到它显示了每个进程的 **PID**,父进程(这是**进程管理器**)的 PID 为`27365`,每个工作进程的 PID 为:`27368``27369` `27370``27367`
## 部署概念
在这里,您学习了如何使用多个**工作进程(workers)**来让应用程序的执行**并行化**,充分利用 CPU 的**多核性能**,并能够处理**更多的请求**。
从上面的部署概念列表来看,使用worker主要有助于**复制**部分,并对**重新启动**有一点帮助,但您仍然需要照顾其他部分:
* **安全 - HTTPS**
* **启动时运行**
* ***重新启动***
* 复制(运行的进程数)
* **内存**
* **启动之前的先前步骤**
## 容器和 Docker
在关于 [容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank} 的下一章中,我将介绍一些可用于处理其他**部署概念**的策略。
我将向您展示如何**从零开始构建自己的镜像**,以运行一个单独的 Uvicorn 进程。这个过程相对简单,并且在使用 **Kubernetes** 等分布式容器管理系统时,这通常是您需要采取的方法。
## 回顾
您可以在使用 `fastapi``uvicorn` 命令时,通过 `--workers` CLI 选项启用多个工作进程(workers),以充分利用**多核 CPU**,以**并行运行多个进程**。
如果您要设置**自己的部署系统**,同时自己处理其他部署概念,则可以使用这些工具和想法。
请查看下一章,了解带有容器(例如 Docker 和 Kubernetes)的 **FastAPI**。 您将看到这些工具也有简单的方法来解决其他**部署概念**。 ✨
+93
View File
@@ -0,0 +1,93 @@
# 关于 FastAPI 版本
**FastAPI** 已在许多应用程序和系统的生产环境中使用。 并且测试覆盖率保持在100%。 但其开发进度仍在快速推进。
经常添加新功能,定期修复错误,并且代码仍在持续改进。
这就是为什么当前版本仍然是`0.x.x`,这反映出每个版本都可能有Breaking changes。 这遵循<a href="https://semver.org/" class="external-link" target="_blank">语义版本控制</a>的约定。
你现在就可以使用 **FastAPI** 创建生产环境应用程序(你可能已经这样做了一段时间),你只需确保使用的版本可以与其余代码正确配合即可。
## 固定你的 `fastapi` 版本
你应该做的第一件事是将你正在使用的 **FastAPI** 版本“固定”到你知道适用于你的应用程序的特定最新版本。
例如,假设你在应用程序中使用版本`0.45.0`
如果你使用`requirements.txt`文件,你可以使用以下命令指定版本:
````txt
fastapi==0.45.0
````
这意味着你将使用版本`0.45.0`。
或者你也可以将其固定为:
````txt
fastapi>=0.45.0,<0.46.0
````
这意味着你将使用`0.45.0`或更高版本,但低于`0.46.0`,例如,版本`0.45.2`仍会被接受。
如果你使用任何其他工具来管理你的安装,例如 Poetry、Pipenv 或其他工具,它们都有一种定义包的特定版本的方法。
## 可用版本
你可以在[发行说明](../release-notes.md){.internal-link target=_blank}中查看可用版本(例如查看当前最新版本)。
## 关于版本
遵循语义版本控制约定,任何低于`1.0.0`的版本都可能会添加 breaking changes。
FastAPI 还遵循这样的约定:任何`PATCH`版本更改都是为了bug修复和non-breaking changes。
/// tip
"PATCH"是最后一个数字,例如,在`0.2.3`中,PATCH版本是`3`。
///
因此,你应该能够固定到如下版本:
```txt
fastapi>=0.45.0,<0.46.0
```
"MINOR"版本中会添加breaking changes和新功能。
/// tip
"MINOR"是中间的数字,例如,在`0.2.3`中,MINOR版本是`2`。
///
## 升级FastAPI版本
你应该为你的应用程序添加测试。
使用 **FastAPI** 编写测试非常简单(感谢 Starlette),请参考文档:[测试](../tutorial/testing.md){.internal-link target=_blank}
添加测试后,你可以将 **FastAPI** 版本升级到更新版本,并通过运行测试来确保所有代码都能正常工作。
如果一切正常,或者在进行必要的更改之后,并且所有测试都通过了,那么你可以将`fastapi`固定到新的版本。
## 关于Starlette
你不应该固定`starlette`的版本。
不同版本的 **FastAPI** 将使用特定的较新版本的 Starlette。
因此,**FastAPI** 自己可以使用正确的 Starlette 版本。
## 关于 Pydantic
Pydantic 包含针对 **FastAPI** 的测试及其自己的测试,因此 Pydantic 的新版本(`1.0.0`以上)始终与 FastAPI 兼容。
你可以将 Pydantic 固定到适合你的`1.0.0`以上和`2.0.0`以下的任何版本。
例如:
````txt
pydantic>=1.2.0,<2.0.0
````
+298
View File
@@ -0,0 +1,298 @@
# 环境变量
/// tip
如果你已经知道什么是“环境变量”并且知道如何使用它们,你可以放心跳过这一部分。
///
环境变量(也称为“**env var**”)是一个独立于 Python 代码**之外**的变量,它存在于**操作系统**中,可以被你的 Python 代码(或其他程序)读取。
环境变量对于处理应用程序**设置**、作为 Python **安装**的一部分等方面非常有用。
## 创建和使用环境变量
你在 **shell(终端)**中就可以**创建**和使用环境变量,并不需要用到 Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// 你可以使用以下命令创建一个名为 MY_NAME 的环境变量
$ export MY_NAME="Wade Wilson"
// 然后,你可以在其他程序中使用它,例如
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// 创建一个名为 MY_NAME 的环境变量
$ $Env:MY_NAME = "Wade Wilson"
// 在其他程序中使用它,例如
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## 在 Python 中读取环境变量
你也可以在 Python **之外**的终端中创建环境变量(或使用任何其他方法),然后在 Python 中**读取**它们。
例如,你可以创建一个名为 `main.py` 的文件,其中包含以下内容:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip
第二个参数是 <a href="https://docs.python.org/zh-cn/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> 的默认返回值。
如果没有提供,默认值为 `None`,这里我们提供 `"World"` 作为默认值。
///
然后你可以调用这个 Python 程序:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// 这里我们还没有设置环境变量
$ python main.py
// 因为我们没有设置环境变量,所以我们得到的是默认值
Hello World from Python
// 但是如果我们事先创建过一个环境变量
$ export MY_NAME="Wade Wilson"
// 然后再次调用程序
$ python main.py
// 现在就可以读取到环境变量了
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// 这里我们还没有设置环境变量
$ python main.py
// 因为我们没有设置环境变量,所以我们得到的是默认值
Hello World from Python
// 但是如果我们事先创建过一个环境变量
$ $Env:MY_NAME = "Wade Wilson"
// 然后再次调用程序
$ python main.py
// 现在就可以读取到环境变量了
Hello Wade Wilson from Python
```
</div>
////
由于环境变量可以在代码之外设置、但可以被代码读取,并且不必与其他文件一起存储(提交到 `git`),因此通常用于配置或**设置**。
你还可以为**特定的程序调用**创建特定的环境变量,该环境变量仅对该程序可用,且仅在其运行期间有效。
要实现这一点,只需在同一行内、程序本身之前创建它:
<div class="termy">
```console
// 在这个程序调用的同一行中创建一个名为 MY_NAME 的环境变量
$ MY_NAME="Wade Wilson" python main.py
// 现在就可以读取到环境变量了
Hello Wade Wilson from Python
// 在此之后这个环境变量将不会依然存在
$ python main.py
Hello World from Python
```
</div>
/// tip
你可以在 <a href="https://12factor.net/zh_cn/config" class="external-link" target="_blank">The Twelve-Factor App: 配置</a>中了解更多信息。
///
## 类型和验证
这些环境变量只能处理**文本字符串**,因为它们是处于 Python 范畴之外的,必须与其他程序和操作系统的其余部分兼容(甚至与不同的操作系统兼容,如 Linux、Windows、macOS)。
这意味着从环境变量中读取的**任何值**在 Python 中都将是一个 `str`,任何类型转换或验证都必须在代码中完成。
你将在[高级用户指南 - 设置和环境变量](./advanced/settings.md)中了解更多关于使用环境变量处理**应用程序设置**的信息。
## `PATH` 环境变量
有一个**特殊的**环境变量称为 **`PATH`**,操作系统(Linux、macOS、Windows)用它来查找要运行的程序。
`PATH` 变量的值是一个长字符串,由 Linux 和 macOS 上的冒号 `:` 分隔的目录组成,而在 Windows 上则是由分号 `;` 分隔的。
例如,`PATH` 环境变量可能如下所示:
//// tab | Linux, macOS
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
这意味着系统应该在以下目录中查找程序:
- `/usr/local/bin`
- `/usr/bin`
- `/bin`
- `/usr/sbin`
- `/sbin`
////
//// tab | Windows
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
```
这意味着系统应该在以下目录中查找程序:
- `C:\Program Files\Python312\Scripts`
- `C:\Program Files\Python312`
- `C:\Windows\System32`
////
当你在终端中输入一个**命令**时,操作系统会在 `PATH` 环境变量中列出的**每个目录**中**查找**程序。
例如,当你在终端中输入 `python` 时,操作系统会在该列表中的**第一个目录**中查找名为 `python` 的程序。
如果找到了,那么操作系统将**使用它**;否则,操作系统会继续在**其他目录**中查找。
### 安装 Python 和更新 `PATH`
安装 Python 时,可能会询问你是否要更新 `PATH` 环境变量。
//// tab | Linux, macOS
假设你安装 Python 并最终将其安装在了目录 `/opt/custompython/bin` 中。
如果你同意更新 `PATH` 环境变量,那么安装程序将会将 `/opt/custompython/bin` 添加到 `PATH` 环境变量中。
它看起来大概会像这样:
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
```
如此一来,当你在终端中输入 `python` 时,系统会在 `/opt/custompython/bin` 中找到 Python 程序(最后一个目录)并使用它。
////
//// tab | Windows
假设你安装 Python 并最终将其安装在了目录 `C:\opt\custompython\bin` 中。
如果你同意更新 `PATH` 环境变量 (在 Python 安装程序中,这个操作是名为 `Add Python x.xx to PATH` 的复选框 —— 译者注),那么安装程序将会将 `C:\opt\custompython\bin` 添加到 `PATH` 环境变量中。
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
```
如此一来,当你在终端中输入 `python` 时,系统会在 `C:\opt\custompython\bin` 中找到 Python 程序(最后一个目录)并使用它。
////
因此,如果你输入:
<div class="termy">
```console
$ python
```
</div>
//// tab | Linux, macOS
系统会在 `/opt/custompython/bin` 中**找到** `python` 程序并运行它。
这和输入以下命令大致等价:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
////
//// tab | Windows
系统会在 `C:\opt\custompython\bin\python` 中**找到** `python` 程序并运行它。
这和输入以下命令大致等价:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
////
当学习[虚拟环境](virtual-environments.md)时,这些信息将会很有用。
## 结论
通过这个教程,你应该对**环境变量**是什么以及如何在 Python 中使用它们有了基本的了解。
你也可以在<a href="https://zh.wikipedia.org/wiki/%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F" class="external-link" target="_blank">环境变量 - 维基百科</a> (<a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia for Environment Variable</a>) 中了解更多关于它们的信息。
在许多情况下,环境变量的用途和适用性并不是很明显。但是在开发过程中,它们会在许多不同的场景中出现,因此了解它们是很有必要的。
例如,你将在下一节关于[虚拟环境](virtual-environments.md)中需要这些信息。
+79
View File
@@ -0,0 +1,79 @@
# FastAPI CLI
**FastAPI CLI** 是一个命令行程序,你可以用它来部署和运行你的 FastAPI 应用程序,管理你的 FastAPI 项目,等等。
当你安装 FastAPI 时(例如使用 `pip install FastAPI` 命令),会包含一个名为 `fastapi-cli` 的软件包,该软件包在终端中提供 `fastapi` 命令。
要在开发环境中运行你的 FastAPI 应用,你可以使用 `fastapi dev` 命令:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories with
<font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
该命令行程序 `fastapi` 就是 **FastAPI CLI**
FastAPI CLI 接收你的 Python 程序路径,自动检测包含 FastAPI 的变量(通常命名为 `app`)及其导入方式,然后启动服务。
在生产环境中,你应该使用 `fastapi run` 命令。🚀
在内部,**FastAPI CLI** 使用了 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a>,这是一个高性能、适用于生产环境的 ASGI 服务器。😎
## `fastapi dev`
当你运行 `fastapi dev` 时,它将以开发模式运行。
默认情况下,它会启用**自动重载**,因此当你更改代码时,它会自动重新加载服务器。该功能是资源密集型的,且相较不启用时更不稳定,因此你应该仅在开发环境下使用它。
默认情况下,它将监听 IP 地址 `127.0.0.1`,这是你的机器与自身通信的 IP 地址(`localhost`)。
## `fastapi run`
当你运行 `fastapi run` 时,它默认以生产环境模式运行。
默认情况下,**自动重载是禁用的**。
它将监听 IP 地址 `0.0.0.0`,即所有可用的 IP 地址,这样任何能够与该机器通信的人都可以公开访问它。这通常是你在生产环境中运行它的方式,例如在容器中运行。
在大多数情况下,你会(且应该)有一个“终止代理”在上层为你处理 HTTPS,这取决于你如何部署应用程序,你的服务提供商可能会为你处理此事,或者你可能需要自己设置。
/// tip | 提示
你可以在 [deployment documentation](deployment/index.md){.internal-link target=_blank} 获得更多信息。
///
+206
View File
@@ -0,0 +1,206 @@
# 特性
## FastAPI 特性
**FastAPI** 提供了以下内容:
### 基于开放标准
* 用于创建 API 的 <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> 包含了<abbr title="也被叫做: endpoints, routes">路径</abbr><abbr title="也叫做HTTP方法, 例如 POST, GET, PUT, DELETE">操作</abbr>,请求参数,请求体,安全性等的声明。
* 使用 <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (因为 OpenAPI 本身就是基于 JSON Schema 的)自动生成数据模型文档。
* 经过了缜密的研究后围绕这些标准而设计。并非狗尾续貂。
* 这也允许了在很多语言中自动**生成客户端代码**。
### 自动生成文档
交互式 API 文档以及具探索性 web 界面。因为该框架是基于 OpenAPI,所以有很多可选项,FastAPI 默认自带两个交互式 API 文档。
* <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank"><strong>Swagger UI</strong></a>,可交互式操作,能在浏览器中直接调用和测试你的 API 。
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* 另外的 API 文档:<a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank"><strong>ReDoc</strong></a>
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### 更主流的 Python
全部都基于标准的 **Python 3.6 类型**声明(感谢 Pydantic )。没有新的语法需要学习。只需要标准的 Python 。
如果你需要2分钟来学习如何使用 Python 类型(即使你不使用 FastAPI ),看看这个简短的教程:[Python Types](python-types.md){.internal-link target=_blank}。
编写带有类型标注的标准 Python:
```Python
from datetime import date
from pydantic import BaseModel
# Declare a variable as a str
# and get editor support inside the function
def main(user_id: str):
return user_id
# A Pydantic model
class User(BaseModel):
id: int
name: str
joined: date
```
可以像这样来使用:
```Python
my_user: User = User(id=3, name="John Doe", joined="2018-07-19")
second_user_data = {
"id": 4,
"name": "Mary",
"joined": "2018-11-30",
}
my_second_user: User = User(**second_user_data)
```
/// info
`**second_user_data` 意思是:
直接将`second_user_data`字典的键和值直接作为key-value参数传递,等同于:`User(id=4, name="Mary", joined="2018-11-30")`
///
### 编辑器支持
整个框架都被设计得易于使用且直观,所有的决定都在开发之前就在多个编辑器上进行了测试,来确保最佳的开发体验。
在最近的 Python 开发者调查中,我们能看到 <a href="https://www.jetbrains.com/research/python-developers-survey-2017/#tools-and-features" class="external-link" target="_blank"> 被使用最多的功能是"自动补全"</a>。
整个 **FastAPI** 框架就是基于这一点的。任何地方都可以进行自动补全。
你几乎不需要经常回来看文档。
在这里,你的编辑器可能会这样帮助你:
* <a href="https://code.visualstudio.com/" class="external-link" target="_blank">Visual Studio Code</a> 中:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
* <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 中:
![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png)
你将能进行代码补全,这是在之前你可能曾认为不可能的事。例如,在来自请求 JSON 体(可能是嵌套的)中的键 `price`
不会再输错键名,来回翻看文档,或者来回滚动寻找你最后使用的 `username` 或者 `user_name`
### 简洁
任何类型都有合理的**默认值**,任何和地方都有可选配置。所有的参数被微调,来满足你的需求,定义成你需要的 API。
但是默认情况下,一切都能**“顺利工作”**。
### 验证
* 校验大部分(甚至所有?)的 Python **数据类型**,包括:
* JSON 对象 (`dict`).
* JSON 数组 (`list`) 定义成员类型。
* 字符串 (`str`) 字段, 定义最小或最大长度。
* 数字 (`int`, `float`) 有最大值和最小值, 等等。
* 校验外来类型, 比如:
* URL.
* Email.
* UUID.
* ...及其他.
所有的校验都由完善且强大的 **Pydantic** 处理。
### 安全性及身份验证
集成了安全性和身份认证。杜绝数据库或者数据模型的渗透风险。
OpenAPI 中定义的安全模式,包括:
* HTTP 基本认证。
* **OAuth2** (也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。
* API 密钥,在:
* 请求头。
* 查询参数。
* Cookies, 等等。
加上来自 Starlette(包括 **session cookie**)的所有安全特性。
所有的这些都是可复用的工具和组件,可以轻松与你的系统,数据仓库,关系型以及 NoSQL 数据库等等集成。
### 依赖注入
FastAPI 有一个使用非常简单,但是非常强大的<abbr title='也叫做 "components", "resources", "services", "providers"'><strong>依赖注入</strong></abbr>系统。
* 甚至依赖也可以有依赖,创建一个层级或者**“图”依赖**。
* 所有**自动化处理**都由框架完成。
* 所有的依赖关系都可以从请求中获取数据,并且**增加了路径操作**约束和自动文档生成。
* 即使在依赖项中被定义的*路径操作* 也会**自动验证**。
* 支持复杂的用户身份认证系统,**数据库连接**等等。
* **不依赖**数据库,前端等。 但是和它们集成很简单。
### 无限制"插件"
或者说,导入并使用你需要的代码,而不需要它们。
任何集成都被设计得被易于使用(用依赖关系),你可以用和*路径操作*相同的结构和语法,在两行代码中为你的应用创建一个“插件”。
### 测试
* 100% <abbr title="自动测试的代码量">测试覆盖</abbr>。
* 代码库100% <abbr title="Python类型注解,有了这个你的编辑器和外部工具可以给你更好的支持">类型注释</abbr>。
* 用于生产应用。
## Starlette 特性
**FastAPI** 和 <a href="https://www.starlette.dev/" class="external-link" target="_blank"><strong>Starlette</strong></a> 完全兼容(并基于)。所以,你有的其他的 Starlette 代码也能正常工作。`FastAPI` 实际上是 `Starlette`的一个子类。所以,如果你已经知道或者使用 Starlette,大部分的功能会以相同的方式工作。
通过 **FastAPI** 你可以获得所有 **Starlette** 的特性 ( FastAPI 就像加强版的 Starlette )
* 令人惊叹的性能。它是 <a href="https://github.com/encode/starlette#performance" class="external-link" target="_blank">Python 可用的最快的框架之一,和 **NodeJS** 及 **Go** 相当</a>。
* **支持 WebSocket** 。
* **支持 GraphQL** 。
* 后台任务处理。
* Startup 和 shutdown 事件。
* 测试客户端基于 HTTPX。
* **CORS**, GZip, 静态文件, 流响应。
* 支持 **Session 和 Cookie**
* 100% 测试覆盖率。
* 代码库 100% 类型注释。
## Pydantic 特性
**FastAPI** 和 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank"><strong>Pydantic</strong></a> 完全兼容(并基于)。所以,你有的其他的 Pydantic 代码也能正常工作。
兼容包括基于 Pydantic 的外部库, 例如用与数据库的 <abbr title="对象关系映射">ORM</abbr>s, <abbr title="对象文档映射">ODM</abbr>s。
这也意味着在很多情况下,你可以将从请求中获得的相同对象**直接传到数据库**,因为所有的验证都是自动的。
反之亦然,在很多情况下,你也可以将从数据库中获取的对象**直接传到客户端**。
通过 **FastAPI** 你可以获得所有 **Pydantic** (FastAPI 基于 Pydantic 做了所有的数据处理):
* **更简单**
* 没有新的模式定义 micro-language 需要学习。
* 如果你知道 Python types,你就知道如何使用 Pydantic。
* 和你 **<abbr title="集成开发环境,和代码编辑器类似">IDE</abbr>/<abbr title="一个检查代码错误的程序">linter</abbr>/brain** 适配:
* 因为 pydantic 数据结构仅仅是你定义的类的实例;自动补全,linting,mypy 以及你的直觉应该可以和你验证的数据一起正常工作。
* 验证**复杂结构**:
* 使用分层的 Pydantic 模型, Python `typing``List``Dict` 等等。
* 验证器使我们能够简单清楚的将复杂的数据模式定义、检查并记录为 JSON Schema。
* 你可以拥有深度**嵌套的 JSON** 对象并对它们进行验证和注释。
* **可扩展**:
* Pydantic 允许定义自定义数据类型或者你可以用验证器装饰器对被装饰的模型上的方法扩展验证。
* 100% 测试覆盖率。
+148
View File
@@ -0,0 +1,148 @@
# 帮助 FastAPI 与求助
您喜欢 **FastAPI** 吗?
想帮助 FastAPI?其它用户?还有项目作者?
或要求助怎么使用 **FastAPI**
以下几种帮助的方式都非常简单(有些只需要点击一两下鼠标)。
求助的渠道也很多。
## 订阅新闻邮件
您可以订阅 [**FastAPI 和它的小伙伴** 新闻邮件](newsletter.md){.internal-link target=_blank}(不会经常收到)
* FastAPI 及其小伙伴的新闻 🚀
* 指南 📝
* 功能 ✨
* 破坏性更改 🚨
* 开发技巧 ✅
## 在推特上关注 FastAPI
<a href="https://x.com/fastapi" class="external-link" target="_blank">在 **X (Twitter)** 上关注 @fastapi</a> 获取 **FastAPI** 的最新消息。🐦
## 在 GitHub 上为 **FastAPI** 加星
您可以在 GitHub 上 **Star** FastAPI(只要点击右上角的星星就可以了): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi。</a>⭐️
**Star** 以后,其它用户就能更容易找到 FastAPI,并了解到已经有其他用户在使用它了。
## 关注 GitHub 资源库的版本发布
您还可以在 GitHub 上 **Watch** FastAPI,(点击右上角的 **Watch** 按钮)<a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi。</a>👀
您可以选择只关注发布(**Releases only**)。
这样,您就可以(在电子邮件里)接收到 **FastAPI** 新版发布的通知,及时了解 bug 修复与新功能。
## 联系作者
您可以联系项目作者,就是<a href="https://tiangolo.com" class="external-link" target="_blank">我(Sebastián Ramírez / `tiangolo`</a>)。
您可以:
* <a href="https://github.com/tiangolo" class="external-link" target="_blank">在 **GitHub** 上关注我</a>
* 了解其它我创建的开源项目,或许对您会有帮助
* 关注我什么时候创建新的开源项目
* <a href="https://x.com/tiangolo" class="external-link" target="_blank">在 **X (Twitter)** 上关注我</a>
* 告诉我您使用 FastAPI(我非常乐意听到这种消息)
* 接收我发布公告或新工具的消息
* 您还可以关注<a href="https://x.com/fastapi" class="external-link" target="_blank">@fastapi on X (Twitter)</a>,这是个独立的账号
* <a href="https://www.linkedin.com/in/tiangolo/" class="external-link" target="_blank">在**领英**上联系我</a>
* 接收我发布公告或新工具的消息(虽然我用 X (Twitter) 比较多)
* 阅读我在 <a href="https://dev.to/tiangolo" class="external-link" target="_blank">**Dev.to**</a> 或 <a href="https://medium.com/@tiangolo" class="external-link" target="_blank">**Medium**</a> 上的文章,或关注我
* 阅读我的其它想法、文章,了解我创建的工具
* 关注我,这样就可以随时看到我发布的新文章
## Tweet about **FastAPI**
<a href="https://x.com/compose/tweet?text=I'm loving @fastapi because... https://github.com/fastapi/fastapi" class="external-link" target="_blank">Tweet about **FastAPI**</a> 让我和大家知道您为什么喜欢 FastAPI。🎉
知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。
## 为 FastAPI 投票
* <a href="https://www.slant.co/options/34241/~fastapi-review" class="external-link" target="_blank">在 Slant 上为 **FastAPI** 投票</a>
* <a href="https://alternativeto.net/software/fastapi/" class="external-link" target="_blank">在 AlternativeTo 上为 **FastAPI** 投票</a>
## 在 GitHub 上帮助其他人解决问题
您可以查看<a href="https://github.com/fastapi/fastapi/issues" class="external-link" target="_blank">现有 issues</a>,并尝试帮助其他人解决问题,说不定您能解决这些问题呢。🤓
如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#_3){.internal-link target=_blank}。🎉
## 监听 GitHub 资源库
您可以在 GitHub 上「监听」FastAPI(点击右上角的 "watch" 按钮): <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">https://github.com/fastapi/fastapi</a>. 👀
如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。
然后您就可以尝试并帮助他们解决问题。
## 创建 Issue
您可以在 GitHub 资源库中<a href="https://github.com/fastapi/fastapi/issues/new/choose" class="external-link" target="_blank">创建 Issue</a>,例如:
* 提出**问题**或**意见**
* 提出新**特性**建议
**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。😉
## 创建 PR
您可以创建 PR 为源代码做[贡献](contributing.md){.internal-link target=_blank},例如:
* 修改文档错别字
* <a href="https://github.com/fastapi/fastapi/edit/master/docs/en/data/external_links.yml" class="external-link" target="_blank">编辑这个文件</a>,分享 FastAPI 的文章、视频、博客,不论是您自己的,还是您看到的都成
* 注意,添加的链接要放在对应区块的开头
* [翻译文档](contributing.md#_8){.internal-link target=_blank}
* 审阅别人翻译的文档
* 添加新的文档内容
* 修复现有问题/Bug
* 添加新功能
## 加入聊天
快加入 👥 <a href="https://discord.gg/VQjSZaeJmf" class="external-link" target="_blank">Discord 聊天服务器</a> 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。
/// tip | 提示
如有问题,请在 <a href="https://github.com/fastapi/fastapi/issues/new/choose" class="external-link" target="_blank">GitHub Issues</a> 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank}的帮助。
聊天室仅供闲聊。
///
### 别在聊天室里提问
注意,聊天室更倾向于“闲聊”,经常有人会提出一些笼统得让人难以回答的问题,所以在这里提问一般没人回答。
GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。但在聊天室里,我就没功夫这么做了。😅
聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。
另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。😄
## 赞助作者
您还可以通过 <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub 赞助商</a>资助本项目的作者(就是我)。
给我买杯咖啡 ☕️ 以示感谢 😄
当然您也可以成为 FastAPI 的金牌或银牌赞助商。🏅🎉
## 赞助 FastAPI 使用的工具
如您在本文档中所见,FastAPI 站在巨人的肩膀上,它们分别是 Starlette 和 Pydantic。
您还可以赞助:
* <a href="https://github.com/sponsors/samuelcolvin" class="external-link" target="_blank">Samuel Colvin Pydantic</a>
* <a href="https://github.com/sponsors/encode" class="external-link" target="_blank">Encode Starlette, Uvicorn</a>
---
谢谢!🚀
+78
View File
@@ -0,0 +1,78 @@
# 历史、设计、未来
不久前,<a href="https://github.com/fastapi/fastapi/issues/3#issuecomment-454956920" class="external-link" target="_blank">曾有 **FastAPI** 用户问过</a>
> 这个项目有怎样的历史?好像它只用了几周就从默默无闻变得众所周知……
在此,我们简单回顾一下 **FastAPI** 的历史。
## 备选方案
有那么几年,我曾领导数个开发团队为诸多复杂需求创建各种 API,这些需求包括机器学习、分布系统、异步任务、NoSQL 数据库等领域。
作为工作的一部分,我需要调研很多备选方案、还要测试并且使用这些备选方案。
**FastAPI** 其实只是延续了这些前辈的历史。
正如[备选方案](alternatives.md){.internal-link target=_blank}一章所述:
<blockquote markdown="1">
没有大家之前所做的工作,**FastAPI** 就不会存在。
以前创建的这些工具为它的出现提供了灵感。
在那几年中,我一直回避创建新的框架。首先,我尝试使用各种框架、插件、工具解决 **FastAPI** 现在的功能。
但到了一定程度之后,我别无选择,只能从之前的工具中汲取最优思路,并以尽量好的方式把这些思路整合在一起,使用之前甚至是不支持的语言特性(Python 3.6+ 的类型提示),从而创建一个能满足我所有需求的框架。
</blockquote>
## 调研
通过使用之前所有的备选方案,我有机会从它们之中学到了很多东西,获取了很多想法,并以我和我的开发团队能想到的最好方式把这些思路整合成一体。
例如,大家都清楚,在理想状态下,它应该基于标准的 Python 类型提示。
而且,最好的方式是使用现有的标准。
因此,甚至在开发 **FastAPI** 前,我就花了几个月的时间研究 OpenAPI、JSON Schema、OAuth2 等规范。深入理解它们之间的关系、重叠及区别之处。
## 设计
然后,我又花了一些时间从用户角度(使用 FastAPI 的开发者)设计了开发者 **API**
同时,我还在最流行的 Python 代码编辑器中测试了很多思路,包括 PyCharm、VS Code、基于 Jedi 的编辑器。
根据最新 <a href="https://www.jetbrains.com/research/python-developers-survey-2018/#development-tools" class="external-link" target="_blank">Python 开发者调研报告</a>显示,这几种编辑器覆盖了约 80% 的用户。
也就是说,**FastAPI** 针对差不多 80% 的 Python 开发者使用的编辑器进行了测试,而且其它大多数编辑器的工作方式也与之类似,因此,**FastAPI** 的优势几乎能在所有编辑器上体现。
通过这种方式,我就能找到尽可能减少代码重复的最佳方式,进而实现处处都有自动补全、类型提示与错误检查等支持。
所有这些都是为了给开发者提供最佳的开发体验。
## 需求项
经过测试多种备选方案,我最终决定使用 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">**Pydantic**</a>,并充分利用它的优势。
我甚至为它做了不少贡献,让它完美兼容了 JSON Schema,支持多种方式定义约束声明,并基于多个编辑器,改进了它对编辑器支持(类型检查、自动补全)。
在开发期间,我还为 <a href="https://www.starlette.dev/" class="external-link" target="_blank">**Starlette**</a> 做了不少贡献,这是另一个关键需求项。
## 开发
当我启动 **FastAPI** 开发的时候,绝大多数部件都已经就位,设计已经定义,需求项和工具也已经准备就绪,相关标准与规范的知识储备也非常清晰而新鲜。
## 未来
至此,**FastAPI** 及其理念已经为很多人所用。
对于很多用例,它比以前很多备选方案都更适用。
很多开发者和开发团队已经依赖 **FastAPI** 开发他们的项目(包括我和我的团队)。
但,**FastAPI** 仍有很多改进的余地,也还需要添加更多的功能。
总之,**FastAPI** 前景光明。
在此,我们衷心感谢[您的帮助](help-fastapi.md){.internal-link target=_blank}。
@@ -0,0 +1,70 @@
# 配置 Swagger UI
你可以配置一些额外的 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">Swagger UI 参数</a>.
如果需要配置它们,可以在创建 `FastAPI()` 应用对象时或调用 `get_swagger_ui_html()` 函数时传递 `swagger_ui_parameters` 参数。
`swagger_ui_parameters` 接受一个直接传递给 Swagger UI的字典,包含配置参数键值对。
FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因为这是 Swagger UI 需要的。
## 不使用语法高亮
比如,你可以禁用 Swagger UI 中的语法高亮。
当没有改变设置时,语法高亮默认启用:
<img src="/img/tutorial/extending-openapi/image02.png">
但是你可以通过设置 `syntaxHighlight``False` 来禁用 Swagger UI 中的语法高亮:
{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *}
...在此之后,Swagger UI 将不会高亮代码:
<img src="/img/tutorial/extending-openapi/image03.png">
## 改变主题
同样地,你也可以通过设置键 `"syntaxHighlight.theme"` 来设置语法高亮主题(注意中间有一个点):
{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *}
这个配置会改变语法高亮主题:
<img src="/img/tutorial/extending-openapi/image04.png">
## 改变默认 Swagger UI 参数
FastAPI 包含了一些默认配置参数,适用于大多数用例。
其包括这些默认配置参数:
{* ../../fastapi/openapi/docs.py ln[7:23] *}
你可以通过在 `swagger_ui_parameters` 中设置不同的值来覆盖它们。
比如,如果要禁用 `deepLinking`,你可以像这样传递设置到 `swagger_ui_parameters` 中:
{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *}
## 其他 Swagger UI 参数
查看其他 Swagger UI 参数,请阅读 <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/" class="external-link" target="_blank">docs for Swagger UI parameters</a>。
## JavaScript-only 配置
Swagger UI 同样允许使用 **JavaScript-only** 配置对象(例如,JavaScript 函数)。
FastAPI 包含这些 JavaScript-only 的 `presets` 设置:
```JavaScript
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
]
```
这些是 **JavaScript** 对象,而不是字符串,所以你不能直接从 Python 代码中传递它们。
如果你需要像这样使用 JavaScript-only 配置,你可以使用上述方法之一。覆盖所有 Swagger UI *path operation* 并手动编写任何你需要的 JavaScript。
+39
View File
@@ -0,0 +1,39 @@
# 通用 - 如何操作 - 诀窍
这里是一些指向文档中其他部分的链接,用于解答一般性或常见问题。
## 数据过滤 - 安全性
为确保不返回超过需要的数据,请阅读 [教程 - 响应模型 - 返回类型](../tutorial/response-model.md){.internal-link target=_blank} 文档。
## 文档的标签 - OpenAPI
在文档界面中添加**路径操作**的标签和进行分组,请阅读 [教程 - 路径操作配置 - Tags 参数](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} 文档。
## 文档的概要和描述 - OpenAPI
在文档界面中添加**路径操作**的概要和描述,请阅读 [教程 - 路径操作配置 - Summary 和 Description 参数](../tutorial/path-operation-configuration.md#summary-description){.internal-link target=_blank} 文档。
## 文档的响应描述 - OpenAPI
在文档界面中定义并显示响应描述,请阅读 [教程 - 路径操作配置 - 响应描述](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} 文档。
## 文档弃用**路径操作** - OpenAPI
在文档界面中显示弃用的**路径操作**,请阅读 [教程 - 路径操作配置 - 弃用](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} 文档。
## 将任何数据转换为 JSON 兼容格式
要将任何数据转换为 JSON 兼容格式,请阅读 [教程 - JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank} 文档。
## OpenAPI 元数据 - 文档
要添加 OpenAPI 的元数据,包括许可证、版本、联系方式等,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md){.internal-link target=_blank} 文档。
## OpenAPI 自定义 URL
要自定义 OpenAPI 的 URL(或删除它),请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} 文档。
## OpenAPI 文档 URL
要更改用于自动生成文档的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}.
+13
View File
@@ -0,0 +1,13 @@
# 如何操作 - 诀窍
在这里,你将看到关于**多个主题**的不同诀窍或“如何操作”指南。
这些方法多数是**相互独立**的,在大多数情况下,你只需在这些内容适用于**你的项目**时才需要学习它们。
如果某些内容看起来对你的项目有用,请继续查阅,否则请直接跳过它们。
/// tip | 小技巧
如果你想以系统的方式**学习 FastAPI**(推荐),请阅读 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 的每一章节。
///
+470
View File
@@ -0,0 +1,470 @@
# FastAPI
<style>
.md-content .md-typeset h1 { display: none; }
</style>
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>FastAPI 框架,高性能,易于学习,高效编码,生产可用</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
<img src="https://github.com/fastapi/fastapi/actions/workflows/test.yml/badge.svg?event=push&branch=master" alt="Test">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
</a>
</p>
---
**文档** <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
**源码** <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
---
FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。
关键特性:
* **快速**:可与 **NodeJS****Go** 并肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。
* **高效编码**:提高功能开发速度约 200% 至 300%。*
* **更少 bug**:减少约 40% 的人为(开发者)导致错误。*
* **智能**:极佳的编辑器支持。处处皆可<abbr title="也被称为自动完成、智能感知">自动补全</abbr>,减少调试时间。
* **简单**:设计的易于使用和学习,阅读文档的时间更短。
* **简短**:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。
* **健壮**:生产可用级别的代码。还有自动生成的交互式文档。
* **标准化**:基于(并完全兼容)API 的相关开放标准:<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (以前被称为 Swagger) 和 <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>。
<small>* 根据对某个构建线上应用的内部开发团队所进行的测试估算得出。</small>
## Sponsors
<!-- sponsors -->
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
<!-- /sponsors -->
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Other sponsors</a>
## 评价
「_[...] 最近我一直在使用 **FastAPI**。[...] 实际上我正在计划将其用于我所在的**微软**团队的所有**机器学习服务**。其中一些服务正被集成进核心 **Windows** 产品和一些 **Office** 产品。_」
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>微软</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
---
「_我们选择了 **FastAPI** 来创建用于获取**预测结果**的 **REST** 服务。[用于 Ludwig]_」
<div style="text-align: right; margin-right: 10%;">Piero MolinoYaroslav Dudin 和 Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
---
「_**Netflix** 非常高兴地宣布,正式开源我们的**危机管理**编排框架:**Dispatch**[使用 **FastAPI** 构建]_」
<div style="text-align: right; margin-right: 10%;">Kevin GlissonMarc VilanovaForest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
---
「_**FastAPI** 让我兴奋的欣喜若狂。它太棒了!_」
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> 播客主持人</strong> <a href="https://x.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
「_老实说,你的作品看起来非常可靠和优美。在很多方面,这就是我想让 **Hug** 成为的样子 - 看到有人实现了它真的很鼓舞人心。_」
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> 作者</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
「_如果你正打算学习一个**现代框架**用来构建 REST API,来看下 **FastAPI** [...] 它快速、易用且易于学习 [...]_」
「_我们已经将 **API** 服务切换到了 **FastAPI** [...] 我认为你会喜欢它的 [...]_」
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> 创始人 - <a href="https://spacy.io" target="_blank">spaCy</a> 作者</strong> <a href="https://x.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://x.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
---
## **Typer**,命令行中的 FastAPI
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
如果你正在开发一个在终端中运行的<abbr title="Command Line Interface">命令行</abbr>应用而不是 web API,不妨试下 <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>。
**Typer** 是 FastAPI 的小同胞。它想要成为**命令行中的 FastAPI**。 ⌨️ 🚀
## 依赖
Python 及更高版本
FastAPI 站在以下巨人的肩膀之上:
* <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> 负责 web 部分。
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 负责数据部分。
## 安装
<div class="termy">
```console
$ pip install fastapi
---> 100%
```
</div>
你还会需要一个 ASGI 服务器,生产环境可以使用 <a href="https://www.uvicorn.dev" class="external-link" target="_blank">Uvicorn</a> 或者 <a href="https://github.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>。
<div class="termy">
```console
$ pip install "uvicorn[standard]"
---> 100%
```
</div>
## 示例
### 创建
* 创建一个 `main.py` 文件并写入以下内容:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
<details markdown="1">
<summary>或者使用 <code>async def</code>...</summary>
如果你的代码里会出现 `async` / `await`,请使用 `async def`
```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
**Note**:
如果你不知道是否会用到,可以查看文档的 _"In a hurry?"_ 章节中 <a href="https://fastapi.tiangolo.com/zh/async/#in-a-hurry" target="_blank">关于 `async` 和 `await` 的部分</a>。
</details>
### 运行
通过以下命令运行服务器:
<div class="termy">
```console
$ uvicorn main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
</div>
<details markdown="1">
<summary>关于 <code>uvicorn main:app --reload</code> 命令......</summary>
`uvicorn main:app` 命令含义如下:
* `main``main.py` 文件(一个 Python "模块")。
* `app`:在 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。
* `--reload`:让服务器在更新代码后重新启动。仅在开发时使用该选项。
</details>
### 检查
使用浏览器访问 <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>。
你将会看到如下 JSON 响应:
```JSON
{"item_id": 5, "q": "somequery"}
```
你已经创建了一个具有以下功能的 API:
* 通过 _路径_ `/` 和 `/items/{item_id}` 接受 HTTP 请求。
* 以上 _路径_ 都接受 `GET` <em>操作</em>(也被称为 HTTP _方法_)。
* `/items/{item_id}` _路径_ 有一个 _路径参数_ `item_id` 并且应该为 `int` 类型。
* `/items/{item_id}` _路径_ 有一个可选的 `str` 类型的 _查询参数_ `q`。
### 交互式 API 文档
现在访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
你会看到自动生成的交互式 API 文档(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>生成):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### 可选的 API 文档
访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
你会看到另一个自动生成的文档(由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 生成):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## 示例升级
现在修改 `main.py` 文件来从 `PUT` 请求中接收请求体。
我们借助 Pydantic 来使用标准的 Python 类型声明请求体。
```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
服务器将会自动重载(因为在上面的步骤中你向 `uvicorn` 命令添加了 `--reload` 选项)。
### 交互式 API 文档升级
访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
* 交互式 API 文档将会自动更新,并加入新的请求体:
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* 点击「Try it out」按钮,之后你可以填写参数并直接调用 API:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png)
* 然后点击「Execute」按钮,用户界面将会和 API 进行通信,发送参数,获取结果并在屏幕上展示:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### 可选文档升级
访问 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
* 可选文档同样会体现新加入的请求参数和请求体:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### 总结
总的来说,你就像声明函数的参数类型一样只声明了**一次**请求参数、请求体等的类型。
你使用了标准的现代 Python 类型来完成声明。
你不需要去学习新的语法、了解特定库的方法或类,等等。
只需要使用标准的 **Python 及更高版本**。
举个例子,比如声明 `int` 类型:
```Python
item_id: int
```
或者一个更复杂的 `Item` 模型:
```Python
item: Item
```
......在进行一次声明之后,你将获得:
* 编辑器支持,包括:
* 自动补全
* 类型检查
* 数据校验:
* 在校验失败时自动生成清晰的错误信息
* 对多层嵌套的 JSON 对象依然执行校验
* <abbr title="也被称为:序列化或解析">转换</abbr> 来自网络请求的输入数据为 Python 数据类型。包括以下数据:
* JSON
* 路径参数
* 查询参数
* Cookies
* 请求头
* 表单
* 文件
* <abbr title="也被称为:序列化或解析">转换</abbr> 输出的数据:转换 Python 数据类型为供网络传输的 JSON 数据:
* 转换 Python 基础类型 `str`、 `int`、 `float`、 `bool`、 `list` 等)
* `datetime` 对象
* `UUID` 对象
* 数据库模型
* ......以及更多其他类型
* 自动生成的交互式 API 文档,包括两种可选的用户界面:
* Swagger UI
* ReDoc
---
回到前面的代码示例,**FastAPI** 将会:
* 校验 `GET` 和 `PUT` 请求的路径中是否含有 `item_id`。
* 校验 `GET` 和 `PUT` 请求中的 `item_id` 是否为 `int` 类型。
* 如果不是,客户端将会收到清晰有用的错误信息。
* 检查 `GET` 请求中是否有命名为 `q` 的可选查询参数(比如 `http://127.0.0.1:8000/items/foo?q=somequery`)。
* 因为 `q` 被声明为 `= None`,所以它是可选的。
* 如果没有 `None` 它将会是必需的 (如 `PUT` 例子中的请求体)。
* 对于访问 `/items/{item_id}` 的 `PUT` 请求,将请求体读取为 JSON 并:
* 检查是否有必需属性 `name` 并且值为 `str` 类型 。
* 检查是否有必需属性 `price` 并且值为 `float` 类型。
* 检查是否有可选属性 `is_offer` 如果有的话值应该为 `bool` 类型。
* 以上过程对于多层嵌套的 JSON 对象同样也会执行
* 自动对 JSON 进行转换或转换成 JSON。
* 通过 OpenAPI 文档来记录所有内容,可被用于:
* 交互式文档系统
* 许多编程语言的客户端代码自动生成系统
* 直接提供 2 种交互式文档 web 界面。
---
虽然我们才刚刚开始,但其实你已经了解了这一切是如何工作的。
尝试更改下面这行代码:
```Python
return {"item_name": item.name, "item_id": item_id}
```
......从:
```Python
... "item_name": item.name ...
```
......改为:
```Python
... "item_price": item.price ...
```
......注意观察编辑器是如何自动补全属性并且还知道它们的类型:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
<a href="https://fastapi.tiangolo.com/zh/tutorial/">教程 - 用户指南</a> 中有包含更多特性的更完整示例。
**剧透警告**: 教程 - 用户指南中的内容有:
* 对来自不同地方的参数进行声明,如:**请求头**、**cookies**、**form 表单**以及**上传的文件**。
* 如何设置**校验约束**如 `maximum_length` 或者 `regex`。
* 一个强大并易于使用的 **<abbr title="也被称为 components, resources, providers, services, injectables">依赖注入</abbr>** 系统。
* 安全性和身份验证,包括通过 **JWT 令牌**和 **HTTP 基本身份认证**来支持 **OAuth2**。
* 更进阶(但同样简单)的技巧来声明 **多层嵌套 JSON 模型** (借助 Pydantic)。
* 许多额外功能(归功于 Starlette)比如:
* **WebSockets**
* **GraphQL**
* 基于 HTTPX 和 `pytest` 的极其简单的测试
* **CORS**
* **Cookie Sessions**
* ......以及更多
## 性能
独立机构 TechEmpower 所作的基准测试结果显示,基于 Uvicorn 运行的 **FastAPI** 程序是 <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">最快的 Python web 框架之一</a>,仅次于 Starlette 和 Uvicorn 本身(FastAPI 内部使用了它们)。(*)
想了解更多,请查阅 <a href="https://fastapi.tiangolo.com/zh/benchmarks/" class="internal-link" target="_blank">基准测试</a> 章节。
## 可选依赖
用于 Pydantic
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - 用于 email 校验。
用于 Starlette
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - 使用 `TestClient` 时安装。
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - 使用默认模板配置时安装。
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - 需要通过 `request.form()` 对表单进行<abbr title="将来自 HTTP 请求中的字符串转换为 Python 数据类型">「解析」</abbr>时安装。
* <a href="https://pythonhosted.org/itsdangerous/" target="_blank"><code>itsdangerous</code></a> - 需要 `SessionMiddleware` 支持时安装。
* <a href="https://pyyaml.org/wiki/PyYAMLDocumentation" target="_blank"><code>pyyaml</code></a> - 使用 Starlette 提供的 `SchemaGenerator` 时安装(有 FastAPI 你可能并不需要它)。
* <a href="https://graphene-python.org/" target="_blank"><code>graphene</code></a> - 需要 `GraphQLApp` 支持时安装。
用于 FastAPI / Starlette
* <a href="https://www.uvicorn.dev" target="_blank"><code>uvicorn</code></a> - 用于加载和运行你的应用程序的服务器。
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - 使用 `ORJSONResponse` 时安装。
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - 使用 `UJSONResponse` 时安装。
你可以通过 `pip install "fastapi[all]"` 命令来安装以上所有依赖。
## 许可协议
该项目遵循 MIT 许可协议。
+5
View File
@@ -0,0 +1,5 @@
# 学习
以下是学习 **FastAPI** 的介绍部分和教程。
您可以认为这是一本 **书**,一门 **课程**,是 **官方** 且推荐的学习FastAPI的方法。😎
+28
View File
@@ -0,0 +1,28 @@
# FastAPI全栈模板
模板通常带有特定的设置,而且被设计为灵活和可定制的。这允许您根据项目的需求修改和调整它们,使它们成为一个很好的起点。🏁
您可以使用此模板开始,因为它包含了许多已经为您完成的初始设置、安全性、数据库和一些API端点。
代码仓: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a>
## FastAPI全栈模板 - 技术栈和特性
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) 用于Python后端API.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于Python和SQL数据库的集成(ORM)。
- 🔍 [Pydantic](https://docs.pydantic.dev) FastAPI的依赖项之一,用于数据验证和配置管理。
- 💾 [PostgreSQL](https://www.postgresql.org) 作为SQL数据库。
- 🚀 [React](https://react.dev) 用于前端。
- 💃 使用了TypeScript、hooks、[Vite](https://vitejs.dev)和其他一些现代化的前端技术栈。
- 🎨 [Chakra UI](https://chakra-ui.com) 用于前端组件。
- 🤖 一个自动化生成的前端客户端。
- 🧪 [Playwright](https://playwright.dev)用于端到端测试。
- 🦇 支持暗黑主题(Dark mode)。
- 🐋 [Docker Compose](https://www.docker.com) 用于开发环境和生产环境。
- 🔒 默认使用密码哈希来保证安全。
- 🔑 JWT令牌用于权限验证。
- 📫 使用邮箱来进行密码恢复。
- ✅ 单元测试用了[Pytest](https://pytest.org).
- 📞 [Traefik](https://traefik.io) 用于反向代理和负载均衡。
- 🚢 部署指南(Docker Compose)包含了如何起一个Traefik前端代理来自动化HTTPS认证。
- 🏭 CI(持续集成)和 CD(持续部署)基于GitHub Actions。
+304
View File
@@ -0,0 +1,304 @@
# Python 类型提示简介
**Python 3.6+ 版本**加入了对"类型提示"的支持。
这些**"类型提示"**是一种新的语法(在 Python 3.6 版本加入)用来声明一个变量的<abbr title="例如:str、int、float、bool">类型</abbr>。
通过声明变量的类型,编辑器和一些工具能给你提供更好的支持。
这只是一个关于 Python 类型提示的**快速入门 / 复习**。它仅涵盖与 **FastAPI** 一起使用所需的最少部分...实际上只有很少一点。
整个 **FastAPI** 都基于这些类型提示构建,它们带来了许多优点和好处。
但即使你不会用到 **FastAPI**,了解一下类型提示也会让你从中受益。
/// note
如果你已经精通 Python,并且了解关于类型提示的一切知识,直接跳到下一章节吧。
///
## 动机
让我们从一个简单的例子开始:
{* ../../docs_src/python_types/tutorial001.py *}
运行这段程序将输出:
```
John Doe
```
这个函数做了下面这些事情:
* 接收 `first_name``last_name` 参数。
* 通过 `title()` 将每个参数的第一个字母转换为大写形式。
* 中间用一个空格来<abbr title="将它们按顺序放置组合成一个整体。">拼接</abbr>它们。
{* ../../docs_src/python_types/tutorial001.py hl[2] *}
### 修改示例
这是一个非常简单的程序。
现在假设你将从头开始编写这段程序。
在某一时刻,你开始定义函数,并且准备好了参数...。
现在你需要调用一个"将第一个字母转换为大写形式的方法"。
等等,那个方法是什么来着?`upper`?还是 `uppercase``first_uppercase``capitalize`
然后你尝试向程序员老手的朋友——编辑器自动补全寻求帮助。
输入函数的第一个参数 `first_name`,输入点号(`.`)然后敲下 `Ctrl+Space` 来触发代码补全。
但遗憾的是并没有起什么作用:
<img src="https://fastapi.tiangolo.com/img/python-types/image01.png">
### 添加类型
让我们来修改上面例子的一行代码。
我们将把下面这段代码中的函数参数从:
```Python
first_name, last_name
```
改成:
```Python
first_name: str, last_name: str
```
就是这样。
这些就是"类型提示"
{* ../../docs_src/python_types/tutorial002.py hl[1] *}
这和声明默认值是不同的,例如:
```Python
first_name="john", last_name="doe"
```
这两者不一样。
我们用的是冒号(`:`),不是等号(`=`)。
而且添加类型提示一般不会改变原来的运行结果。
现在假设我们又一次正在创建这个函数,这次添加了类型提示。
在同样的地方,通过 `Ctrl+Space` 触发自动补全,你会发现:
<img src="https://fastapi.tiangolo.com/img/python-types/image02.png">
这样,你可以滚动查看选项,直到你找到看起来眼熟的那个:
<img src="https://fastapi.tiangolo.com/img/python-types/image03.png">
## 更多动机
下面是一个已经有类型提示的函数:
{* ../../docs_src/python_types/tutorial003.py hl[1] *}
因为编辑器已经知道了这些变量的类型,所以不仅能对代码进行补全,还能检查其中的错误:
<img src="https://fastapi.tiangolo.com/img/python-types/image04.png">
现在你知道了必须先修复这个问题,通过 `str(age)``age` 转换成字符串:
{* ../../docs_src/python_types/tutorial004.py hl[2] *}
## 声明类型
你刚刚看到的就是声明类型提示的主要场景。用于函数的参数。
这也是你将在 **FastAPI** 中使用它们的主要场景。
### 简单类型
不只是 `str`,你能够声明所有的标准 Python 类型。
比如以下类型:
* `int`
* `float`
* `bool`
* `bytes`
{* ../../docs_src/python_types/tutorial005.py hl[1] *}
### 嵌套类型
有些容器数据结构可以包含其他的值,比如 `dict``list``set``tuple`。它们内部的值也会拥有自己的类型。
你可以使用 Python 的 `typing` 标准库来声明这些类型以及子类型。
它专门用来支持这些类型提示。
#### 列表
例如,让我们来定义一个由 `str` 组成的 `list` 变量。
`typing` 模块导入 `List`(注意是大写的 `L`):
{* ../../docs_src/python_types/tutorial006.py hl[1] *}
同样以冒号(`:`)来声明这个变量。
输入 `List` 作为类型。
由于列表是带有"子类型"的类型,所以我们把子类型放在方括号中:
{* ../../docs_src/python_types/tutorial006.py hl[4] *}
这表示:"变量 `items` 是一个 `list`,并且这个列表里的每一个元素都是 `str`"。
这样,即使在处理列表中的元素时,你的编辑器也可以提供支持。
没有类型,几乎是不可能实现下面这样:
<img src="https://fastapi.tiangolo.com/img/python-types/image05.png">
注意,变量 `item` 是列表 `items` 中的元素之一。
而且,编辑器仍然知道它是一个 `str`,并为此提供了支持。
#### 元组和集合
声明 `tuple``set` 的方法也是一样的:
{* ../../docs_src/python_types/tutorial007.py hl[1,4] *}
这表示:
* 变量 `items_t` 是一个 `tuple`,其中的前两个元素都是 `int` 类型, 最后一个元素是 `str` 类型。
* 变量 `items_s` 是一个 `set`,其中的每个元素都是 `bytes` 类型。
#### 字典
定义 `dict` 时,需要传入两个子类型,用逗号进行分隔。
第一个子类型声明 `dict` 的所有键。
第二个子类型声明 `dict` 的所有值:
{* ../../docs_src/python_types/tutorial008.py hl[1,4] *}
这表示:
* 变量 `prices` 是一个 `dict`
* 这个 `dict` 的所有键为 `str` 类型(可以看作是字典内每个元素的名称)。
* 这个 `dict` 的所有值为 `float` 类型(可以看作是字典内每个元素的价格)。
### 类作为类型
你也可以将类声明为变量的类型。
假设你有一个名为 `Person` 的类,拥有 name 属性:
{* ../../docs_src/python_types/tutorial010.py hl[1:3] *}
接下来,你可以将一个变量声明为 `Person` 类型:
{* ../../docs_src/python_types/tutorial010.py hl[6] *}
然后,你将再次获得所有的编辑器支持:
<img src="https://fastapi.tiangolo.com/img/python-types/image06.png">
## Pydantic 模型
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 是一个用来执行数据校验的 Python 库。
你可以将数据的"结构"声明为具有属性的类。
每个属性都拥有类型。
接着你用一些值来创建这个类的实例,这些值会被校验,并被转换为适当的类型(在需要的情况下),返回一个包含所有数据的对象。
然后,你将获得这个对象的所有编辑器支持。
下面的例子来自 Pydantic 官方文档:
//// tab | Python 3.10+
```Python
{!> ../../docs_src/python_types/tutorial011_py310.py!}
```
////
//// tab | Python 3.9+
```Python
{!> ../../docs_src/python_types/tutorial011_py39.py!}
```
////
//// tab | Python 3.8+
```Python
{!> ../../docs_src/python_types/tutorial011.py!}
```
////
/// info
想进一步了解 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic,请阅读其文档</a>.
///
整个 **FastAPI** 建立在 Pydantic 的基础之上。
实际上你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 看到很多这种情况。
## **FastAPI** 中的类型提示
**FastAPI** 利用这些类型提示来做下面几件事。
使用 **FastAPI** 时用类型提示声明参数可以获得:
* **编辑器支持**。
* **类型检查**。
...并且 **FastAPI** 还会用这些类型声明来:
* **定义参数要求**:声明对请求路径参数、查询参数、请求头、请求体、依赖等的要求。
* **转换数据**:将来自请求的数据转换为需要的类型。
* **校验数据**: 对于每一个请求:
* 当数据校验失败时自动生成**错误信息**返回给客户端。
* 使用 OpenAPI **记录** API
* 然后用于自动生成交互式文档的用户界面。
听上去有点抽象。不过不用担心。你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 中看到所有的实战。
最重要的是,通过使用标准的 Python 类型,只需要在一个地方声明(而不是添加更多的类、装饰器等),**FastAPI** 会为你完成很多的工作。
/// info
如果你已经阅读了所有教程,回过头来想了解有关类型的更多信息,<a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">来自 `mypy` 的"速查表"</a>是不错的资源。
///
+124
View File
@@ -0,0 +1,124 @@
# 后台任务
你可以定义在返回响应后运行的后台任务。
这对需要在请求之后执行的操作很有用,但客户端不必在接收响应之前等待操作完成。
包括这些例子:
* 执行操作后发送的电子邮件通知:
* 由于连接到电子邮件服务器并发送电子邮件往往很“慢”(几秒钟),您可以立即返回响应并在后台发送电子邮件通知。
* 处理数据:
* 例如,假设您收到的文件必须经过一个缓慢的过程,您可以返回一个"Accepted"(HTTP 202)响应并在后台处理它。
## 使用 `BackgroundTasks`
首先导入 `BackgroundTasks` 并在 *路径操作函数* 中使用类型声明 `BackgroundTasks` 定义一个参数:
{* ../../docs_src/background_tasks/tutorial001.py hl[1, 13] *}
**FastAPI** 会创建一个 `BackgroundTasks` 类型的对象并作为该参数传入。
## 创建一个任务函数
创建要作为后台任务运行的函数。
它只是一个可以接收参数的标准函数。
它可以是 `async def` 或普通的 `def` 函数,**FastAPI** 知道如何正确处理。
在这种情况下,任务函数将写入一个文件(模拟发送电子邮件)。
由于写操作不使用 `async``await`,我们用普通的 `def` 定义函数:
{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *}
## 添加后台任务
在你的 *路径操作函数* 里,用 `.add_task()` 方法将任务函数传到 *后台任务* 对象中:
{* ../../docs_src/background_tasks/tutorial001.py hl[14] *}
`.add_task()` 接收以下参数:
* 在后台运行的任务函数(`write_notification`)。
* 应按顺序传递给任务函数的任意参数序列(`email`)。
* 应传递给任务函数的任意关键字参数(`message="some notification"`)。
## 依赖注入
使用 `BackgroundTasks` 也适用于依赖注入系统,你可以在多个级别声明 `BackgroundTasks` 类型的参数:在 *路径操作函数* 里,在依赖中(可依赖),在子依赖中,等等。
**FastAPI** 知道在每种情况下该做什么以及如何复用同一对象,因此所有后台任务被合并在一起并且随后在后台运行:
//// tab | Python 3.10+
{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13, 15, 22, 25] *}
////
//// tab | Python 3.9+
{* ../../docs_src/background_tasks/tutorial002_an_py39.py hl[13, 15, 22, 25] *}
////
//// tab | Python 3.8+
{* ../../docs_src/background_tasks/tutorial002_an.py hl[14, 16, 23, 26] *}
////
//// tab | Python 3.10+ 没Annotated
/// tip
尽可能选择使用 `Annotated` 的版本。
///
{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11, 13, 20, 23] *}
////
//// tab | Python 3.8+ 没Annotated
/// tip
尽可能选择使用 `Annotated` 的版本。
///
{* ../../docs_src/background_tasks/tutorial002.py hl[13, 15, 22, 25] *}
////
该示例中,信息会在响应发出 *之后* 被写到 `log.txt` 文件。
如果请求中有查询,它将在后台任务中写入日志。
然后另一个在 *路径操作函数* 生成的后台任务会使用路径参数 `email` 写入一条信息。
## 技术细节
`BackgroundTasks` 类直接来自 <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">`starlette.background`</a>。
它被直接导入/包含到FastAPI以便你可以从 `fastapi` 导入,并避免意外从 `starlette.background` 导入备用的 `BackgroundTask` (后面没有 `s`)。
通过仅使用 `BackgroundTasks` (而不是 `BackgroundTask`),使得能将它作为 *路径操作函数* 的参数 ,并让**FastAPI**为您处理其余部分, 就像直接使用 `Request` 对象。
在FastAPI中仍然可以单独使用 `BackgroundTask`,但您必须在代码中创建对象,并返回包含它的Starlette `Response`
更多细节查看 <a href="https://www.starlette.dev/background/" class="external-link" target="_blank">Starlette's official docs for Background Tasks</a>.
## 告诫
如果您需要执行繁重的后台计算,并且不一定需要由同一进程运行(例如,您不需要共享内存、变量等),那么使用其他更大的工具(如 <a href="https://docs.celeryq.dev" class="external-link" target="_blank">Celery</a>)可能更好。
它们往往需要更复杂的配置,即消息/作业队列管理器,如RabbitMQ或Redis,但它们允许您在多个进程中运行后台任务,甚至是在多个服务器中。
但是,如果您需要从同一个**FastAPI**应用程序访问变量和对象,或者您需要执行小型后台任务(如发送电子邮件通知),您只需使用 `BackgroundTasks` 即可。
## 回顾
导入并使用 `BackgroundTasks` 通过 *路径操作函数* 中的参数和依赖项来添加后台任务。
@@ -0,0 +1,530 @@
# 更大的应用 - 多个文件
如果你正在开发一个应用程序或 Web API,很少会将所有的内容都放在一个文件中。
**FastAPI** 提供了一个方便的工具,可以在保持所有灵活性的同时构建你的应用程序。
/// info
如果你来自 Flask,那这将相当于 Flask 的 Blueprints。
///
## 一个文件结构示例
假设你的文件结构如下:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │ ├── __init__.py
│   │ ├── items.py
│   │ └── users.py
│   └── internal
│   ├── __init__.py
│   └── admin.py
```
/// tip
上面有几个 `__init__.py` 文件:每个目录或子目录中都有一个。
这就是能将代码从一个文件导入到另一个文件的原因。
例如,在 `app/main.py` 中,你可以有如下一行:
```
from app.routers import items
```
///
* `app` 目录包含了所有内容。并且它有一个空文件 `app/__init__.py`,因此它是一个「Python 包」(「Python 模块」的集合):`app`
* 它包含一个 `app/main.py` 文件。由于它位于一个 Python 包(一个包含 `__init__.py` 文件的目录)中,因此它是该包的一个「模块」:`app.main`
* 还有一个 `app/dependencies.py` 文件,就像 `app/main.py` 一样,它是一个「模块」:`app.dependencies`
* 有一个子目录 `app/routers/` 包含另一个 `__init__.py` 文件,因此它是一个「Python 子包」:`app.routers`
* 文件 `app/routers/items.py` 位于 `app/routers/` 包中,因此它是一个子模块:`app.routers.items`
* 同样适用于 `app/routers/users.py`,它是另一个子模块:`app.routers.users`
* 还有一个子目录 `app/internal/` 包含另一个 `__init__.py` 文件,因此它是又一个「Python 子包」:`app.internal`
* `app/internal/admin.py` 是另一个子模块:`app.internal.admin`
<img src="https://fastapi.tiangolo.com/img/tutorial/bigger-applications/package.drawio.svg">
带有注释的同一文件结构:
```
.
├── app # 「app」是一个 Python 包
│   ├── __init__.py # 这个文件使「app」成为一个 Python 包
│   ├── main.py # 「main」模块,例如 import app.main
│   ├── dependencies.py # 「dependencies」模块,例如 import app.dependencies
│   └── routers # 「routers」是一个「Python 子包」
│   │ ├── __init__.py # 使「routers」成为一个「Python 子包」
│   │ ├── items.py # 「items」子模块,例如 import app.routers.items
│   │ └── users.py # 「users」子模块,例如 import app.routers.users
│   └── internal # 「internal」是一个「Python 子包」
│   ├── __init__.py # 使「internal」成为一个「Python 子包」
│   └── admin.py # 「admin」子模块,例如 import app.internal.admin
```
## `APIRouter`
假设专门用于处理用户逻辑的文件是位于 `/app/routers/users.py` 的子模块。
你希望将与用户相关的*路径操作*与其他代码分开,以使其井井有条。
但它仍然是同一 **FastAPI** 应用程序/web API 的一部分(它是同一「Python 包」的一部分)。
你可以使用 `APIRouter` 为该模块创建*路径操作*。
### 导入 `APIRouter`
你可以导入它并通过与 `FastAPI` 类相同的方式创建一个「实例」:
```Python hl_lines="1 3" title="app/routers/users.py"
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
### 使用 `APIRouter` 的*路径操作*
然后你可以使用它来声明*路径操作*。
使用方式与 `FastAPI` 类相同:
```Python hl_lines="6 11 16" title="app/routers/users.py"
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
你可以将 `APIRouter` 视为一个「迷你 `FastAPI`」类。
所有相同的选项都得到支持。
所有相同的 `parameters`、`responses`、`dependencies`、`tags` 等等。
/// tip
在此示例中,该变量被命名为 `router`,但你可以根据你的想法自由命名。
///
我们将在主 `FastAPI` 应用中包含该 `APIRouter`,但首先,让我们来看看依赖项和另一个 `APIRouter`。
## 依赖项
我们了解到我们将需要一些在应用程序的好几个地方所使用的依赖项。
因此,我们将它们放在它们自己的 `dependencies` 模块(`app/dependencies.py`)中。
现在我们将使用一个简单的依赖项来读取一个自定义的 `X-Token` 请求首部:
```Python hl_lines="1 4-6" title="app/dependencies.py"
{!../../docs_src/bigger_applications/app/dependencies.py!}
```
/// tip
我们正在使用虚构的请求首部来简化此示例。
但在实际情况下,使用集成的[安全性实用工具](security/index.md){.internal-link target=_blank}会得到更好的效果。
///
## 其他使用 `APIRouter` 的模块
假设你在位于 `app/routers/items.py` 的模块中还有专门用于处理应用程序中「项目」的端点。
你具有以下*路径操作*
* `/items/`
* `/items/{item_id}`
这和 `app/routers/users.py` 的结构完全相同。
但是我们想变得更聪明并简化一些代码。
我们知道此模块中的所有*路径操作*都有相同的:
* 路径 `prefix``/items`。
* `tags`:(仅有一个 `items` 标签)。
* 额外的 `responses`。
* `dependencies`:它们都需要我们创建的 `X-Token` 依赖项。
因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。
```Python hl_lines="5-10 16 21" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
由于每个*路径操作*的路径都必须以 `/` 开头,例如:
```Python hl_lines="1"
@router.get("/{item_id}")
async def read_item(item_id: str):
...
```
...前缀不能以 `/` 作为结尾。
因此,本例中的前缀为 `/items`。
我们还可以添加一个 `tags` 列表和额外的 `responses` 列表,这些参数将应用于此路由器中包含的所有*路径操作*。
我们可以添加一个 `dependencies` 列表,这些依赖项将被添加到路由器中的所有*路径操作*中,并将针对向它们发起的每个请求执行/解决。
/// tip
请注意,和[*路径操作装饰器*中的依赖项](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}很类似,没有值会被传递给你的*路径操作函数*。
///
最终结果是项目相关的路径现在为:
* `/items/`
* `/items/{item_id}`
...如我们所愿。
* 它们将被标记为仅包含单个字符串 `"items"` 的标签列表。
* 这些「标签」对于自动化交互式文档系统(使用 OpenAPI)特别有用。
* 所有的路径操作都将包含预定义的 `responses`。
* 所有的这些*路径操作*都将在自身之前计算/执行 `dependencies` 列表。
* 如果你还在一个具体的*路径操作*中声明了依赖项,**它们也会被执行**。
* 路由器的依赖项最先执行,然后是[装饰器中的 `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank},再然后是普通的参数依赖项。
* 你还可以添加[具有 `scopes` 的 `Security` 依赖项](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}。
/// tip
在 `APIRouter`中具有 `dependencies` 可以用来,例如,对一整组的*路径操作*要求身份认证。即使这些依赖项并没有分别添加到每个路径操作中。
///
/// check
`prefix`、`tags`、`responses` 以及 `dependencies` 参数只是(和其他很多情况一样)**FastAPI** 的一个用于帮助你避免代码重复的功能。
///
### 导入依赖项
这些代码位于 `app.routers.items` 模块,`app/routers/items.py` 文件中。
我们需要从 `app.dependencies` 模块即 `app/dependencies.py` 文件中获取依赖函数。
因此,我们通过 `..` 对依赖项使用了相对导入:
```Python hl_lines="3" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
#### 相对导入如何工作
/// tip
如果你完全了解导入的工作原理,请从下面的下一部分继续。
///
一个单点 `.`,例如:
```Python
from .dependencies import get_token_header
```
表示:
* 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始...
* 找到 `dependencies` 模块(一个位于 `app/routers/dependencies.py` 的虚构文件)...
* 然后从中导入函数 `get_token_header`。
但是该文件并不存在,我们的依赖项位于 `app/dependencies.py` 文件中。
请记住我们的程序/文件结构是怎样的:
<img src="https://fastapi.tiangolo.com/img/tutorial/bigger-applications/package.drawio.svg">
---
两个点 `..`,例如:
```Python
from ..dependencies import get_token_header
```
表示:
* 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始...
* 跳转到其父包(`app/` 目录)...
* 在该父包中,找到 `dependencies` 模块(位于 `app/dependencies.py` 的文件)...
* 然后从中导入函数 `get_token_header`。
正常工作了!🎉
---
同样,如果我们使用了三个点 `...`,例如:
```Python
from ...dependencies import get_token_header
```
那将意味着:
* 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始...
* 跳转到其父包(`app/` 目录)...
* 然后跳转到该包的父包(该父包并不存在,`app` 已经是最顶层的包 😱)...
* 在该父包中,找到 `dependencies` 模块(位于 `app/` 更上一级目录中的 `dependencies.py` 文件)...
* 然后从中导入函数 `get_token_header`。
这将引用 `app/` 的往上一级,带有其自己的 `__init __.py` 等文件的某个包。但是我们并没有这个包。因此,这将在我们的示例中引发错误。🚨
但是现在你知道了它的工作原理,因此无论它们多么复杂,你都可以在自己的应用程序中使用相对导入。🤓
### 添加一些自定义的 `tags`、`responses` 和 `dependencies`
我们不打算在每个*路径操作*中添加前缀 `/items` 或 `tags =["items"]`,因为我们将它们添加到了 `APIRouter` 中。
但是我们仍然可以添加*更多*将会应用于特定的*路径操作*的 `tags`,以及一些特定于该*路径操作*的额外 `responses`
```Python hl_lines="30-31" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
/// tip
最后的这个路径操作将包含标签的组合:`["items""custom"]`。
并且在文档中也会有两个响应,一个用于 `404`,一个用于 `403`。
///
## `FastAPI` 主体
现在,让我们来看看位于 `app/main.py` 的模块。
在这里你导入并使用 `FastAPI` 类。
这将是你的应用程序中将所有内容联结在一起的主文件。
并且由于你的大部分逻辑现在都存在于其自己的特定模块中,因此主文件的内容将非常简单。
### 导入 `FastAPI`
你可以像平常一样导入并创建一个 `FastAPI` 类。
我们甚至可以声明[全局依赖项](dependencies/global-dependencies.md){.internal-link target=_blank},它会和每个 `APIRouter` 的依赖项组合在一起:
```Python hl_lines="1 3 7" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
### 导入 `APIRouter`
现在,我们导入具有 `APIRouter` 的其他子模块:
```Python hl_lines="5" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 ` .` 通过「相对导入」来导入它们。
### 导入是如何工作的
这段代码:
```Python
from .routers import items, users
```
表示:
* 从该模块(`app/main.py` 文件)所在的同一个包(`app/` 目录)开始...
* 寻找 `routers` 子包(位于 `app/routers/` 的目录)...
* 从该包中,导入子模块 `items` (位于 `app/routers/items.py` 的文件) 以及 `users` (位于 `app/routers/users.py` 的文件)...
`items` 模块将具有一个 `router` 变量(`items.router`)。这与我们在 `app/routers/items.py` 文件中创建的变量相同,它是一个 `APIRouter` 对象。
然后我们对 `users` 模块进行相同的操作。
我们也可以像这样导入它们:
```Python
from app.routers import items, users
```
/// info
第一个版本是「相对导入」:
```Python
from .routers import items, users
```
第二个版本是「绝对导入」:
```Python
from app.routers import items, users
```
要了解有关 Python 包和模块的更多信息,请查阅<a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">关于 Modules 的 Python 官方文档</a>。
///
### 避免名称冲突
我们将直接导入 `items` 子模块,而不是仅导入其 `router` 变量。
这是因为我们在 `users` 子模块中也有另一个名为 `router` 的变量。
如果我们一个接一个地导入,例如:
```Python
from .routers.items import router
from .routers.users import router
```
来自 `users` 的 `router` 将覆盖来自 `items` 中的 `router`,我们将无法同时使用它们。
因此,为了能够在同一个文件中使用它们,我们直接导入子模块:
```Python hl_lines="5" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
### 包含 `users` 和 `items` 的 `APIRouter`
现在,让我们来包含来自 `users` 和 `items` 子模块的 `router`。
```Python hl_lines="10-11" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
/// info
`users.router` 包含了 `app/routers/users.py` 文件中的 `APIRouter`。
`items.router` 包含了 `app/routers/items.py` 文件中的 `APIRouter`。
///
使用 `app.include_router()`,我们可以将每个 `APIRouter` 添加到主 `FastAPI` 应用程序中。
它将包含来自该路由器的所有路由作为其一部分。
/// note | 技术细节
实际上,它将在内部为声明在 `APIRouter` 中的每个*路径操作*创建一个*路径操作*。
所以,在幕后,它实际上会像所有的东西都是同一个应用程序一样工作。
///
/// check
包含路由器时,你不必担心性能问题。
这将花费几微秒时间,并且只会在启动时发生。
因此,它不会影响性能。⚡
///
### 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter`
现在,假设你的组织为你提供了 `app/internal/admin.py` 文件。
它包含一个带有一些由你的组织在多个项目之间共享的管理员*路径操作*的 `APIRouter`。
对于此示例,它将非常简单。但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等:
```Python hl_lines="3" title="app/internal/admin.py"
{!../../docs_src/bigger_applications/app/internal/admin.py!}
```
但是我们仍然希望在包含 `APIRouter` 时设置一个自定义的 `prefix`,以便其所有*路径操作*以 `/admin` 开头,我们希望使用本项目已经有的 `dependencies` 保护它,并且我们希望它包含自定义的 `tags` 和 `responses`。
我们可以通过将这些参数传递给 `app.include_router()` 来完成所有的声明,而不必修改原始的 `APIRouter`
```Python hl_lines="14-17" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
这样,原始的 `APIRouter` 将保持不变,因此我们仍然可以与组织中的其他项目共享相同的 `app/internal/admin.py` 文件。
结果是在我们的应用程序中,来自 `admin` 模块的每个*路径操作*都将具有:
* `/admin` 前缀 。
* `admin` 标签。
* `get_token_header` 依赖项。
* `418` 响应。 🍵
但这只会影响我们应用中的 `APIRouter`,而不会影响使用它的任何其他代码。
因此,举例来说,其他项目能够以不同的身份认证方法使用相同的 `APIRouter`。
### 包含一个*路径操作*
我们还可以直接将*路径操作*添加到 `FastAPI` 应用中。
这里我们这样做了...只是为了表明我们可以做到🤷:
```Python hl_lines="21-23" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
它将与通过 `app.include_router()` 添加的所有其他*路径操作*一起正常运行。
/// info | 特别的技术细节
**注意**:这是一个非常技术性的细节,你也许可以**直接跳过**。
---
`APIRouter` 没有被「挂载」,它们与应用程序的其余部分没有隔离。
这是因为我们想要在 OpenAPI 模式和用户界面中包含它们的*路径操作*。
由于我们不能仅仅隔离它们并独立于其余部分来「挂载」它们,因此*路径操作*是被「克隆的」(重新创建),而不是直接包含。
///
## 查看自动化的 API 文档
现在,使用 `app.main` 模块和 `app` 变量运行 `uvicorn`
<div class="termy">
```console
$ uvicorn app.main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
然后打开位于 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> 的文档。
你将看到使用了正确路径(和前缀)和正确标签的自动化 API 文档,包括了来自所有子模块的路径:
<img src="https://fastapi.tiangolo.com/img/tutorial/bigger-applications/image01.png">
## 多次使用不同的 `prefix` 包含同一个路由器
你也可以在*同一*路由器上使用不同的前缀来多次使用 `.include_router()`。
在有些场景这可能有用,例如以不同的前缀公开同一个的 API,比方说 `/api/v1` 和 `/api/latest`。
这是一个你可能并不真正需要的高级用法,但万一你有需要了就能够用上。
## 在另一个 `APIRouter` 中包含一个 `APIRouter`
与在 `FastAPI` 应用程序中包含 `APIRouter` 的方式相同,你也可以在另一个 `APIRouter` 中包含 `APIRouter`,通过:
```Python
router.include_router(other_router)
```
请确保在你将 `router` 包含到 `FastAPI` 应用程序之前进行此操作,以便 `other_router` 中的`路径操作`也能被包含进来。
+53
View File
@@ -0,0 +1,53 @@
# 请求体 - 字段
与在*路径操作函数*中使用 `Query``Path``Body` 声明校验与元数据的方式一样,可以使用 Pydantic 的 `Field` 在 Pydantic 模型内部声明校验和元数据。
## 导入 `Field`
首先,从 Pydantic 中导入 `Field`
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *}
/// warning | 警告
注意,与从 `fastapi` 导入 `Query``Path``Body` 不同,要直接从 `pydantic` 导入 `Field`
///
## 声明模型属性
然后,使用 `Field` 定义模型的属性:
{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *}
`Field` 的工作方式和 `Query``Path``Body` 相同,参数也相同。
/// note | 技术细节
实际上,`Query``Path` 都是 `Params` 的子类,而 `Params` 类又是 Pydantic 中 `FieldInfo` 的子类。
Pydantic 的 `Field` 返回也是 `FieldInfo` 的类实例。
`Body` 直接返回的也是 `FieldInfo` 的子类的对象。后文还会介绍一些 `Body` 的子类。
注意,从 `fastapi` 导入的 `Query``Path` 等对象实际上都是返回特殊类的函数。
///
/// tip | 提示
注意,模型属性的类型、默认值及 `Field` 的代码结构与*路径操作函数*的参数相同,只不过是用 `Field` 替换了`Path``Query``Body`
///
## 添加更多信息
`Field``Query``Body` 等对象里可以声明更多信息,并且 JSON Schema 中也会集成这些信息。
*声明示例*一章中将详细介绍添加更多信息的知识。
## 小结
Pydantic 的 `Field` 可以为模型属性声明更多校验和元数据。
传递 JSON Schema 元数据还可以使用更多关键字参数。
@@ -0,0 +1,167 @@
# 请求体 - 多个参数
既然我们已经知道了如何使用 `Path``Query`,下面让我们来了解一下请求体声明的更高级用法。
## 混合使用 `Path`、`Query` 和请求体参数
首先,毫无疑问地,你可以随意地混合使用 `Path``Query` 和请求体参数声明,**FastAPI** 会知道该如何处理。
你还可以通过将默认值设置为 `None` 来将请求体参数声明为可选参数:
{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *}
/// note
请注意,在这种情况下,将从请求体获取的 `item` 是可选的。因为它的默认值为 `None`
///
## 多个请求体参数
在上面的示例中,*路径操作*将期望一个具有 `Item` 的属性的 JSON 请求体,就像:
```JSON
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
```
但是你也可以声明多个请求体参数,例如 `item``user`
{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *}
在这种情况下,**FastAPI** 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。
因此,它将使用参数名称作为请求体中的键(字段名称),并期望一个类似于以下内容的请求体:
```JSON
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
}
}
```
/// note
请注意,即使 `item` 的声明方式与之前相同,但现在它被期望通过 `item` 键内嵌在请求体中。
///
**FastAPI** 将自动对请求中的数据进行转换,因此 `item` 参数将接收指定的内容,`user` 参数也是如此。
它将执行对复合数据的校验,并且像现在这样为 OpenAPI 模式和自动化文档对其进行记录。
## 请求体中的单一值
与使用 `Query``Path` 为查询参数和路径参数定义额外数据的方式相同,**FastAPI** 提供了一个同等的 `Body`
例如,为了扩展先前的模型,你可能决定除了 `item``user` 之外,还想在同一请求体中具有另一个键 `importance`
如果你就按原样声明它,因为它是一个单一值,**FastAPI** 将假定它是一个查询参数。
但是你可以使用 `Body` 指示 **FastAPI** 将其作为请求体的另一个键进行处理。
{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *}
在这种情况下,**FastAPI** 将期望像这样的请求体:
```JSON
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}
```
同样的,它将转换数据类型,校验,生成文档等。
## 多个请求体参数和查询参数
当然,除了请求体参数外,你还可以在任何需要的时候声明额外的查询参数。
由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 `Query`,你可以仅执行以下操作:
```Python
q: str = None
```
比如:
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *}
/// info
`Body` 同样具有与 `Query``Path` 以及其他后面将看到的类完全相同的额外校验和元数据参数。
///
## 嵌入单个请求体参数
假设你只有一个来自 Pydantic 模型 `Item` 的请求体参数 `item`
默认情况下,**FastAPI** 将直接期望这样的请求体。
但是,如果你希望它期望一个拥有 `item` 键并在值中包含模型内容的 JSON,就像在声明额外的请求体参数时所做的那样,则可以使用一个特殊的 `Body` 参数 `embed`
```Python
item: Item = Body(embed=True)
```
比如:
{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *}
在这种情况下,**FastAPI** 将期望像这样的请求体:
```JSON hl_lines="2"
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}
```
而不是:
```JSON
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
```
## 总结
你可以添加多个请求体参数到*路径操作函数*中,即使一个请求只能有一个请求体。
但是 **FastAPI** 会处理它,在函数中为你提供正确的数据,并在*路径操作*中校验并记录正确的模式。
你还可以声明将作为请求体的一部分所接收的单一值。
你还可以指示 **FastAPI** 在仅声明了一个请求体参数的情况下,将原本的请求体嵌入到一个键中。
+231
View File
@@ -0,0 +1,231 @@
# 请求体 - 嵌套模型
使用 **FastAPI**,你可以定义、校验、记录文档并使用任意深度嵌套的模型(归功于Pydantic)。
## List 字段
你可以将一个属性定义为拥有子元素的类型。例如 Python `list`
{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *}
这将使 `tags` 成为一个由元素组成的列表。不过它没有声明每个元素的类型。
## 具有子类型的 List 字段
但是 Python 有一种特定的方法来声明具有子类型的列表:
### 从 typing 导入 `List`
首先,从 Python 的标准库 `typing` 模块中导入 `List`
{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *}
### 声明具有子类型的 List
要声明具有子类型的类型,例如 `list``dict``tuple`
*`typing` 模块导入它们
* 使用方括号 `[``]` 将子类型作为「类型参数」传入
```Python
from typing import List
my_list: List[str]
```
这完全是用于类型声明的标准 Python 语法。
对具有子类型的模型属性也使用相同的标准语法。
因此,在我们的示例中,我们可以将 `tags` 明确地指定为一个「字符串列表」:
{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *}
## Set 类型
但是随后我们考虑了一下,意识到标签不应该重复,它们很大可能会是唯一的字符串。
Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `set`
然后我们可以导入 `Set` 并将 `tag` 声明为一个由 `str` 组成的 `set`
{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *}
这样,即使你收到带有重复数据的请求,这些数据也会被转换为一组唯一项。
而且,每当你输出该数据时,即使源数据有重复,它们也将作为一组唯一项输出。
并且还会被相应地标注 / 记录文档。
## 嵌套模型
Pydantic 模型的每个属性都具有类型。
但是这个类型本身可以是另一个 Pydantic 模型。
因此,你可以声明拥有特定属性名称、类型和校验的深度嵌套的 JSON 对象。
上述这些都可以任意的嵌套。
### 定义子模型
例如,我们可以定义一个 `Image` 模型:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *}
### 将子模型用作类型
然后我们可以将其用作一个属性的类型:
{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *}
这意味着 **FastAPI** 将期望类似于以下内容的请求体:
```JSON
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
```
再一次,仅仅进行这样的声明,你将通过 **FastAPI** 获得:
* 对被嵌入的模型也适用的编辑器支持(自动补全等)
* 数据转换
* 数据校验
* 自动生成文档
## 特殊的类型和校验
除了普通的单一值类型(如 `str``int``float` 等)外,你还可以使用从 `str` 继承的更复杂的单一值类型。
要了解所有的可用选项,请查看关于 <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">来自 Pydantic 的外部类型</a> 的文档。你将在下一章节中看到一些示例。
例如,在 `Image` 模型中我们有一个 `url` 字段,我们可以把它声明为 Pydantic 的 `HttpUrl`,而不是 `str`
{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *}
该字符串将被检查是否为有效的 URL,并在 JSON Schema / OpenAPI 文档中进行记录。
## 带有一组子模型的属性
你还可以将 Pydantic 模型用作 `list``set` 等的子类型:
{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *}
这将期望(转换,校验,记录文档等)下面这样的 JSON 请求体:
```JSON hl_lines="11"
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
```
/// info
请注意 `images` 键现在具有一组 image 对象是如何发生的。
///
## 深度嵌套模型
你可以定义任意深度的嵌套模型:
{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *}
/// info
请注意 `Offer` 拥有一组 `Item` 而反过来 `Item` 又是一个可选的 `Image` 列表是如何发生的。
///
## 纯列表请求体
如果你期望的 JSON 请求体的最外层是一个 JSON `array`(即 Python `list`),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样:
```Python
images: List[Image]
```
例如:
{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *}
## 无处不在的编辑器支持
你可以随处获得编辑器支持。
即使是列表中的元素:
<img src="https://fastapi.tiangolo.com/img/tutorial/body-nested-models/image01.png">
如果你直接使用 `dict` 而不是 Pydantic 模型,那你将无法获得这种编辑器支持。
但是你根本不必担心这两者,传入的字典会自动被转换,你的输出也会自动被转换为 JSON。
## 任意 `dict` 构成的请求体
你也可以将请求体声明为使用某类型的键和其他类型值的 `dict`。
无需事先知道有效的字段/属性(在使用 Pydantic 模型的场景)名称是什么。
如果你想接收一些尚且未知的键,这将很有用。
---
其他有用的场景是当你想要接收其他类型的键时,例如 `int`。
这也是我们在接下来将看到的。
在下面的例子中,你将接受任意键为 `int` 类型并且值为 `float` 类型的 `dict`
{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *}
/// tip
请记住 JSON 仅支持将 `str` 作为键。
但是 Pydantic 具有自动转换数据的功能。
这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。
然后你接收的名为 `weights` 的 `dict` 实际上将具有 `int` 类型的键和 `float` 类型的值。
///
## 总结
使用 **FastAPI** 你可以拥有 Pydantic 模型提供的极高灵活性,同时保持代码的简单、简短和优雅。
而且还具有下列好处:
* 编辑器支持(处处皆可自动补全!)
* 数据转换(也被称为解析/序列化)
* 数据校验
* 模式文档
* 自动生成的文档
+99
View File
@@ -0,0 +1,99 @@
# 请求体 - 更新数据
## 用 `PUT` 更新数据
更新数据请用 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/PUT" class="external-link" target="_blank">HTTP `PUT`</a> 操作。
把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 `jsonable_encoder`。例如,把 `datetime` 转换为 `str`
{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *}
`PUT` 用于接收替换现有数据的数据。
### 关于更新数据的警告
`PUT` 把数据项 `bar` 更新为以下内容时:
```Python
{
"name": "Barz",
"price": 3,
"description": None,
}
```
因为上述数据未包含已存储的属性 `"tax": 20.2`,新的输入模型会把 `"tax": 10.5` 作为默认值。
因此,本次操作把 `tax` 的值「更新」为 `10.5`
## 用 `PATCH` 进行部分更新
<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/PATCH" class="external-link" target="_blank">HTTP `PATCH`</a> 操作用于更新 *部分* 数据。
即,只发送要更新的数据,其余数据保持不变。
/// note | 笔记
`PATCH` 没有 `PUT` 知名,也怎么不常用。
很多人甚至只用 `PUT` 实现部分更新。
**FastAPI** 对此没有任何限制,可以**随意**互换使用这两种操作。
但本指南也会分别介绍这两种操作各自的用途。
///
### 使用 Pydantic 的 `exclude_unset` 参数
更新部分数据时,可以在 Pydantic 模型的 `.dict()` 中使用 `exclude_unset` 参数。
比如,`item.dict(exclude_unset=True)`
这段代码生成的 `dict` 只包含创建 `item` 模型时显式设置的数据,而不包括默认值。
然后再用它生成一个只含已设置(在请求中所发送)数据,且省略了默认值的 `dict`
{* ../../docs_src/body_updates/tutorial002.py hl[34] *}
### 使用 Pydantic 的 `update` 参数
接下来,用 `.copy()` 为已有模型创建调用 `update` 参数的副本,该参数为包含更新数据的 `dict`
例如,`stored_item_model.copy(update=update_data)`
{* ../../docs_src/body_updates/tutorial002.py hl[35] *}
### 更新部分数据小结
简而言之,更新部分数据应:
* 使用 `PATCH` 而不是 `PUT` (可选,也可以用 `PUT`);
* 提取存储的数据;
* 把数据放入 Pydantic 模型;
* 生成不含输入模型默认值的 `dict` (使用 `exclude_unset` 参数);
* 只更新用户设置过的值,不用模型中的默认值覆盖已存储过的值。
* 为已存储的模型创建副本,用接收的数据更新其属性 (使用 `update` 参数)。
* 把模型副本转换为可存入数据库的形式(比如,使用 `jsonable_encoder`)。
* 这种方式与 Pydantic 模型的 `.dict()` 方法类似,但能确保把值转换为适配 JSON 的数据类型,例如, 把 `datetime` 转换为 `str`
* 把数据保存至数据库;
* 返回更新后的模型。
{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *}
/// tip | 提示
实际上,HTTP `PUT` 也可以完成相同的操作。
但本节以 `PATCH` 为例的原因是,该操作就是为了这种用例创建的。
///
/// note | 笔记
注意,输入模型仍需验证。
因此,如果希望接收的部分更新数据可以省略其他所有属性,则要把模型中所有的属性标记为可选(使用默认值或 `None`)。
为了区分用于**更新**所有可选值的模型与用于**创建**包含必选值的模型,请参照[更多模型](extra-models.md){.internal-link target=_blank} 一节中的思路。
///
+162
View File
@@ -0,0 +1,162 @@
# 请求体
FastAPI 使用**请求体**从客户端(例如浏览器)向 API 发送数据。
**请求体**是客户端发送给 API 的数据。**响应体**是 API 发送给客户端的数据。
API 基本上肯定要发送**响应体**,但是客户端不一定发送**请求体**。
使用 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 模型声明**请求体**,能充分利用它的功能和优点。
/// info | 说明
发送数据使用 `POST`(最常用)、`PUT``DELETE``PATCH` 等操作。
规范中没有定义使用 `GET` 发送请求体的操作,但不管怎样,FastAPI 也支持这种方式,只不过仅用于非常复杂或极端的用例。
我们不建议使用 `GET`,因此,在 Swagger UI 交互文档中不会显示有关 `GET` 的内容,而且代理协议也不一定支持 `GET`
///
## 导入 Pydantic 的 `BaseModel`
`pydantic` 中导入 `BaseModel`
{* ../../docs_src/body/tutorial001_py310.py hl[2] *}
## 创建数据模型
把数据模型声明为继承 `BaseModel` 的类。
使用 Python 标准类型声明所有属性:
{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *}
与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。默认值为 `None` 的模型属性也是可选的。
例如,上述模型声明如下 JSON **对象**(即 Python **字典**):
```JSON
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
```
……由于 `description``tax` 是可选的(默认值为 `None`),下面的 JSON **对象**也有效:
```JSON
{
"name": "Foo",
"price": 45.2
}
```
## 声明请求体参数
使用与声明路径和查询参数相同的方式声明请求体,把请求体添加至*路径操作*:
{* ../../docs_src/body/tutorial001_py310.py hl[16] *}
……此处,请求体参数的类型为 `Item` 模型。
## 结论
仅使用 Python 类型声明,**FastAPI** 就可以:
* 以 JSON 形式读取请求体
* (在必要时)把请求体转换为对应的类型
* 校验数据:
* 数据无效时返回错误信息,并指出错误数据的确切位置和内容
* 把接收的数据赋值给参数 `item`
* 把函数中请求体参数的类型声明为 `Item`,还能获得代码补全等编辑器支持
* 为模型生成 <a href="https://json-schema.org" class="external-link" target="_blank">JSON Schema</a>,在项目中所需的位置使用
* 这些概图是 OpenAPI 概图的部件,用于 API 文档 <abbr title="用户界面">UI</abbr>
## API 文档
Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文档中显示:
<img src="/img/tutorial/body/image01.png">
而且,还会用于 API 文档中使用了概图的*路径操作*:
<img src="/img/tutorial/body/image02.png">
## 编辑器支持
在编辑器中,函数内部均可使用类型提示、代码补全(如果接收的不是 Pydantic 模型,而是**字典**,就没有这样的支持):
<img src="/img/tutorial/body/image03.png">
还支持检查错误的类型操作:
<img src="/img/tutorial/body/image04.png">
这并非偶然,整个 **FastAPI** 框架都是围绕这种思路精心设计的。
并且,在 FastAPI 的设计阶段,我们就已经进行了全面测试,以确保 FastAPI 可以获得所有编辑器的支持。
我们还改进了 Pydantic,让它也支持这些功能。
虽然上面的截图取自 <a href="https://code.visualstudio.com" class="external-link" target="_blank">Visual Studio Code</a>。
但 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 和大多数 Python 编辑器也支持同样的功能:
<img src="/img/tutorial/body/image05.png">
/// tip | 提示
使用 <a href="https://www.jetbrains.com/pycharm/" class="external-link" target="_blank">PyCharm</a> 编辑器时,推荐安装 <a href="https://github.com/koxudaxi/pydantic-pycharm-plugin/" class="external-link" target="_blank">Pydantic PyCharm 插件</a>。
该插件用于完善 PyCharm 对 Pydantic 模型的支持,优化的功能如下:
* 自动补全
* 类型检查
* 代码重构
* 查找
* 代码审查
///
## 使用模型
在*路径操作*函数内部直接访问模型对象的属性:
{* ../../docs_src/body/tutorial002_py310.py hl[19] *}
## 请求体 + 路径参数
**FastAPI** 支持同时声明路径参数和请求体。
**FastAPI** 能识别与**路径参数**匹配的函数参数,还能识别从**请求体**中获取的类型为 Pydantic 模型的函数参数。
{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *}
## 请求体 + 路径参数 + 查询参数
**FastAPI** 支持同时声明**请求体**、**路径参数**和**查询参数**。
**FastAPI** 能够正确识别这三种参数,并从正确的位置获取数据。
{* ../../docs_src/body/tutorial004_py310.py hl[16] *}
函数参数按如下规则进行识别:
- **路径**中声明了相同参数的参数,是路径参数
- 类型是(`int``float``str``bool` 等)**单类型**的参数,是**查询**参数
- 类型是 **Pydantic 模型**的参数,是**请求体**
/// note | 笔记
因为默认值是 `None` FastAPI 会把 `q` 当作可选参数。
FastAPI 不使用 `Optional[str]` 中的 `Optional``Optional` 可以让编辑器提供更好的支持,并检测错误。
///
## 不使用 Pydantic
即便不使用 Pydantic 模型也能使用 **Body** 参数。详见[请求体 - 多参数:请求体中的单值](body-multiple-params.md#_2){.internal-link target=\_blank}。
@@ -0,0 +1,76 @@
# Cookie 参数模型
如果您有一组相关的 **cookie**,您可以创建一个 **Pydantic 模型**来声明它们。🍪
这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证方式和元数据。😎
/// note
自 FastAPI 版本 `0.115.0` 起支持此功能。🤓
///
/// tip
此技术同样适用于 `Query``Cookie``Header` 。😎
///
## 带有 Pydantic 模型的 Cookie
**Pydantic** 模型中声明所需的 **cookie** 参数,然后将参数声明为 `Cookie`
{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *}
**FastAPI** 将从请求中接收到的 **cookie** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
## 查看文档
您可以在文档 UI 的 `/docs` 中查看定义的 cookie
<div class="screenshot">
<img src="/img/tutorial/cookie-param-models/image01.png">
</div>
/// info
请记住,由于**浏览器**以特殊方式**处理 cookie**,并在后台进行操作,因此它们**不会**轻易允许 **JavaScript** 访问这些 cookie。
如果您访问 `/docs`**API 文档 UI**,您将能够查看您*路径操作*的 cookie **文档**
但是即使您**填写数据**并点击“执行”,由于文档界面使用 **JavaScript**,cookie 将不会被发送。而您会看到一条**错误**消息,就好像您没有输入任何值一样。
///
## 禁止额外的 Cookie
在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 cookie。
您的 API 现在可以控制自己的 <abbr title="顺带一提,这是一个笑话。它与 cookie 同意无关,但现在连API都能拒绝那些可怜的 cookie,真是太有意思了。来,吃块小饼干(cookie)吧。🍪">cookie 同意</abbr>。🤪🍪
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *}
如果客户尝试发送一些**额外的 cookie**,他们将收到**错误**响应。
可怜的 cookie 通知条,费尽心思为了获得您的同意,却被<abbr title="这又是一个笑话,别管我了,给您的小饼干(cookie)配上点咖啡吧。☕">API 拒绝了</abbr>。🍪
例如,如果客户端尝试发送一个值为 `good-list-please``santa_tracker` cookie,客户端将收到一个**错误**响应,告知他们 `santa_tracker` <abbr title="圣诞老人(Santa)不赞成没有小饼干(cookie)。🎅 好吧,不会再开 cookie 的玩笑了。">cookie 是不允许的</abbr>
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["cookie", "santa_tracker"],
"msg": "Extra inputs are not permitted",
"input": "good-list-please",
}
]
}
```
## 总结
您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 <abbr title="走之前再来块小饼干吧。 🍪">**cookie**</abbr>。😎
+36
View File
@@ -0,0 +1,36 @@
# Cookie 参数
定义 `Cookie` 参数与定义 `Query``Path` 参数一样。
## 导入 `Cookie`
首先,导入 `Cookie`
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *}
## 声明 `Cookie` 参数
声明 `Cookie` 参数的方式与声明 `Query``Path` 参数相同。
第一个值是默认值,还可以传递所有验证参数或注释参数:
{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *}
/// note | 技术细节
`Cookie``Path``Query` 是**兄弟类**,都继承自共用的 `Param` 类。
注意,从 `fastapi` 导入的 `Query``Path``Cookie` 等对象,实际上是返回特殊类的函数。
///
/// info | 说明
必须使用 `Cookie` 声明 cookie 参数,否则该参数会被解释为查询参数。
///
## 小结
使用 `Cookie` 声明 cookie 参数的方式与 `Query``Path` 相同。
+85
View File
@@ -0,0 +1,85 @@
# CORS(跨域资源共享)
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">CORS 或者「跨域资源共享」</a> 指浏览器中运行的前端拥有与后端通信的 JavaScript 代码,而后端处于与前端不同的「源」的情况。
## 源
源是协议(`http``https`)、域(`myapp.com``localhost``localhost.tiangolo.com`)以及端口(`80``443``8080`)的组合。
因此,这些都是不同的源:
* `http://localhost`
* `https://localhost`
* `http://localhost:8080`
即使它们都在 `localhost` 中,但是它们使用不同的协议或者端口,所以它们都是不同的「源」。
## 步骤
假设你的浏览器中有一个前端运行在 `http://localhost:8080`,并且它的 JavaScript 正在尝试与运行在 `http://localhost` 的后端通信(因为我们没有指定端口,浏览器会采用默认的端口 `80`)。
然后,浏览器会向后端发送一个 HTTP `OPTIONS` 请求,如果后端发送适当的 headers 来授权来自这个不同源(`http://localhost:8080`)的通信,浏览器将允许前端的 JavaScript 向后端发送请求。
为此,后端必须有一个「允许的源」列表。
在这种情况下,它必须包含 `http://localhost:8080`,前端才能正常工作。
## 通配符
也可以使用 `"*"`(一个「通配符」)声明这个列表,表示全部都是允许的。
但这仅允许某些类型的通信,不包括所有涉及凭据的内容:像 Cookies 以及那些使用 Bearer 令牌的授权 headers 等。
因此,为了一切都能正常工作,最好显式地指定允许的源。
## 使用 `CORSMiddleware`
你可以在 **FastAPI** 应用中使用 `CORSMiddleware` 来配置它。
* 导入 `CORSMiddleware`
* 创建一个允许的源列表(由字符串组成)。
* 将其作为「中间件」添加到你的 **FastAPI** 应用中。
你也可以指定后端是否允许:
* 凭证(授权 headersCookies 等)。
* 特定的 HTTP 方法(`POST``PUT`)或者使用通配符 `"*"` 允许所有方法。
* 特定的 HTTP headers 或者使用通配符 `"*"` 允许所有 headers。
{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *}
默认情况下,这个 `CORSMiddleware` 实现所使用的默认参数较为保守,所以你需要显式地启用特定的源、方法或者 headers,以便浏览器能够在跨域上下文中使用它们。
支持以下参数:
* `allow_origins` - 一个允许跨域请求的源列表。例如 `['https://example.org', 'https://www.example.org']`。你可以使用 `['*']` 允许任何源。
* `allow_origin_regex` - 一个正则表达式字符串,匹配的源允许跨域请求。例如 `'https://.*\.example\.org'`
* `allow_methods` - 一个允许跨域请求的 HTTP 方法列表。默认为 `['GET']`。你可以使用 `['*']` 来允许所有标准方法。
* `allow_headers` - 一个允许跨域请求的 HTTP 请求头列表。默认为 `[]`。你可以使用 `['*']` 允许所有的请求头。`Accept``Accept-Language``Content-Language` 以及 `Content-Type` 请求头总是允许 CORS 请求。
* `allow_credentials` - 指示跨域请求支持 cookies。默认是 `False`。另外,允许凭证时 `allow_origins` 不能设定为 `['*']`,必须指定源。
* `expose_headers` - 指示可以被浏览器访问的响应头。默认为 `[]`
* `max_age` - 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 `600`
中间件响应两种特定类型的 HTTP 请求……
### CORS 预检请求
这是些带有 `Origin``Access-Control-Request-Method` 请求头的 `OPTIONS` 请求。
在这种情况下,中间件将拦截传入的请求并进行响应,出于提供信息的目的返回一个使用了适当的 CORS headers 的 `200``400` 响应。
### 简单请求
任何带有 `Origin` 请求头的请求。在这种情况下,中间件将像平常一样传递请求,但是在响应中包含适当的 CORS headers。
## 更多信息
更多关于 <abbr title="Cross-Origin Resource Sharing">CORS</abbr> 的信息,请查看 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" class="external-link" target="_blank">Mozilla CORS 文档</a>。
/// note | 技术细节
你也可以使用 `from starlette.middleware.cors import CORSMiddleware`
出于方便,**FastAPI** 在 `fastapi.middleware` 中为开发者提供了几个中间件。但是大多数可用的中间件都是直接来自 Starlette。
///
+109
View File
@@ -0,0 +1,109 @@
# 调试
你可以在编辑器中连接调试器,例如使用 Visual Studio Code 或 PyCharm。
## 调用 `uvicorn`
在你的 FastAPI 应用中直接导入 `uvicorn` 并运行:
{* ../../docs_src/debugging/tutorial001.py hl[1,15] *}
### 关于 `__name__ == "__main__"`
`__name__ == "__main__"` 的主要目的是使用以下代码调用文件时执行一些代码:
<div class="termy">
```console
$ python myapp.py
```
</div>
而当其它文件导入它时并不会被调用,像这样:
```Python
from myapp import app
```
#### 更多细节
假设你的文件命名为 `myapp.py`
如果你这样运行:
<div class="termy">
```console
$ python myapp.py
```
</div>
那么文件中由 Python 自动创建的内部变量 `__name__`,会将字符串 `"__main__"` 作为值。
所以,下面这部分代码才会运行:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
---
如果你是导入这个模块(文件)就不会这样。
因此,如果你的另一个文件 `importer.py` 像这样:
```Python
from myapp import app
# Some more code
```
在这种情况下,`myapp.py` 内部的自动变量不会有值为 `"__main__"` 的变量 `__name__`
所以,下面这一行不会被执行:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
/// info
更多信息请检查 <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">Python 官方文档</a>.
///
## 使用你的调试器运行代码
由于是从代码直接运行的 Uvicorn 服务器,所以你可以从调试器直接调用 Python 程序(你的 FastAPI 应用)。
---
例如,你可以在 Visual Studio Code 中:
* 进入到「调试」面板。
* 「添加配置...」。
* 选中「Python」
* 运行「Python:当前文件(集成终端)」选项的调试器。
然后它会使用你的 **FastAPI** 代码开启服务器,停在断点处,等等。
看起来可能是这样:
<img src="/img/tutorial/debugging/image01.png">
---
如果使用 Pycharm,你可以:
* 打开「运行」菜单。
* 选中「调试...」。
* 然后出现一个上下文菜单。
* 选择要调试的文件(本例中的 `main.py`)。
然后它会使用你的 **FastAPI** 代码开启服务器,停在断点处,等等。
看起来可能是这样:
<img src="/img/tutorial/debugging/image02.png">
@@ -0,0 +1,180 @@
# 类作为依赖项
在深入探究 **依赖注入** 系统之前,让我们升级之前的例子。
## 来自前一个例子的`dict`
在前面的例子中, 我们从依赖项 ("可依赖对象") 中返回了一个 `dict`:
{* ../../docs_src/dependencies/tutorial001_py310.py hl[7] *}
但是后面我们在路径操作函数的参数 `commons` 中得到了一个 `dict`
我们知道编辑器不能为 `dict` 提供很多支持(比如补全),因为编辑器不知道 `dict` 的键和值类型。
对此,我们可以做的更好...
## 什么构成了依赖项?
到目前为止,您看到的依赖项都被声明为函数。
但这并不是声明依赖项的唯一方法(尽管它可能是更常见的方法)。
关键因素是依赖项应该是 "可调用对象"。
Python 中的 "**可调用对象**" 是指任何 Python 可以像函数一样 "调用" 的对象。
所以,如果你有一个对象 `something` (可能*不是*一个函数),你可以 "调用" 它(执行它),就像:
```Python
something()
```
或者
```Python
something(some_argument, some_keyword_argument="foo")
```
这就是 "可调用对象"。
## 类作为依赖项
您可能会注意到,要创建一个 Python 类的实例,您可以使用相同的语法。
举个例子:
```Python
class Cat:
def __init__(self, name: str):
self.name = name
fluffy = Cat(name="Mr Fluffy")
```
在这个例子中, `fluffy` 是一个 `Cat` 类的实例。
为了创建 `fluffy`,你调用了 `Cat`
所以,Python 类也是 **可调用对象**
因此,在 **FastAPI** 中,你可以使用一个 Python 类作为一个依赖项。
实际上 FastAPI 检查的是它是一个 "可调用对象"(函数,类或其他任何类型)以及定义的参数。
如果您在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。
这也适用于完全没有参数的可调用对象。这与不带参数的路径操作函数一样。
所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`:
{* ../../docs_src/dependencies/tutorial002_py310.py hl[9:13] *}
注意用于创建类实例的 `__init__` 方法:
{* ../../docs_src/dependencies/tutorial002_py310.py hl[10] *}
...它与我们以前的 `common_parameters` 具有相同的参数:
{* ../../docs_src/dependencies/tutorial001_py310.py hl[6] *}
这些参数就是 **FastAPI** 用来 "处理" 依赖项的。
在两个例子下,都有:
* 一个可选的 `q` 查询参数,是 `str` 类型。
* 一个 `skip` 查询参数,是 `int` 类型,默认值为 `0`
* 一个 `limit` 查询参数,是 `int` 类型,默认值为 `100`
在两个例子下,数据都将被转换、验证、在 OpenAPI schema 上文档化,等等。
## 使用它
现在,您可以使用这个类来声明你的依赖项了。
{* ../../docs_src/dependencies/tutorial002_py310.py hl[17] *}
**FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。
## 类型注解 vs `Depends`
注意,我们在上面的代码中编写了两次`CommonQueryParams`
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
最后的 `CommonQueryParams`:
```Python
... = Depends(CommonQueryParams)
```
...实际上是 **Fastapi** 用来知道依赖项是什么的。
FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用的。
---
在本例中,第一个 `CommonQueryParams`
```Python
commons: CommonQueryParams ...
```
...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `= Depends(CommonQueryParams)`)。
你实际上可以只这样编写:
```Python
commons = Depends(CommonQueryParams)
```
..就像:
{* ../../docs_src/dependencies/tutorial003_py310.py hl[17] *}
但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等:
<img src="/img/tutorial/dependencies/image02.png">
## 快捷方式
但是您可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次:
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
**FastAPI** 为这些情况提供了一个快捷方式,在这些情况下,依赖项 *明确地* 是一个类,**FastAPI** 将 "调用" 它来创建类本身的一个实例。
对于这些特定的情况,您可以跟随以下操作:
不是写成这样:
```Python
commons: CommonQueryParams = Depends(CommonQueryParams)
```
...而是这样写:
```Python
commons: CommonQueryParams = Depends()
```
您声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 编写完整的类。
同样的例子看起来像这样:
{* ../../docs_src/dependencies/tutorial004_py310.py hl[17] *}
... **FastAPI** 会知道怎么处理。
/// tip
如果这看起来更加混乱而不是更加有帮助,那么请忽略它,你不*需要*它。
这只是一个快捷方式。因为 **FastAPI** 关心的是帮助您减少代码重复。
///
@@ -0,0 +1,69 @@
# 路径操作装饰器依赖项
有时,我们并不需要在*路径操作函数*中使用依赖项的返回值。
或者说,有些依赖项不返回值。
但仍要执行或解析该依赖项。
对于这种情况,不必在声明*路径操作函数*的参数时使用 `Depends`,而是可以在*路径操作装饰器*中添加一个由 `dependencies` 组成的 `list`
## 在*路径操作装饰器*中添加 `dependencies` 参数
*路径操作装饰器*支持可选参数 ~ `dependencies`
该参数的值是由 `Depends()` 组成的 `list`
{* ../../docs_src/dependencies/tutorial006.py hl[17] *}
路径操作装饰器依赖项(以下简称为**“路径装饰器依赖项”**)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。
/// tip | 提示
有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。
在*路径操作装饰器*中使用 `dependencies` 参数,可以确保在执行依赖项的同时,避免编辑器显示错误提示。
使用路径装饰器依赖项还可以避免开发新人误会代码中包含无用的未使用参数。
///
/// info | 说明
本例中,使用的是自定义响应头 `X-Key``X-Token`
但实际开发中,尤其是在实现安全措施时,最好使用 FastAPI 内置的[安全工具](../security/index.md){.internal-link target=_blank}(详见下一章)。
///
## 依赖项错误和返回值
路径装饰器依赖项也可以使用普通的依赖项*函数*。
### 依赖项的需求项
路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项:
{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *}
### 触发异常
路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常:
{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *}
### 返回值
无论路径装饰器依赖项是否返回值,路径操作都不会使用这些值。
因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项:
{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *}
## 为一组路径操作定义依赖项
稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=\_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。
## 全局依赖项
接下来,我们将学习如何为 `FastAPI` 应用程序添加全局依赖项,创建应用于每个*路径操作*的依赖项。
@@ -0,0 +1,267 @@
# 使用yield的依赖项
FastAPI支持在完成后执行一些<abbr title='有时也被称为"退出"("exit")"清理"("cleanup")"拆卸"("teardown")"关闭"("close")"上下文管理器"("context managers")。 ...'>额外步骤</abbr>的依赖项.
为此,你需要使用 `yield` 而不是 `return`,然后再编写这些额外的步骤(代码)。
/// tip | 提示
确保在每个依赖中只使用一次 `yield`
///
/// note | 技术细节
任何一个可以与以下内容一起使用的函数:
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> 或者
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
都可以作为 **FastAPI** 的依赖项。
实际上,FastAPI内部就使用了这两个装饰器。
///
## 使用 `yield` 的数据库依赖项
例如,你可以使用这种方式创建一个数据库会话,并在完成后关闭它。
在发送响应之前,只会执行 `yield` 语句及之前的代码:
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
生成的值会注入到 *路由函数* 和其他依赖项中:
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
`yield` 语句后面的代码会在创建响应后,发送响应前执行:
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
/// tip | 提示
你可以使用 `async` 或普通函数。
**FastAPI** 会像处理普通依赖一样,对每个依赖做正确的处理。
///
## 包含 `yield` 和 `try` 的依赖项
如果在包含 `yield` 的依赖中使用 `try` 代码块,你会捕获到使用依赖时抛出的任何异常。
例如,如果某段代码在另一个依赖中或在 *路由函数* 中使数据库事务"回滚"或产生任何其他错误,你将会在依赖中捕获到异常。
因此,你可以使用 `except SomeException` 在依赖中捕获特定的异常。
同样,你也可以使用 `finally` 来确保退出步骤得到执行,无论是否存在异常。
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
## 使用 `yield` 的子依赖项
你可以声明任意数量和层级的树状依赖,而且它们中的任何一个或所有的都可以使用 `yield`
**FastAPI** 会确保每个带有 `yield` 的依赖中的"退出代码"按正确顺序运行。
例如,`dependency_c` 可以依赖于 `dependency_b`,而 `dependency_b` 则依赖于 `dependency_a`
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
所有这些依赖都可以使用 `yield`
在这种情况下,`dependency_c` 在执行其退出代码时需要 `dependency_b`(此处称为 `dep_b`)的值仍然可用。
`dependency_b` 反过来则需要 `dependency_a`(此处称为 `dep_a` )的值在其退出代码中可用。
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
同样,你可以混合使用带有 `yield``return` 的依赖。
你也可以声明一个依赖于多个带有 `yield` 的依赖,等等。
你可以拥有任何你想要的依赖组合。
**FastAPI** 将确保按正确的顺序运行所有内容。
/// note | 技术细节
这是由 Python 的<a href="https://docs.python.org/3/library/contextlib.html" class="external-link" target="_blank">上下文管理器</a>完成的。
**FastAPI** 在内部使用它们来实现这一点。
///
## 包含 `yield` 和 `HTTPException` 的依赖项
你可以使用带有 `yield` 的依赖项,并且可以包含 `try` 代码块用于捕获异常。
同样,你可以在 `yield` 之后的退出代码中抛出一个 `HTTPException` 或类似的异常。
/// tip | 提示
这是一种相对高级的技巧,在大多数情况下你并不需要使用它,因为你可以在其他代码中抛出异常(包括 `HTTPException` ),例如在 *路由函数* 中。
但是如果你需要,你也可以在依赖项中做到这一点。🤓
///
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
你还可以创建一个 [自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} 用于捕获异常(同时也可以抛出另一个 `HTTPException`)。
## 包含 `yield` 和 `except` 的依赖项
如果你在包含 `yield` 的依赖项中使用 `except` 捕获了一个异常,然后你没有重新抛出该异常(或抛出一个新异常),与在普通的Python代码中相同,FastAPI不会注意到发生了异常。
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
在示例代码的情况下,客户端将会收到 *HTTP 500 Internal Server Error* 的响应,因为我们没有抛出 `HTTPException` 或者类似的异常,并且服务器也 **不会有任何日志** 或者其他提示来告诉我们错误是什么。😱
### 在包含 `yield` 和 `except` 的依赖项中一定要 `raise`
如果你在使用 `yield` 的依赖项中捕获到了一个异常,你应该再次抛出捕获到的异常,除非你抛出 `HTTPException` 或类似的其他异常,
你可以使用 `raise` 再次抛出捕获到的异常。
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
现在客户端同样会得到 *HTTP 500 Internal Server Error* 响应,但是服务器日志会记录下我们自定义的 `InternalError`
## 使用 `yield` 的依赖项的执行
执行顺序大致如下时序图所示。时间轴从上到下,每一列都代表交互或者代码执行的一部分。
```mermaid
sequenceDiagram
participant client as Client
participant handler as Exception handler
participant dep as Dep with yield
participant operation as Path Operation
participant tasks as Background tasks
Note over client,operation: Can raise exceptions, including HTTPException
client ->> dep: Start request
Note over dep: Run code up to yield
opt raise Exception
dep -->> handler: Raise Exception
handler -->> client: HTTP error response
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
operation -->> dep: Raise Exception (e.g. HTTPException)
opt handle
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
end
handler -->> client: HTTP error response
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
opt Tasks
operation -->> tasks: Send background tasks
end
opt Raise other exception
tasks -->> tasks: Handle exceptions in the background task code
end
```
/// info | 说明
只会向客户端发送 **一次响应** ,可能是一个错误响应,也可能是来自 *路由函数* 的响应。
在发送了其中一个响应之后,就无法再发送其他响应了。
///
/// tip | 提示
这个时序图展示了 `HTTPException`,除此之外你也可以抛出任何你在使用 `yield` 的依赖项中或者[自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}中捕获的异常。
如果你引发任何异常,它将传递给使用 `yield` 的依赖项,包括 `HTTPException`。在大多数情况下你应当从使用 `yield` 的依赖项中重新抛出捕获的异常或者一个新的异常来确保它会被正确的处理。
///
## 包含 `yield`, `HTTPException`, `except` 的依赖项和后台任务
/// warning | 注意
你大概率不需要了解这些技术细节,可以跳过这一章节继续阅读后续的内容。
如果你使用的FastAPI的版本早于0.106.0,并且在使用后台任务中使用了包含 `yield` 的依赖项中的资源,那么这些细节会对你有一些用处。
///
### 包含 `yield` 和 `except` 的依赖项的技术细节
在FastAPI 0.110.0版本之前,如果使用了一个包含 `yield` 的依赖项,你在依赖项中使用 `except` 捕获了一个异常,但是你没有再次抛出该异常,这个异常会被自动抛出/转发到异常处理器或者内部服务错误处理器。
### 后台任务和使用 `yield` 的依赖项的技术细节
在FastAPI 0.106.0版本之前,在 `yield` 后面抛出异常是不可行的,因为 `yield` 之后的退出代码是在响应被发送之后再执行,这个时候异常处理器已经执行过了。
这样设计的目的主要是为了允许在后台任务中使用被依赖项`yield`的对象,因为退出代码会在后台任务结束后再执行。
然而这也意味着在等待响应通过网络传输的同时,非必要的持有一个 `yield` 依赖项中的资源(例如数据库连接),这一行为在FastAPI 0.106.0被改变了。
/// tip | 提示
除此之外,后台任务通常是一组独立的逻辑,应该被单独处理,并且使用它自己的资源(例如它自己的数据库连接)。
这样也会让你的代码更加简洁。
///
如果你之前依赖于这一行为,那么现在你应该在后台任务中创建并使用它自己的资源,不要在内部使用属于 `yield` 依赖项的资源。
例如,你应该在后台任务中创建一个新的数据库会话用于查询数据,而不是使用相同的会话。你应该将对象的ID作为参数传递给后台任务函数,然后在该函数中重新获取该对象,而不是直接将数据库对象作为参数。
## 上下文管理器
### 什么是"上下文管理器"
"上下文管理器"是你可以在 `with` 语句中使用的任何Python对象。
例如,<a href="https://docs.python.org/zh-cn/3/tutorial/inputoutput.html#reading-and-writing-files" class="external-link" target="_blank">你可以使用`with`读取文件</a>
```Python
with open("./somefile.txt") as f:
contents = f.read()
print(contents)
```
在底层,`open("./somefile.txt")`创建了一个被称为"上下文管理器"的对象。
`with` 代码块结束时,它会确保关闭文件,即使发生了异常也是如此。
当你使用 `yield` 创建一个依赖项时,**FastAPI** 会在内部将其转换为上下文管理器,并与其他相关工具结合使用。
### 在使用 `yield` 的依赖项中使用上下文管理器
/// warning | 注意
这是一个更为"高级"的想法。
如果你刚开始使用 **FastAPI** ,你可以暂时可以跳过它。
///
在Python中,你可以通过<a href="https://docs.python.org/3/reference/datamodel.html#context-managers" class="external-link" target="_blank">创建一个带有`__enter__()``__exit__()`方法的类</a>来创建上下文管理器。
你也可以在 **FastAPI**`yield` 依赖项中通过 `with` 或者 `async with` 语句来使用它们:
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
/// tip | 提示
另一种创建上下文管理器的方法是:
* <a href="https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a>或者
* <a href="https://docs.python.org/zh-cn/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
使用它们装饰一个只有单个 `yield` 的函数。这就是 **FastAPI** 内部对于 `yield` 依赖项的处理方式。
但是你不需要为FastAPI的依赖项使用这些装饰器(而且也不应该)。FastAPI会在内部为你处理这些。
///
@@ -0,0 +1,15 @@
# 全局依赖项
有时,我们要为整个应用添加依赖项。
通过与定义[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。
这样一来,就可以为所有*路径操作*应用该依赖项:
{* ../../docs_src/dependencies/tutorial012.py hl[15] *}
[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。
## 为一组路径操作定义依赖项
稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。
+212
View File
@@ -0,0 +1,212 @@
# 依赖项
FastAPI 提供了简单易用,但功能强大的**<abbr title="也称为组件、资源、提供者、服务、可注入项">依赖注入</abbr>**系统。
这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 **FastAPI**
## 什么是「依赖注入」
编程中的**「依赖注入」**是声明代码(本文中为*路径操作函数* )运行所需的,或要使用的「依赖」的一种方式。
然后,由系统(本文中为 **FastAPI**)负责执行任意需要的逻辑,为代码提供这些依赖(「注入」依赖项)。
依赖注入常用于以下场景:
* 共享业务逻辑(复用相同的代码逻辑)
* 共享数据库连接
* 实现安全、验证、角色权限
* 等……
上述场景均可以使用**依赖注入**,将代码重复最小化。
## 第一步
接下来,我们学习一个非常简单的例子,尽管它过于简单,不是很实用。
但通过这个例子,您可以初步了解「依赖注入」的工作机制。
### 创建依赖项
首先,要关注的是依赖项。
依赖项就是一个函数,且可以使用与*路径操作函数*相同的参数:
{* ../../docs_src/dependencies/tutorial001.py hl[8:11] *}
大功告成。
只用了**2 行**代码。
依赖项函数的形式和结构与*路径操作函数*一样。
因此,可以把依赖项当作没有「装饰器」(即,没有 `@app.get("/some-path")` )的路径操作函数。
依赖项可以返回各种内容。
本例中的依赖项预期接收如下参数:
* 类型为 `str` 的可选查询参数 `q`
* 类型为 `int` 的可选查询参数 `skip`,默认值是 `0`
* 类型为 `int` 的可选查询参数 `limit`,默认值是 `100`
然后,依赖项函数返回包含这些值的 `dict`
### 导入 `Depends`
{* ../../docs_src/dependencies/tutorial001.py hl[3] *}
### 声明依赖项
与在*路径操作函数*参数中使用 `Body``Query` 的方式相同,声明依赖项需要使用 `Depends` 和一个新的参数:
{* ../../docs_src/dependencies/tutorial001.py hl[15,20] *}
虽然,在路径操作函数的参数中使用 `Depends` 的方式与 `Body``Query` 相同,但 `Depends` 的工作方式略有不同。
这里只能传给 Depends 一个参数。
且该参数必须是可调用对象,比如函数。
该函数接收的参数和*路径操作函数*的参数一样。
/// tip | 提示
下一章介绍,除了函数还有哪些「对象」可以用作依赖项。
///
接收到新的请求时,**FastAPI** 执行如下操作:
* 用正确的参数调用依赖项函数(「可依赖项」)
* 获取函数返回的结果
* 把函数返回的结果赋值给*路径操作函数*的参数
```mermaid
graph TB
common_parameters(["common_parameters"])
read_items["/items/"]
read_users["/users/"]
common_parameters --> read_items
common_parameters --> read_users
```
这样,只编写一次代码,**FastAPI** 就可以为多个*路径操作*共享这段代码 。
/// check | 检查
注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。
只要把它传递给 `Depends`**FastAPI** 就知道该如何执行后续操作。
///
## 要不要使用 `async`
**FastAPI** 调用依赖项的方式与*路径操作函数*一样,因此,定义依赖项函数,也要应用与路径操作函数相同的规则。
即,既可以使用异步的 `async def`,也可以使用普通的 `def` 定义依赖项。
在普通的 `def` *路径操作函数*中,可以声明异步的 `async def` 依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项。
上述这些操作都是可行的,**FastAPI** 知道该怎么处理。
/// note | 笔记
如里不了解异步,请参阅[异步:*“着急了?”*](../../async.md){.internal-link target=_blank} 一章中 `async``await` 的内容。
///
## 与 OpenAPI 集成
依赖项及子依赖项的所有请求声明、验证和需求都可以集成至同一个 OpenAPI 概图。
所以,交互文档里也会显示依赖项的所有信息:
<img src="/img/tutorial/dependencies/image01.png">
## 简单用法
观察一下就会发现,只要*路径* 和*操作*匹配,就可以使用声明的路径操作函数。然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。
实际上,所有(或大多数)网络框架的工作方式都是这样的。
开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。
通过依赖注入系统,只要告诉 **FastAPI** *路径操作函数* 还要「依赖」其他在*路径操作函数*之前执行的内容,**FastAPI** 就会执行函数代码,并「注入」函数返回的结果。
其他与「依赖注入」概念相同的术语为:
* 资源(Resource
* 提供方(Provider
* 服务(Service
* 可注入(Injectable
* 组件(Component
## **FastAPI** 插件
**依赖注入**系统支持构建集成和「插件」。但实际上,FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。
创建依赖项非常简单、直观,并且还支持导入 Python 包。毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。
下一章将详细介绍在关系型数据库、NoSQL 数据库、安全等方面使用依赖项的例子。
## **FastAPI** 兼容性
依赖注入系统如此简洁的特性,让 **FastAPI** 可以与下列系统兼容:
* 关系型数据库
* NoSQL 数据库
* 外部支持库
* 外部 API
* 认证和鉴权系统
* API 使用监控系统
* 响应数据注入系统
* 等等……
## 简单而强大
虽然,**层级式依赖注入系统**的定义与使用十分简单,但它却非常强大。
比如,可以定义依赖其他依赖项的依赖项。
最后,依赖项层级树构建后,**依赖注入系统**会处理所有依赖项及其子依赖项,并为每一步操作提供(注入)结果。
比如,下面有 4 个 API 路径操作(*端点*):
* `/items/public/`
* `/items/private/`
* `/users/{user_id}/activate`
* `/items/pro/`
开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限:
```mermaid
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items
```
## 与 **OpenAPI** 集成
在声明需求时,所有这些依赖项还会把参数、验证等功能添加至路径操作。
**FastAPI** 负责把上述内容全部添加到 OpenAPI 概图,并显示在交互文档中。
@@ -0,0 +1,86 @@
# 子依赖项
FastAPI 支持创建含**子依赖项**的依赖项。
并且,可以按需声明任意**深度**的子依赖项嵌套层级。
**FastAPI** 负责处理解析不同深度的子依赖项。
### 第一层依赖项
下列代码创建了第一层依赖项:
{* ../../docs_src/dependencies/tutorial005.py hl[8:9] *}
这段代码声明了类型为 `str` 的可选查询参数 `q`,然后返回这个查询参数。
这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。
### 第二层依赖项
接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项(所以这也是一个「依赖项」):
{* ../../docs_src/dependencies/tutorial005.py hl[13] *}
这里重点说明一下声明的参数:
* 尽管该函数自身是依赖项,但还声明了另一个依赖项(它「依赖」于其他对象)
* 该函数依赖 `query_extractor`, 并把 `query_extractor` 的返回值赋给参数 `q`
* 同时,该函数还声明了类型是 `str` 的可选 cookie`last_query`
* 用户未提供查询参数 `q` 时,则使用上次使用后保存在 cookie 中的查询
### 使用依赖项
接下来,就可以使用依赖项:
{* ../../docs_src/dependencies/tutorial005.py hl[22] *}
/// info | 信息
注意,这里在*路径操作函数*中只声明了一个依赖项,即 `query_or_cookie_extractor`
**FastAPI** 必须先处理 `query_extractor`,以便在调用 `query_or_cookie_extractor` 时使用 `query_extractor` 返回的结果。
///
```mermaid
graph TB
query_extractor(["query_extractor"])
query_or_cookie_extractor(["query_or_cookie_extractor"])
read_query["/items/"]
query_extractor --> query_or_cookie_extractor --> read_query
```
## 多次使用同一个依赖项
如果在同一个*路径操作* 多次声明了同一个依赖项,例如,多个依赖项共用一个子依赖项,**FastAPI** 在处理同一请求时,只调用一次该子依赖项。
FastAPI 不会为同一个请求多次调用同一个依赖项,而是把依赖项的返回值进行<abbr title="一个实用程序/系统来存储计算/生成的值,以便重用它们,而不是再次计算它们。">「缓存」</abbr>,并把它传递给同一请求中所有需要使用该返回值的「依赖项」。
在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False` :
```Python hl_lines="1"
async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
return {"fresh_value": fresh_value}
```
## 小结
千万别被本章里这些花里胡哨的词藻吓倒了,其实**依赖注入**系统非常简单。
依赖注入无非是与*路径操作函数*一样的函数罢了。
但它依然非常强大,能够声明任意嵌套深度的「图」或树状的依赖结构。
/// tip | 提示
这些简单的例子现在看上去虽然没有什么实用价值,
但在**安全**一章中,您会了解到这些例子的用途,
以及这些例子所能节省的代码量。
///
+35
View File
@@ -0,0 +1,35 @@
# JSON 兼容编码器
在某些情况下,您可能需要将数据类型(如Pydantic模型)转换为与JSON兼容的数据类型(如`dict``list`等)。
比如,如果您需要将其存储在数据库中。
对于这种要求, **FastAPI**提供了`jsonable_encoder()`函数。
## 使用`jsonable_encoder`
让我们假设你有一个数据库名为`fake_db`,它只能接收与JSON兼容的数据。
例如,它不接收`datetime`这类的对象,因为这些对象与JSON不兼容。
因此,`datetime`对象必须将转换为包含<a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO格式化</a>的`str`类型对象。
同样,这个数据库也不会接收Pydantic模型(带有属性的对象),而只接收`dict`
对此你可以使用`jsonable_encoder`
它接收一个对象,比如Pydantic模型,并会返回一个JSON兼容的版本:
{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *}
在这个例子中,它将Pydantic模型转换为`dict`,并将`datetime`转换为`str`
调用它的结果后就可以使用Python标准编码中的<a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>。
这个操作不会返回一个包含JSON格式(作为字符串)数据的庞大的`str`。它将返回一个Python标准数据结构(例如`dict`),其值和子值都与JSON兼容。
/// note
`jsonable_encoder`实际上是FastAPI内部用来转换数据的。但是它在许多其他场景中也很有用。
///
+62
View File
@@ -0,0 +1,62 @@
# 额外数据类型
到目前为止,您一直在使用常见的数据类型,如:
* `int`
* `float`
* `str`
* `bool`
但是您也可以使用更复杂的数据类型。
您仍然会拥有现在已经看到的相同的特性:
* 很棒的编辑器支持。
* 传入请求的数据转换。
* 响应数据转换。
* 数据验证。
* 自动补全和文档。
## 其他数据类型
下面是一些你可以使用的其他数据类型:
* `UUID`:
* 一种标准的 "通用唯一标识符" ,在许多数据库和系统中用作ID。
* 在请求和响应中将以 `str` 表示。
* `datetime.datetime`:
* 一个 Python `datetime.datetime`.
* 在请求和响应中将表示为 ISO 8601 格式的 `str` ,比如: `2008-09-15T15:53:00+05:00`.
* `datetime.date`:
* Python `datetime.date`.
* 在请求和响应中将表示为 ISO 8601 格式的 `str` ,比如: `2008-09-15`.
* `datetime.time`:
* 一个 Python `datetime.time`.
* 在请求和响应中将表示为 ISO 8601 格式的 `str` ,比如: `14:23:55.003`.
* `datetime.timedelta`:
* 一个 Python `datetime.timedelta`.
* 在请求和响应中将表示为 `float` 代表总秒数。
* Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", <a href="https://docs.pydantic.dev/latest/concepts/serialization/#json_encoders" class="external-link" target="_blank">查看文档了解更多信息</a>。
* `frozenset`:
* 在请求和响应中,作为 `set` 对待:
* 在请求中,列表将被读取,消除重复,并将其转换为一个 `set`
* 在响应中 `set` 将被转换为 `list`
* 产生的模式将指定那些 `set` 的值是唯一的 (使用 JSON 模式的 `uniqueItems`)。
* `bytes`:
* 标准的 Python `bytes`
* 在请求和响应中被当作 `str` 处理。
* 生成的模式将指定这个 `str``binary` "格式"。
* `Decimal`:
* 标准的 Python `Decimal`
* 在请求和响应中被当做 `float` 一样处理。
* 您可以在这里检查所有有效的pydantic数据类型: <a href="https://docs.pydantic.dev/latest/concepts/types/" class="external-link" target="_blank">Pydantic data types</a>.
## 例子
下面是一个*路径操作*的示例,其中的参数使用了上面的一些类型。
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *}
注意,函数内的参数有原生的数据类型,你可以,例如,执行正常的日期操作,如:
{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *}
+199
View File
@@ -0,0 +1,199 @@
# 更多模型
书接上文,多个关联模型这种情况很常见。
特别是用户模型,因为:
* **输入模型**应该含密码
* **输出模型**不应含密码
* **数据库模型**需要加密的密码
/// danger | 危险
千万不要存储用户的明文密码。始终存储可以进行验证的**安全哈希值**。
如果不了解这方面的知识,请参阅[安全性中的章节](security/simple-oauth2.md#password-hashing){.internal-link target=_blank},了解什么是**密码哈希**。
///
## 多个模型
下面的代码展示了不同模型处理密码字段的方式,及使用位置的大致思路:
{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *}
### `**user_in.dict()` 简介
#### Pydantic 的 `.dict()`
`user_in` 是类 `UserIn` 的 Pydantic 模型。
Pydantic 模型支持 `.dict()` 方法,能返回包含模型数据的**字典**。
因此,如果使用如下方式创建 Pydantic 对象 `user_in`
```Python
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
```
就能以如下方式调用:
```Python
user_dict = user_in.dict()
```
现在,变量 `user_dict`中的就是包含数据的**字典**(变量 `user_dict` 是字典,不是 Pydantic 模型对象)。
以如下方式调用:
```Python
print(user_dict)
```
输出的就是 Python **字典**
```Python
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
```
#### 解包 `dict`
把**字典** `user_dict``**user_dict` 形式传递给函数(或类),Python 会执行**解包**操作。它会把 `user_dict` 的键和值作为关键字参数直接传递。
因此,接着上面的 `user_dict` 继续编写如下代码:
```Python
UserInDB(**user_dict)
```
就会生成如下结果:
```Python
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
```
或更精准,直接把可能会用到的内容与 `user_dict` 一起使用:
```Python
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
```
#### 用其它模型中的内容生成 Pydantic 模型
上例中 ,从 `user_in.dict()` 中得到了 `user_dict`,下面的代码:
```Python
user_dict = user_in.dict()
UserInDB(**user_dict)
```
等效于:
```Python
UserInDB(**user_in.dict())
```
……因为 `user_in.dict()` 是字典,在传递给 `UserInDB` 时,把 `**` 加在 `user_in.dict()` 前,可以让 Python 进行**解包**。
这样,就可以用其它 Pydantic 模型中的数据生成 Pydantic 模型。
#### 解包 `dict` 和更多关键字
接下来,继续添加关键字参数 `hashed_password=hashed_password`,例如:
```Python
UserInDB(**user_in.dict(), hashed_password=hashed_password)
```
……输出结果如下:
```Python
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
```
/// warning | 警告
辅助的附加函数只是为了演示可能的数据流,但它们显然不能提供任何真正的安全机制。
///
## 减少重复
**FastAPI** 的核心思想就是减少代码重复。
代码重复会导致 bug、安全问题、代码失步等问题(更新了某个位置的代码,但没有同步更新其它位置的代码)。
上面的这些模型共享了大量数据,拥有重复的属性名和类型。
FastAPI 可以做得更好。
声明 `UserBase` 模型作为其它模型的基类。然后,用该类衍生出继承其属性(类型声明、验证等)的子类。
所有数据转换、校验、文档等功能仍将正常运行。
这样,就可以仅声明模型之间的差异部分(具有明文的 `password`、具有 `hashed_password` 以及不包括密码)。
通过这种方式,可以只声明模型之间的区别(分别包含明文密码、哈希密码,以及无密码的模型)。
{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *}
## `Union` 或者 `anyOf`
响应可以声明为两种类型的 `Union` 类型,即该响应可以是两种类型中的任意类型。
在 OpenAPI 中可以使用 `anyOf` 定义。
为此,请使用 Python 标准类型提示 <a href="https://docs.python.org/3/library/typing.html#typing.Union" class="external-link" target="_blank">`typing.Union`</a>
/// note | 笔记
定义 <a href="https://docs.pydantic.dev/latest/concepts/types/#unions" class="external-link" target="_blank">`Union`</a> 类型时,要把详细的类型写在前面,然后是不太详细的类型。下例中,更详细的 `PlaneItem` 位于 `Union[PlaneItemCarItem]` 中的 `CarItem` 之前。
///
{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *}
## 模型列表
使用同样的方式也可以声明由对象列表构成的响应。
为此,请使用标准的 Python `typing.List`
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
## 任意 `dict` 构成的响应
任意的 `dict` 都能用于声明响应,只要声明键和值的类型,无需使用 Pydantic 模型。
事先不知道可用的字段 / 属性名时(Pydantic 模型必须知道字段是什么),这种方式特别有用。
此时,可以使用 `typing.Dict`
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}
## 小结
针对不同场景,可以随意使用不同的 Pydantic 模型继承定义的基类。
实体必须具有不同的**状态**时,不必为不同状态的实体单独定义数据模型。例如,用户**实体**就有包含 `password`、包含 `password_hash` 以及不含密码等多种状态。
+323
View File
@@ -0,0 +1,323 @@
# 第一步
最简单的 FastAPI 文件可能像下面这样:
{* ../../docs_src/first_steps/tutorial001.py *}
将其复制到 `main.py` 文件中。
运行实时服务器:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
在输出中,会有一行信息像下面这样:
```hl_lines="4"
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
该行显示了你的应用在本机所提供服务的 URL 地址。
### 查看
打开浏览器访问 <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>。
你将看到如下的 JSON 响应:
```JSON
{"message": "Hello World"}
```
### 交互式 API 文档
跳转到 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
你将会看到自动生成的交互式 API 文档(由 <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a> 提供):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### 可选的 API 文档
前往 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>。
你将会看到可选的自动生成文档 (由 <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a> 提供):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
### OpenAPI
**FastAPI** 使用定义 API 的 **OpenAPI** 标准将你的所有 API 转换成「模式」。
#### 「模式」
「模式」是对事物的一种定义或描述。它并非具体的实现代码,而只是抽象的描述。
#### API「模式」
在这种场景下,<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> 是一种规定如何定义 API 模式的规范。
「模式」的定义包括你的 API 路径,以及它们可能使用的参数等等。
#### 数据「模式」
「模式」这个术语也可能指的是某些数据比如 JSON 的结构。
在这种情况下,它可以表示 JSON 的属性及其具有的数据类型,等等。
#### OpenAPI 和 JSON Schema
OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送和接收的数据的定义(或称为「模式」),这些定义通过 JSON 数据模式标准 **JSON Schema** 所生成。
#### 查看 `openapi.json`
如果你对原始的 OpenAPI 模式长什么样子感到好奇,FastAPI 自动生成了包含所有 API 描述的 JSON(模式)。
你可以直接在:<a href="http://127.0.0.1:8000/openapi.json" class="external-link" target="_blank">http://127.0.0.1:8000/openapi.json</a> 看到它。
它将显示以如下内容开头的 JSON
```JSON
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
...
```
#### OpenAPI 的用途
驱动 FastAPI 内置的 2 个交互式文档系统的正是 OpenAPI 模式。
并且还有数十种替代方案,它们全部都基于 OpenAPI。你可以轻松地将这些替代方案中的任何一种添加到使用 **FastAPI** 构建的应用程序中。
你还可以使用它自动生成与你的 API 进行通信的客户端代码。例如 web 前端,移动端或物联网嵌入程序。
## 分步概括
### 步骤 1:导入 `FastAPI`
{* ../../docs_src/first_steps/tutorial001.py hl[1] *}
`FastAPI` 是一个为你的 API 提供了所有功能的 Python 类。
/// note | 技术细节
`FastAPI` 是直接从 `Starlette` 继承的类。
你可以通过 `FastAPI` 使用所有的 <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> 的功能。
///
### 步骤 2:创建一个 `FastAPI`「实例」
{* ../../docs_src/first_steps/tutorial001.py hl[3] *}
这里的变量 `app` 会是 `FastAPI` 类的一个「实例」。
这个实例将是创建你所有 API 的主要交互对象。
### 步骤 3:创建一个*路径操作*
#### 路径
这里的「路径」指的是 URL 中从第一个 `/` 起的后半部分。
所以,在一个这样的 URL 中:
```
https://example.com/items/foo
```
...路径会是:
```
/items/foo
```
/// info
「路径」也通常被称为「端点」或「路由」。
///
开发 API 时,「路径」是用来分离「关注点」和「资源」的主要手段。
#### 操作
这里的「操作」指的是一种 HTTP「方法」。
下列之一:
* `POST`
* `GET`
* `PUT`
* `DELETE`
...以及更少见的几种:
* `OPTIONS`
* `HEAD`
* `PATCH`
* `TRACE`
在 HTTP 协议中,你可以使用以上的其中一种(或多种)「方法」与每个路径进行通信。
---
在开发 API 时,你通常使用特定的 HTTP 方法去执行特定的行为。
通常使用:
* `POST`:创建数据。
* `GET`:读取数据。
* `PUT`:更新数据。
* `DELETE`:删除数据。
因此,在 OpenAPI 中,每一个 HTTP 方法都被称为「操作」。
我们也打算称呼它们为「操作」。
#### 定义一个*路径操作装饰器*
{* ../../docs_src/first_steps/tutorial001.py hl[6] *}
`@app.get("/")` 告诉 **FastAPI** 在它下方的函数负责处理如下访问请求:
* 请求路径为 `/`
* 使用 <abbr title="HTTP GET 方法"><code>get</code> 操作</abbr>
/// info | `@decorator` Info
`@something` 语法在 Python 中被称为「装饰器」。
像一顶漂亮的装饰帽一样,将它放在一个函数的上方(我猜测这个术语的命名就是这么来的)。
装饰器接收位于其下方的函数并且用它完成一些工作。
在我们的例子中,这个装饰器告诉 **FastAPI** 位于其下方的函数对应着**路径** `/` 加上 `get` **操作**。
它是一个「**路径操作装饰器**」。
///
你也可以使用其他的操作:
* `@app.post()`
* `@app.put()`
* `@app.delete()`
以及更少见的:
* `@app.options()`
* `@app.head()`
* `@app.patch()`
* `@app.trace()`
/// tip
你可以随意使用任何一个操作(HTTP方法)。
**FastAPI** 没有强制要求操作有任何特定的含义。
此处提供的信息仅作为指导,而不是要求。
比如,当使用 GraphQL 时通常你所有的动作都通过 `POST` 一种方法执行。
///
### 步骤 4:定义**路径操作函数**
这是我们的「**路径操作函数**」:
* **路径**:是 `/`。
* **操作**:是 `get`。
* **函数**:是位于「装饰器」下方的函数(位于 `@app.get("/")` 下方)。
{* ../../docs_src/first_steps/tutorial001.py hl[7] *}
这是一个 Python 函数。
每当 **FastAPI** 接收一个使用 `GET` 方法访问 URL「`/`」的请求时这个函数会被调用。
在这个例子中,它是一个 `async` 函数。
---
你也可以将其定义为常规函数而不使用 `async def`:
{* ../../docs_src/first_steps/tutorial003.py hl[7] *}
/// note
如果你不知道两者的区别,请查阅 [并发: *赶时间吗?*](../async.md#_1){.internal-link target=_blank}。
///
### 步骤 5:返回内容
{* ../../docs_src/first_steps/tutorial001.py hl[8] *}
你可以返回一个 `dict`、`list`,像 `str`、`int` 一样的单个值,等等。
你还可以返回 Pydantic 模型(稍后你将了解更多)。
还有许多其他将会自动转换为 JSON 的对象和模型(包括 ORM 对象等)。尝试下使用你最喜欢的一种,它很有可能已经被支持。
## 总结
* 导入 `FastAPI`。
* 创建一个 `app` 实例。
* 编写一个**路径操作装饰器**,如 `@app.get("/")`。
* 定义一个**路径操作函数**,如 `def root(): ...`。
* 使用命令 `fastapi dev` 运行开发服务器。
+269
View File
@@ -0,0 +1,269 @@
# 处理错误
某些情况下,需要向客户端返回错误提示。
这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。
需要向客户端返回错误提示的场景主要如下:
- 客户端没有执行操作的权限
- 客户端没有访问资源的权限
- 客户端要访问的项目不存在
- 等等 ...
遇到这些情况时,通常要返回 **4XX**400 至 499**HTTP 状态码**。
**4XX** 状态码与表示请求成功的 **2XX**200 至 299 HTTP 状态码类似。
只不过,**4XX** 状态码表示客户端发生的错误。
大家都知道**「404 Not Found」**错误,还有调侃这个错误的笑话吧?
## 使用 `HTTPException`
向客户端返回 HTTP 错误响应,可以使用 `HTTPException`
### 导入 `HTTPException`
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *}
### 触发 `HTTPException`
`HTTPException` 是额外包含了和 API 有关数据的常规 Python 异常。
因为是 Python 异常,所以不能 `return`,只能 `raise`
如在调用*路径操作函数*里的工具函数时,触发了 `HTTPException`,FastAPI 就不再继续执行*路径操作函数*中的后续代码,而是立即终止请求,并把 `HTTPException` 的 HTTP 错误发送至客户端。
在介绍依赖项与安全的章节中,您可以了解更多用 `raise` 异常代替 `return` 值的优势。
本例中,客户端用 `ID` 请求的 `item` 不存在时,触发状态码为 `404` 的异常:
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *}
### 响应结果
请求为 `http://example.com/items/foo``item_id``「foo」`)时,客户端会接收到 HTTP 状态码 - 200 及如下 JSON 响应结果:
```JSON
{
"item": "The Foo Wrestlers"
}
```
但如果客户端请求 `http://example.com/items/bar``item_id` `「bar」` 不存在时),则会接收到 HTTP 状态码 - 404(「未找到」错误)及如下 JSON 响应结果:
```JSON
{
"detail": "Item not found"
}
```
/// tip | 提示
触发 `HTTPException` 时,可以用参数 `detail` 传递任何能转换为 JSON 的值,不仅限于 `str`
还支持传递 `dict``list` 等数据结构。
**FastAPI** 能自动处理这些数据,并将之转换为 JSON。
///
## 添加自定义响应头
有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些方面的安全需要。
一般情况下可能不会需要在代码中直接使用响应头。
但对于某些高级应用场景,还是需要添加自定义响应头:
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *}
## 安装自定义异常处理器
添加自定义处理器,要使用 [Starlette 的异常工具](https://www.starlette.dev/exceptions/)。
假设要触发的自定义异常叫作 `UnicornException`
且需要 FastAPI 实现全局处理该异常。
此时,可以用 `@app.exception_handler()` 添加自定义异常控制器:
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *}
请求 `/unicorns/yolo` 时,路径操作会触发 `UnicornException`
但该异常将会被 `unicorn_exception_handler` 处理。
接收到的错误信息清晰明了,HTTP 状态码为 `418`JSON 内容如下:
```JSON
{"message": "Oops! yolo did something. There goes a rainbow..."}
```
/// note | 技术细节
`from starlette.requests import Request``from starlette.responses import JSONResponse` 也可以用于导入 `Request``JSONResponse`
**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应操作都可以直接从 Starlette 导入。同理,`Request` 也是如此。
///
## 覆盖默认异常处理器
**FastAPI** 自带了一些默认异常处理器。
触发 `HTTPException` 或请求无效数据时,这些处理器返回默认的 JSON 响应结果。
不过,也可以使用自定义处理器覆盖默认异常处理器。
### 覆盖请求验证异常
请求中包含无效数据时,**FastAPI** 内部会触发 `RequestValidationError`
该异常也内置了默认异常处理器。
覆盖默认异常处理器时需要导入 `RequestValidationError`,并用 `@app.excption_handler(RequestValidationError)` 装饰异常处理器。
这样,异常处理器就可以接收 `Request` 与异常。
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *}
访问 `/items/foo`,可以看到默认的 JSON 错误信息:
```JSON
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
```
被替换为了以下文本格式的错误信息:
```
1 validation error
path -> item_id
value is not a valid integer (type=type_error.integer)
```
### `RequestValidationError` vs `ValidationError`
/// warning | 警告
如果您觉得现在还用不到以下技术细节,可以先跳过下面的内容。
///
`RequestValidationError` 是 Pydantic 的 <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a> 的子类。
**FastAPI** 调用的就是 `RequestValidationError` 类,因此,如果在 `response_model` 中使用 Pydantic 模型,且数据有错误时,在日志中就会看到这个错误。
但客户端或用户看不到这个错误。反之,客户端接收到的是 HTTP 状态码为 `500` 的「内部服务器错误」。
这是因为在*响应*或代码(不是在客户端的请求里)中出现的 Pydantic `ValidationError` 是代码的 bug。
修复错误时,客户端或用户不能访问错误的内部信息,否则会造成安全隐患。
### 覆盖 `HTTPException` 错误处理器
同理,也可以覆盖 `HTTPException` 处理器。
例如,只为错误返回纯文本响应,而不是返回 JSON 格式的内容:
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *}
/// note | 技术细节
还可以使用 `from starlette.responses import PlainTextResponse`
**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应都可以直接从 Starlette 导入。
///
### 使用 `RequestValidationError` 的请求体
`RequestValidationError` 包含其接收到的无效数据请求的 `body`
开发时,可以用这个请求体生成日志、调试错误,并返回给用户。
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *}
现在试着发送一个无效的 `item`,例如:
```JSON
{
"title": "towel",
"size": "XL"
}
```
收到的响应包含 `body` 信息,并说明数据是无效的:
```JSON hl_lines="12-15"
{
"detail": [
{
"loc": [
"body",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}
```
### FastAPI `HTTPException` vs Starlette `HTTPException`
**FastAPI** 也提供了自有的 `HTTPException`。
**FastAPI** 的 `HTTPException` 继承自 Starlette 的 `HTTPException` 错误类。
它们之间的唯一区别是,**FastAPI** 的 `HTTPException` 可以在响应中添加响应头。
OAuth 2.0 等安全工具需要在内部调用这些响应头。
因此你可以继续像平常一样在代码中触发 **FastAPI** 的 `HTTPException` 。
但注册异常处理器时,应该注册到来自 Starlette 的 `HTTPException`。
这样做是为了,当 Starlette 的内部代码、扩展或插件触发 Starlette `HTTPException` 时,处理程序能够捕获、并处理此异常。
注意,本例代码中同时使用了这两个 `HTTPException`,此时,要把 Starlette 的 `HTTPException` 命名为 `StarletteHTTPException`
```Python
from starlette.exceptions import HTTPException as StarletteHTTPException
```
### 复用 **FastAPI** 异常处理器
FastAPI 支持先对异常进行某些处理,然后再使用 **FastAPI** 中处理该异常的默认异常处理器。
从 `fastapi.exception_handlers` 中导入要复用的默认异常处理器:
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *}
虽然,本例只是输出了夸大其词的错误信息。
但也足以说明,可以在处理异常之后再复用默认的异常处理器。
@@ -0,0 +1,56 @@
# Header 参数模型
如果您有一组相关的 **header 参数**,您可以创建一个 **Pydantic 模型**来声明它们。
这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证和元数据。😎
/// note
自 FastAPI 版本 `0.115.0` 起支持此功能。🤓
///
## 使用 Pydantic 模型的 Header 参数
**Pydantic 模型**中声明所需的 **header 参数**,然后将参数声明为 `Header` :
{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *}
**FastAPI** 将从请求中接收到的 **headers** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
## 查看文档
您可以在文档 UI 的 `/docs` 中查看所需的 headers
<div class="screenshot">
<img src="/img/tutorial/header-param-models/image01.png">
</div>
## 禁止额外的 Headers
在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 headers。
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *}
如果客户尝试发送一些**额外的 headers**,他们将收到**错误**响应。
例如,如果客户端尝试发送一个值为 `plumbus``tool` header,客户端将收到一个**错误**响应,告知他们 header 参数 `tool` 是不允许的:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["header", "tool"],
"msg": "Extra inputs are not permitted",
"input": "plumbus",
}
]
}
```
## 总结
您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **headers**。😎
+91
View File
@@ -0,0 +1,91 @@
# Header 参数
定义 `Header` 参数的方式与定义 `Query``Path``Cookie` 参数相同。
## 导入 `Header`
首先,导入 `Header`
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *}
## 声明 `Header` 参数
然后,使用和 `Path``Query``Cookie` 一样的结构定义 header 参数。
第一个值是默认值,还可以传递所有验证参数或注释参数:
{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *}
/// note | 技术细节
`Header``Path``Query``Cookie` 的**兄弟类**,都继承自共用的 `Param` 类。
注意,从 `fastapi` 导入的 `Query``Path``Header` 等对象,实际上是返回特殊类的函数。
///
/// info | 说明
必须使用 `Header` 声明 header 参数,否则该参数会被解释为查询参数。
///
## 自动转换
`Header``Path``Query``Cookie` 提供了更多功能。
大部分标准请求头用**连字符**分隔,即**减号**(`-`)。
但是 `user-agent` 这样的变量在 Python 中是无效的。
因此,默认情况下,`Header` 把参数名中的字符由下划线(`_`)改为连字符(`-`)来提取并存档请求头 。
同时,HTTP 的请求头不区分大小写,可以使用 Python 标准样式(即 **snake_case**)进行声明。
因此,可以像在 Python 代码中一样使用 `user_agent` ,无需把首字母大写为 `User_Agent` 等形式。
如需禁用下划线自动转换为连字符,可以把 `Header``convert_underscores` 参数设置为 `False`
{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *}
/// warning | 警告
注意,使用 `convert_underscores = False` 要慎重,有些 HTTP 代理和服务器不支持使用带有下划线的请求头。
///
## 重复的请求头
有时,可能需要接收重复的请求头。即同一个请求头有多个值。
类型声明中可以使用 `list` 定义多个请求头。
使用 Python `list` 可以接收重复请求头所有的值。
例如,声明 `X-Token` 多次出现的请求头,可以写成这样:
{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *}
与*路径操作*通信时,以下面的方式发送两个 HTTP 请求头:
```
X-Token: foo
X-Token: bar
```
响应结果是:
```JSON
{
"X-Token values": [
"bar",
"foo"
]
}
```
## 小结
使用 `Header` 声明请求头的方式与 `Query``Path``Cookie` 相同。
不用担心变量中的下划线,**FastAPI** 可以自动转换。
+93
View File
@@ -0,0 +1,93 @@
# 教程 - 用户指南
本教程将一步步向您展示如何使用 **FastAPI** 的绝大部分特性。
各个章节的内容循序渐进,但是又围绕着单独的主题,所以您可以直接跳转到某个章节以解决您的特定需求。
本教程同样可以作为将来的参考手册,所以您可以随时回到本教程并查阅您需要的内容。
## 运行代码
所有代码片段都可以复制后直接使用(它们实际上是经过测试的 Python 文件)。
要运行任何示例,请将代码复制到 `main.py` 文件中,然后使用以下命令启动 `fastapi dev`
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
**强烈建议**您在本地编写或复制代码,对其进行编辑并运行。
在编辑器中使用 FastAPI 会真正地展现出它的优势:只需要编写很少的代码,所有的类型检查,代码补全等等。
---
## 安装 FastAPI
第一个步骤是安装 FastAPI.
请确保您创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后**安装 FastAPI**
<div class="termy">
```console
$ pip install "fastapi[standard]"
---> 100%
```
</div>
/// note
当您使用 `pip install "fastapi[standard]"` 进行安装时,它会附带一些默认的可选标准依赖项。
如果您不想安装这些可选依赖,可以选择安装 `pip install fastapi`
///
## 进阶用户指南
在本**教程-用户指南**之后,您可以阅读**进阶用户指南**。
**进阶用户指南**以本教程为基础,使用相同的概念,并教授一些额外的特性。
但是您应该先阅读**教程-用户指南**(即您现在正在阅读的内容)。
教程经过精心设计,使您可以仅通过**教程-用户指南**来开发一个完整的应用程序,然后根据您的需要,使用**进阶用户指南**中的一些其他概念,以不同的方式来扩展它。
+100
View File
@@ -0,0 +1,100 @@
# 元数据和文档 URL
你可以在 FastAPI 应用程序中自定义多个元数据配置。
## API 元数据
你可以在设置 OpenAPI 规范和自动 API 文档 UI 中使用的以下字段:
| 参数 | 类型 | 描述 |
|------------|------|-------------|
| `title` | `str` | API 的标题。 |
| `summary` | `str` | API 的简短摘要。 <small>自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。.</small> |
| `description` | `str` | API 的简短描述。可以使用Markdown。 |
| `version` | `string` | API 的版本。这是您自己的应用程序的版本,而不是 OpenAPI 的版本。例如 `2.5.0` 。 |
| `terms_of_service` | `str` | API 服务条款的 URL。如果提供,则必须是 URL。 |
| `contact` | `dict` | 公开的 API 的联系信息。它可以包含多个字段。<details><summary><code>contact</code> 字段</summary><table><thead><tr><th>参数</th><th>Type</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td>联系人/组织的识别名称。</td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>指向联系信息的 URL。必须采用 URL 格式。</td></tr><tr><td><code>email</code></td><td><code>str</code></td><td>联系人/组织的电子邮件地址。必须采用电子邮件地址的格式。</td></tr></tbody></table></details> |
| `license_info` | `dict` | 公开的 API 的许可证信息。它可以包含多个字段。<details><summary><code>license_info</code> 字段</summary><table><thead><tr><th>参数</th><th>类型</th><th>描述</th></tr></thead><tbody><tr><td><code>name</code></td><td><code>str</code></td><td><strong>必须的</strong> (如果设置了<code>license_info</code>). 用于 API 的许可证名称。</td></tr><tr><td><code>identifier</code></td><td><code>str</code></td><td>一个API的<a href="https://spdx.org/licenses/" class="external-link" target="_blank">SPDX</a>许可证表达。 The <code>identifier</code> field is mutually exclusive of the <code>url</code> field. <small>自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。</small></td></tr><tr><td><code>url</code></td><td><code>str</code></td><td>用于 API 的许可证的 URL。必须采用 URL 格式。</td></tr></tbody></table></details> |
你可以按如下方式设置它们:
{* ../../docs_src/metadata/tutorial001.py hl[4:6] *}
/// tip
您可以在 `description` 字段中编写 Markdown,它将在输出中呈现。
///
通过这样设置,自动 API 文档看起来会像:
<img src="/img/tutorial/metadata/image01.png">
## 标签元数据
### 创建标签元数据
让我们在带有标签的示例中为 `users``items` 试一下。
创建标签元数据并把它传递给 `openapi_tags` 参数:
{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *}
注意你可以在描述内使用 Markdown,例如「login」会显示为粗体(**login**)以及「fancy」会显示为斜体(_fancy_)。
/// tip | 提示
不必为你使用的所有标签都添加元数据。
///
### 使用你的标签
`tags` 参数和*路径操作*(以及 `APIRouter`)一起使用,将其分配给不同的标签:
{* ../../docs_src/metadata/tutorial004.py hl[21,26] *}
/// info | 信息
阅读更多关于标签的信息[路径操作配置](path-operation-configuration.md#tags){.internal-link target=_blank}。
///
### 查看文档
如果你现在查看文档,它们会显示所有附加的元数据:
<img src="/img/tutorial/metadata/image02.png">
### 标签顺序
每个标签元数据字典的顺序也定义了在文档用户界面显示的顺序。
例如按照字母顺序,即使 `users` 排在 `items` 之后,它也会显示在前面,因为我们将它的元数据添加为列表内的第一个字典。
## OpenAPI URL
默认情况下,OpenAPI 模式服务于 `/openapi.json`
但是你可以通过参数 `openapi_url` 对其进行配置。
例如,将其设置为服务于 `/api/v1/openapi.json`
{* ../../docs_src/metadata/tutorial002.py hl[3] *}
如果你想完全禁用 OpenAPI 模式,可以将其设置为 `openapi_url=None`,这样也会禁用使用它的文档用户界面。
## 文档 URLs
你可以配置两个文档用户界面,包括:
* **Swagger UI**:服务于 `/docs`
* 可以使用参数 `docs_url` 设置它的 URL。
* 可以通过设置 `docs_url=None` 禁用它。
* ReDoc:服务于 `/redoc`
* 可以使用参数 `redoc_url` 设置它的 URL。
* 可以通过设置 `redoc_url=None` 禁用它。
例如,设置 Swagger UI 服务于 `/documentation` 并禁用 ReDoc
{* ../../docs_src/metadata/tutorial003.py hl[3] *}
+66
View File
@@ -0,0 +1,66 @@
# 中间件
你可以向 **FastAPI** 应用添加中间件.
"中间件"是一个函数,它在每个**请求**被特定的*路径操作*处理之前,以及在每个**响应**返回之前工作.
* 它接收你的应用程序的每一个**请求**.
* 然后它可以对这个**请求**做一些事情或者执行任何需要的代码.
* 然后它将**请求**传递给应用程序的其他部分 (通过某种*路径操作*).
* 然后它获取应用程序生产的**响应** (通过某种*路径操作*).
* 它可以对该**响应**做些什么或者执行任何需要的代码.
* 然后它返回这个 **响应**.
/// note | 技术细节
如果你使用了 `yield` 关键字依赖, 依赖中的退出代码将在执行中间件*后*执行.
如果有任何后台任务(稍后记录), 它们将在执行中间件*后*运行.
///
## 创建中间件
要创建中间件你可以在函数的顶部使用装饰器 `@app.middleware("http")`.
中间件参数接收如下参数:
* `request`.
* 一个函数 `call_next` 它将接收 `request` 作为参数.
* 这个函数将 `request` 传递给相应的 *路径操作*.
* 然后它将返回由相应的*路径操作*生成的 `response`.
* 然后你可以在返回 `response` 前进一步修改它.
{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *}
/// tip
请记住可以 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" class="external-link" target="_blank">用'X-' 前缀</a>添加专有自定义请求头.
但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 <a href="https://www.starlette.dev/middleware/#corsmiddleware" class="external-link" target="_blank">Starlette's CORS docs</a>文档中.
///
/// note | 技术细节
你也可以使用 `from starlette.requests import Request`.
**FastAPI** 为了开发者方便提供了该对象. 但其实它直接来自于 Starlette.
///
### 在 `response` 的前和后
在任何*路径操作*收到`request`前,可以添加要和请求一起运行的代码.
也可以在*响应*生成但是返回之前添加代码.
例如你可以添加自定义请求头 `X-Process-Time` 包含以秒为单位的接收请求和生成响应的时间:
{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *}
## 其他中间件
你可以稍后在 [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}阅读更多关于中间件的教程.
你将在下一节中学习如何使用中间件处理 <abbr title="Cross-Origin Resource Sharing">CORS</abbr> .
@@ -0,0 +1,97 @@
# 路径操作配置
*路径操作装饰器*支持多种配置参数。
/// warning | 警告
注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。
///
## `status_code` 状态码
`status_code` 用于定义*路径操作*响应中的 HTTP 状态码。
可以直接传递 `int` 代码, 比如 `404`
如果记不住数字码的涵义,也可以用 `status` 的快捷常量:
{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *}
状态码在响应中使用,并会被添加到 OpenAPI 概图。
/// note | 技术细节
也可以使用 `from starlette import status` 导入状态码。
**FastAPI**`fastapi.status``starlette.status` 一样,只是快捷方式。实际上,`fastapi.status` 直接继承自 Starlette。
///
## `tags` 参数
`tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签:
{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *}
OpenAPI 概图会自动添加标签,供 API 文档接口使用:
<img src="/img/tutorial/path-operation-configuration/image01.png">
## `summary` 和 `description` 参数
路径装饰器还支持 `summary``description` 这两个参数:
{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *}
## 文档字符串(`docstring`
描述内容比较长且占用多行时,可以在函数的 <abbr title="函数中作为第一个表达式,用于文档目的的一个多行字符串(并没有被分配个任何变量)">docstring</abbr> 中声明*路径操作*的描述,**FastAPI** 支持从文档字符串中读取描述内容。
文档字符串支持 <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a>,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。
{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *}
下图为 Markdown 文本在 API 文档中的显示效果:
<img src="/img/tutorial/path-operation-configuration/image02.png">
## 响应描述
`response_description` 参数用于定义响应的描述说明:
{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *}
/// info | 说明
注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。
///
/// check | 检查
OpenAPI 规定每个*路径操作*都要有响应描述。
如果没有定义响应描述,**FastAPI** 则自动生成内容为 "Successful response" 的响应描述。
///
<img src="/img/tutorial/path-operation-configuration/image03.png">
## 弃用*路径操作*
`deprecated` 参数可以把*路径操作*标记为<abbr title="过时,建议不要使用">弃用</abbr>,无需直接删除:
{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *}
API 文档会把该路径操作标记为弃用:
<img src="/img/tutorial/path-operation-configuration/image04.png">
下图显示了正常*路径操作*与弃用*路径操作* 的区别:
<img src="/img/tutorial/path-operation-configuration/image05.png">
## 小结
通过传递参数给*路径操作装饰器* ,即可轻松地配置*路径操作*、添加元数据。
@@ -0,0 +1,117 @@
# 路径参数和数值校验
与使用 `Query` 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 `Path` 为路径参数声明相同类型的校验和元数据。
## 导入 Path
首先,从 `fastapi` 导入 `Path`
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *}
## 声明元数据
你可以声明与 `Query` 相同的所有参数。
例如,要声明路径参数 `item_id``title` 元数据值,你可以输入:
{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *}
/// note
路径参数总是必需的,因为它必须是路径的一部分。
所以,你应该在声明时使用 `...` 将其标记为必需参数。
然而,即使你使用 `None` 声明路径参数或设置一个其他默认值也不会有任何影响,它依然会是必需参数。
///
## 按需对参数排序
假设你想要声明一个必需的 `str` 类型查询参数 `q`
而且你不需要为该参数声明任何其他内容,所以实际上你并不需要使用 `Query`
但是你仍然需要使用 `Path` 来声明路径参数 `item_id`
如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错。
但是你可以对其重新排序,并将不带默认值的值(查询参数 `q`)放到最前面。
**FastAPI** 来说这无关紧要。它将通过参数的名称、类型和默认值声明(`Query``Path` 等)来检测参数,而不在乎参数的顺序。
因此,你可以将函数声明为:
{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *}
## 按需对参数排序的技巧
如果你想不使用 `Query` 声明没有默认值的查询参数 `q`,同时使用 `Path` 声明路径参数 `item_id`,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。
传递 `*` 作为函数的第一个参数。
Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 <abbr title="来自:K-ey W-ord Arg-uments"><code>kwargs</code></abbr>,来调用。即使它们没有默认值。
{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *}
## 数值校验:大于等于
使用 `Query``Path`(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。
像下面这样,添加 `ge=1` 后,`item_id` 将必须是一个大于(`g`reater than)或等于(`e`qual`1` 的整数。
{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
## 数值校验:大于和小于等于
同样的规则适用于:
* `gt`:大于(`g`reater `t`han
* `le`:小于等于(`l`ess than or `e`qual
{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *}
## 数值校验:浮点数、大于和小于
数值校验同样适用于 `float` 值。
能够声明 <abbr title="大于"><code>gt</code></abbr> 而不仅仅是 <abbr title="大于等于"><code>ge</code></abbr> 在这个前提下变得重要起来。例如,你可以要求一个值必须大于 `0`,即使它小于 `1`
因此,`0.5` 将是有效值。但是 `0.0``0` 不是。
对于 <abbr title="less than"><code>lt</code></abbr> 也是一样的。
{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *}
## 总结
你能够以与 [查询参数和字符串校验](query-params-str-validations.md){.internal-link target=_blank} 相同的方式使用 `Query``Path`(以及其他你还没见过的类)声明元数据和字符串校验。
而且你还可以声明数值校验:
* `gt`:大于(`g`reater `t`han
* `ge`:大于等于(`g`reater than or `e`qual
* `lt`:小于(`l`ess `t`han
* `le`:小于等于(`l`ess than or `e`qual
/// info
`Query``Path` 以及你后面会看到的其他类继承自一个共同的 `Param` 类(不需要直接使用它)。
而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。
///
/// note | 技术细节
当你从 `fastapi` 导入 `Query``Path` 和其他同类对象时,它们实际上是函数。
当被调用时,它们返回同名类的实例。
如此,你导入 `Query` 这个函数。当你调用它时,它将返回一个同样命名为 `Query` 的类的实例。
因为使用了这些函数(而不是直接使用类),所以你的编辑器不会标记有关其类型的错误。
这样,你可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。
///
+250
View File
@@ -0,0 +1,250 @@
# 路径参数
FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**变量**):
{* ../../docs_src/path_params/tutorial001.py hl[6:7] *}
这段代码把路径参数 `item_id` 的值传递给路径函数的参数 `item_id`
运行示例并访问 <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>,可获得如下响应:
```JSON
{"item_id":"foo"}
```
## 声明路径参数的类型
使用 Python 标准类型注解,声明路径操作函数中路径参数的类型。
{* ../../docs_src/path_params/tutorial002.py hl[7] *}
本例把 `item_id` 的类型声明为 `int`
/// check | 检查
类型声明将为函数提供错误检查、代码补全等编辑器支持。
///
## 数据<abbr title="也称为:序列化、解析">转换</abbr>
运行示例并访问 <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>,返回的响应如下:
```JSON
{"item_id":3}
```
/// check | 检查
注意,函数接收并返回的值是 `3` `int`),不是 `"3"``str`)。
**FastAPI** 通过类型声明自动<abbr title="将来自 HTTP 请求中的字符串转换为 Python 数据类型">**解析**请求中的数据</abbr>。
///
## 数据校验
通过浏览器访问 <a href="http://127.0.0.1:8000/items/foo" class="external-link" target="_blank">http://127.0.0.1:8000/items/foo</a>,接收如下 HTTP 错误信息:
```JSON
{
"detail": [
{
"loc": [
"path",
"item_id"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
```
这是因为路径参数 `item_id` 的值 `"foo"`)的类型不是 `int`
值的类型不是 `int ` 而是浮点数(`float`)时也会显示同样的错误,比如: <a href="http://127.0.0.1:8000/items/4.2" class="external-link" target="_blank">http://127.0.0.1:8000/items/4.2。</a>
/// check | 检查
**FastAPI** 使用 Python 类型声明实现了数据校验。
注意,上面的错误清晰地指出了未通过校验的具体原因。
这在开发调试与 API 交互的代码时非常有用。
///
## 查看文档
访问 <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>,查看自动生成的 API 文档:
<img src="/img/tutorial/path-params/image01.png">
/// check | 检查
还是使用 Python 类型声明,**FastAPI** 提供了(集成 Swagger UI 的)API 文档。
注意,路径参数的类型是整数。
///
## 基于标准的好处,备选文档
**FastAPI** 使用 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md" class="external-link" target="_blank">OpenAPI</a> 生成概图,所以能兼容很多工具。
因此,**FastAPI** 还内置了 ReDoc 生成的备选 API 文档,可在此查看 <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>
<img src="/img/tutorial/path-params/image02.png">
同样,还有很多兼容工具,包括多种语言的代码生成工具。
## Pydantic
FastAPI 充分地利用了 <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> 的优势,用它在后台校验数据。众所周知,Pydantic 擅长的就是数据校验。
同样,`str``float``bool` 以及很多复合数据类型都可以使用类型声明。
下一章介绍详细内容。
## 顺序很重要
有时,*路径操作*中的路径是写死的。
比如要使用 `/users/me` 获取当前用户的数据。
然后还要使用 `/users/{user_id}`,通过用户 ID 获取指定用户的数据。
由于*路径操作*是按顺序依次运行的,因此,一定要在 `/users/{user_id}` 之前声明 `/users/me`
{* ../../docs_src/path_params/tutorial003.py hl[6,11] *}
否则,`/users/{user_id}` 将匹配 `/users/me`FastAPI 会**认为**正在接收值为 `"me"``user_id` 参数。
## 预设值
路径操作使用 Python 的 <abbr title="Enumeration">`Enum`</abbr> 类型接收预设的*路径参数*。
### 创建 `Enum` 类
导入 `Enum` 并创建继承自 `str``Enum` 的子类。
通过从 `str` 继承,API 文档就能把值的类型定义为**字符串**,并且能正确渲染。
然后,创建包含固定值的类属性,这些固定值是可用的有效值:
{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *}
/// info | 说明
Python 3.4 及之后版本支持<a href="https://docs.python.org/zh-cn/3/library/enum.html" class="external-link" target="_blank">枚举(即 enums</a>。
///
/// tip | 提示
**AlexNet**、**ResNet**、**LeNet** 是机器学习<abbr title="技术上来说是深度学习模型架构">模型</abbr>。
///
### 声明*路径参数*
使用 Enum 类(`ModelName`)创建使用类型注解的*路径参数*
{* ../../docs_src/path_params/tutorial005.py hl[16] *}
### 查看文档
API 文档会显示预定义*路径参数*的可用值:
<img src="/img/tutorial/path-params/image03.png">
### 使用 Python _枚举类型_
*路径参数*的值是枚举的元素。
#### 比较*枚举元素*
枚举类 `ModelName` 中的*枚举元素*支持比较操作:
{* ../../docs_src/path_params/tutorial005.py hl[17] *}
#### 获取*枚举值*
使用 `model_name.value``your_enum_member.value` 获取实际的值(本例中为**字符串**):
{* ../../docs_src/path_params/tutorial005.py hl[20] *}
/// tip | 提示
使用 `ModelName.lenet.value` 也能获取值 `"lenet"`
///
#### 返回*枚举元素*
即使嵌套在 JSON 请求体里(例如, `dict`),也可以从*路径操作*返回*枚举元素*。
返回给客户端之前,要把枚举元素转换为对应的值(本例中为字符串):
{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *}
客户端中的 JSON 响应如下:
```JSON
{
"model_name": "alexnet",
"message": "Deep Learning FTW!"
}
```
## 包含路径的路径参数
假设*路径操作*的路径为 `/files/{file_path}`
但需要 `file_path` 中也包含*路径*,比如,`home/johndoe/myfile.txt`
此时,该文件的 URL 是这样的:`/files/home/johndoe/myfile.txt`
### OpenAPI 支持
OpenAPI 不支持声明包含路径的*路径参数*,因为这会导致测试和定义更加困难。
不过,仍可使用 Starlette 内置工具在 **FastAPI** 中实现这一功能。
而且不影响文档正常运行,但是不会添加该参数包含路径的说明。
### 路径转换器
直接使用 Starlette 的选项声明包含*路径*的*路径参数*:
```
/files/{file_path:path}
```
本例中,参数名为 `file_path`,结尾部分的 `:path` 说明该参数应匹配*路径*。
用法如下:
{* ../../docs_src/path_params/tutorial004.py hl[6] *}
/// tip | 提示
注意,包含 `/home/johndoe/myfile.txt` 的路径参数要以斜杠(`/`)开头。
本例中的 URL 是 `/files//home/johndoe/myfile.txt`。注意,`files``home` 之间要使用**双斜杠**`//`)。
///
## 小结
通过简短、直观的 Python 标准类型声明,**FastAPI** 可以获得:
- 编辑器支持:错误检查,代码自动补全等
- 数据**<abbr title="把来自 HTTP 请求中的字符串转换为 Python 数据类型">解析</abbr>**
- 数据校验
- API 注解和 API 文档
只需要声明一次即可。
这可能是除了性能以外,**FastAPI** 与其它框架相比的主要优势。
@@ -0,0 +1,68 @@
# 查询参数模型
如果你有一组具有相关性的**查询参数**,你可以创建一个 **Pydantic 模型**来声明它们。
这将允许你在**多个地方**去**复用模型**,并且一次性为所有参数声明验证和元数据。😎
/// note
FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓
///
## 使用 Pydantic 模型的查询参数
在一个 **Pydantic 模型**中声明你需要的**查询参数**,然后将参数声明为 `Query`
{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *}
**FastAPI** 将会从请求的**查询参数**中**提取**出**每个字段**的数据,并将其提供给你定义的 Pydantic 模型。
## 查看文档
你可以在 `/docs` 页面的 UI 中查看查询参数:
<div class="screenshot">
<img src="/img/tutorial/query-param-models/image01.png">
</div>
## 禁止额外的查询参数
在一些特殊的使用场景中(可能不是很常见),你可能希望**限制**你要接收的查询参数。
你可以使用 Pydantic 的模型配置来 `forbid`(意为禁止 —— 译者注)任何 `extra`(意为额外的 —— 译者注)字段:
{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *}
假设有一个客户端尝试在**查询参数**中发送一些**额外的**数据,它将会收到一个**错误**响应。
例如,如果客户端尝试发送一个值为 `plumbus``tool` 查询参数,如:
```http
https://example.com/items/?limit=10&tool=plumbus
```
他们将收到一个**错误**响应,告诉他们查询参数 `tool` 是不允许的:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["query", "tool"],
"msg": "Extra inputs are not permitted",
"input": "plumbus"
}
]
}
```
## 总结
你可以使用 **Pydantic 模型**在 **FastAPI** 中声明**查询参数**。😎
/// tip
剧透警告:你也可以使用 Pydantic 模型来声明 cookie 和 headers,但你将在本教程的后面部分阅读到这部分内容。🤫
///
@@ -0,0 +1,274 @@
# 查询参数和字符串校验
**FastAPI** 允许你为参数声明额外的信息和校验。
让我们以下面的应用程序为例:
{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *}
查询参数 `q` 的类型为 `str`,默认值为 `None`,因此它是可选的。
## 额外的校验
我们打算添加约束条件:即使 `q` 是可选的,但只要提供了该参数,则该参数值**不能超过50个字符的长度**。
### 导入 `Query`
为此,首先从 `fastapi` 导入 `Query`
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[1] *}
## 使用 `Query` 作为默认值
现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50
{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *}
由于我们必须用 `Query(default=None)` 替换默认值 `None``Query` 的第一个参数同样也是用于定义默认值。
所以:
```Python
q: Union[str, None] = Query(default=None)
```
...使得参数可选,等同于:
```Python
q: str = None
```
但是 `Query` 显式地将其声明为查询参数。
然后,我们可以将更多的参数传递给 `Query`。在本例中,适用于字符串的 `max_length` 参数:
```Python
q: Union[str, None] = Query(default=None, max_length=50)
```
将会校验数据,在数据无效时展示清晰的错误信息,并在 OpenAPI 模式的*路径操作*中记录该参​​数。
## 添加更多校验
你还可以添加 `min_length` 参数:
{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *}
## 添加正则表达式
你可以定义一个参数值必须匹配的<abbr title="正则表达式或正则是定义字符串搜索模式的字符序列。">正则表达式</abbr>
{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *}
这个指定的正则表达式通过以下规则检查接收到的参数值:
* `^`:以该符号之后的字符开头,符号之前没有字符。
* `fixedquery`: 值精确地等于 `fixedquery`
* `$`: 到此结束,在 `fixedquery` 之后没有更多字符。
如果你对所有的这些**「正则表达式」**概念感到迷茫,请不要担心。对于许多人来说这都是一个困难的主题。你仍然可以在无需正则表达式的情况下做很多事情。
但是,一旦你需要用到并去学习它们时,请了解你已经可以在 **FastAPI** 中直接使用它们。
## 默认值
你可以向 `Query` 的第一个参数传入 `None` 用作查询参数的默认值,以同样的方式你也可以传递其他默认值。
假设你想要声明查询参数 `q`,使其 `min_length``3`,并且默认值为 `fixedquery`
{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *}
/// note
具有默认值还会使该参数成为可选参数。
///
## 声明为必需参数
当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 `q` 参数成为必需参数,例如:
```Python
q: str
```
代替:
```Python
q: Union[str, None] = None
```
但是现在我们正在用 `Query` 声明它,例如:
```Python
q: Union[str, None] = Query(default=None, min_length=3)
```
因此,当你在使用 `Query` 且需要声明一个值是必需的时,只需不声明默认参数:
{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *}
### 使用`None`声明必需参数
你可以声明一个参数可以接收`None`值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是`None`
为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...`
{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *}
/// tip
Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没有设默认值的情况下使用 `Optional``Union[Something, None]` 时,它具有特殊行为,你可以在 Pydantic 文档中阅读有关<a href="https://docs.pydantic.dev/latest/concepts/models/#required-optional-fields" class="external-link" target="_blank">必需可选字段</a>的更多信息。
///
## 查询参数列表 / 多个值
当你使用 `Query` 显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。
例如,要声明一个可在 URL 中出现多次的查询参数 `q`,你可以这样写:
{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *}
然后,输入如下网址:
```
http://localhost:8000/items/?q=foo&q=bar
```
你会在*路径操作函数*的*函数参数* `q` 中以一个 Python `list` 的形式接收到*查询参数* `q` 的多个值(`foo``bar`)。
因此,该 URL 的响应将会是:
```JSON
{
"q": [
"foo",
"bar"
]
}
```
/// tip
要声明类型为 `list` 的查询参数,如上例所示,你需要显式地使用 `Query`,否则该参数将被解释为请求体。
///
交互式 API 文档将会相应地进行更新,以允许使用多个值:
<img src="https://fastapi.tiangolo.com/img/tutorial/query-params-str-validations/image02.png">
### 具有默认值的查询参数列表 / 多个值
你还可以定义在没有任何给定值时的默认 `list` 值:
{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *}
如果你访问:
```
http://localhost:8000/items/
```
`q` 的默认值将为:`["foo", "bar"]`,你的响应会是:
```JSON
{
"q": [
"foo",
"bar"
]
}
```
#### 使用 `list`
你也可以直接使用 `list` 代替 `List [str]`
{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *}
/// note
请记住,在这种情况下 FastAPI 将不会检查列表的内容。
例如,`List[int]` 将检查(并记录到文档)列表的内容必须是整数。但是单独的 `list` 不会。
///
## 声明更多元数据
你可以添加更多有关该参数的信息。
这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。
/// note
请记住,不同的工具对 OpenAPI 的支持程度可能不同。
其中一些可能不会展示所有已声明的额外信息,尽管在大多数情况下,缺少的这部分功能已经计划进行开发。
///
你可以添加 `title`
{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *}
以及 `description`
{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *}
## 别名参数
假设你想要查询参数为 `item-query`
像下面这样:
```
http://127.0.0.1:8000/items/?item-query=foobaritems
```
但是 `item-query` 不是一个有效的 Python 变量名称。
最接近的有效名称是 `item_query`
但是你仍然要求它在 URL 中必须是 `item-query`...
这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值:
{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *}
## 弃用参数
现在假设你不再喜欢此参数。
你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为<abbr title ="已过时,建议不要使用它">已弃用</abbr>。
那么将参数 `deprecated=True` 传入 `Query`
{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *}
文档将会像下面这样展示它:
<img src="https://fastapi.tiangolo.com/img/tutorial/query-params-str-validations/image01.png">
## 总结
你可以为查询参数声明额外的校验和元数据。
通用的校验和元数据:
* `alias`
* `title`
* `description`
* `deprecated`
特定于字符串的校验:
* `min_length`
* `max_length`
* `regex`
在这些示例中,你了解了如何声明对 `str` 值的校验。
请参阅下一章节,以了解如何声明对其他类型例如数值的校验。
+196
View File
@@ -0,0 +1,196 @@
# 查询参数
声明的参数不是路径参数时,路径操作函数会把该参数自动解释为**查询**参数。
{* ../../docs_src/query_params/tutorial001.py hl[9] *}
查询字符串是键值对的集合,这些键值对位于 URL 的 `?` 之后,以 `&` 分隔。
例如,以下 URL 中:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
……查询参数为:
* `skip`:值为 `0`
* `limit`:值为 `10`
这些值都是 URL 的组成部分,因此,它们的类型**本应**是字符串。
但声明 Python 类型(上例中为 `int`)之后,这些值就会转换为声明的类型,并进行类型校验。
所有应用于路径参数的流程也适用于查询参数:
* (显而易见的)编辑器支持
* 数据<abbr title="将来自 HTTP 请求的字符串转换为 Python 数据类型">**解析**</abbr>
* 数据校验
* API 文档
## 默认值
查询参数不是路径的固定内容,它是可选的,还支持默认值。
上例用 `skip=0``limit=10` 设定默认值。
访问 URL
```
http://127.0.0.1:8000/items/
```
与访问以下地址相同:
```
http://127.0.0.1:8000/items/?skip=0&limit=10
```
但如果访问:
```
http://127.0.0.1:8000/items/?skip=20
```
查询参数的值就是:
* `skip=20`:在 URL 中设定的值
* `limit=10`:使用默认值
## 可选参数
同理,把默认值设为 `None` 即可声明**可选的**查询参数:
{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *}
本例中,查询参数 `q` 是可选的,默认值为 `None`
/// check | 检查
注意,**FastAPI** 可以识别出 `item_id` 是路径参数,`q` 不是路径参数,而是查询参数。
///
/// note | 笔记
因为默认值为 `= None`FastAPI 把 `q` 识别为可选参数。
FastAPI 不使用 `Optional[str]` 中的 `Optional`(只使用 `str`),但 `Optional[str]` 可以帮助编辑器发现代码中的错误。
///
## 查询参数类型转换
参数还可以声明为 `bool` 类型,FastAPI 会自动转换参数类型:
{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *}
本例中,访问:
```
http://127.0.0.1:8000/items/foo?short=1
```
```
http://127.0.0.1:8000/items/foo?short=True
```
```
http://127.0.0.1:8000/items/foo?short=true
```
```
http://127.0.0.1:8000/items/foo?short=on
```
```
http://127.0.0.1:8000/items/foo?short=yes
```
或其它任意大小写形式(大写、首字母大写等),函数接收的 `short` 参数都是布尔值 `True`。值为 `False` 时也一样。
## 多个路径和查询参数
**FastAPI** 可以识别同时声明的多个路径参数和查询参数。
而且声明查询参数的顺序并不重要。
FastAPI 通过参数名进行检测:
{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *}
## 必选查询参数
为不是路径参数的参数声明默认值(至此,仅有查询参数),该参数就**不是必选**的了。
如果只想把参数设为**可选**,但又不想指定参数的值,则要把默认值设为 `None`
如果要把查询参数设置为**必选**,就不要声明默认值:
{* ../../docs_src/query_params/tutorial005.py hl[6:7] *}
这里的查询参数 `needy` 是类型为 `str` 的必选查询参数。
在浏览器中打开如下 URL
```
http://127.0.0.1:8000/items/foo-item
```
……因为路径中没有必选参数 `needy`,返回的响应中会显示如下错误信息:
```JSON
{
"detail": [
{
"loc": [
"query",
"needy"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
```
`needy` 是必选参数,因此要在 URL 中设置值:
```
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```
……这样就正常了:
```JSON
{
"item_id": "foo-item",
"needy": "sooooneedy"
}
```
当然,把一些参数定义为必选,为另一些参数设置默认值,再把其它参数定义为可选,这些操作都是可以的:
{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *}
本例中有 3 个查询参数:
* `needy`,必选的 `str` 类型参数
* `skip`,默认值为 `0``int` 类型参数
* `limit`,可选的 `int` 类型参数
/// tip | 提示
还可以像在[路径参数](path-params.md#_8){.internal-link target=_blank} 中那样使用 `Enum`
///
+172
View File
@@ -0,0 +1,172 @@
# 请求文件
`File` 用于定义客户端的上传文件。
/// info | 说明
因为上传文件以「表单数据」形式发送。
所以接收上传文件,要预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
例如: `pip install python-multipart`
///
## 导入 `File`
`fastapi` 导入 `File``UploadFile`
{* ../../docs_src/request_files/tutorial001.py hl[1] *}
## 定义 `File` 参数
创建文件(`File`)参数的方式与 `Body``Form` 一样:
{* ../../docs_src/request_files/tutorial001.py hl[7] *}
/// info | 说明
`File` 是直接继承自 `Form` 的类。
注意,从 `fastapi` 导入的 `Query``Path``File` 等项,实际上是返回特定类的函数。
///
/// tip | 提示
声明文件体必须使用 `File`,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。
///
文件作为「表单数据」上传。
如果把*路径操作函数*参数的类型声明为 `bytes`**FastAPI** 将以 `bytes` 形式读取和接收文件内容。
这种方式把文件的所有内容都存储在内存里,适用于小型文件。
不过,很多情况下,`UploadFile` 更好用。
## 含 `UploadFile` 的文件参数
定义文件参数时使用 `UploadFile`
{* ../../docs_src/request_files/tutorial001.py hl[12] *}
`UploadFile``bytes` 相比有更多优势:
* 使用 `spooled` 文件:
* 存储在内存的文件超出最大上限时,FastAPI 会把文件存入磁盘;
* 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存;
* 可获取上传文件的元数据;
* 自带 <a href="https://docs.python.org/zh-cn/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> `async` 接口;
* 暴露的 Python <a href="https://docs.python.org/zh-cn/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> 对象,可直接传递给其他预期「file-like」对象的库。
### `UploadFile`
`UploadFile` 的属性如下:
* `filename`:上传文件名字符串(`str`),例如, `myimage.jpg`
* `content_type`:内容类型(MIME 类型 / 媒体类型)字符串(`str`),例如,`image/jpeg`
* `file` <a href="https://docs.python.org/zh-cn/3/library/tempfile.html#tempfile.SpooledTemporaryFile" class="external-link" target="_blank">`SpooledTemporaryFile`</a> <a href="https://docs.python.org/zh-cn/3/glossary.html#term-file-like-object" class="external-link" target="_blank">file-like</a> 对象)。其实就是 Python文件,可直接传递给其他预期 `file-like` 对象的函数或支持库。
`UploadFile` 支持以下 `async` 方法,(使用内部 `SpooledTemporaryFile`)可调用相应的文件方法。
* `write(data)`:把 `data` `str``bytes`)写入文件;
* `read(size)`:按指定数量的字节或字符(`size` (`int`))读取文件内容;
* `seek(offset)`:移动至文件 `offset` `int`)字节处的位置;
* 例如,`await myfile.seek(0) ` 移动到文件开头;
* 执行 `await myfile.read()` 后,需再次读取已读取内容时,这种方法特别好用;
* `close()`:关闭文件。
因为上述方法都是 `async` 方法,要搭配「await」使用。
例如,在 `async` *路径操作函数* 内,要用以下方式读取文件内容:
```Python
contents = await myfile.read()
```
在普通 `def` *路径操作函数* 内,则可以直接访问 `UploadFile.file`,例如:
```Python
contents = myfile.file.read()
```
/// note | `async` 技术细节
使用 `async` 方法时,**FastAPI** 在线程池中执行文件方法,并 `await` 操作完成。
///
/// note | Starlette 技术细节
**FastAPI**`UploadFile` 直接继承自 **Starlette**`UploadFile`,但添加了一些必要功能,使之与 **Pydantic** 及 FastAPI 的其它部件兼容。
///
## 什么是 「表单数据」
与 JSON 不同,HTML 表单(`<form></form>`)向服务器发送数据通常使用「特殊」的编码。
**FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。
/// note | 技术细节
不包含文件时,表单数据一般用 `application/x-www-form-urlencoded`「媒体类型」编码。
但表单包含文件时,编码为 `multipart/form-data`。使用了 `File`**FastAPI** 就知道要从请求体的正确位置获取文件。
编码和表单字段详见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> Web 文档的 <code>POST </code></a> 小节。
///
/// warning | 警告
可在一个*路径操作*中声明多个 `File``Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `multipart/form-data`,不是 `application/json`
这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。
///
## 可选文件上传
您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选:
{* ../../docs_src/request_files/tutorial001_02_py310.py hl[7,14] *}
## 带有额外元数据的 `UploadFile`
您也可以将 `File()``UploadFile` 一起使用,例如,设置额外的元数据:
{* ../../docs_src/request_files/tutorial001_03.py hl[13] *}
## 多文件上传
FastAPI 支持同时上传多个文件。
可用同一个「表单字段」发送含多个文件的「表单数据」。
上传多个文件时,要声明含 `bytes``UploadFile` 的列表(`List`):
{* ../../docs_src/request_files/tutorial002_py39.py hl[8,13] *}
接收的也是含 `bytes``UploadFile` 的列表(`list`)。
/// note | 技术细节
也可以使用 `from starlette.responses import HTMLResponse`
`fastapi.responses` 其实与 `starlette.responses` 相同,只是为了方便开发者调用。实际上,大多数 **FastAPI** 的响应都直接从 Starlette 调用。
///
### 带有额外元数据的多文件上传
和之前的方式一样, 您可以为 `File()` 设置额外参数, 即使是 `UploadFile`:
{* ../../docs_src/request_files/tutorial003_py39.py hl[16] *}
## 小结
本节介绍了如何用 `File` 把上传文件声明为(表单数据的)输入参数。
@@ -0,0 +1,78 @@
# 表单模型
您可以使用 **Pydantic 模型**在 FastAPI 中声明**表单字段**
/// info
要使用表单,需预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a> 。
确保您创建、激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}后再安装。
```console
$ pip install python-multipart
```
///
/// note
自 FastAPI 版本 `0.113.0` 起支持此功能。🤓
///
## 表单的 Pydantic 模型
您只需声明一个 **Pydantic 模型**,其中包含您希望接收的**表单字段**,然后将参数声明为 `Form` :
{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *}
**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。
## 检查文档
您可以在文档 UI 中验证它,地址为 `/docs`
<div class="screenshot">
<img src="/img/tutorial/request-form-models/image01.png">
</div>
## 禁止额外的表单字段
在某些特殊使用情况下(可能并不常见),您可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。
/// note
自 FastAPI 版本 `0.114.0` 起支持此功能。🤓
///
您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段:
{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *}
如果客户端尝试发送一些额外的数据,他们将收到**错误**响应。
例如,如果客户端尝试发送这样的表单字段:
* `username`: `Rick`
* `password`: `Portal Gun`
* `extra`: `Mr. Poopybutthole`
他们将收到一条错误响应,表明字段 `extra` 是不被允许的:
```json
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["body", "extra"],
"msg": "Extra inputs are not permitted",
"input": "Mr. Poopybutthole"
}
]
}
```
## 总结
您可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎
@@ -0,0 +1,37 @@
# 请求表单与文件
FastAPI 支持同时使用 `File``Form` 定义文件和表单字段。
/// info | 说明
接收上传文件或表单数据,要预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
例如,`pip install python-multipart`
///
## 导入 `File` 与 `Form`
{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *}
## 定义 `File` 与 `Form` 参数
创建文件和表单参数的方式与 `Body``Query` 一样:
{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *}
文件和表单字段作为表单数据上传与接收。
声明文件可以使用 `bytes``UploadFile`
/// warning | 警告
可在一个*路径操作*中声明多个 `File``Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码为 `multipart/form-data`,不是 `application/json`
这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。
///
## 小结
在同一个请求中接收数据和文件时,应同时使用 `File``Form`
+69
View File
@@ -0,0 +1,69 @@
# 表单数据
接收的不是 JSON,而是表单字段时,要使用 `Form`
/// info | 说明
要使用表单,需预先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
例如,`pip install python-multipart`
///
## 导入 `Form`
`fastapi` 导入 `Form`
{* ../../docs_src/request_forms/tutorial001.py hl[1] *}
## 定义 `Form` 参数
创建表单(`Form`)参数的方式与 `Body``Query` 一样:
{* ../../docs_src/request_forms/tutorial001.py hl[7] *}
例如,OAuth2 规范的 "密码流" 模式规定要通过表单字段发送 `username``password`
<abbr title="specification">该规范</abbr>要求字段必须命名为 `username``password`,并通过表单字段发送,不能用 JSON。
使用 `Form` 可以声明与 `Body` (及 `Query``Path``Cookie`)相同的元数据和验证。
/// info | 说明
`Form` 是直接继承自 `Body` 的类。
///
/// tip | 提示
声明表单体要显式使用 `Form` ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。
///
## 关于 "表单字段"
与 JSON 不同,HTML 表单(`<form></form>`)向服务器发送数据通常使用「特殊」的编码。
**FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。
/// note | 技术细节
表单数据的「媒体类型」编码一般为 `application/x-www-form-urlencoded`
但包含文件的表单编码为 `multipart/form-data`。文件处理详见下节。
编码和表单字段详见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/POST" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN</abbr> Web 文档的 <code>POST</code></a>小节。
///
/// warning | 警告
可在一个*路径操作*中声明多个 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `application/x-www-form-urlencoded`,不是 `application/json`
这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。
///
## 小结
本节介绍了如何使用 `Form` 声明表单数据输入参数。
+213
View File
@@ -0,0 +1,213 @@
# 响应模型
你可以在任意的*路径操作*中使用 `response_model` 参数来声明用于响应的模型:
* `@app.get()`
* `@app.post()`
* `@app.put()`
* `@app.delete()`
* 等等。
{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *}
/// note
注意,`response_model`是「装饰器」方法(`get``post` 等)的一个参数。不像之前的所有参数和请求体,它不属于*路径操作函数*。
///
它接收的类型与你将为 Pydantic 模型属性所声明的类型相同,因此它可以是一个 Pydantic 模型,但也可以是一个由 Pydantic 模型组成的 `list`,例如 `List[Item]`
FastAPI 将使用此 `response_model` 来:
* 将输出数据转换为其声明的类型。
* 校验数据。
* 在 OpenAPI 的*路径操作*中为响应添加一个 JSON Schema。
* 并在自动生成文档系统中使用。
但最重要的是:
* 会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。
/// note | 技术细节
响应模型在参数中被声明,而不是作为函数返回类型的注解,这是因为路径函数可能不会真正返回该响应模型,而是返回一个 `dict`、数据库对象或其他模型,然后再使用 `response_model` 来执行字段约束和序列化。
///
## 返回与输入相同的数据
现在我们声明一个 `UserIn` 模型,它将包含一个明文密码属性。
{* ../../docs_src/response_model/tutorial002.py hl[9,11] *}
我们正在使用此模型声明输入数据,并使用同一模型声明输出数据:
{* ../../docs_src/response_model/tutorial002.py hl[17:18] *}
现在,每当浏览器使用一个密码创建用户时,API 都会在响应中返回相同的密码。
在这个案例中,这可能不算是问题,因为用户自己正在发送密码。
但是,如果我们在其他的*路径操作*中使用相同的模型,则可能会将用户的密码发送给每个客户端。
/// danger
永远不要存储用户的明文密码,也不要在响应中发送密码。
///
## 添加输出模型
相反,我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型:
{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *}
这样,即便我们的*路径操作函数*将会返回包含密码的相同输入用户:
{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *}
...我们已经将 `response_model` 声明为了不包含密码的 `UserOut` 模型:
{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *}
因此,**FastAPI** 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。
## 在文档中查看
当你查看自动化文档时,你可以检查输入模型和输出模型是否都具有自己的 JSON Schema
<img src="https://fastapi.tiangolo.com/img/tutorial/response-model/image01.png">
并且两种模型都将在交互式 API 文档中使用:
<img src="https://fastapi.tiangolo.com/img/tutorial/response-model/image02.png">
## 响应模型编码参数
你的响应模型可以具有默认值,例如:
{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *}
* `description: Union[str, None] = None` 具有默认值 `None`
* `tax: float = 10.5` 具有默认值 `10.5`.
* `tags: List[str] = []` 具有一个空列表作为默认值: `[]`.
但如果它们并没有存储实际的值,你可能想从结果中忽略它们的默认值。
举个例子,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但你又不想发送充满默认值的很长的 JSON 响应。
### 使用 `response_model_exclude_unset` 参数
你可以设置*路径操作装饰器*的 `response_model_exclude_unset=True` 参数:
{* ../../docs_src/response_model/tutorial004.py hl[24] *}
然后响应中将不会包含那些默认值,而是仅有实际设置的值。
因此,如果你向*路径操作*发送 ID 为 `foo` 的商品的请求,则响应(不包括默认值)将为:
```JSON
{
"name": "Foo",
"price": 50.2
}
```
/// info
FastAPI 通过 Pydantic 模型的 `.dict()` 配合 <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">该方法的 `exclude_unset` 参数</a> 来实现此功能。
///
/// info
你还可以使用:
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
参考 <a href="https://docs.pydantic.dev/latest/concepts/serialization/#modeldict" class="external-link" target="_blank">Pydantic 文档</a> 中对 `exclude_defaults``exclude_none` 的描述。
///
#### 默认值字段有实际值的数据
但是,如果你的数据在具有默认值的模型字段中有实际的值,例如 ID 为 `bar` 的项:
```Python hl_lines="3 5"
{
"name": "Bar",
"description": "The bartenders",
"price": 62,
"tax": 20.2
}
```
这些值将包含在响应中。
#### 具有与默认值相同值的数据
如果数据具有与默认值相同的值,例如 ID 为 `baz` 的项:
```Python hl_lines="3 5-6"
{
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": []
}
```
即使 `description`、`tax` 和 `tags` 具有与默认值相同的值,FastAPI 足够聪明 (实际上是 Pydantic 足够聪明) 去认识到这一点,它们的值被显式地所设定(而不是取自默认值)。
因此,它们将包含在 JSON 响应中。
/// tip
请注意默认值可以是任何值,而不仅是`None`。
它们可以是一个列表(`[]`),一个值为 `10.5`的 `float`,等等。
///
### `response_model_include` 和 `response_model_exclude`
你还可以使用*路径操作装饰器*的 `response_model_include` 和 `response_model_exclude` 参数。
它们接收一个由属性名称 `str` 组成的 `set` 来包含(忽略其他的)或者排除(包含其他的)这些属性。
如果你只有一个 Pydantic 模型,并且想要从输出中移除一些数据,则可以使用这种快捷方法。
/// tip
但是依然建议你使用上面提到的主意,使用多个类而不是这些参数。
这是因为即使使用 `response_model_include` 或 `response_model_exclude` 来省略某些属性,在应用程序的 OpenAPI 定义(和文档)中生成的 JSON Schema 仍将是完整的模型。
这也适用于作用类似的 `response_model_by_alias`。
///
{* ../../docs_src/response_model/tutorial005.py hl[31,37] *}
/// tip
`{"name", "description"}` 语法创建一个具有这两个值的 `set`。
等同于 `set(["name", "description"])`。
///
#### 使用 `list` 而不是 `set`
如果你忘记使用 `set` 而是使用 `list` 或 `tuple`FastAPI 仍会将其转换为 `set` 并且正常工作:
{* ../../docs_src/response_model/tutorial006.py hl[31,37] *}
## 总结
使用*路径操作装饰器*的 `response_model` 参数来定义响应模型,特别是确保私有数据被过滤掉。
使用 `response_model_exclude_unset` 来仅返回显式设定的值。
@@ -0,0 +1,101 @@
# 响应状态码
与指定响应模型的方式相同,在以下任意*路径操作*中,可以使用 `status_code` 参数声明用于响应的 HTTP 状态码:
* `@app.get()`
* `@app.post()`
* `@app.put()`
* `@app.delete()`
* 等……
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
/// note | 笔记
注意,`status_code` 是(`get``post` 等)**装饰器**方法中的参数。与之前的参数和请求体不同,不是*路径操作函数*的参数。
///
`status_code` 参数接收表示 HTTP 状态码的数字。
/// info | 说明
`status_code` 还能接收 `IntEnum` 类型,比如 Python 的 <a href="https://docs.python.org/3/library/http.html#http.HTTPStatus" class="external-link" target="_blank">`http.HTTPStatus`</a>。
///
它可以:
* 在响应中返回状态码
* 在 OpenAPI 概图(及用户界面)中存档:
<img src="/img/tutorial/response-status-code/image01.png">
/// note | 笔记
某些响应状态码表示响应没有响应体(参阅下一章)。
FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。
///
## 关于 HTTP 状态码
/// note | 笔记
如果已经了解 HTTP 状态码,请跳到下一章。
///
在 HTTP 协议中,发送 3 位数的数字状态码是响应的一部分。
这些状态码都具有便于识别的关联名称,但是重要的还是数字。
简言之:
* `100` 及以上的状态码用于返回**信息**。这类状态码很少直接使用。具有这些状态码的响应不能包含响应体
* **`200`** 及以上的状态码用于表示**成功**。这些状态码是最常用的
* `200` 是默认状态代码,表示一切**正常**
* `201` 表示**已创建**,通常在数据库中创建新记录后使用
* `204` 是一种特殊的例子,表示**无内容**。该响应在没有为客户端返回内容时使用,因此,该响应不能包含响应体
* **`300`** 及以上的状态码用于**重定向**。具有这些状态码的响应不一定包含响应体,但 `304`**未修改**是个例外,该响应不得包含响应体
* **`400`** 及以上的状态码用于表示**客户端错误**。这些可能是第二常用的类型
* `404`,用于**未找到**响应
* 对于来自客户端的一般错误,可以只使用 `400`
* `500` 及以上的状态码用于表示服务器端错误。几乎永远不会直接使用这些状态码。应用代码或服务器出现问题时,会自动返回这些状态代码
/// tip | 提示
状态码及适用场景的详情,请参阅 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" class="external-link" target="_blank"><abbr title="Mozilla Developer Network">MDN 的 HTTP 状态码</abbr>文档</a>。
///
## 状态码名称快捷方式
再看下之前的例子:
{* ../../docs_src/response_status_code/tutorial001.py hl[6] *}
`201` 表示**已创建**的状态码。
但我们没有必要记住所有代码的含义。
可以使用 `fastapi.status` 中的快捷变量。
{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *}
这只是一种快捷方式,具有相同的数字代码,但它可以使用编辑器的自动补全功能:
<img src="../../../../../../img/tutorial/response-status-code/image02.png">
/// note | 技术细节
也可以使用 `from starlette import status`
为了让开发者更方便,**FastAPI** 提供了与 `starlette.status` 完全相同的 `fastapi.status`。但它直接来自于 Starlette。
///
## 更改默认状态码
[高级用户指南](../advanced/response-change-status-code.md){.internal-link target=_blank}中,将介绍如何返回与在此声明的默认状态码不同的状态码。
@@ -0,0 +1,55 @@
# 模式的额外信息 - 例子
您可以在JSON模式中定义额外的信息。
一个常见的用例是添加一个将在文档中显示的`example`
有几种方法可以声明额外的 JSON 模式信息。
## Pydantic `schema_extra`
您可以使用 `Config``schema_extra` 为Pydantic模型声明一个示例,如<a href="https://docs.pydantic.dev/latest/concepts/json_schema/#schema-customization" class="external-link" target="_blank">Pydantic 文档:定制 Schema </a>中所述:
{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *}
这些额外的信息将按原样添加到输出的JSON模式中。
## `Field` 的附加参数
`Field`, `Path`, `Query`, `Body` 和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 `example`:
{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *}
/// warning
请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。
///
## `Body` 额外参数
你可以通过传递额外信息给 `Field` 同样的方式操作`Path`, `Query`, `Body`等。
比如,你可以将请求体的一个 `example` 传递给 `Body`:
{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *}
## 文档 UI 中的例子
使用上面的任何方法,它在 `/docs` 中看起来都是这样的:
<img src="/img/tutorial/body-fields/image01.png">
## 技术细节
关于 `example``examples`...
JSON Schema在最新的一个版本中定义了一个字段 <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> ,但是 OpenAPI 基于之前的一个旧版JSON Schema,并没有 `examples`.
所以 OpenAPI为了相似的目的定义了自己的 <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> (使用 `example`, 而不是 `examples`), 这也是文档 UI 所使用的 (使用 Swagger UI).
所以,虽然 `example` 不是JSON Schema的一部分,但它是OpenAPI的一部分,这将被文档UI使用。
## 其他信息
同样的方法,你可以添加你自己的额外信息,这些信息将被添加到每个模型的JSON模式中,例如定制前端用户界面,等等。
@@ -0,0 +1,197 @@
# 安全 - 第一步
假设**后端** API 在某个域。
**前端**在另一个域,或(移动应用中)在同一个域的不同路径下。
并且,前端要使用后端的 **username****password** 验证用户身份。
固然,**FastAPI** 支持 **OAuth2** 身份验证。
但为了节省开发者的时间,不要只为了查找很少的内容,不得不阅读冗长的规范文档。
我们建议使用 **FastAPI** 的安全工具。
## 概览
首先,看看下面的代码是怎么运行的,然后再回过头来了解其背后的原理。
## 创建 `main.py`
把下面的示例代码复制到 `main.py`
{* ../../docs_src/security/tutorial001_an_py39.py *}
## 运行
/// info | 说明
先安装 <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>。
安装命令: `pip install python-multipart`
这是因为 **OAuth2** 使用**表单数据**发送 `username``password`
///
用下面的命令运行该示例:
<div class="termy">
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
## 查看文档
打开 API 文档: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs。</a>
界面如下图所示:
<img src="/img/tutorial/security/image01.png">
/// check | Authorize 按钮!
页面右上角出现了一个「**Authorize**」按钮。
*路径操作*的右上角也出现了一个可以点击的小锁图标。
///
点击 **Authorize** 按钮,弹出授权表单,输入 `username``password` 及其它可选字段:
<img src="/img/tutorial/security/image02.png">
/// note | 笔记
目前,在表单中输入内容不会有任何反应,后文会介绍相关内容。
///
虽然此文档不是给前端最终用户使用的,但这个自动工具非常实用,可在文档中与所有 API 交互。
前端团队(可能就是开发者本人)可以使用本工具。
第三方应用与系统也可以调用本工具。
开发者也可以用它来调试、检查、测试应用。
## 密码流
现在,我们回过头来介绍这段代码的原理。
`Password` **流**是 OAuth2 定义的,用于处理安全与身份验证的方式(**流**)。
OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户身份。
但在本例中,**FastAPI** 应用会处理 API 与身份验证。
下面,我们来看一下简化的运行流程:
- 用户在前端输入 `username``password`,并点击**回车**
- (用户浏览器中运行的)前端把 `username``password` 发送至 API 中指定的 URL(使用 `tokenUrl="token"` 声明)
- API 检查 `username``password`,并用令牌(`Token` 响应(暂未实现此功能):
- 令牌只是用于验证用户的字符串
- 一般来说,令牌会在一段时间后过期
- 过时后,用户要再次登录
- 这样一来,就算令牌被人窃取,风险也较低。因为它与永久密钥不同,**在绝大多数情况下**不会长期有效
- 前端临时将令牌存储在某个位置
- 用户点击前端,前往前端应用的其它部件
- 前端需要从 API 中提取更多数据:
- 为指定的端点(Endpoint)进行身份验证
- 因此,用 API 验证身份时,要发送值为 `Bearer` + 令牌的请求头 `Authorization`
- 假如令牌为 `foobar``Authorization` 请求头就是: `Bearer foobar`
## **FastAPI** 的 `OAuth2PasswordBearer`
**FastAPI** 提供了不同抽象级别的安全工具。
本例使用 **OAuth2****Password** 流以及 **Bearer** 令牌(`Token`)。为此要使用 `OAuth2PasswordBearer` 类。
/// info | 说明
`Bearer` 令牌不是唯一的选择。
但它是最适合这个用例的方案。
甚至可以说,它是适用于绝大多数用例的最佳方案,除非您是 OAuth2 的专家,知道为什么其它方案更合适。
本例中,**FastAPI** 还提供了构建工具。
///
创建 `OAuth2PasswordBearer` 的类实例时,要传递 `tokenUrl` 参数。该参数包含客户端(用户浏览器中运行的前端) 的 URL,用于发送 `username``password`,并获取令牌。
{* ../../docs_src/security/tutorial001.py hl[6] *}
/// tip | 提示
在此,`tokenUrl="token"` 指向的是暂未创建的相对 URL `token`。这个相对 URL 相当于 `./token`
因为使用的是相对 URL,如果 API 位于 `https://example.com/`,则指向 `https://example.com/token`。但如果 API 位于 `https://example.com/api/v1/`,它指向的就是`https://example.com/api/v1/token`
使用相对 URL 非常重要,可以确保应用在遇到[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}这样的高级用例时,也能正常运行。
///
该参数不会创建端点或*路径操作*,但会声明客户端用来获取令牌的 URL `/token` 。此信息用于 OpenAPI 及 API 文档。
接下来,学习如何创建实际的路径操作。
/// info | 说明
严苛的 **Pythonista** 可能不喜欢用 `tokenUrl` 这种命名风格代替 `token_url`
这种命名方式是因为要使用与 OpenAPI 规范中相同的名字。以便在深入校验安全方案时,能通过复制粘贴查找更多相关信息。
///
`oauth2_scheme` 变量是 `OAuth2PasswordBearer` 的实例,也是**可调用项**。
以如下方式调用:
```Python
oauth2_scheme(some, parameters)
```
因此,`Depends` 可以调用 `oauth2_scheme` 变量。
### 使用
接下来,使用 `Depends``oauth2_scheme` 传入依赖项。
{* ../../docs_src/security/tutorial001.py hl[10] *}
该依赖项使用字符串(`str`)接收*路径操作函数*的参数 `token`
**FastAPI** 使用依赖项在 OpenAPI 概图(及 API 文档)中定义**安全方案**。
/// info | 技术细节
**FastAPI** 使用(在依赖项中声明的)类 `OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,这是因为它继承自 `fastapi.security.oauth2.OAuth2`,而该类又是继承自`fastapi.security.base.SecurityBase`
所有与 OpenAPI(及 API 文档)集成的安全工具都继承自 `SecurityBase` 这就是为什么 **FastAPI** 能把它们集成至 OpenAPI 的原因。
///
## 实现的操作
FastAPI 校验请求中的 `Authorization` 请求头,核对请求头的值是不是由 `Bearer ` 令牌组成, 并返回令牌字符串(`str`)。
如果没有找到 `Authorization` 请求头,或请求头的值不是 `Bearer ` 令牌。FastAPI 直接返回 401 错误状态码(`UNAUTHORIZED`)。
开发者不需要检查错误信息,查看令牌是否存在,只要该函数能够执行,函数中就会包含令牌字符串。
正如下图所示,API 文档已经包含了这项功能:
<img src="/img/tutorial/security/image03.png">
目前,暂时还没有实现验证令牌是否有效的功能,不过后文很快就会介绍的。
## 小结
看到了吧,只要多写三四行代码,就可以添加基础的安全表单。
@@ -0,0 +1,107 @@
# 获取当前用户
上一章中,(基于依赖注入系统的)安全系统向*路径操作函数*传递了 `str` 类型的 `token`
{* ../../docs_src/security/tutorial001.py hl[10] *}
但这并不实用。
接下来,我们学习如何返回当前用户。
## 创建用户模型
首先,创建 Pydantic 用户模型。
与使用 Pydantic 声明请求体相同,并且可在任何位置使用:
{* ../../docs_src/security/tutorial002.py hl[5,12:16] *}
## 创建 `get_current_user` 依赖项
创建 `get_current_user` 依赖项。
还记得依赖项支持子依赖项吗?
`get_current_user` 使用 `oauth2_scheme` 作为依赖项。
与之前直接在路径操作中的做法相同,新的 `get_current_user` 依赖项从子依赖项 `oauth2_scheme` 中接收 `str` 类型的 `token`
{* ../../docs_src/security/tutorial002.py hl[25] *}
## 获取用户
`get_current_user` 使用创建的(伪)工具函数,该函数接收 `str` 类型的令牌,并返回 Pydantic 的 `User` 模型:
{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *}
## 注入当前用户
在*路径操作* 的 `Depends` 中使用 `get_current_user`
{* ../../docs_src/security/tutorial002.py hl[31] *}
注意,此处把 `current_user` 的类型声明为 Pydantic 的 `User` 模型。
这有助于在函数内部使用代码补全和类型检查。
/// tip | 提示
还记得请求体也是使用 Pydantic 模型声明的吧。
放心,因为使用了 `Depends`**FastAPI** 不会搞混。
///
/// check | 检查
依赖系统的这种设计方式可以支持不同的依赖项返回同一个 `User` 模型。
而不是局限于只能有一个返回该类型数据的依赖项。
///
## 其它模型
接下来,直接在*路径操作函数*中获取当前用户,并用 `Depends` 在**依赖注入**系统中处理安全机制。
开发者可以使用任何模型或数据满足安全需求(本例中是 Pydantic 的 `User` 模型)。
而且,不局限于只能使用特定的数据模型、类或类型。
不想在模型中使用 `username`,而是使用 `id``email`?当然可以。这些工具也支持。
只想使用字符串?或字典?甚至是数据库类模型的实例?工作方式都一样。
实际上,就算登录应用的不是用户,而是只拥有访问令牌的机器人、程序或其它系统?工作方式也一样。
尽管使用应用所需的任何模型、类、数据库。**FastAPI** 通过依赖注入系统都能帮您搞定。
## 代码大小
这个示例看起来有些冗长。毕竟这个文件同时包含了安全、数据模型的工具函数,以及路径操作等代码。
但,关键是:
**安全和依赖注入的代码只需要写一次。**
就算写得再复杂,也只是在一个位置写一次就够了。所以,要多复杂就可以写多复杂。
但是,就算有数千个端点(*路径操作*),它们都可以使用同一个安全系统。
而且,所有端点(或它们的任何部件)都可以利用这些依赖项或任何其它依赖项。
所有*路径操作*只需 3 行代码就可以了:
{* ../../docs_src/security/tutorial002.py hl[30:32] *}
## 小结
现在,我们可以直接在*路径操作函数*中获取当前用户。
至此,安全的内容已经讲了一半。
只要再为用户或客户端的*路径操作*添加真正发送 `username``password` 的功能就可以了。
下一章见。
+106
View File
@@ -0,0 +1,106 @@
# 安全性
有许多方法可以处理安全性、身份认证和授权等问题。
而且这通常是一个复杂而「困难」的话题。
在许多框架和系统中,仅处理安全性和身份认证就会花费大量的精力和代码(在许多情况下,可能占编写的所有代码的 50% 或更多)。
**FastAPI** 提供了多种工具,可帮助你以标准的方式轻松、快速地处理**安全性**,而无需研究和学习所有的安全规范。
但首先,让我们来看一些小的概念。
## 没有时间?
如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到下一章。
## OAuth2
OAuth2是一个规范,它定义了几种处理身份认证和授权的方法。
它是一个相当广泛的规范,涵盖了一些复杂的使用场景。
它包括了使用「第三方」进行身份认证的方法。
这就是所有带有「使用 FacebookGoogleX (Twitter)GitHub 登录」的系统背后所使用的机制。
### OAuth 1
有一个 OAuth 1,它与 OAuth2 完全不同,并且更为复杂,因为它直接包含了有关如何加密通信的规范。
如今它已经不是很流行,没有被广泛使用了。
OAuth2 没有指定如何加密通信,它期望你为应用程序使用 HTTPS 进行通信。
/// tip
在有关**部署**的章节中,你将了解如何使用 Traefik 和 Let's Encrypt 免费设置 HTTPS。
///
## OpenID Connect
OpenID Connect 是另一个基于 **OAuth2** 的规范。
它只是扩展了 OAuth2,并明确了一些在 OAuth2 中相对模糊的内容,以尝试使其更具互操作性。
例如,Google 登录使用 OpenID Connect(底层使用OAuth2)。
但是 Facebook 登录不支持 OpenID Connect。它具有自己的 OAuth2 风格。
### OpenID(非「OpenID Connect」)
还有一个「OpenID」规范。它试图解决与 **OpenID Connect** 相同的问题,但它不是基于 OAuth2。
因此,它是一个完整的附加系统。
如今它已经不是很流行,没有被广泛使用了。
## OpenAPI
OpenAPI(以前称为 Swagger)是用于构建 API 的开放规范(现已成为 Linux Foundation 的一部分)。
**FastAPI** 基于 **OpenAPI**
这就是使多个自动交互式文档界面,代码生成等成为可能的原因。
OpenAPI 有一种定义多个安全「方案」的方法。
通过使用它们,你可以利用所有这些基于标准的工具,包括这些交互式文档系统。
OpenAPI 定义了以下安全方案:
* `apiKey`:一个特定于应用程序的密钥,可以来自:
* 查询参数。
* 请求头。
* cookie。
* `http`:标准的 HTTP 身份认证系统,包括:
* `bearer`: 一个值为 `Bearer` 加令牌字符串的 `Authorization` 请求头。这是从 OAuth2 继承的。
* HTTP Basic 认证方式。
* HTTP Digest,等等。
* `oauth2`:所有的 OAuth2 处理安全性的方式(称为「流程」)。
*以下几种流程适合构建 OAuth 2.0 身份认证的提供者(例如 GoogleFacebookX (Twitter)GitHub 等):
* `implicit`
* `clientCredentials`
* `authorizationCode`
* 但是有一个特定的「流程」可以完美地用于直接在同一应用程序中处理身份认证:
* `password`:接下来的几章将介绍它的示例。
* `openIdConnect`:提供了一种定义如何自动发现 OAuth2 身份认证数据的方法。
* 此自动发现机制是 OpenID Connect 规范中定义的内容。
/// tip
集成其他身份认证/授权提供者(例如GoogleFacebookX (Twitter)GitHub等)也是可能的,而且较为容易。
最复杂的问题是创建一个像这样的身份认证/授权提供程序,但是 **FastAPI** 为你提供了轻松完成任务的工具,同时为你解决了重活。
///
## **FastAPI** 实用工具
FastAPI 在 `fastapi.security` 模块中为每个安全方案提供了几种工具,这些工具简化了这些安全机制的使用方法。
在下一章中,你将看到如何使用 **FastAPI** 所提供的这些工具为你的 API 增加安全性。
而且你还将看到它如何自动地被集成到交互式文档系统中。
@@ -0,0 +1,270 @@
# OAuth2 实现密码哈希与 Bearer JWT 令牌验证
至此,我们已经编写了所有安全流,本章学习如何使用 <abbr title="JSON Web Tokens">JWT</abbr> 令牌(Token)和安全密码哈希(Hash)实现真正的安全机制。
本章的示例代码真正实现了在应用的数据库中保存哈希密码等功能。
接下来,我们紧接上一章,继续完善安全机制。
## JWT 简介
JWT 即**JSON 网络令牌**JSON Web Tokens)。
JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。JWT 的内容如下所示:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
JWT 字符串没有加密,任何人都能用它恢复原始信息。
但 JWT 使用了签名机制。接受令牌时,可以用签名校验令牌。
使用 JWT 创建有效期为一周的令牌。第二天,用户持令牌再次访问时,仍为登录状态。
令牌于一周后过期,届时,用户身份验证就会失败。只有再次登录,才能获得新的令牌。如果用户(或第三方)篡改令牌的过期时间,因为签名不匹配会导致身份验证失败。
如需深入了解 JWT 令牌,了解它的工作方式,请参阅 <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>。
## 安装 `PyJWT`
安装 `PyJWT`,在 Python 中生成和校验 JWT 令牌:
<div class="termy">
```console
$ pip install pyjwt
---> 100%
```
</div>
/// info | 说明
如果您打算使用类似 RSA 或 ECDSA 的数字签名算法,您应该安装加密库依赖项 `pyjwt[crypto]`
您可以在 <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">PyJWT Installation docs</a> 获得更多信息。
///
## 密码哈希
**哈希**是指把特定内容(本例中为密码)转换为乱码形式的字节序列(其实就是字符串)。
每次传入完全相同的内容时(比如,完全相同的密码),返回的都是完全相同的乱码。
但这个乱码无法转换回传入的密码。
### 为什么使用密码哈希
原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。
这样一来,窃贼就无法在其它应用中使用窃取的密码(要知道,很多用户在所有系统中都使用相同的密码,风险超大)。
## 安装 `passlib`
Passlib 是处理密码哈希的 Python 包。
它支持很多安全哈希算法及配套工具。
本教程推荐的算法是 **Bcrypt**
因此,请先安装附带 Bcrypt 的 PassLib
<div class="termy">
```console
$ pip install passlib[bcrypt]
---> 100%
```
</div>
/// tip | 提示
`passlib` 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。
例如,把 Django 应用的数据共享给 FastAPI 应用的数据库。或利用同一个数据库,可以逐步把应用从 Django 迁移到 FastAPI。
并且,用户可以同时从 Django 应用或 FastAPI 应用登录。
///
## 密码哈希与校验
`passlib` 导入所需工具。
创建用于密码哈希和身份校验的 PassLib **上下文**
/// tip | 提示
PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验的已弃用旧算法等。
例如,用它读取和校验其它系统(如 Django)生成的密码,但要使用其它算法,如 Bcrypt,生成新的哈希密码。
同时,这些功能都是兼容的。
///
接下来,创建三个工具函数,其中一个函数用于哈希用户的密码。
第一个函数用于校验接收的密码是否匹配存储的哈希值。
第三个函数用于身份验证,并返回用户。
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
/// note | 笔记
查看新的(伪)数据库 `fake_users_db`,就能看到哈希后的密码:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`
///
## 处理 JWT 令牌
导入已安装的模块。
创建用于 JWT 令牌签名的随机密钥。
使用以下命令,生成安全的随机密钥:
<div class="termy">
```console
$ openssl rand -hex 32
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
```
</div>
然后,把生成的密钥复制到变量**SECRET_KEY**,注意,不要使用本例所示的密钥。
创建指定 JWT 令牌签名算法的变量 **ALGORITHM**,本例中的值为 `"HS256"`
创建设置令牌过期时间的变量。
定义令牌端点响应的 Pydantic 模型。
创建生成新的访问令牌的工具函数。
{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *}
## 更新依赖项
更新 `get_current_user` 以接收与之前相同的令牌,但这里用的是 JWT 令牌。
解码并校验接收到的令牌,然后,返回当前用户。
如果令牌无效,则直接返回 HTTP 错误。
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
## 更新 `/token` *路径操作*
用令牌过期时间创建 `timedelta` 对象。
创建并返回真正的 JWT 访问令牌。
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
### JWT `sub` 的技术细节
JWT 规范还包括 `sub` 键,值是令牌的主题。
该键是可选的,但要把用户标识放在这个键里,所以本例使用了该键。
除了识别用户与许可用户在 API 上直接执行操作之外,JWT 还可能用于其它事情。
例如,识别**汽车**或**博客**。
接着,为实体添加权限,比如**驾驶**(汽车)或**编辑**(博客)。
然后,把 JWT 令牌交给用户(或机器人),他们就可以执行驾驶汽车,或编辑博客等操作。无需注册账户,只要有 API 生成的 JWT 令牌就可以。
同理,JWT 可以用于更复杂的场景。
在这些情况下,多个实体的 ID 可能是相同的,以 ID `foo` 为例,用户的 ID 是 `foo`,车的 ID 是 `foo`,博客的 ID 也是 `foo`
为了避免 ID 冲突,在给用户创建 JWT 令牌时,可以为 `sub` 键的值加上前缀,例如 `username:`。因此,在本例中,`sub` 的值可以是:`username:johndoe`
注意,划重点,`sub` 键在整个应用中应该只有一个唯一的标识符,而且应该是字符串。
## 检查
运行服务器并访问文档: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
可以看到如下用户界面:
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image07.png">
用与上一章同样的方式实现应用授权。
使用如下凭证:
用户名: `johndoe` 密码: `secret`
/// check | 检查
注意,代码中没有明文密码**`secret`**,只保存了它的哈希值。
///
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image08.png">
调用 `/users/me/` 端点,收到下面的响应:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false
}
```
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image09.png">
打开浏览器的开发者工具,查看数据是怎么发送的,而且数据里只包含了令牌,只有验证用户的第一个请求才发送密码,并获取访问令牌,但之后不会再发送密码:
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image10.png">
/// note | 笔记
注意,请求中 `Authorization` 响应头的值以 `Bearer` 开头。
///
## `scopes` 高级用法
OAuth2 支持**`scopes`**(作用域)。
**`scopes`**为 JWT 令牌添加指定权限。
让持有令牌的用户或第三方在指定限制条件下与 API 交互。
**高级用户指南**中将介绍如何使用 `scopes`,及如何把 `scopes` 集成至 **FastAPI**
## 小结
至此,您可以使用 OAuth2 和 JWT 等标准配置安全的 **FastAPI** 应用。
几乎在所有框架中,处理安全问题很快都会变得非常复杂。
有些包为了简化安全流,不得不在数据模型、数据库和功能上做出妥协。而有些过于简化的软件包其实存在了安全隐患。
---
**FastAPI** 不向任何数据库、数据模型或工具做妥协。
开发者可以灵活选择最适合项目的安全机制。
还可以直接使用 `passlib``PyJWT` 等维护良好、使用广泛的包,这是因为 **FastAPI** 不需要任何复杂机制,就能集成外部的包。
而且,**FastAPI** 还提供了一些工具,在不影响灵活、稳定和安全的前提下,尽可能地简化安全机制。
**FastAPI** 还支持以相对简单的方式,使用 OAuth2 等安全、标准的协议。
**高级用户指南**中详细介绍了 OAuth2**`scopes`**的内容,遵循同样的标准,实现更精密的权限系统。OAuth2 的作用域是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制,让用户授权第三方应用与 API 交互。
@@ -0,0 +1,289 @@
# OAuth2 实现简单的 Password 和 Bearer 验证
本章添加上一章示例中欠缺的部分,实现完整的安全流。
## 获取 `username` 和 `password`
首先,使用 **FastAPI** 安全工具获取 `username``password`
OAuth2 规范要求使用**密码流**时,客户端或用户必须以表单数据形式发送 `username``password` 字段。
并且,这两个字段必须命名为 `username``password` ,不能使用 `user-name``email` 等其它名称。
不过也不用担心,前端仍可以显示终端用户所需的名称。
数据库模型也可以使用所需的名称。
但对于登录*路径操作*,则要使用兼容规范的 `username``password`,(例如,实现与 API 文档集成)。
该规范要求必须以表单数据形式发送 `username``password`,因此,不能使用 JSON 对象。
### `Scope`(作用域)
OAuth2 还支持客户端发送**`scope`**表单字段。
虽然表单字段的名称是 `scope`(单数),但实际上,它是以空格分隔的,由多个**scope**组成的长字符串。
**作用域**只是不带空格的字符串。
常用于声明指定安全权限,例如:
* 常见用例为,`users:read``users:write`
* 脸书和 Instagram 使用 `instagram_basic`
* 谷歌使用 `https://www.googleapis.com/auth/drive`
/// info | 说明
OAuth2 中,**作用域**只是声明指定权限的字符串。
是否使用冒号 `:` 等符号,或是不是 URL 并不重要。
这些细节只是特定的实现方式。
对 OAuth2 来说,都只是字符串而已。
///
## 获取 `username` 和 `password` 的代码
接下来,使用 **FastAPI** 工具获取用户名与密码。
### `OAuth2PasswordRequestForm`
首先,导入 `OAuth2PasswordRequestForm`,然后,在 `/token` *路径操作* 中,用 `Depends` 把该类作为依赖项。
{* ../../docs_src/security/tutorial003.py hl[4,76] *}
`OAuth2PasswordRequestForm` 是用以下几项内容声明表单请求体的类依赖项:
* `username`
* `password`
* 可选的 `scope` 字段,由多个空格分隔的字符串组成的长字符串
* 可选的 `grant_type`
/// tip | 提示
实际上,OAuth2 规范*要求* `grant_type` 字段使用固定值 `password`,但 `OAuth2PasswordRequestForm` 没有作强制约束。
如需强制使用固定值 `password`,则不要用 `OAuth2PasswordRequestForm`,而是用 `OAuth2PasswordRequestFormStrict`
///
* 可选的 `client_id`(本例未使用)
* 可选的 `client_secret`(本例未使用)
/// info | 说明
`OAuth2PasswordRequestForm``OAuth2PasswordBearer` 一样,都不是 FastAPI 的特殊类。
**FastAPI**`OAuth2PasswordBearer` 识别为安全方案。因此,可以通过这种方式把它添加至 OpenAPI。
`OAuth2PasswordRequestForm` 只是可以自行编写的类依赖项,也可以直接声明 `Form` 参数。
但由于这种用例很常见,FastAPI 为了简便,就直接提供了对它的支持。
///
### 使用表单数据
/// tip | 提示
`OAuth2PasswordRequestForm` 类依赖项的实例没有以空格分隔的长字符串属性 `scope`,但它支持 `scopes` 属性,由已发送的 scope 字符串列表组成。
本例没有使用 `scopes`,但开发者也可以根据需要使用该属性。
///
现在,即可使用表单字段 `username`,从(伪)数据库中获取用户数据。
如果不存在指定用户,则返回错误消息,提示**用户名或密码错误**。
本例使用 `HTTPException` 异常显示此错误:
{* ../../docs_src/security/tutorial003.py hl[3,77:79] *}
### 校验密码
至此,我们已经从数据库中获取了用户数据,但尚未校验密码。
接下来,首先将数据放入 Pydantic 的 `UserInDB` 模型。
注意:永远不要保存明文密码,本例暂时先使用(伪)哈希密码系统。
如果密码不匹配,则返回与上面相同的错误。
#### 密码哈希
**哈希**是指,将指定内容(本例中为密码)转换为形似乱码的字节序列(其实就是字符串)。
每次传入完全相同的内容(比如,完全相同的密码)时,得到的都是完全相同的乱码。
但这个乱码无法转换回传入的密码。
##### 为什么使用密码哈希
原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。
这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大。
{* ../../docs_src/security/tutorial003.py hl[80:83] *}
#### 关于 `**user_dict`
`UserInDB(**user_dict)` 是指:
*直接把 `user_dict` 的键与值当作关键字参数传递,等效于:*
```Python
UserInDB(
username = user_dict["username"],
email = user_dict["email"],
full_name = user_dict["full_name"],
disabled = user_dict["disabled"],
hashed_password = user_dict["hashed_password"],
)
```
/// info | 说明
`user_dict` 的说明,详见[**更多模型**一章](../extra-models.md#user_indict){.internal-link target=_blank}。
///
## 返回 Token
`token` 端点的响应必须是 JSON 对象。
响应返回的内容应该包含 `token_type`。本例中用的是**Bearer**Token,因此, Token 类型应为**`bearer`**。
返回内容还应包含 `access_token` 字段,它是包含权限 Token 的字符串。
本例只是简单的演示,返回的 Token 就是 `username`,但这种方式极不安全。
/// tip | 提示
下一章介绍使用哈希密码和 <abbr title="JSON Web Tokens">JWT</abbr> Token 的真正安全机制。
但现在,仅关注所需的特定细节。
///
{* ../../docs_src/security/tutorial003.py hl[85] *}
/// tip | 提示
按规范的要求,应像本示例一样,返回带有 `access_token``token_type` 的 JSON 对象。
这是开发者必须在代码中自行完成的工作,并且要确保使用这些 JSON 的键。
这几乎是唯一需要开发者牢记在心,并按规范要求正确执行的事。
**FastAPI** 则负责处理其它的工作。
///
## 更新依赖项
接下来,更新依赖项。
使之仅在当前用户为激活状态时,才能获取 `current_user`
为此,要再创建一个依赖项 `get_current_active_user`,此依赖项以 `get_current_user` 依赖项为基础。
如果用户不存在,或状态为未激活,这两个依赖项都会返回 HTTP 错误。
因此,在端点中,只有当用户存在、通过身份验证、且状态为激活时,才能获得该用户:
{* ../../docs_src/security/tutorial003.py hl[58:67,69:72,90] *}
/// info | 说明
此处返回值为 `Bearer` 的响应头 `WWW-Authenticate` 也是规范的一部分。
任何 401**UNAUTHORIZED**HTTP(错误)状态码都应返回 `WWW-Authenticate` 响应头。
本例中,因为使用的是 Bearer Token,该响应头的值应为 `Bearer`
实际上,忽略这个附加响应头,也不会有什么问题。
之所以在此提供这个附加响应头,是为了符合规范的要求。
说不定什么时候,就有工具用得上它,而且,开发者或用户也可能用得上。
这就是遵循标准的好处……
///
## 实际效果
打开 API 文档:<a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>。
### 身份验证
点击**Authorize**按钮。
使用以下凭证:
用户名:`johndoe`
密码:`secret`
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image04.png">
通过身份验证后,显示下图所示的内容:
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image05.png">
### 获取当前用户数据
使用 `/users/me` 路径的 `GET` 操作。
可以提取如下当前用户数据:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false,
"hashed_password": "fakehashedsecret"
}
```
<img src="https://fastapi.tiangolo.com/img/tutorial/security/image06.png">
点击小锁图标,注销后,再执行同样的操作,则会得到 HTTP 401 错误:
```JSON
{
"detail": "Not authenticated"
}
```
### 未激活用户
测试未激活用户,输入以下信息,进行身份验证:
用户名:`alice`
密码:`secret2`
然后,执行 `/users/me` 路径的 `GET` 操作。
显示下列**未激活用户**错误信息:
```JSON
{
"detail": "Inactive user"
}
```
## 小结
使用本章的工具实现基于 `username``password` 的完整 API 安全系统。
这些工具让安全系统兼容任何数据库、用户及数据模型。
唯一欠缺的是,它仍然不是真的**安全**。
下一章,介绍使用密码哈希支持库与 <abbr title="JSON Web Tokens">JWT</abbr> 令牌实现真正的安全机制。
+360
View File
@@ -0,0 +1,360 @@
# SQL(关系型)数据库
**FastAPI** 并不要求您使用 SQL(关系型)数据库。您可以使用**任何**想用的数据库。
这里,我们来看一个使用 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel</a> 的示例。
**SQLModel** 是基于 <a href="https://www.sqlalchemy.org/" class="external-link" target="_blank">SQLAlchemy</a> 和 Pydantic 构建的。它由 **FastAPI** 的同一作者制作,旨在完美匹配需要使用 **SQL 数据库**的 FastAPI 应用程序。
/// tip
您可以使用任何其他您想要的 SQL 或 NoSQL 数据库(在某些情况下称为 <abbr title="对象关系映射器(Object Relational MapperORM),一个术语,用来指代一种库,其中某些类对应于 SQL 数据表,这些类的实例则对应于表中的行。">“ORM”</abbr>),FastAPI 不会强迫您使用任何东西。😎
///
由于 SQLModel 基于 SQLAlchemy,因此您可以轻松使用任何由 SQLAlchemy **支持的数据库**(这也让它们被 SQLModel 支持),例如:
* PostgreSQL
* MySQL
* SQLite
* Oracle
* Microsoft SQL Server 等.
在这个例子中,我们将使用 **SQLite**,因为它使用单个文件,并且 Python 对其有集成支持。因此,您可以直接复制这个例子并运行。
之后,对于您的生产应用程序,您可能会想要使用像 PostgreSQL 这样的数据库服务器。
/// tip
有一个使用 **FastAPI****PostgreSQL** 的官方的项目生成器,其中包括了前端和更多工具: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">https://github.com/fastapi/full-stack-fastapi-template</a>
///
这是一个非常简单和简短的教程。如果您想了解一般的数据库、SQL 或更高级的功能,请查看 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">SQLModel 文档</a>。
## 安装 `SQLModel`
首先,确保您创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装了 `sqlmodel` :
<div class="termy">
```console
$ pip install sqlmodel
---> 100%
```
</div>
## 创建含有单一模型的应用程序
我们首先创建应用程序的最简单的第一个版本,只有一个 **SQLModel** 模型。
稍后我们将通过下面的**多个模型**提高其安全性和多功能性。🤓
### 创建模型
导入 `SQLModel` 并创建一个数据库模型:
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *}
`Hero` 类与 Pydantic 模型非常相似(实际上,从底层来看,它确实*就是一个 Pydantic 模型*)。
有一些区别:
* `table=True` 会告诉 SQLModel 这是一个*表模型*,它应该表示 SQL 数据库中的一个*表*,而不仅仅是一个*数据模型*(就像其他常规的 Pydantic 类一样)。
* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**(您可以在 SQLModel 文档中了解更多关于 SQL 主键的信息)。
把类型设置为 `int | None` SQLModel 就能知道该列在 SQL 数据库中应该是 `INTEGER` 类型,并且应该是 `NULLABLE`
* `Field(index=True)` 会告诉 SQLModel 应该为此列创建一个 **SQL 索引**,这样在读取按此列过滤的数据时,程序能在数据库中进行更快的查找。
SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT` (或 `VARCHAR` ,具体取决于数据库)的 SQL 列。
### 创建引擎(Engine
SQLModel 的引擎 `engine`(实际上它是一个 SQLAlchemy `engine` )是用来与数据库**保持连接**的。
您只需构建**一个 `engine`**,来让您的所有代码连接到同一个数据库。
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *}
使用 `check_same_thread=False` 可以让 FastAPI 在不同线程中使用同一个 SQLite 数据库。这很有必要,因为**单个请求**可能会使用**多个线程**(例如在依赖项中)。
不用担心,我们会按照代码结构确保**每个请求使用一个单独的 SQLModel *会话***,这实际上就是 `check_same_thread` 想要实现的。
### 创建表
然后,我们来添加一个函数,使用 `SQLModel.metadata.create_all(engine)` 为所有*表模型***创建表**。
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *}
### 创建会话(Session)依赖项
**`Session`** 会存储**内存中的对象**并跟踪数据中所需更改的内容,然后它**使用 `engine`** 与数据库进行通信。
我们会使用 `yield` 创建一个 FastAPI **依赖项**,为每个请求提供一个新的 `Session` 。这确保我们每个请求使用一个单独的会话。🤓
然后我们创建一个 `Annotated` 的依赖项 `SessionDep` 来简化其他也会用到此依赖的代码。
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *}
### 在启动时创建数据库表
我们会在应用程序启动时创建数据库表。
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *}
此处,在应用程序启动事件中,我们创建了表。
而对于生产环境,您可能会用一个能够在启动应用程序之前运行的迁移脚本。🤓
/// tip
SQLModel 将会拥有封装 Alembic 的迁移工具,但目前您可以直接使用 <a href="https://alembic.sqlalchemy.org/en/latest/" class="external-link" target="_blank">Alembic</a>。
///
### 创建 Hero 类
因为每个 SQLModel 模型同时也是一个 Pydantic 模型,所以您可以在与 Pydantic 模型相同的**类型注释**中使用它。
例如,如果您声明一个类型为 `Hero` 的参数,它将从 **JSON 主体**中读取数据。
同样,您可以将其声明为函数的**返回类型**,然后数据的结构就会显示在自动生成的 API 文档界面中。
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *}
</details>
这里,我们使用 `SessionDep` 依赖项(一个 `Session` )将新的 `Hero` 添加到 `Session` 实例中,提交更改到数据库,刷新 hero 中的数据,并返回它。
### 读取 Hero 类
我们可以使用 `select()` 从数据库中**读取** `Hero` 类,并利用 `limit``offset` 来对结果进行分页。
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *}
### 读取单个 Hero
我们可以**读取**单个 `Hero`
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *}
### 删除单个 Hero
我们也可以**删除**单个 `Hero`
{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *}
### 运行应用程序
您可以运行这个应用程序:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
然后在 `/docs` UI 中,您能够看到 **FastAPI** 会用这些**模型**来**记录** API,并且还会用它们来**序列化**和**验证**数据。
<div class="screenshot">
<img src="/img/tutorial/sql-databases/image01.png">
</div>
## 更新应用程序以支持多个模型
现在让我们稍微**重构**一下这个应用,以提高**安全性**和**多功能性**。
如果您查看之前的应用程序,您可以在 UI 界面中看到,到目前为止,由客户端决定要创建的 `Hero``id` 值。😱
我们不应该允许这样做,因为他们可能会覆盖我们在数据库中已经分配的 `id` 。决定 `id` 的行为应该由**后端**或**数据库**来完成,**而非客户端**。
此外,我们为 hero 创建了一个 `secret_name` ,但到目前为止,我们在各处都返回了它,这就不太**秘密**了……😅
我们将通过添加一些**额外的模型**来解决这些问题,而 SQLModel 将在这里大放异彩。✨
### 创建多个模型
**SQLModel** 中,任何含有 `table=True` 属性的模型类都是一个**表模型**。
任何不含有 `table=True` 属性的模型类都是**数据模型**,这些实际上只是 Pydantic 模型(附带一些小的额外功能)。🤓
有了 SQLModel,我们就可以利用**继承**来在所有情况下**避免重复**所有字段。
#### `HeroBase` - 基类
我们从一个 `HeroBase` 模型开始,该模型具有所有模型**共享的字段**:
* `name`
* `age`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *}
#### `Hero` - *表模型*
接下来,我们创建 `Hero` ,实际的*表模型*,并添加那些不总是在其他模型中的**额外字段**:
* `id`
* `secret_name`
因为 `Hero` 继承自 HeroBase ,所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为:
* `id`
* `name`
* `age`
* `secret_name`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *}
#### `HeroPublic` - 公共*数据模型*
接下来,我们创建一个 `HeroPublic` 模型,这是将**返回**给 API 客户端的模型。
它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name`
终于,我们英雄(hero)的身份得到了保护! 🥷
它还重新声明了 `id: int` 。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None` )。
/// tip
确保返回模型始终提供一个值并且始终是 `int` (而不是 `None` )对 API 客户端非常有用,他们可以在这种确定性下编写更简单的代码。
此外,**自动生成的客户端**将拥有更简洁的接口,这样与您的 API 交互的开发者就能更轻松地使用您的 API。😎
///
`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同,其中 `id` 声明为 `int` (不是 `None` ):
* `id`
* `name`
* `age`
* `secret_name`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *}
#### `HeroCreate` - 用于创建 hero 的*数据模型*
现在我们创建一个 `HeroCreate` 模型,这是用于**验证**客户数据的模型。
它不仅拥有与 `HeroBase` 相同的字段,还有 `secret_name`
现在,当客户端**创建一个新的 hero** 时,他们会发送 `secret_name` ,它会被存储到数据库中,但这些 `secret_name` 不会通过 API 返回给客户端。
/// tip
这应当是**密码**被处理的方式:接收密码,但不要通过 API 返回它们。
在存储密码之前,您还应该对密码的值进行**哈希**处理,**绝不要以明文形式存储它们**。
///
`HeroCreate` 的字段包括:
* `name`
* `age`
* `secret_name`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *}
#### `HeroUpdate` - 用于更新 hero 的*数据模型*
在之前的应用程序中,我们没有办法**更新 hero**,但现在有了**多个模型**,我们便能做到这一点了。🎉
`HeroUpdate` *数据模型*有些特殊,它包含创建新 hero 所需的**所有相同字段**,但所有字段都是**可选的**(它们都有默认值)。这样,当您更新一个 hero 时,您可以只发送您想要更新的字段。
因为所有**字段实际上**都发生了**变化**(类型现在包括 `None` ,并且它们现在有一个默认值 `None` ),我们需要**重新声明**它们。
我们会重新声明所有字段,因此我们并不是真的需要从 `HeroBase` 继承。我会让它继承只是为了保持一致,但这并不必要。这更多是个人喜好的问题。🤷
`HeroUpdate` 的字段包括:
* `name`
* `age`
* `secret_name`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *}
### 使用 `HeroCreate` 创建并返回 `HeroPublic`
既然我们有了**多个模型**,我们就可以对使用它们的应用程序部分进行更新。
我们在请求中接收到一个 `HeroCreate` *数据模型*,然后从中创建一个 `Hero` *表模型*
这个新的*表模型* `Hero` 会包含客户端发送的字段,以及一个由数据库生成的 `id`
然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model` **FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *}
/// tip
现在我们使用 `response_model=HeroPublic` 来代替**返回类型注释** `-> HeroPublic` ,因为我们返回的值实际上**并不是** `HeroPublic` 类型。
如果我们声明了 `-> HeroPublic` ,您的编辑器和代码检查工具会抱怨(但也确实理所应当)您返回了一个 `Hero` 而不是一个 `HeroPublic`
通过 `response_model` 的声明,我们让 **FastAPI** 按照它自己的方式处理,而不会干扰类型注解以及编辑器和其他工具提供的帮助。
///
### 用 `HeroPublic` 读取 Hero
我们可以像之前一样**读取** `Hero` 。同样,使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *}
### 用 `HeroPublic` 读取单个 Hero
我们可以**读取**单个 `hero`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *}
### 用 `HeroUpdate` 更新单个 Hero
我们可以**更新**单个 `hero` 。为此,我们会使用 HTTP 的 `PATCH` 操作。
在代码中,我们会得到一个 `dict` ,其中包含客户端发送的所有数据,**只有客户端发送的数据**,并排除了任何一个仅仅作为默认值存在的值。为此,我们使用 `exclude_unset=True` 。这是最主要的技巧。🪄
然后我们会使用 `hero_db.sqlmodel_update(hero_data)` ,来利用 `hero_data` 的数据更新 `hero_db`
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *}
### (又一次)删除单个 Hero
**删除**一个 hero 基本保持不变。
我们不会满足在这一部分中重构一切的愿望。😅
{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *}
### (又一次)运行应用程序
您可以再运行一次应用程序:
<div class="termy">
```console
$ fastapi dev main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
您会在 `/docs` API UI 看到它现在已经更新,并且在进行创建 hero 等操作时,它不会再期望从客户端接收 `id` 数据。
<div class="screenshot">
<img src="/img/tutorial/sql-databases/image02.png">
</div>
## 总结
您可以使用 <a href="https://sqlmodel.tiangolo.com/" class="external-link" target="_blank">**SQLModel**</a> 与 SQL 数据库进行交互,并通过*数据模型*和*表模型*简化代码。
您可以在 SQLModel 的文档中学习到更多内容,其中有一个更详细的关于<a href="https://sqlmodel.tiangolo.com/tutorial/fastapi/" class="external-link" target="_blank">如何将 SQLModel 与 FastAPI 一起使用的教程</a>。🚀
+40
View File
@@ -0,0 +1,40 @@
# 静态文件
您可以使用 `StaticFiles`从目录中自动提供静态文件。
## 使用`StaticFiles`
* 导入`StaticFiles`
* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
/// note | 技术细节
你也可以用 `from starlette.staticfiles import StaticFiles`
**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。
///
### 什么是"挂载"(Mounting)
"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。
这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。
你可以在**高级用户指南**中了解更多。
## 细节
这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。
`directory="static"` 指向包含你的静态文件的目录名字。
`name="static"` 提供了一个能被**FastAPI**内部使用的名字。
所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。
## 更多信息
更多细节和选择查阅 <a href="https://www.starlette.dev/staticfiles/" class="external-link" target="_blank">Starlette's docs about Static Files</a>.
+235
View File
@@ -0,0 +1,235 @@
# 测试
感谢 <a href="https://www.starlette.dev/testclient/" class="external-link" target="_blank">Starlette</a>,测试**FastAPI** 应用轻松又愉快。
它基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a> 而HTTPX又是基于Requests设计的,所以很相似且易懂。
有了它,你可以直接与**FastAPI**一起使用 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>。
## 使用 `TestClient`
/// info | 信息
要使用 `TestClient`,先要安装 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
例:`pip install httpx`.
///
导入 `TestClient`.
通过传入你的**FastAPI**应用创建一个 `TestClient`
创建名字以 `test_` 开头的函数(这是标准的 `pytest` 约定)。
像使用 `httpx` 那样使用 `TestClient` 对象。
为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。
{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *}
/// tip | 提示
注意测试函数是普通的 `def`,不是 `async def`
还有client的调用也是普通的调用,不是用 `await`
这让你可以直接使用 `pytest` 而不会遇到麻烦。
///
/// note | 技术细节
你也可以用 `from starlette.testclient import TestClient`
**FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。
///
/// tip | 提示
除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。
///
## 分离测试
在实际应用中,你可能会把你的测试放在另一个文件里。
您的**FastAPI**应用程序也可能由一些文件/模块组成等等。
### **FastAPI** app 文件
假设你有一个像 [更大的应用](bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构:
```
.
├── app
│   ├── __init__.py
│   └── main.py
```
`main.py` 文件中你有一个 **FastAPI** app:
{* ../../docs_src/app_testing/main.py *}
### 测试文件
然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件):
``` hl_lines="5"
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象:
{* ../../docs_src/app_testing/test_main.py hl[3] *}
...然后测试代码和之前一样的。
## 测试:扩展示例
现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。
### 扩展后的 **FastAPI** app 文件
让我们继续之前的文件结构:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
假设现在包含**FastAPI** app的文件 `main.py` 有些其他**路径操作**。
有个 `GET` 操作会返回错误。
有个 `POST` 操作会返回一些错误。
所有*路径操作* 都需要一个`X-Token` 头。
//// tab | Python 3.10+
```Python
{!> ../../docs_src/app_testing/app_b_an_py310/main.py!}
```
////
//// tab | Python 3.9+
```Python
{!> ../../docs_src/app_testing/app_b_an_py39/main.py!}
```
////
//// tab | Python 3.8+
```Python
{!> ../../docs_src/app_testing/app_b_an/main.py!}
```
////
//// tab | Python 3.10+ non-Annotated
/// tip | 提示
Prefer to use the `Annotated` version if possible.
///
```Python
{!> ../../docs_src/app_testing/app_b_py310/main.py!}
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | 提示
Prefer to use the `Annotated` version if possible.
///
```Python
{!> ../../docs_src/app_testing/app_b/main.py!}
```
////
### 扩展后的测试文件
然后您可以使用扩展后的测试更新`test_main.py`
{* ../../docs_src/app_testing/app_b/test_main.py *}
每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。
接着只需在测试中同样操作。
示例:
* 传一个*路径* 或*查询* 参数,添加到URL上。
* 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。
* 如果你需要发送 *Form Data* 而不是 JSON,使用 `data` 参数。
* 要发送 *headers*,传 `dict` 给 `headers` 参数。
* 对于 *cookies*,传 `dict` 给 `cookies` 参数。
关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX 文档</a>.
/// info | 信息
注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。
如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。
///
## 运行起来
之后,你只需要安装 `pytest`:
<div class="termy">
```console
$ pip install pytest
---> 100%
```
</div>
他会自动检测文件和测试,执行测试,然后向你报告结果。
执行测试:
<div class="termy">
```console
$ pytest
================ test session starts ================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/user/code/superawesome-cli/app
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
collected 6 items
---> 100%
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span>
<span style="color: green;">================= 1 passed in 0.03s =================</span>
```
</div>

Some files were not shown because too many files have changed in this diff Show More