校验 / 闯关模式

Pydantic 接口输入校验

学习必填/可选、字段规则、枚举、嵌套对象、自定义校验和响应模型。

一句话:Pydantic 就是接口门口的检查员,前端传来的数据必须符合规则,后端才继续处理。

本篇学完你会什么:能用 Pydantic 定义请求体、响应体、字段限制、自定义校验,并知道什么时候返回 422。

1. 为什么要校验输入

前端传来的数据不能直接相信。

比如新增用户:

{
  "username": "",
  "age": "abc"
}

如果后端不检查,数据库可能写脏数据,业务逻辑也可能报错。

Pydantic 的作用:

  • 检查类型
  • 检查长度
  • 检查范围
  • 自动生成接口文档
  • 把错误告诉前端

Pydantic 请求校验流程

2. BaseModel 是什么

BaseModel 是数据模型的基础模板。

from pydantic import BaseModel

class UserCreate(BaseModel):
    username: str
    age: int

FastAPI 接口里使用:

@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: strNone = 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
published

6. 嵌套对象

请求体里可以有对象套对象。

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 连接数据库并实现增删改查