路径参数

有类型的路径参数

在这个例子中,item_id 被声明为 int 类型。

1
2
3
@app.get("/items/{item_id}")
async def read_item(item_id:int):
return {"item_id": item_id}

你可以使用同样的类型声明来声明 str、float、bool 以及许多其他的复合数据类型
所有的数据校验都由 Pydantic 在幕后完成

顺序很重要

1
2
3
4
5
6
7
8
@app.get("/users/me")
async def read_user_me():
return {"user_id": "the current user"}


@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}

由于路径操作是按顺序依次运行的,你需要确保路径 /users/me 声明在路径 /users/{user_id}之前
否则,/users/{user_id}的路径还将与 /users/me 相匹配,”认为”自己正在接收一个值为 “me” 的 user_id 参数。

预设值

如果你有一个接收路径参数的路径操作,但你希望预先设定可能的有效参数值,则可以使用标准的 Python Enum 类型。

创建一个 Enum 类

1
2
3
4
5
6
from enum import Enum

class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"

声明路径参数

1
2
3
4
5
6
7
8
9
10
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}

if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}

return {"model_name": model_name, "message": "Have some residuals"}

包含路径的路径参数

假设你有一个路径操作,它的路径为 /files/{file_path}
但是你需要 file_path 自身也包含路径,比如 home/johndoe/myfile.txt
因此,该文件的URL将类似于这样:/files/home/johndoe/myfile.txt

你可以使用直接来自 Starlette 的选项来声明一个包含路径的路径参数:
而且文档依旧可以使用,但是不会添加任何该参数应包含路径的说明。

1
/files/{file_path:path}

在这种情况下,参数的名称为 file_path,结尾部分的 :path 说明该参数应匹配任意的路径。

1
2
3
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}

你可能会需要参数包含 /home/johndoe/myfile.txt,以斜杠(/)开头。
在这种情况下,URL 将会是 /files//home/johndoe/myfile.txt,在files 和 home 之间有一个双斜杠(//)。

查询参数

声明不属于路径参数的其他函数参数时,它们将被自动解释为”查询字符串”参数

1
2
3
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]

例如:http://127.0.0.1:8000/items/?skip=0&limit=10
查询参数为:

  • skip : 0
  • limit : 10

由于它们是 URL 的一部分,因此它们的”原始值”是字符串。
但是,当你为它们声明了 Python 类型(在上面的示例中为 int)时,它们将转换为该类型并针对该类型进行校验。

默认值

在上面的示例中,它们具有 skip=0 和 limit=10 的默认值。

可选参数

通过同样的方式,你可以将它们的默认值设置为 None 来声明可选查询参数:

1
2
3
4
5
6
7
from typing import Union

@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}

在这个例子中,函数参数 q 将是可选的,并且默认值为 None。

查询参数类型转换

你还可以声明 bool 类型,它们将被自动转换:

1
2
3
4
5
6
7
8
9
10
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None, short: bool = False):
item = {"item_id": item_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item

这个例子中,如果你访问:
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 能够识别它们。
而且你不需要以任何特定的顺序来声明。
它们将通过名称被检测到:

1
2
3
4
5
6
7
8
9
10
11
12
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
user_id: int, item_id: str, q: Union[str, None] = None, short: bool = False
):
item = {"item_id": item_id, "owner_id": user_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item

必需查询参数

当你为非路径参数声明了默认值时(目前而言,我们所知道的仅有查询参数),则该参数不是必需的。
如果你不想添加一个特定的值,而只是想使该参数成为可选的,则将默认值设置为 None。
但当你想让一个查询参数成为必需的,不声明任何默认值就可以:

1
2
3
4
@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str):
item = {"item_id": item_id, "needy": needy}
return item

这里的查询参数 needy 是类型为 str 的必需查询参数。

当然,你也可以定义一些参数为必需的,一些具有默认值,而某些则完全是可选的:

