
本文详解如何在 Pydantic v2+ 中使用 @model_validator(mode="after") 实现多字段依赖校验,例如“当 relation_type 非空时,document_list 必须至少包含一个元素”,并提供可直接运行的代码示例与关键注意事项。
本文详解如何在 pydantic v2+ 中使用 `@model_validator(mode="after")` 实现多字段依赖校验,例如“当 `relation_type` 非空时,`document_list` 必须至少包含一个元素”,并提供可直接运行的代码示例与关键注意事项。
在构建 FastAPI 数据模型时,单字段校验(如类型、长度、枚举)往往不够——真实业务常需跨字段逻辑约束,例如:“若指定了关系类型,则必须关联至少一份原始单据”。Pydantic v2 引入的 @model_validator(mode="after") 正是为此类场景设计的权威解决方案。
以下是一个完整、健壮且符合生产环境要求的实现:
from pydantic import BaseModel, model_validator, Field, ValidationError
from typing import Optional, List, Annotated
from enum import StrEnum
class TipoRelacionEnum(StrEnum):
nota_credito = "01"
nota_debito = "02"
devolucion_mercancias = "03"
sustitucion = "04"
traslado = "05"
facturacion_generada = "06"
anticipo = "07"
class Cfdi(BaseModel):
relation_type: Optional[Annotated[
TipoRelacionEnum,
Field(
title="Tipo de Relación",
description="Se debe registrar la clave de la relación que existe entre este comprobante y los CFDI previos.",
examples=["04", "01"],
max_length=2
)
]] = None
document_list: Optional[List[str]] = Field(
default=None,
description="Lista de UUID o folios de documentos relacionados (ej. facturas originales)."
)
@model_validator(mode="after")
def validate_relation_and_documents(self) -> "Cfdi":
# ✅ 使用 self 访问已解析后的字段值(类型安全、已转换)
if self.relation_type is not None and (
self.document_list is None or len(self.document_list) == 0
):
raise ValueError(
"Si 'relation_type' está especificado, 'document_list' debe contener al menos un elemento."
)
return self
? 关键说明:
- mode="after" 表示校验发生在所有字段解析、类型转换及单字段验证之后,因此 self.relation_type 和 self.document_list 均为最终 Python 对象(非原始输入),可直接进行逻辑判断;
- 使用 self 而非 data: dict 是 Pydantic v2 推荐方式(更类型安全、IDE 支持更好);
- 错误信息应清晰指向业务语义(而非技术细节),便于前端或日志快速定位问题;
- List[str] 比 list[str] | None 更规范(typing.List 已被推荐,且与 OpenAPI 文档生成兼容性更佳)。
✅ 测试用例验证:
# 合法:无 relation_type → document_list 可为空或缺失
Cfdi()
# 合法:有 relation_type 且 document_list 非空
Cfdi(relation_type="04", document_list=["UUID-123"])
# ❌ 触发 ValidationError
try:
Cfdi(relation_type="04", document_list=[])
except ValidationError as e:
print(e.errors())
# → [{'type': 'value_error', 'loc': (), 'msg': 'Si ... al menos un elemento.', ...}]
? 注意事项总结:
- 不要使用 @validator(v1 API,已弃用);
- 避免在 mode="before" 中访问未解析字段(可能仍是字符串或 None,类型不可靠);
- 若需访问原始输入(极少数场景),可用 info.context 或自定义 BeforeValidator,但本例无需;
- FastAPI 会自动捕获该异常并返回标准 422 Unprocessable Entity 响应,含结构化错误详情。
通过 @model_validator(mode="after"),你不仅能精准表达业务规则,还能获得类型安全、可调试、可文档化的校验逻辑——这是现代 Pydantic 模型设计的核心实践之一。










