在 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 # 字符串

# 可选字段,默认为 None
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, # 请求体(Pydantic 模型 = body)
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 文档,显著提升开发效率和数据质量。