1
2
3
4
5
6
@app.get("/items/{item_id}")
async def read_user_item(
item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None
):
item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
return item

在这个例子中,有3个查询参数:

  • needy,一个必需的 str 类型参数。
  • skip,一个默认值为 0 的 int 类型参数。
  • limit,一个可选的 int 类型参数。

请求体

我们使用 Pydantic 模型来声明请求体,并能够获得它们所具有的所有能力和优点。

导入 Pydantic 的 BaseModel

1
from pydantic import BaseModel

创建数据模型

然后,将你的数据模型声明为继承自 BaseModel 的类。
使用标准的 Python 类型来声明所有属性:

1
2
3
4
5
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None

和声明查询参数时一样,当一个模型属性具有默认值时,它不是必需的。否则它是一个必需属性。将默认值设为 None 可使其成为可选属性。

声明为参数

使用与声明路径和查询参数的相同方式声明请求体,即可将其添加到「路径操作」中:

1
2
3
@app.post("/items/")
async def create_item(item: Item):
return item

结果

仅仅使用了 Python 类型声明,FastAPI 将会:

  • 将请求体作为 JSON 读取。
  • 转换为相应的类型(在需要时)。
  • 校验数据。
  • 如果数据无效,将返回一条清晰易读的错误信息,指出不正确数据的确切位置和内容。
  • 将接收的数据赋值到参数 item 中。
  • 由于你已经在函数中将它声明为 Item 类型,你还将获得对于所有属性及其类型的一切编辑器支持(代码补全等)。
  • 为你的模型生成 JSON 模式 定义,你还可以在其他任何对你的项目有意义的地方使用它们。
  • 这些模式将成为生成的 OpenAPI 模式的一部分,并且被自动化文档 UI 所使用。

使用模型

在函数内部,你可以直接访问模型对象的所有属性:

1
2
3
4
5
6
7
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict

请求体 + 路径参数

你可以同时声明路径参数和请求体。
FastAPI 将识别出与路径参数匹配的函数参数应从路径中获取,而声明为 Pydantic 模型的函数参数应从请求体中获取。

请求体 + 路径参数 + 查询参数

你还可以同时声明请求体、路径参数和查询参数。
FastAPI 会识别它们中的每一个,并从正确的位置获取数据。

函数参数将依次按如下规则进行识别:

  • 如果在路径中也声明了该参数,它将被用作路径参数。
  • 如果参数属于单一类型(比如 int、float、str、bool 等)它将被解释为查询参数。
  • 如果参数的类型被声明为一个 Pydantic 模型,它将被解释为请求体。

不使用 Pydantic

如果你不想使用 Pydantic 模型,你还可以使用 Body 参数。请参阅文档 请求体 - 多个参数:请求体中的单一值。

查询参数和字符串校验

FastAPI 允许你为参数声明额外的信息和校验。
让我们以下面的应用程序为例:

