← 中文资源

简体中文版本

Pydantic 字段校验与强制转换

用 Pydantic 的字段校验器归一化脏表单输入:先于类型转换修剪邮箱、校验纯数字邮编、模型级校验两次密码一致。

阅读英文原文

示例说明

真实用户的输入永远比想象脏:邮箱带空格混大小写甚至 mailto 前缀、年龄是字符串、邮编混入字母。mode=before 的校验器在类型转换前介入——修剪归一做完再交给类型系统,标准注解的区间约束照常生效;model_validator 则处理跨字段一致性,单字段无法表达的规则在这里收口。

技术要点

  • before 模式先归一化再类型转换
  • Annotated 注解集中字段约束
  • 模型级校验处理跨字段规则

代码解读

normalize_email 修剪、转小写并剥掉 mailto 前缀;digits_only 要求邮编恰好五位纯数字否则抛值错误;passwords_match 在模型构造后比对两次输入。示例输入的年龄是字符串却被正确转为 int,邮箱归一为小写无前缀形态。

使用建议

归一化规则越靠近入口越好,别让脏数据流进业务层。校验失败信息要可指导用户修正。before 校验器里别做网络请求等重操作。

代码示例

from typing import Annotated

from pydantic import BaseModel, Field, field_validator, model_validator


class SignupForm(BaseModel):
    email: Annotated[str, Field(min_length=5, max_length=120)]
    age: Annotated[int, Field(ge=18, le=120)]
    zipcode: str
    password: str
    password_confirm: str

    @field_validator('email', mode='before')
    @classmethod
    def normalize_email(cls, raw: object) -> object:
        if isinstance(raw, str):
            candidate = raw.strip().lower()
            if candidate.startswith('mailto:'):
                candidate = candidate.removeprefix('mailto:')
            return candidate
        return raw

    @field_validator('zipcode', mode='before')
    @classmethod
    def digits_only(cls, raw: object) -> object:
        if isinstance(raw, str) and raw.isdigit() and len(raw) == 5:
            return raw
        raise ValueError('zipcode must be exactly 5 digits')

    @model_validator(mode='after')
    def passwords_match(self) -> 'SignupForm':
        if self.password != self.password_confirm:
            raise ValueError('passwords do not match')
        return self


form = SignupForm(
    email='  MAILTO:[email protected] ',
    age='30',
    zipcode='02138',
    password='hunter2xx',
    password_confirm='hunter2xx',
)
print('normalized email:', form.email, 'age type:', type(form.age).__name__)