
本文介绍如何在 Pydantic(v2+)中通过 @model_validator(mode="after") 实现多字段联动校验,例如根据 name 限制可选 category,再根据 category 动态约束 subcategory,适用于 FastAPI 接口参数强一致性验证。
本文介绍如何在 pydantic(v2+)中通过 `@model_validator(mode="after")` 实现多字段联动校验,例如根据 `name` 限制可选 `category`,再根据 `category` 动态约束 `subcategory`,适用于 fastapi 接口参数强一致性验证。
在构建高可靠性 API(如 FastAPI 服务)时,常需对模型字段施加跨字段业务逻辑约束——例如 subcategory 的合法值必须依赖于 category,而 category 又受限于 name。Pydantic 原生字段级验证(如 @field_validator)无法直接访问其他字段值,此时应使用 @model_validator(mode="after") 进行全模型后置校验。
核心思路是:将业务规则抽象为结构化字典(events_schema),以 name 为一级键,category 为二级键,其值为允许的 subcategory 列表(支持 None 表示“不允许子类”或“该 category 不接受 subcategory”)。校验逻辑按层级逐级检查:
- name 是否存在于规则字典中;
- category 是否为该 name 下允许的键;
- subcategory 是否在该 (name, category) 组合对应的允许列表中。
以下为完整可运行示例:
from pydantic import BaseModel, model_validator
from typing import Optional, Dict, List, Any
# 定义层级校验规则:{name: {category: [allowed_subcategories]}}
events_schema: Dict[str, Dict[Optional[str], List[Optional[str]]]] = {
"foo": {
"foo_1": ["foo_11", "foo_111"],
"foo_2": ["foo_22", "foo_222"],
None: [None], # 允许 name="foo" 且 category/subcategory 均为空
},
"goo": {
None: [None], # name="goo" 时仅允许 category 和 subcategory 均为 None
},
"bar": {
"bar_a": ["bar_a1", "bar_a2"],
"bar_b": [None], # bar_b 允许无 subcategory
}
}
class EventRequest(BaseModel):
name: str
category: Optional[str] = None
subcategory: Optional[str] = None
@model_validator(mode="after")
def verify_event_hierarchy(self) -> "EventRequest":
# Step 1: 校验 name 是否有效
if self.name not in events_schema:
raise ValueError(f"Invalid name: '{self.name}'. Supported names: {list(events_schema.keys())}")
name_rules = events_schema[self.name]
# Step 2: 校验 category 是否在当前 name 下合法(注意:None 是合法 key)
if self.category not in name_rules:
valid_categories = list(name_rules.keys())
raise ValueError(
f"Invalid category '{self.category}' for name '{self.name}'. "
f"Valid categories: {valid_categories}"
)
# Step 3: 校验 subcategory 是否属于 (name, category) 组合下允许的值
allowed_subcategories = name_rules[self.category]
if self.subcategory not in allowed_subcategories:
raise ValueError(
f"Invalid subcategory '{self.subcategory}' for category '{self.category}' "
f"under name '{self.name}'. "
f"Allowed subcategories: {allowed_subcategories}"
)
return self
✅ 优势说明:
- 解耦清晰:规则与校验逻辑分离,便于维护和单元测试;
- 类型安全:Pydantic v2 的 model_validator 支持完整的类型提示和 IDE 自动补全;
- FastAPI 无缝集成:该模型可直接作为 FastAPI 路由参数,错误会自动转为 422 Unprocessable Entity 并附带清晰错误信息;
- 扩展性强:新增事件类型只需扩充 events_schema 字典,无需修改校验逻辑。
⚠️ 注意事项:
- None 在字典中作为 key 或 value 时需显式声明(如 Optional[str]),避免 KeyError;
- 若业务规则极复杂(如涉及数据库查询、外部 API),建议将校验逻辑抽离为独立函数,并在 model_validator 中调用,保持模型轻量;
- 对高频接口,可考虑将 events_schema 预编译为 frozenset 或缓存哈希结构,但本例中字典查找已足够高效。
通过该模式,你不仅能精准表达“名称 → 分类 → 子分类”的树状依赖关系,还能让验证错误具备明确上下文,大幅提升 API 的健壮性与开发者体验。