1
2
3
4
5
6
@app.get("/items/")
async def read_items(q: str | None = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

额外的校验

我们打算添加约束条件:即使 q 是可选的,但只要提供了该参数,则该参数值不能超过50个字符的长度。

1
2
3
4
5
6
7
8
from fastapi import FastAPI, Query

@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

Query 显式地将q声明为查询参数。

添加更多校验

  • min_length
  • pattern

默认值

你可以向 Query 的第一个参数传入 None 用作查询参数的默认值,以同样的方式你也可以传递其他默认值。
假设你想要声明查询参数 q,使其 min_length 为 3,并且默认值为 fixedquery:

1
2
3
4
5
6
@app.get("/items/")
async def read_items(q: str = Query(default="fixedquery", min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

声明为必需参数

当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 q 参数成为必需参数,例如:
q: str
当你在使用 Query 且需要声明一个值是必需的时,只需不声明默认参数:
q: str = Query(min_length=3)

使用省略号(…)声明必需参数

有另一种方法可以显式的声明一个值是必需的,即将默认参数的默认值设为 … :

1
2
3
4
5
6
@app.get("/items/")
async def read_items(q: str = Query(default=..., min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

如果你之前没见过 … 这种用法:它是一个特殊的单独值,它是 Python 的一部分并且被称为「省略号」
Pydantic 和 FastAPI 使用它来显式的声明需要一个值。

这将使 FastAPI 知道此查询参数是必需的。

使用None声明必需参数

你可以声明一个参数可以接收None值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是None。

1
2
3
4
5
6
@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=..., min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

使用Pydantic中的Required代替省略号(…)

如果你觉得使用 … 不舒服,你也可以从 Pydantic 导入并使用 Required:

1
2
3
4
5
6
7
8
from pydantic import Required

@app.get("/items/")
async def read_items(q: str = Query(default=Required, min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

请记住,在大多数情况下,当你需要某些东西时,可以简单地省略 default 参数,因此你通常不必使用 … 或 Required

查询参数列表 / 多个值

当你使用 Query 显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。
例如,要声明一个可在 URL 中出现多次的查询参数 q,你可以这样写:

1
2
3
4
@app.get("/items/")
async def read_items(q: Union[List[str], None] = Query(default=None)):
query_items = {"q": q}
return query_items

然后,输入如下网址:
http://localhost:8000/items/?q=foo&q=bar
你会在路径操作函数的函数参数 q 中以一个 Python list 的形式接收到查询参数 q 的多个值(foo 和 bar)。
因此,该 URL 的响应将会是:

1
2
3
4
5
6
{
"q": [
"foo",
"bar"
]
}

要声明类型为 list 的查询参数,如上例所示,你需要显式地使用 Query,否则该参数将被解释为请求体。

具有默认值的查询参数列表 / 多个值

你还可以定义在没有任何给定值时的默认 list 值:

1
2
3
4
@app.get("/items/")
async def read_items(q: List[str] = Query(default=["foo", "bar"])):
query_items = {"q": q}
return query_items

使用 list

你也可以直接使用 list 代替 List [str]:

1
2
3
4
@app.get("/items/")
async def read_items(q: list = Query(default=[])):
query_items = {"q": q}
return query_items

请记住,在这种情况下 FastAPI 将不会检查列表的内容。
例如,List[int] 将检查(并记录到文档)列表的内容必须是整数。但是单独的 list 不会。

声明更多元数据

你可以添加更多有关该参数的信息。
这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.get("/items/")
async def read_items(
q: Union[str, None] = Query(
default=None,
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
)
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

别名参数

假设你想要查询参数为 item-query。
像下面这样:
http://127.0.0.1:8000/items/?item-query=foobaritems

但是 item-query 不是一个有效的 Python 变量名称。
最接近的有效名称是 item_query。
但是你仍然要求它在 URL 中必须是 item-query…
这时你可以用 alias 参数声明一个别名,该别名将用于在 URL 中查找查询参数值:

1
2
3
4
5
6
@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

弃用参数

现在假设你不再喜欢此参数。
你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为已弃用。
那么将参数 deprecated=True 传入 Query

路径参数和数值校验

与使用 Query 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 Path 为路径参数声明相同类型的校验和元数据。

声明元数据

你可以声明与 Query 相同的所有参数。
例如,要声明路径参数 item_id的 title 元数据值,你可以输入:

1
2
3
4
5
6
7
8
9
10
11
12
from fastapi import FastAPI, Path, Query
from typing import Annotated

@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get")],
q: Annotated[str | None, Query(alias="item-query")] = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results

按需对参数排序

如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错。
但是你可以对其重新排序,并将不带默认值的值(查询参数 q)放到最前面。

1
2
3
4
5
6
@app.get("/items/{item_id}")
async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results

如果你想不使用 Query 声明没有默认值的查询参数 q,同时使用 Path 声明路径参数 item_id,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。
传递 * 作为函数的第一个参数。
Python 不会对该 * 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。即使它们没有默认值。

1
2
3
4
5
6
@app.get("/items/{item_id}")
async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results

数值校验

使用 Query 和 Path(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。

1
2
3
4
5
6
7
8
@app.get("/items/{item_id}")
async def read_items(
*, item_id: int = Path(title="The ID of the item to get", ge=1), q: str
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
  • gt:大于(greater than)
  • ge:大于等于(greater than or equal)
  • lt:小于(less than)
  • le:小于等于(less than or equal)

请求体 - 多个参数

混合使用 Path、Query 和请求体参数

首先,毫无疑问地,你可以随意地混合使用 Path、Query 和请求体参数声明,FastAPI 会知道该如何处理。
你还可以通过将默认值设置为 None 来将请求体参数声明为可选参数

1
2
3
4
5
6
7
@app.put("/items/{item_id}")
async def update_item(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str | None = None,
item: Item | None = None,
):
pass

路径操作将期望一个具有 Item 的属性的 JSON 请求体,就像:

1
2
3
4
5
6
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}

多个请求体参数

1
2
3
4
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
results = {"item_id": item_id, "item": item, "user": user}
return results

在这种情况下,FastAPI 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。
因此,它将使用参数名称作为请求体中的键(字段名称),并期望一个类似于以下内容的请求体:

1
2
3
4
5
6
7
8
9
10
11
12
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
}
}

请求体中的单一值

与使用 Query 和 Path 为查询参数和路径参数定义额外数据的方式相同,FastAPI 提供了一个同等的 Body。

例如,为了扩展先前的模型,你可能决定除了 item 和 user 之外,还想在同一请求体中具有另一个键 importance。

如果你就按原样声明它,因为它是一个单一值,FastAPI 将假定它是一个查询参数。

但是你可以使用 Body 指示 FastAPI 将其作为请求体的另一个键进行处理。

1
2
3
4
5
6
7
8
from fastapi import Body, FastAPI

@app.put("/items/{item_id}")
async def update_item(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results

在这种情况下,FastAPI 将期望像这样的请求体:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}

多个请求体参数和查询参数

当然,除了请求体参数外,你还可以在任何需要的时候声明额外的查询参数。
由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 Query,你可以仅执行以下操作:
q: str = None

嵌入单个请求体参数

假设你只有一个来自 Pydantic 模型 Item 的请求体参数 item。
默认情况下,FastAPI 将直接期望这样的请求体。
但是,如果你希望它期望一个拥有 item 键并在值中包含模型内容的 JSON,就像在声明额外的请求体参数时所做的那样,则可以使用一个特殊的 Body 参数 embed:
item: Item = Body(embed=True)

1
2
3
4
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results

在这种情况下,FastAPI 将期望像这样的请求体:

1
2
3
4
5
6
7
8
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}

请求体 - 字段

与使用 Query、Path 和 Body 在路径操作函数中声明额外的校验和元数据的方式相同,你可以使用 Pydantic 的 Field 在 Pydantic 模型内部声明校验和元数据。

Field 的工作方式和 Query、Path 和 Body 相同,包括它们的参数等等也完全相同。

1
2
3
4
5
6
7
8
9
from pydantic import BaseModel, Field

class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None

请求体 - 嵌套模型

使用 FastAPI,你可以定义、校验、记录文档并使用任意深度嵌套的模型(归功于Pydantic)。

List 字段

你可以将一个属性定义为拥有子元素的类型。例如 Python list:

1
2
3
4
5
6
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []

Set 字段

1
tags: set[str] = set()

嵌套模型

我们可以定义一个 Image 模型:
然后我们可以将其用作一个属性的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Image(BaseModel):
url: str
name: str


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None

@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

这意味着 FastAPI 将期望类似于以下内容的请求体:

1
2
3
4
5
6
7
8
9
10
11
{
"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"
}
}

特殊的类型和校验

除了普通的单一值类型(如 str、int、float 等)外,你还可以使用从 str 继承的更复杂的单一值类型。
要了解所有的可用选项,请查看关于 来自 Pydantic 的外部类型 的文档

例如,在 Image 模型中我们有一个 url 字段,我们可以把它声明为 Pydantic 的 HttpUrl,而不是 str:

1
2
3
4
5
from pydantic import BaseModel, HttpUrl

class Image(BaseModel):
url: HttpUrl
name: str

带有一组子模型的属性

你还可以将 Pydantic 模型用作 list、set 等的子类型:

1
2
3
4
5
6
7
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None

深度嵌套模型

你可以定义任意深度的嵌套模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Image(BaseModel):
url: HttpUrl
name: str


class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None


class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list[Item]


@app.post("/offers/")
async def create_offer(offer: Offer):
return offer

纯列表请求体

如果你期望的 JSON 请求体的最外层是一个 JSON array(即 Python list),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样:

1
2
3
4
5
6
7
8
class Image(BaseModel):
url: HttpUrl
name: str


@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
return images

任意 dict 构成的请求体

你也可以将请求体声明为使用某类型的键和其他类型值的 dict。
无需事先知道有效的字段/属性(在使用 Pydantic 模型的场景)名称是什么。

在下面的例子中,你将接受任意键为 int 类型并且值为 float 类型的 dict:

1
2
3
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights

请记住 JSON 仅支持将 str 作为键。
但是 Pydantic 具有自动转换数据的功能。
这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。
然后你接收的名为 weights 的 dict 实际上将具有 int 类型的键和 float 类型的值。

模式的额外信息 - 例子

您可以在JSON模式中定义额外的信息。
一个常见的用例是添加一个将在文档中显示的example。
有几种方法可以声明额外的 JSON 模式信息。

其他数据类型

到目前为止,您一直在使用常见的数据类型,如:

  • 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 时间差异编码”, 查看文档了解更多信息。
  • frozenset:
    • 在请求和响应中,作为 set 对待:
    • 在请求中,列表将被读取,消除重复,并将其转换为一个 set。
    • 在响应中 set 将被转换为 list 。
    • 产生的模式将指定那些 set 的值是唯一的 (使用 JSON 模式的 uniqueItems)。
  • bytes:
    标准的 Python bytes。
    • 在请求和相应中被当作 str 处理。
    • 生成的模式将指定这个 str 是 binary “格式”。
  • Decimal:
    • 标准的 Python Decimal。
    • 在请求和相应中被当做 float 一样处理。
    • 您可以在这里检查所有有效的pydantic数据类型: Pydantic data types.

Cookie 参数

你可以像定义 Query 参数和 Path 参数一样来定义 Cookie 参数。

1
2
3
4
5
from fastapi import Cookie, FastAPI

@app.get("/items/")
async def read_items(ads_id: Annotated[str | None, Cookie()] = None):
return {"ads_id": ads_id}

Header 参数

你可以使用定义 Query, Path 和 Cookie 参数一样的方法定义 Header 参数。

1
2
3
4
5
from fastapi import FastAPI, Header

@app.get("/items/")
async def read_items(user_agent: Annotated[str | None, Header()] = None):
return {"User-Agent": user_agent}

自动转换

Header 在 Path, Query 和 Cookie 提供的功能之上有一点额外的功能。
大多数标准的headers用 “连字符” 分隔,也称为 “减号” (-)。
但是像 user-agent 这样的变量在Python中是无效的。

因此, 默认情况下, Header 将把参数名称的字符从下划线 (_) 转换为连字符 (-) 来提取并记录 headers.
同时,HTTP headers 是大小写不敏感的,因此,因此可以使用标准Python样式(也称为 “snake_case”)声明它们。

因此,您可以像通常在Python代码中那样使用 user_agent ,而不需要将首字母大写为 User_Agent 或类似的东西。
如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置Header的参数 convert_underscores 为 False:

1
2
3
4
5
@app.get("/items/")
async def read_items(
strange_header: Annotated[str | None, Header(convert_underscores=False)] = None
):
return {"strange_header": strange_header}

重复的 headers

有可能收到重复的headers。这意味着,相同的header具有多个值。
您可以在类型声明中使用一个list来定义这些情况。
你可以通过一个Python list 的形式获得重复header的所有值。

比如, 为了声明一个 X-Token header 可以出现多次,你可以这样写:

1
2
3
@app.get("/items/")
async def read_items(x_token: Annotated[list[str] | None, Header()] = None):
return {"X-Token values": x_token}

响应

响应模型

你可以在任意的路径操作中使用 response_model 参数来声明用于响应的模型:

1
2
3
4
5
6
7
8
9
10
11
@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item


@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]

FastAPI 将使用此 response_model 来:

  • 将输出数据转换为其声明的类型。
  • 校验数据。
  • 在 OpenAPI 的路径操作中为响应添加一个 JSON Schema。
  • 并在自动生成文档系统中使用。

但最重要的是:

  • 会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None


class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None

# FastAPI 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user

响应模型编码参数

你的响应模型可以具有默认值

1
2
3
4
5
6
7
8
9
10
11
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: float = 10.5
tags: List[str] = []


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]

response_model_exclude_unset=True 表明响应中将不会包含那些默认值,而是仅有实际设置的值。

你还可以使用:

  • response_model_exclude_defaults=True
  • response_model_exclude_none=True

response_model_include 和 response_model_exclude
你还可以使用路径操作装饰器的 response_model_include 和 response_model_exclude 参数。

它们接收一个由属性名称 str 组成的 set 来包含(忽略其他的)或者排除(包含其他的)这些属性。

1
2
3
4
5
@app.get(
"/items/{item_id}/name",
response_model=Item,
response_model_include={"name", "description"},
)

额外的模型

多个模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str | None = None


class UserIn(UserBase):
password: str


class UserOut(UserBase):
pass


class UserInDB(UserBase):
hashed_password: str


def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password


def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db


@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved

多个响应声明

你可以将一个响应声明为两种类型的 Union,

1
2
3
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]

模型列表

你可以用同样的方式声明由对象列表构成的响应。

1
2
3
@app.get("/items/", response_model=list[Item])
async def read_items():
return items

任意 dict 构成的响应

1
2
3
@app.get("/keyword-weights/", response_model=dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}

响应状态码

1
2
3
4
5
6
from fastapi import FastAPI, status

# 或者直接写 201
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}

表单数据

接收的不是 JSON,而是表单字段时,要使用 Form。

1
2
3
4
5
from fastapi import FastAPI, Form

@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}

