FastAPI-P1:Pydantic模型与参数额外信息
欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。
目录
- 引言
- 1 Pydantic
- 1.1 Pydantic的便利
- 2 为参数声明额外信息和校验
- 2.1 Query
- 2.2 Annotated
- 2.3 Path
- 2.3.1 核心概念与用法
- 2.4 Body
- 2.4.1 核心概念与用法
引言
本篇主要内容:FastAPI常用的Pydantic模型、接口参数声明额外信息和校验做法。
适合快速复习、预览。
资料:
- fastapi官网:https://fastapi.tiangolo.com/tutorial
1 Pydantic
FastAPI 是基于 Starlette (Web 部分) 和 Pydantic (数据验证部分) 构建的。
其中,Pydantic 是一款用于 Python 数据验证和设置管理 的库。
简单来说,它的核心作用就是:
- 把普通的数据(比如字典、JSON)变成结构化的数据对象。
- 在转换过程中,严格检查数据是否符合你定义的规则。
Pydantic的核心是BaseModel。通过继承它来定义自己的数据模型,这个模型就是告诉Pydantic开发者期望的数据结构。
from pydantic import BaseModel, EmailStr
from typing import Optionalclass User(BaseModel):name: strage: intemail: Optional[EmailStr] = None
上面这个 User
类就定义了一个模型:
- 它必须有
name
字段,且类型是str
。 - 它必须有
age
字段,且类型是int
。 - 它有一个可选的
email
字段,类型是EmailStr
(Pydantic 内置的邮箱验证类型),默认值为None
。
Pydantic会自动校验模型内容:
data = {"name": "张三","age": "25", # 注意这里 age 是字符串"email": "zhangsan@example.com"
}try:user = User(**data)print(user.name) # 输出: 张三print(user.age) # 输出: 25 (Pydantic 自动转换为整数)print(user.email) # 输出: zhangsan@example.comprint(user) # 输出: name='张三' age=25 email='zhangsan@example.com'
except Exception as e:print(e)
在这个例子中,Pydantic 做了以下几件事:
- 它检查了所有字段。
- 它发现
age
是字符串"25"
,但模型要求是int
,于是它 自动完成了类型转换。 - 它甚至检查了
email
的格式,确保它是一个有效的邮箱地址。
与在路径操作函数参数中使用 Query
、 Path
和 Body
声明额外验证和元数据相同的方式一样,可以在 Pydantic 模型内部使用 Pydantic 的 Field
声明验证和元数据。
from typing import Annotatedfrom fastapi import Body, FastAPI
from pydantic import BaseModel, Fieldapp = FastAPI()class Item(BaseModel):name: strdescription: 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@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
Field
的工作方式与 Query
、 Path
和 Body
相同,它具有所有相同的参数等。
1.1 Pydantic的便利
同样是继承,Java 的 Object
类是所有类的最终父类,主要提供了一些基础方法和抽象约定,是面向对象体系的根本标识,目的是为了多态和通用性。
Pydantic的BaseModel继承后,Python Class的管理会简约、便利许多。其则赋予了 Python 类强大的数据管理能力,让数据模型定义、验证和使用变得异常简洁和高效。
- 自动化数据验证和类型转换:你不用再手动写大量的
if
语句来检查数据,大大减少了冗余代码。 - 清晰的数据模型:你的数据结构一目了然,方便团队协作和代码维护。
- 兼容 Python 类型提示:它完美结合了 Python 3.5+ 的类型提示,让你的代码更现代、更易读。
- 自动生成文档:Pydantic 模型可以自动生成 OpenAPI (Swagger) 格式的 API 文档,这在构建 API 时非常有用。
- 高性能:底层使用 Rust 编写,验证速度非常快,尤其适合处理大量数据。
尽管和Java的Object设计上不一样。但是在继承、组合上与Java对象的用法还是一样的。继承 Pydantic 模型的 Python 类仍然是 Pydantic 模型。 Pydantic 模型可以互相嵌套。
2 为参数声明额外信息和校验
可以通过Query和Annotated来做。
2.1 Query
在 FastAPI 中,Query 是一个专门用于定义 查询参数(Query Parameters) 的函数。
使用 Query
的主要目的是:
- 声明参数是查询参数:告诉 FastAPI 这个变量对应的是 URL 中的查询参数。
- 设置默认值:如果客户端没有提供这个参数,它会有一个默认值。
- 添加额外的验证和元数据:比如最小值、最大长度、描述信息等。
from fastapi import FastAPI, Queryapp = FastAPI()@app.get("/products/")
async def read_products(# 必需的查询参数,描述为 "产品ID",最小长度1,最大长度50product_id: str = Query(..., min_length=1, max_length=50, description="The ID of the product"),# 可选的查询参数,默认值是0,描述为 "跳过多少个产品"skip: int = Query(0, description="Number of products to skip"),# 可选的查询参数,默认值是10,描述为 "获取多少个产品",最小值1,最大值100limit: int = Query(10, description="Number of products to retrieve", ge=1, le=100)
):return {"product_id": product_id, "skip": skip, "limit": limit}
-
...
(Ellipsis):表示这个参数是必需的,没有默认值。 -
min_length
,max_length
:字符串长度验证。 -
ge
(Greater than or Equal)、le
(Less than or Equal):数值范围验证。 -
description
:在生成的 OpenAPI 文档中显示这个参数的说明。 -
alias
:如果查询参数名和变量名不一致,可以使用alias
指定。 -
deprecated
:标记参数已废弃。
2.2 Annotated
Annotated
是 Python 3.9 引入的一个类型提示工具,位于 typing
模块中。它允许你在类型提示中添加额外的元数据。FastAPI 从 0.95.0 版本开始,强烈推荐使用 Annotated
结合 Query
, Path
, Body
等函数。
from typing import Annotated, Optional # 导入 Optional 以便演示旧式可选参数
from fastapi import FastAPI, Query, Path, Bodyapp = FastAPI()# --- 查询参数 (Query Parameters) ---
@app.get("/items/")
async def read_items(# 示例 1: 可选查询参数 (Python 3.10+ 推荐语法: 'str | None')# 如果不提供 'q',它将是 Noneq: Annotated[str | None, Query(min_length=3, max_length=50, description="搜索查询字符串")] = None,# 示例 2: 带有默认值的可选查询参数# 如果不提供 'offset',它将是 0offset: Annotated[int, Query(description="跳过的结果数量")] = 0,# 示例 3: 必填查询参数 (使用 '...' 明确标记)# 客户端必须提供 'limit' 参数limit: Annotated[int, Query(ge=1, le=100, description="返回的最大结果数量")] = ...,# 示例 4: 旧式可选查询参数 (Python 3.9 及更早版本常见)# 如果不提供 'category',它将是 Nonecategory: Annotated[Optional[str], Query(max_length=20, description="产品类别")] = None,
):results = {"q": q, "offset": offset, "limit": limit, "category": category, "items": []}# 这里可以根据参数进行数据查询逻辑if q:results["items"].append({"item_id": "Foo", "name": f"Foo {q}"})if category:results["items"].append({"item_id": "Bar", "name": f"Bar in {category}"})return results# --- 路径参数 (Path Parameters) ---
@app.get("/users/{user_id}")
async def get_user_by_id(# 路径参数默认就是必填的,但我们可以用 Annotated 添加元数据user_id: Annotated[int, Path(ge=1, description="用户的唯一ID")]
):return {"user_id": user_id, "message": "Fetching user details"}# --- 请求体 (Request Body) ---
from pydantic import BaseModelclass Item(BaseModel):name: strdescription: str | None = Noneprice: floattax: float | None = None@app.post("/items/")
async def create_item(# 整个请求体默认就是必填的,Annotated 用于添加更多信息item: Annotated[Item, Body(embed=True, description="要创建的商品数据")]
):return {"message": "Item created successfully", "item": item}
Annotated
的优势:
- 分离关注点:将参数的类型定义和 FastAPI 特定的元数据定义清晰地分离开来。
q: str
负责类型提示Query(...)
负责 FastAPI 参数定义和验证
- 更易读:当你有多个装饰器或复杂的参数定义时,
Annotated
让代码结构更清晰。 - 兼容性:它与 Python 现有的类型检查工具(如 MyPy)更好地配合。
2.3 Path
Path()
用于从 URL 路径中提取参数。这些参数是 URL 本身的一部分,例如 /items/{item_id}
中的 item_id
。
2.3.1 核心概念与用法
-
路径参数: 在 FastAPI 路由中,你可以在 URL 路径中使用大括号
{}
定义路径参数。 -
类型注解:
Path()
通常与 Python 的类型注解一起使用,FastAPI 会自动为你进行数据类型转换和验证。 -
默认值与校验: 你可以使用
Path()
来设置默认值、添加校验规则(如最小/最大长度、最小值/最大值等)以及添加元数据(如描述、示例)。
from fastapi import FastAPI, Path, Queryapp = FastAPI()@app.get("/items/{item_id}")
async def read_item(# item_id 是路径参数,且是必需的。# 使用 `| None` 表示可选,但这里我们希望它是必需的,所以没有 `| None`item_id: int = Path(..., # `...` 表示此参数是必需的 (required)title="The ID of the item to get",description="Must be an integer greater than 0 and less than 1000",ge=1, # Greater than or equal to 1lt=1000 # Less than 1000),# q 是查询参数,可以是字符串或 None,默认值为 Noneq: str | None = Query(None, # 默认值min_length=3,max_length=50,description="Optional query string for the item search")
):"""检索特定 ID 的商品信息。"""results = {"item_id": item_id}if q:results.update({"q": q})return results# 💡 运行 FastAPI 应用:
# 保存以上代码为 `main.py`
# 在终端运行: `uvicorn main:app --reload`# 🌐 访问示例:
# - http://127.0.0.1:8000/items/500 (item_id = 500, q = None)
# - http://127.00.1:8000/items/10?q=somequery (item_id = 10, q = "somequery")
# - http://127.00.1:8000/items/0 (会触发校验错误,因为 item_id 必须 >= 1)
2.4 Body
Body()
用于从 HTTP 请求的请求体中提取数据。这通常用于 POST
、PUT
和 PATCH
请求,这些请求通常会发送结构化的数据(如 JSON)。
2.4.1 核心概念与用法
-
请求体: 客户端向服务器发送的数据,通常以 JSON、XML 或表单数据的形式存在。FastAPI 主要推荐使用 JSON。
-
Pydantic 模型:
Body()
最常与 Pydantic 模型一起使用。Pydantic 模型定义了请求体的结构和数据类型,FastAPI 会自动将接收到的 JSON 数据解析并验证为 Pydantic 模型的实例。 -
单个或多个体参数: 你可以在一个路径操作函数中定义一个或多个
Body()
参数。 -
默认值与校验: 类似于
Path()
,你可以设置默认值和添加校验规则。
from fastapi import FastAPI, Body
from pydantic import BaseModelapp = FastAPI()# 定义一个 Pydantic 模型来表示请求体的数据结构
class Item(BaseModel):name: str# description 是可选的字符串,默认值是 Nonedescription: str | None = Noneprice: float# tax 是可选的浮点数,默认值是 Nonetax: float | None = Noneclass User(BaseModel):username: strfull_name: str | None = None@app.post("/items/")
async def create_item(# item 是请求体参数,它是 Item 类型的 Pydantic 模型实例。# 同样,`...` 表示这个请求体是必需的。item: Item = Body(...,title="Item to be created",description="Details of the new item including name, description, price, and optional tax.",example={ # 为 OpenAPI 文档提供示例"name": "Super Laptop","description": "A powerful machine for all your needs.","price": 1500.0,"tax": 120.0})
):"""创建一个新的商品记录。"""return item@app.post("/offers/{item_id}")
async def make_offer(item_id: int = Path(..., description="The ID of the item for the offer"),# item 作为请求体参数,FastAPI 默认会将其解析为 Item 实例item: Item,# user 明确指定为另一个请求体参数,使用 `Body(...)`user: User = Body(...,title="User making the offer",description="Details of the user making the offer.",example={"username": "jane.doe","full_name": "Jane Doe"})
):"""为一个商品创建报价。"""return {"item_id": item_id, "item": item, "user": user}# 🌐 测试示例 (使用 Postman, Insomnia 或 cURL):# 🚀 POST http://127.0.0.1:8000/items/
# Headers: Content-Type: application/json
# Body (JSON):
# {
# "name": "Wireless Mouse",
# "description": "Ergonomic design",
# "price": 25.99
# }
# (tax 字段由于是 `None` 默认值,可以不传)# 🚀 POST http://127.0.0.1:8000/offers/123
# Headers: Content-Type: application/json
# Body (JSON):
# {
# "item": {
# "name": "Cool Gadget",
# "price": 99.99
# },
# "user": {
# "username": "alice_smith"
# }
# }