递归Pydantic模型验证失败时未触发错误问题求助
递归Pydantic模型验证不生效的问题解决
问题现象
为递归结构的Pydantic数据模型添加验证逻辑后,即使子节点的字段不符合验证规则,验证器也未抛出错误,导致非法数据被成功实例化。
代码示例
from __future__ import annotations from typing import Optional, Union, List import orjson from pydantic import BaseModel, validator def orjson_dumps(v, *, default): return orjson.dumps(v, default=default).decode() class Condition(BaseModel): operation: str value: Union[List[Condition], Condition, float, int, list, str] attribute_name: Optional[str] @validator("operation", pre=True, always=True) @classmethod def validate_operations(cls, field_value): if field_value not in ["and", "or", "equal"]: msg = f"Supported operations: [and, or, equal]. Passed operation: {field_value}" raise ValueError(msg) return field_value if __name__ == '__main__': condition4 = {'operation': 'or', 'value': [ {'operation': 'equal', 'attribute_name': 'num_pay_txns_month', 'value': 6}, {'operation': 'incorrect_operation', 'attribute_name': 'num_pay_txns_week', 'value': 3}]} print(Condition(**condition4))
实际输出
operation='or' value=[{'operation': 'equal', 'attribute_name': 'num_pay_txns_month', 'value': 6}, {'operation': 'incorrect_operation', 'attribute_name': 'num_pay_txns_week', 'value': 3}] attribute_name=None
预期输出
ValueError: Supported operations: [and, or, equal]. Passed operation: incorrect_operation
问题原因
Pydantic在解析Union类型时,会按照从左到右的顺序匹配类型。原代码中list类型位于List[Condition]之前,当value字段传入列表形式的字典时,Pydantic会优先将其识别为普通list类型,而非List[Condition],导致嵌套的字典不会被转换成Condition实例,自然无法触发operation字段的验证器。
解决方案
方案1:调整Union类型顺序
将List[Condition]和Condition放在普通类型(如list)之前,让Pydantic优先尝试解析为模型实例:
class Condition(BaseModel): operation: str # 把List[Condition]、Condition移到list前面 value: Union[List[Condition], Condition, float, int, str, list] attribute_name: Optional[str] # 验证器代码不变 @validator("operation", pre=True, always=True) @classmethod def validate_operations(cls, field_value): if field_value not in ["and", "or", "equal"]: msg = f"Supported operations: [and, or, equal]. Passed operation: {field_value}" raise ValueError(msg) return field_value
方案2:启用smart_union配置
在模型的Config类中开启smart_union=True,让Pydantic忽略Union类型的顺序,自动选择最匹配的类型进行解析:
class Condition(BaseModel): operation: str value: Union[List[Condition], Condition, float, int, list, str] attribute_name: Optional[str] @validator("operation", pre=True, always=True) @classmethod def validate_operations(cls, field_value): if field_value not in ["and", "or", "equal"]: msg = f"Supported operations: [and, or, equal]. Passed operation: {field_value}" raise ValueError(msg) return field_value class Config: smart_union = True
方案3:自定义递归验证器
给value字段添加自定义验证器,手动递归验证嵌套结构:
class Condition(BaseModel): operation: str value: Union[List[Condition], Condition, float, int, list, str] attribute_name: Optional[str] @validator("operation", pre=True, always=True) @classmethod def validate_operations(cls, field_value): if field_value not in ["and", "or", "equal"]: msg = f"Supported operations: [and, or, equal]. Passed operation: {field_value}" raise ValueError(msg) return field_value @validator("value") @classmethod def validate_nested_conditions(cls, field_value): if isinstance(field_value, list): for item in field_value: if isinstance(item, dict): # 手动实例化Condition触发验证 Condition(**item) elif isinstance(field_value, dict): Condition(**field_value) return field_value
验证效果
修改后运行测试代码,会触发预期的ValueError:
ValueError: Supported operations: [and, or, equal]. Passed operation: incorrect_operation
内容的提问来源于stack exchange,提问作者Kunal Kishore Singh
相关产品推荐
相关产品推荐

