在 Web API 开发中,客户端通过请求体(Request Body)向服务端发送 JSON、XML 等结构化数据是最常见的场景。FastAPI 借助 Pydantic 实现了强大的数据校验和序列化能力——你只需定义一个 Python 类,FastAPI 就能自动完成请求体的解析、校验和文档生成。本文带你全面掌握这一核心特性。
什么是请求体
请求体是客户端通过 POST、PUT、PATCH 等方法发送给服务端的数据。与路径参数和查询参数不同,请求体不在 URL 中,而是放在 HTTP 消息的 body 部分,通常以 JSON 格式传输。
在 FastAPI 中声明请求体非常简单——定义一个 Pydantic 模型类,然后将其作为路由函数的参数类型注解即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from fastapi import FastAPI from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel): name: str price: float is_offer: bool = False
@app.post("/items/") async def create_item(item: Item): return item
|
FastAPI 会自动:
- 读取请求体中的 JSON 数据
- 根据
Item 模型校验字段类型
- 将数据转换为对应的 Python 类型
- 生成 OpenAPI 文档(Swagger UI)
Pydantic 基础字段类型
Pydantic 支持丰富的字段类型,覆盖日常开发所需:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from pydantic import BaseModel from datetime import datetime from typing import Optional, List
class User(BaseModel): id: int name: str email: str
avatar: Optional[str] = None
is_active: bool = True
tags: List[str] = []
created_at: datetime
|
当请求体传入 "id": "123" 时,Pydantic 会自动将字符串 "123" 转为整数 123;如果传入 "abc",则会返回 422 验证错误及清晰的错误信息。
字段校验与约束
除了类型注解,Pydantic 的 Field 函数提供了丰富的校验能力:
1 2 3 4 5 6 7 8 9 10 11 12
| from pydantic import BaseModel, Field
class Product(BaseModel): name: str = Field(..., min_length=1, max_length=100, description="商品名称") price: float = Field(..., gt=0, description="价格,必须大于0") stock: int = Field(default=0, ge=0, description="库存,不能为负数") description: str = Field( default="暂无描述", max_length=500, description="商品描述" )
|
常用的 Field 校验参数:
| 参数 |
说明 |
... |
必填字段(三个点代表 Required) |
min_length / max_length |
字符串长度范围 |
gt / ge / lt / le |
数值大于/大于等于/小于/小于等于 |
regex |
正则表达式匹配 |
default |
默认值 |
description |
字段描述(会显示在 API 文档中) |
正则校验示例
1 2 3 4 5 6 7 8 9 10 11
| from pydantic import BaseModel, Field
class RegisterForm(BaseModel): username: str = Field(..., min_length=3, max_length=20) email: str = Field( ..., pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', description="邮箱格式" ) password: str = Field(..., min_length=6, max_length=128)
|
当传入不合规的邮箱时,FastAPI 返回:
1 2 3 4 5 6 7 8 9
| { "detail": [ { "loc": ["body", "email"], "msg": "string does not match regex ...", "type": "value_error.str.regex" } ] }
|
嵌套模型
一个请求体往往不止一层结构,Pydantic 支持模型嵌套——将复杂数据拆解为多个模型组合:
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
| from pydantic import BaseModel from typing import List
class Address(BaseModel): province: str city: str detail: str zip_code: str = Field(..., pattern=r'^\d{6}$')
class OrderItem(BaseModel): product_id: int product_name: str quantity: int = Field(..., gt=0) unit_price: float = Field(..., gt=0)
class Order(BaseModel): order_id: str customer_name: str address: Address items: List[OrderItem] total_amount: float = Field(..., gt=0) note: str = ""
|
对应请求体 JSON 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { "order_id": "ORD-20240001", "customer_name": "张三", "address": { "province": "广东省", "city": "深圳市", "detail": "科技园路1号", "zip_code": "518000" }, "items": [ { "product_id": 1001, "product_name": "机械键盘", "quantity": 2, "unit_price": 299.00 } ], "total_amount": 598.00 }
|
FastAPI 会递归校验每一层嵌套结构,保证数据完整性。
自定义验证器
当内置校验无法满足需求时,可以使用 @validator 装饰器实现自定义校验逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from pydantic import BaseModel, validator
class UserRegister(BaseModel): username: str password: str confirm_password: str
@validator('username') def username_must_not_contain_special(cls, v: str) -> str: if not v.isalnum(): raise ValueError('用户名只能包含字母和数字') return v
@validator('confirm_password') def passwords_match(cls, v: str, values: dict) -> str: if 'password' in values and v != values['password']: raise ValueError('两次输入的密码不一致') return v
|
values 参数包含前面已验证过的字段值,因此可以在 confirm_password 的验证器中拿到 password 进行比较。
注意:Pydantic V2 中 @validator 已改为 @field_validator,用法类似但参数名略有不同。
结合路径参数与查询参数
请求体、路径参数、查询参数可以同时存在,FastAPI 根据参数声明自动区分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from fastapi import FastAPI from pydantic import BaseModel
app = FastAPI()
class ItemUpdate(BaseModel): name: str = Field(..., min_length=1) price: float = Field(..., gt=0) is_offer: bool = False
@app.put("/items/{item_id}") async def update_item( item_id: int, item: ItemUpdate, force: bool = False, ): return { "item_id": item_id, "force": force, "update": item.dict() }
|
识别规则很简单:
- 在路径中也声明的 → 路径参数
- 是 Pydantic 模型类型的 → 请求体
- 是简单类型(str/int/float/bool)且不在路径中 → 查询参数
请求体中的额外字段
默认情况下,如果客户端多传了模型中未定义的字段,Pydantic V1 会忽略它们。如果需要严格模式(拒绝多余字段),可以在模型内部配置:
1 2 3 4 5 6 7 8 9
| from pydantic import BaseModel
class StrictItem(BaseModel): name: str price: float
class Config: extra = "forbid"
|
此时多传字段会直接返回 422 错误。
小结
FastAPI 结合 Pydantic 的请求体处理机制让数据校验变得极其简洁:
- 用
BaseModel 定义数据结构,类型即校验规则
- 用
Field 添加约束(长度、范围、正则)
- 用嵌套模型处理复杂 JSON 结构
- 用
@validator 处理跨字段校验和自定义逻辑
这套机制让开发者无需手写校验代码,同时自动产出清晰的 API 文档,显著提升开发效率和数据质量。