请求文件

  • 定义 File 参数
  • 含 UploadFile 的文件参数

UploadFile 与 bytes 相比有更多优势:

  • 使用 spooled 文件:
    • 存储在内存的文件超出最大上限时,FastAPI 会把文件存入磁盘;
  • 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存;
  • 可获取上传文件的元数据;
  • 自带 file-like async 接口;
  • 暴露的 Python SpooledTemporaryFile 对象,可直接传递给其他预期「file-like」对象的库。
1
2
3
4
5
6
7
8
@app.post("/files/")
async def create_file(file: bytes = File()):
return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
return {"filename": file.filename}

处理错误

使用 HTTPException

1
2
3
4
5
6
7
from fastapi import FastAPI, HTTPException

@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}

添加自定义响应头

1
2
3
4
5
6
7
8
9
@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=404,
detail="Item not found",
headers={"X-Error": "There goes my error"},
)
return {"item": items[item_id]}

安装自定义异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

class UnicornException(Exception):
def __init__(self, name: str):
self.name = name

@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
)

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
if name == "yolo":
raise UnicornException(name=name)
return {"unicorn_name": name}

覆盖默认异常处理器

FastAPI 自带了一些默认异常处理器。
触发 HTTPException 或请求无效数据时,这些处理器返回默认的 JSON 响应结果。
不过,也可以使用自定义处理器覆盖默认异常处理器

TODO

……