
本文介绍如何使用 Pydantic v2 的 @model_validator(mode="after") 实现层级化枚举校验——当主类别(如 "meat")确定后,自动限制子类别仅能从预定义的对应枚举中选取,确保数据结构强一致性。
本文介绍如何使用 pydantic v2 的 `@model_validator(mode="after")` 实现层级化枚举校验——当主类别(如 "meat")确定后,自动限制子类别仅能从预定义的对应枚举中选取,确保数据结构强一致性。
在构建具有业务语义约束的 API 数据模型时,简单的枚举类型(str, Enum)往往不足以表达“条件依赖”关系。例如:商品分类中,选择 "meat" 类别后,子类必须属于 {beef, lamb, poultry};而选 "veg" 时则只允许 {carrot, potato, spinach}。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"
dairy = "dairy"
class MeatSubCategory(str, Enum):
beef = "beef"
lamb = "lamb"
poultry = "poultry"
class VegSubCategory(str, Enum):
carrot = "carrot"
potato = "potato"
spinach = "spinach"
class DairySubCategory(str, Enum):
milk = "milk"
cheese = "cheese"
yogurt = "yogurt"
# 使用 Union 构建统一子类类型(仅用于类型提示,不影响运行时校验)
SubCategory = Union[MeatSubCategory, VegSubCategory, DairySubCategory]
class Product(BaseModel):
category: Category
sub_category: SubCategory
@model_validator(mode="after")
def validate_sub_category(self) -> "Product":
# 定义每个主类对应的合法子类枚举映射
category_to_sub_enum = {
Category.meat: MeatSubCategory,
Category.veg: VegSubCategory,
Category.dairy: DairySubCategory,
}
expected_enum = category_to_sub_enum.get(self.category)
if expected_enum is None:
raise ValueError(f"Unsupported category: {self.category}")
# 检查 sub_category 是否属于预期枚举的成员(注意:isinstance 对 str-Enum 成员有效)
if not isinstance(self.sub_category, expected_enum):
allowed_values = [item.value for item in expected_enum]
raise ValueError(
f"Invalid sub-category '{self.sub_category}' for category '{self.category}'. "
f"Allowed: {allowed_values}"
)
return self
✅ 使用示例:
# ✅ 合法:meat → beef
p1 = Product(category="meat", sub_category="beef")
print(p1) # category='meat' sub_category=<meatsubcategory.beef:>
# ❌ 报错:meat → carrot(跨类非法)
try:
p2 = Product(category="meat", sub_category="carrot")
except ValueError as e:
print(e) # Invalid sub-category 'carrot' for category 'meat'. Allowed: ['beef', 'lamb', 'poultry']</meatsubcategory.beef:>
? 关键要点说明:
- Union[...] 仅用于类型提示:它让 IDE 和类型检查器(如 mypy)理解 sub_category 可能是多种枚举之一,但不参与运行时校验;真正的约束由 @model_validator 承担。
- isinstance(..., EnumClass) 是可靠判断方式:因所有子枚举均继承自 str, Enum,其值为字符串,但实例仍保有枚举身份,isinstance 可精准识别归属。
- 错误信息友好:动态生成允许值列表,便于前端或调试快速定位问题。
- 扩展性强:新增类别只需扩展 Category 枚举、定义对应子枚举,并在 category_to_sub_enum 中添加映射即可,无需修改验证逻辑。
⚠️ 注意事项:
- 避免在 @model_validator 中修改 self 字段值(除非明确需要),本例仅做校验并返回原对象;
- 若需支持 None 或可选子类,应先处理空值逻辑,并调整 Union 类型与验证分支;
- Pydantic v1 用户需改用 @validator(..., always=True) + pre=False,且 self 是字典而非模型实例,迁移时请注意 API 差异。
通过该模式,你能在保持模型简洁性的同时,严格保障多层业务枚举间的语义一致性,是构建高可靠性数据接口的重要实践。











