
本文介绍如何使用 Pydantic 的 @model_validator 在嵌套模型中实现「主类别决定可选子类别」的强约束校验,避免硬编码逻辑,确保 sub_category 的合法性严格依赖于 category 的实际值。
本文介绍如何使用 pydantic 的 `@model_validator` 在嵌套模型中实现「主类别决定可选子类别」的强约束校验,避免硬编码逻辑,确保 `sub_category` 的合法性严格依赖于 `category` 的实际值。
在构建具有层级关系的业务数据模型(如商品分类、内容标签、配置项分组)时,常需保证子字段的取值范围受父字段动态约束——例如选择 "meat" 类别后,子类别仅允许 "beef"、"lamb" 等肉类专属选项;若选 "veg",则只接受 "carrot"、"potato" 等蔬菜子类。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"
pork = "pork"
class VegSubCategory(str, Enum):
carrot = "carrot"
potato = "potato"
tomato = "tomato"
# 使用 Union 构建统一类型提示(兼容类型检查与文档生成)
SubCategory = Union[MeatSubCategory, VegSubCategory]
class ProductCategory(BaseModel):
category: Category
sub_category: SubCategory
@model_validator(mode="after")
def validate_sub_category(self) -> "ProductCategory":
# 定义类别到子类枚举的映射关系
valid_subcategories = {
Category.meat: MeatSubCategory,
Category.veg: VegSubCategory,
}
# 获取当前 category 对应的合法子类枚举
expected_enum = valid_subcategories.get(self.category)
# 校验 sub_category 是否属于预期枚举类型
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✅ 关键设计说明:
- SubCategory = Union[...] 提供类型安全与 IDE 自动补全支持,同时兼容 OpenAPI/Swagger 文档生成;
- @model_validator(mode="after") 在所有字段解析完成后执行,可安全访问 self.category 和 self.sub_category;
- 映射字典 valid_subcategories 清晰表达业务规则,易于维护和扩展(如新增 Category.fruit 及其对应子类);
- 错误信息包含具体可选值列表,提升调试与用户提示体验。
⚠️ 注意事项:
- 不要将 sub_category 声明为 str 后再手动校验——这会丢失枚举的类型约束与序列化优势;
- 若需支持更多动态规则(如数据库驱动的类别配置),可将映射逻辑抽离为外部函数或依赖注入服务;
- Pydantic v2 要求显式返回 self(如示例所示),遗漏将导致校验失败。
运行验证示例:
# ✅ 合法组合 print(ProductCategory(category="meat", sub_category="beef")) # → category='meat' sub_category=<MeatSubCategory.beef: 'beef'> # ❌ 非法组合(抛出 ValueError) ProductCategory(category="meat", sub_category="carrot") # → ValueError: Sub-category 'carrot' is not valid for category 'meat'. Expected one of: ['beef', 'lamb', 'pork']
该方案兼顾类型安全性、可读性与可维护性,是 Pydantic 中处理条件化枚举依赖的经典实践。

















