Python FastApi(5):请求体、查询参数和字符串校验
1 请求体
FastAPI 使用请求体从客户端(例如浏览器)向 API 发送数据。请求体是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据。API 基本上肯定要发送响应体,但是客户端不一定发送请求体。使用 Pydantic 模型声明请求体,能充分利用它的功能和优点。
1.1 使用Pydantic 的 BaseModel
从 pydantic
中导入 BaseModel
:
from fastapi import FastAPI
from pydantic import BaseModel
把数据模型声明为继承 BaseModel
的类。使用 Python 标准类型声明所有属性:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。默认值为 None
的模型属性也是可选的。例如,上述模型声明如下 JSON 对象(即 Python 字典):
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
由于 description
和 tax
是可选的(默认值为 None
),下面的 JSON 对象也有效:
{
"name": "Foo",
"price": 45.2
}
使用与声明路径和查询参数相同的方式声明请求体,把请求体添加至路径操作。此处,请求体参数的类型为 Item
模型。
@app.post("/items/")
async def create_item(item: Item):
return item
仅使用 Python 类型声明,FastAPI 就可以:
- 以 JSON 形式读取请求体
- (在必要时)把请求体转换为对应的类型
- 校验数据:
- 数据无效时返回错误信息,并指出错误数据的确切位置和内容
- 把接收的数据赋值给参数
item
- 把函数中请求体参数的类型声明为
Item
,还能获得代码补全等编辑器支持
- 把函数中请求体参数的类型声明为
- 为模型生成 JSON Schema,在项目中所需的位置使用
- 这些概图是 OpenAPI 概图的部件,用于 API 文档 UI
1.2 API 文档
Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文档中显示:
1.3 使用模型
在路径操作函数内部直接访问模型对象的属性:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
1.4 请求体 + 路径参数
FastAPI 支持同时声明路径参数和请求体。FastAPI 能识别与路径参数匹配的函数参数,还能识别从请求体中获取的类型为 Pydantic 模型的函数参数。
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, **item.dict()}
1.5 请求体 + 路径参数 + 查询参数
FastAPI 支持同时声明请求体、路径参数和查询参数。FastAPI 能够正确识别这三种参数,并从正确的位置获取数据。
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, q: str | None = None):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
函数参数按如下规则进行识别:
- 路径中声明了相同参数的参数,是路径参数
- 类型是(
int
、float
、str
、bool
等)单类型的参数,是查询参数 - 类型是 Pydantic 模型的参数,是请求体
2 查询参数和字符串校验
FastAPI 允许你为参数声明额外的信息和校验。让我们以下面的应用程序为例:
from fastapi import FastAPI
app = FastAPI()
@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
2.1 Query使用
查询参数 q
的类型为 str
,默认值为 None
,因此它是可选的。我们打算添加约束条件:即使 q
是可选的,但只要提供了该参数,则该参数值不能超过50个字符的长度。为此,首先从 fastapi
导入 Query
:
from typing import Union
from fastapi import FastAPI, Query
现在,将 Query
用作查询参数的默认值,并将它的 max_length
参数设置为 50:
@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(default=None)
替换默认值 None
,Query
的第一个参数同样也是用于定义默认值。
q: Union[str, None] = Query(default=None)
然后,我们可以将更多的参数传递给 Query
。在本例中,适用于字符串的 max_length
参数,将会校验数据,在数据无效时展示清晰的错误信息,并在 OpenAPI 模式的路径操作中记录该参数。
你还可以更多校验:
# 最小值校验
q: Union[str, None] = Query(default=None, min_length=3, max_length=50)
# 正则表达式
q: Union[str, None] = Query(
default=None, min_length=3, max_length=50, pattern="^fixedquery$"
)
2.2 默认值和必需参数
你可以向 Query
的第一个参数传入 None
用作查询参数的默认值,以同样的方式你也可以传递其他默认值。假设你想要声明查询参数 q
,使其 min_length
为 3
,并且默认值为 fixedquery,
具有默认值还会使该参数成为可选参数。
q: str = Query(default="fixedquery", min_length=3)
当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 q
参数成为必需参数,例如:
q: str = Query(min_length=3)
你可以声明一个参数可以接收None
值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是None
。为此,你可以声明None
是一个有效的类型,并仍然使用default
:
q: Union[str, None] = Query(min_length=3)
2.3 查询参数列表
当你使用 Query
显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。例如,要声明一个可在 URL 中出现多次的查询参数 q
,你可以这样写:
from typing import List, Union
from fastapi import FastAPI, Query
app = FastAPI()
@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 的响应将会是:
{
"q": [
"foo",
"bar"
]
}
要声明类型为 list
的查询参数,如上例所示,你需要显式地使用 Query
,否则该参数将被解释为请求体。交互式 API 文档将会相应地进行更新,以允许使用多个值:
还可以定义在没有任何给定值时的默认 list
值:
q: List[str] = Query(default=["foo", "bar"])
如果访问:
http://localhost:8000/items/
q
的默认值将为:["foo", "bar"]
,你的响应会是:
{
"q": [
"foo",
"bar"
]
}
你也可以直接使用 list
代替 List [str]
:
q: list = Query(default=[])
请记住,在这种情况下 FastAPI 将不会检查列表的内容。例如,List[int]
将检查(并记录到文档)列表的内容必须是整数。但是单独的 list
不会。
2.4 声明更多元数据
你可以添加更多有关该参数的信息。这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。你可以添加 title以及description
:
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,
)
2.5 别名参数
假设你想要查询参数为 item-query
。像下面这样:
http://127.0.0.1:8000/items/?item-query=foobaritems
但是 item-query
不是一个有效的 Python 变量名称。最接近的有效名称是 item_query
。但是你仍然要求它在 URL 中必须是 item-query。
这时你可以用 alias
参数声明一个别名,该别名将用于在 URL 中查找查询参数值:
q: Union[str, None] = Query(default=None, alias="item-query"
2.6 弃用参数
现在假设你不再喜欢此参数。你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为已弃用。那么将参数 deprecated=True
传入 Query
:
q: Union[str, None] = Query(
default=None,
alias="item-query",
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
max_length=50,
pattern="^fixedquery$",
deprecated=True,
)
文档将会像下面这样展示它: