校验 / 闯关模式
Pydantic 接口输入校验
学习必填/可选、字段规则、枚举、嵌套对象、自定义校验和响应模型。
一句话:Pydantic 就是接口门口的检查员,前端传来的数据必须符合规则,后端才继续处理。
本篇学完你会什么:能用 Pydantic 定义请求体、响应体、字段限制、自定义校验,并知道什么时候返回 422。
1. 为什么要校验输入
前端传来的数据不能直接相信。
比如新增用户:
{
"username": "",
"age": "abc"
}如果后端不检查,数据库可能写脏数据,业务逻辑也可能报错。
Pydantic 的作用:
- 检查类型
- 检查长度
- 检查范围
- 自动生成接口文档
- 把错误告诉前端

2. BaseModel 是什么
BaseModel 是数据模型的基础模板。
from pydantic import BaseModel
class UserCreate(BaseModel):
username: str
age: intFastAPI 接口里使用:
@router.post("/users")
async def create_user(payload: UserCreate):
return payload这表示请求体必须长这样:
{
"username": "tom",
"age": 18
}3. 必填和可选
默认是必填:
class UserCreate(BaseModel):
username: str可选字段:
class UserUpdate(BaseModel):
nickname: str | None = None大白话:
| 写法 | 意思 | |
|---|---|---|
username: str | 必须传 | |
| `nickname: str | None = None` | 可以不传 |
age: int = 18 | 不传就默认 18 |
容易混淆的一点:str | None 只是表示“值可以是字符串或空”,如果没有写 = None,这个字段在 Pydantic 里仍然可能是必填字段。初学时记住:想让字段真正可不传,要给它默认值。
4. Field 字段规则
Field 可以写更细的规则。
from pydantic import BaseModel, Field
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=32, pattern=r"^[a-zA-Z0-9_]+$")
age: int = Field(ge=0, le=120)规则解释:
| 规则 | 意思 |
|---|---|
min_length | 最短长度 |
max_length | 最长长度 |
pattern | 正则规则 |
ge | 大于等于 |
le | 小于等于 |
如果前端传错,FastAPI 会返回 422。
5. 枚举和固定选项
有些字段只能选几个值。
from enum import Enum
class ArticleStatus(str, Enum):
draft = "draft"
published = "published"
class ArticleCreate(BaseModel):
title: str
status: ArticleStatus这样 status 只能是:
draft
published6. 嵌套对象
请求体里可以有对象套对象。
class Author(BaseModel):
name: str
email: str
class ArticleCreate(BaseModel):
title: str
author: Author
tags: list[str] = []请求:
{
"title": "Python 入门",
"author": {
"name": "tom",
"email": "tom@example.com"
},
"tags": ["python", "api"]
}7. 自定义校验
有些规则不是长度能表达的。
from pydantic import BaseModel, field_validator
class UserCreate(BaseModel):
username: str
@field_validator("username")
@classmethod
def username_not_admin(cls, value: str):
if value.lower() == "admin":
raise ValueError("用户名不能是 admin")
return value大白话:字段进来以后,先跑你写的检查函数。
8. 请求模型和响应模型
请求模型:前端传什么。
class UserCreate(BaseModel):
username: str
password: str响应模型:后端返回什么。
class UserResponse(BaseModel):
id: int
username: str接口:
@router.post("/users", response_model=UserResponse)
async def create_user(payload: UserCreate):
...注意:密码不应该出现在响应模型里。
9. 常见错误
| 问题 | 原因 | 解决 |
|---|---|---|
| 返回 422 | 请求体不符合模型 | 看响应里的 detail |
| 字段没校验 | 没用 Pydantic 模型 | 把参数放到 BaseModel |
| 更新时字段被清空 | 可选字段处理不对 | 用 exclude_unset=True |
| 密码返回给前端 | 响应模型写错 | 请求/响应模型分开 |
更新接口常用:
data = payload.model_dump(exclude_unset=True)它的意思是:只拿前端真正传了的字段。
10. 校验清单
[ ] 必填字段是否明确
[ ] 可选字段是否默认 None
[ ] 字符串是否限制长度
[ ] 数字是否限制范围
[ ] 固定选项是否用 Enum
[ ] 密码是否只在请求模型里
[ ] 响应模型是否隐藏敏感字段
[ ] 更新接口是否使用 exclude_unset=True
[ ] 错误提示是否能看懂总结表
| 名词 | 大白话 |
|---|---|
| BaseModel | 数据检查模板 |
| Field | 字段细规则 |
| Enum | 固定选项 |
| validator | 自定义检查员 |
| request model | 前端传什么 |
| response model | 后端回什么 |
| 422 | 参数校验失败 |
| exclude_unset | 只更新传了的字段 |
下一篇建议:大白话讲解——FastAPI 连接数据库并实现增删改查。