Python接口数据校验方法_pydantic实战解析【技巧】

Pydantic v2 默认禁止额外字段且校验错误提示不友好,需配置 model_config={"extra": "allow"} 并扁平化 errors() 输出;自定义校验器必须显式 return 值,model_dump() 替代 dict() 且默认行为不同。

Pydantic v2 里 BaseModel 默认不再允许额外字段

很多老项目升级到 Pydantic v2 后,接口接收 JSON 时突然报 ValidationError,提示 “extra fields not permitted”,其实不是数据错了,而是模型默认关掉了未知字段。这和 v1 的宽松行为完全不同。

解决方法是显式开启允许:

from pydantic import BaseModel

class UserRequest(BaseModel): name: str age: int

class Config:
    extra = "allow"  # v1 写法,v2 中已弃用

v2 正确写法改用 model_config

class UserRequest(BaseModel):
    name: str
    age: int
model_config = {"extra": "allow"}
  • "allow":忽略多余字段,不报错
  • "ignore":静默丢弃(不推荐,容易掩盖前端传参错误)
  • "forbid":严格禁止(默认值,也是最安全的选择)

嵌套结构校验失败时,ValidationErrorerrors() 输出难读

当请求体含多层嵌套(比如 address.city 缺失),默认的 e.errors() 返回的是带 loc 元组的字典,像 [{"loc": ["address", "city"], "msg": "Field required"}],直接返回给前端不友好。

建议封装一层扁平化处理:

def format_pydantic_errors(e):
    return [
        {"field": ".".join(err["loc"]), "message": err["msg"]}
        for err in e.errors()
    ]

使用示例

try: UserRequest(**data) except ValidationError as e: return {"errors": format_pydantic_errors(e)}

  • 避免直接暴露 loc 给前端,尤其是内部字段名(如 __root__
  • 如果用了 Field(alias=...)loc 仍显示原始字段名,需手动映射别名
  • 批量校验多个对象时,ValidationError 会合并所有错误,errors() 仍可用

@field_validator 做业务逻辑校验,但忘记 return value

自定义校验器里最常踩的坑:写了逻辑判断却没返回值,导致字段被清空为 None。Pydantic 不会帮你兜底。

比如校验手机号格式后忘了返回:

from pydantic import field_validator

@field_validator("phone") def validate_phone(cls, v): if not re.match(r"^1[3-9]\d{9}$", v): raise ValueError("invalid phone number")

❌ 忘了 return v → 字段值变成 None

  • 所有 @field_validator 函数必须显式 return 处理后的值(哪怕没改)
  • 若想支持空值跳过校验,加参数 skip_on_failure=True,但慎用,容易掩盖必填逻辑
  • 涉及数据库查重等 IO 操作,不要放在这里——validator 是同步且无 await 支持的

model_dump()dict() 在 v2 中行为不同

v2 废弃了 .dict(),改用 .model_dump()。但很多人迁移时只改函数名,没注意默认参数变化:旧版 .dict(exclude_unset=True) 对应新版的 .model_dump(exclude_unset=True),但默认值变了。

关键差异:

  • .model_dump() 默认导出所有字段(含未设置的默认值)
  • .model_dump(exclude_unset=True) 才等价于旧版 .dict()
  • 如果字段有 default_factory,且没被赋值,exclude_unset=True 也不会排除它——得用 exclude_defaults=True

API 响应常用组合:

user = UserResponse(name="Alice", age=25)
user.model_dump(exclude_unset=True, exclude_none=True)

这样既不传未设置字段,也不传 None 值,更符合 RESTful 接口习惯。

字段级别控制还有 aliasby_aliasexclude 等,但多数场景只需记住:不加 exclude_unset 就很可能把默认值也吐出去,前端拿到意外字段。