基于Pydantic实现按字段类型自动应用通用验证器的继承方案问询
在Pydantic BaseModel中按字段类型自动应用通用验证器
问题核心
需要在父类BaseModel中定义通用验证器,让子类继承后自动根据字段类型应用对应验证逻辑:int类型字段用to_int转换,bytes类型字段用to_bytes转换。当前使用通配符*的验证器会对所有字段生效(导致类型不匹配的字段报错),重写model_validate又会丢失Pydantic自带的别名解析功能。
解决方案
利用Pydantic的ValidationInfo参数获取当前字段的类型注解,在验证器内部判断是否执行对应转换,既保留别名解析逻辑,又实现按类型自动应用验证器。
修改后的完整代码
from typing import Union, Optional, Any, get_origin, get_args from pydantic import BaseModel, ConfigDict, AliasGenerator, AliasChoices, Field, field_validator, ValidationInfo def to_int(v: Union[str, int]) -> int: if isinstance(v, str): if v.startswith("0x"): return int(v, 16) return int(v) return v def to_bytes(v: Union[str, bytes, list[int]]) -> bytes: if isinstance(v, bytes): return v elif isinstance(v, str): if v.startswith("0x"): return bytes.fromhex(v[2:]) return v.encode() else: return bytes(v) class BaseModelCamelCase(BaseModel): model_config = ConfigDict( populate_by_name=True, alias_generator=AliasGenerator( validation_alias=lambda name: AliasChoices(name, name) # 保留原别名生成逻辑,实际可替换为to_camel ), ) @field_validator("*", mode="before") def validate_int_fields(cls, v: Any, info: ValidationInfo) -> Any: field_type = info.field_annotation # 处理Optional等泛型类型 origin = get_origin(field_type) if origin is Union: args = get_args(field_type) if int in args: field_type = int if field_type is int: return to_int(v) return v @field_validator("*", mode="before") def validate_bytes_fields(cls, v: Any, info: ValidationInfo) -> Any: field_type = info.field_annotation # 处理Optional等泛型类型 origin = get_origin(field_type) if origin is Union: args = get_args(field_type) if bytes in args: field_type = bytes if field_type is bytes: return to_bytes(v) return v class BaseTransactionModel(BaseModelCamelCase): nonce: int gas: int = Field(validation_alias=AliasChoices("gasLimit", "gas_limit", "gas")) to: Optional[bytes] value: int data: bytes r: int = 0 s: int = 0
关键说明
- 类型判断逻辑:通过
ValidationInfo.field_annotation获取当前字段的类型,同时处理Optional[int]这类泛型类型(用get_origin和get_args解析泛型参数)。 - 验证器触发规则:两个验证器都用通配符
*,但内部只对匹配类型的字段执行转换,其他字段直接返回原值,避免类型不匹配报错。 - 保留别名解析:没有重写
model_validate,完全沿用Pydantic原生的字段填充和别名解析逻辑,包括自定义的validation_alias配置。
测试示例
# 测试数据,包含别名和不同格式的字段值 test_data = { "nonce": "0xa", "gasLimit": "0x100", "to": "0x1234567890abcdef1234567890abcdef12345678", "value": "100", "data": "0xdeadbeef" } model = BaseTransactionModel(**test_data) print(model.nonce) # 输出: 10(十六进制字符串转int) print(model.gas) # 输出: 256(别名gasLimit被解析,十六进制转int) print(model.to) # 输出: b'\x124Vx\x90\xab\xcd\xef\x124Vx\x90\xab\xcd\xef\x124Vx'(十六进制字符串转bytes) print(model.data) # 输出: b'\xde\xad\xbe\xef'(十六进制字符串转bytes)
内容的提问来源于stack exchange,提问作者ClementWalter
相关产品推荐
相关产品推荐

