FastAPI + Pydantic 数据校验实战:Field 约束与自定义字段验证器
在上一步完成学生信息的添加接口后,你会发现接口虽然能跑通,但数据质量完全不受控:姓名只写一个“张”也能提交,学号可以随便填 ABCD,性别能输入任意字符,手机号、生日更是毫无约束。本文就基于 Pydantic 的校验能力,给 StudentCreate / StudentUpdate 模型补上完整的字段校验。
一、目标:需要约束哪些字段
先梳理一下现有模型存在的问题与期望规则:
| 字段 | 问题 | 期望规则 |
|---|---|---|
| name | 单个字符也能通过 | 长度 2~50 个字符,且不允许纯空白 |
| student_no | 可随意输入 ABCD | 10 位数字,且前四位在 2000~2099 之间 |
| gender | 可输入任意内容 | 只能是“男”或“女”,默认“男” |
| birthday | 无任何约束 | 年龄应在 5~100 岁之间 |
| phone | 无任何约束 | 11 位手机号,1 开头、第二位 3~9 |
| class_id | 无任何约束 | 大于等于 1 |
对应的实现手段分为四类:
- Field 自身约束:长度、正则、枚举、数值范围等;
- Field Validator 字段验证器:针对单个字段做自定义逻辑校验(如姓名去空白、学号前缀判断);
- 模型级验证:跨字段联合校验(本文暂不展开);
- 查询参数快速约束:用于 Query 参数校验(本文暂不展开)。
本篇重点落地前两种。
二、代码实现:用 Field 给字段加约束
打开定义模型的代码文件,找到 Student、StudentCreate、StudentUpdate,先从 Pydantic 导入 Field,然后逐个字段改造。
1. 姓名:长度 + 描述 + 示例
name: str = Field(
...,
min_length=2,
max_length=50,
title="姓名",
description="学生姓名长度是2到50个字符",
examples=["Andy"],
)
examples 会在接口文档中作为示例数据展示,方便调用方知道该怎么填。
2. 学号:正则限定 10 位数字
student_no: str = Field(
...,
pattern=r"^\d{10}$",
title="学号",
description="学号,长度是10位数字",
examples=["2026000001"],
)
\d 表示数字,{10} 表示长度,^ 与 $ 分别锁定开头和结尾,保证整串匹配。
3. 性别:枚举限定
从 typing 导入 Literal,把类型声明为字面量枚举:
gender: Literal["男", "女"] = Field(
default="男",
title="性别",
description="性别只能为男或女",
)
4. 出生日期、手机号、班级 ID
birthday: date = Field(
...,
title="出生日期",
description="年龄应该在5-100岁之间",
examples=["2000-01-01"],
)
phone: str = Field(
...,
pattern=r"^1[3-9]\d{9}$",
title="手机号",
description="手机号,长度为11位",
)
class_id: int = Field(
...,
ge=1,
title="班级ID",
description="班级ID必须大于等于1",
)
手机号正则含义:1 开头,第二位是 3~9,后面再跟 9 位数字,共 11 位。
三、代码实现:Field Validator 自定义校验
Field 的通用约束覆盖不了的逻辑,就用 @field_validator 单独写。先导入:
from pydantic import field_validator
1. 姓名不允许为纯空白
@field_validator("name")
@classmethod
def clean_name(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("姓名不能为空白字符")
return v
装饰器指定校验的字段名,配合 @classmethod 定义类方法,参数 v 是字段当前值,返回值即校验后的值。这里先 strip() 去掉首尾空白,若结果为空则抛出 ValueError。如果还有“只能是中文或英文”之类的需求,可以继续用 if 加正则判断,不满足就抛异常。
2. 学号前四位必须在 2000~2099 之间
@field_validator("student_no")
@classmethod
def validate_student_no_year(cls, v: str) -> str:
year = int(v[:4])
if not (2000 <= year <= 2099):
raise ValueError("学号前四位应在2000-2099之间")
return v
取学号前四位转成整数,判断是否落在区间内,否则抛出异常。
完整代码如下:
class StudentCreate(BaseModel):
name: str = Field(min_length=2, max_length=50, title="姓名", description="学生姓名长度是2-50个字符",examples=["andy", "安迪"])
student_no: str = Field(pattern=r"^\d{10}$", title="学号", description="学号长度是10位数字",examples=["2026010101"])
gender: Literal["男", "女"] = Field(title="性别", default="男", description="性别只能为男或女")
birthday: datetime.date | None = Field(default=None, title="出生日期", description="应该在5-100岁之间", examples=["2020-01-01"])
phone: str = Field(pattern=r"^1[3-9]\d{9}$",title="手机号", description="手机号长度为11位")
class_id: int = Field(ge=1, title="班级ID", description="班级ID大于1")
@field_validator("name")
@classmethod
def clean_name(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("姓名不能为空白字符")
return v
@field_validator("student_no")
@classmethod
def validate_student_no_year(cls, v: str) -> str:
# 学号前4位应该是2000-2099
year = int(v[:4])
if not (2000 <= year <= 2099):
raise ValueError("学号前4为应该为2000-2099之间")
return v
四、启动服务与接口文档验证
写完代码启动服务时,第一次运行报了 field_validator 未定义 的错误,原因是忘记导入。在 Pydantic 的导入语句中补上 field_validator 后重新启动即可成功。
启动成功后访问接口文档,进入添加学生信息的接口点击 Try it out,可以看到每个字段都自动填充了我们配置的 examples 示例数据:name 默认是 Andy,gender 默认是“男”,birthday 也带上了示例日期。这说明 Field 的 title、description、examples 已经正确反映到文档中。
五、测试校验效果
测试一:姓名为纯空白
把 name 改成若干空格后点击 Execute,接口返回 422 校验失败,错误信息明确指出 name 字段的 ValueError:姓名不能为空白字符。
小插曲:第一次测试时返回的是 500 服务器错误,提示“小于等于不支持在整型和字符串上比较”。原因是学号校验函数里把
v(字符串)直接拿去和 2000 比较,改为使用转换后的year变量判断后就正常了。
测试二:学号前缀不合法
把学号改成 10 开头的值再次运行,接口同样返回校验失败,错误信息为:学号前四位应在 2000-2099 之间。
六、小结
本篇通过两类手段完成了学生模型的字段校验:
- Field 约束:用
min_length/max_length控制姓名长度,用pattern正则约束学号与手机号格式,用Literal限定性别枚举,用ge约束班级 ID 下限,同时借助title、description、examples让接口文档更友好; - Field Validator:用
@field_validator+@classmethod实现姓名去空白判空、学号年份区间判断等自定义逻辑,校验不通过时抛出ValueError,FastAPI 会自动转换为 422 响应并给出具体错误原因。
至此,单字段的复杂校验已经全部生效。后续如果需要跨字段联合校验(例如验证某字段时同时依赖其他字段),可以继续使用模型级验证器来实现。
想系统学习这门课程?
你已阅读完本文。需要更完整的知识体系,可以继续了解「FastAPI 项目实战」。
查看课程详情