
本文介绍如何使用 Pydantic v2 的 @model_validator(mode="after") 实现嵌套字段的条件校验,确保 sub_category 的取值严格依赖于 category 的实际值,从而构建语义一致、类型安全的层级枚举结构。
本文介绍如何使用 pydantic v2 的 `@model_validator(mode="after")` 实现嵌套字段的条件校验,确保 `sub_category` 的取值严格依赖于 `category` 的实际值,从而构建语义一致、类型安全的层级枚举结构。
在构建具有层级语义的数据模型(如商品分类体系)时,简单的枚举组合无法表达“某子类仅属于特定父类”的业务约束。例如,“牛肉”只应出现在“肉类”下,而不能搭配“蔬菜”类别。Pydantic 本身不原生支持跨字段的动态枚举绑定,但可通过 @model_validator(mode="after") 在模型实例化后进行运行时逻辑校验,精准实现该需求。
以下是一个完整、可运行的示例:
from enum import Enum
from pydantic import BaseModel, model_validator
from typing import Union
class Category(str, Enum):
meat = "meat"
veg = "veg"
class MeatSubCategory(str, Enum):
beef = "beef"
lamb = "lamb"
class VegSubCategory(str, Enum):
carrot = "carrot"
potato = "potato"
# 使用 Union 构建联合类型,便于类型提示和序列化兼容
SubCategory = Union[MeatSubCategory, VegSubCategory]
class ProductCategory(BaseModel):
category: Category
sub_category: SubCategory
@model_validator(mode="after")
def validate_sub_category(self) -> "ProductCategory":
# 定义每个 category 对应的有效子类枚举映射
valid_subcategories = {
Category.meat: MeatSubCategory,
Category.veg: VegSubCategory,
}
expected_enum = valid_subcategories.get(self.category)
# 检查 sub_category 是否属于预期枚举的成员(注意:isinstance 可正确识别 str-Enum 实例)
if not isinstance(self.sub_category, expected_enum):
raise ValueError(
f"Sub-category '{self.sub_category}' is not valid for category '{self.category}'. "
f"Expected one of: {[e.value for e in expected_enum]}"
)
return self
✅ 关键要点说明:
- str 枚举(class X(str, Enum))保证了字段在 JSON 序列化/反序列化时表现为字符串,同时保留枚举语义;
- Union[MeatSubCategory, VegSubCategory] 使 sub_category 类型提示更准确,并支持自动解析字符串为对应枚举实例;
- @model_validator(mode="after") 在所有字段赋值完成后执行,此时 self.category 和 self.sub_category 均已解析为对应枚举对象,可安全比对;
- 校验失败时提供清晰的错误信息,包含非法值、所属类别及合法选项列表,利于调试与前端反馈。
⚠️ 注意事项:
- 此方案依赖运行时校验,不会在静态类型检查(如 mypy)中捕获错误,需配合单元测试保障健壮性;
- 若子类枚举规模庞大或需动态加载(如从数据库读取),可将 valid_subcategories 映射改为函数式查询,保持扩展性;
- 避免在 validator 中修改 self 字段值(除非明确需要归一化),此处仅做验证并返回 self 即可。
通过该模式,你可在保持 Pydantic 强类型优势的同时,灵活建模复杂的业务约束关系,让数据验证真正贴合领域逻辑。











