Pydantic 跨字段校验实战:用 model_validator 校验生日与学号的业务关联
在上一节中,我们使用 Field 和 field_validator 完成了单个字段的校验。但在真实业务里,很多规则并不是孤立存在的——比如「出生日期」和「学号」必须联合起来判断才合理。本文就带你用 model_validator 实现跨字段校验。
一、需求分析:为什么要跨字段校验
先明确两个业务规则:
- 年龄校验:用今天的年份减去出生年份得到年龄,年龄必须在 5 到 100 岁之间。
- 入学年龄校验:学号前四位代表入学年份,用「入学年份 − 出生年份」得到入学年龄,正常应在 5 到 20 岁之间(放宽后仍异常则说明数据有问题)。
这里的关键在于:入学年龄同时依赖 birthday 和 studentno 两个字段。field_validator 一次只能处理一个字段,无法胜任,因此需要引入 model_validator。
model_validator 的 mode 参数支持两个取值:
after:其它字段全部校验完成后再执行本校验(联合校验通常选它);before:在字段校验之前执行。
二、代码实现
1. 导入 model_validator
在 Pydantic 中导入 model_validator:
from pydantic import model_validator
2. 编写跨字段校验方法
在模型类中新增一个校验方法,mode 设为 after,方法返回对象自身:
@model_validator(mode="after")
def validate_birthday_and_studentno(self) -> "StudentCreate":
# birthday 允许为空,为空时直接放行
if self.birthday is None:
return self
# 计算当前年龄
today = datetime.date.today()
age_now = today.year - self.birthday.year
if not (5 <= age_now <= 100):
raise ValueError("年龄应该是5-100之间")
# 计算入学年龄:学号前四位为入学年份
age_at_enrollment = int(self.studentno[:4]) - self.birthday.year
if not (5 <= age_at_enrollment <= 10):
raise ValueError("出生日期与学号不符")
return self
要点说明:
- 由于
birthday之前被设置为可为None,方法开头先做空值判断,为空则直接return self; - 用
datetime.date.today()获取今天日期,用年份相减得到年龄; - 学号前四位通过
self.studentno[:4]截取并转为int,再减去出生年份得到入学年龄; - 校验不通过时抛出
ValueError,校验通过后务必return self。
代码编写完成后的模型结构如下:
完整代码如下:
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
@model_validator(mode="after")
def validate_birthday_and_student_no(self) -> "StudentCreate":
if self.birthday is None:
return self
today = datetime.date.today()
age_now = today.year - self.birthday.year
if not( 5 <= age_now <= 100):
raise ValueError("年龄应该是5-100之间")
age_at_enrollment = int(self.student_no[:4]) - self.birthday.year
if not ( 5<= age_at_enrollment <= 10):
raise ValueError("出生日期与学号不符")
return self
三、测试验证
打开接口文档的 Try it out 面板,依次验证三种场景。
场景一:入学年龄过小
将出生日期设为 2020 年、学号设为 2022,意味着 2 岁就入学,显然不合理。执行后接口返回错误提示「出生日期与学号不符」,说明跨字段规则生效:
场景二:年龄超出范围
将出生日期改为 1020 年,年龄远超 100 岁上限。执行后返回「年龄应该是5-100之间」,年龄校验同样生效:
场景三:正常数据
使用出生日期 2020 年、学号 2026,入学年龄为 6 岁,符合规则。注意此时若学号已存在会提示重复,换一个未使用的学号后即可提交成功:
再试一组学号 2025、5 岁上学,同样创建成功,说明边界值也能正常通过:
四、小结
本文通过「生日 + 学号」的联合校验,演示了 model_validator 的完整用法:
field_validator只能校验单个字段,model_validator可以同时使用多个字段,适合处理跨字段业务规则;mode="after"表示在其它字段全部校验完成后再执行联合校验,是联合校验的常用选择;- 校验方法中记得处理可空字段,并在校验通过后
return self。
至此,跨字段业务规则校验就实现完毕了,下节课再见。
想系统学习这门课程?
你已阅读完本文。需要更完整的知识体系,可以继续了解「FastAPI 项目实战」。
查看课程详情