如何为Pydantic 2.x所有Literal字段实现自动验证?
为Pydantic 2.x的所有Literal字段统一添加自动strip验证
需求背景
有多个Pydantic 2.x模型,希望避免为每个模型的每个Literal字段单独编写strip验证逻辑,而是通过类似Annotated Validators的方式,让所有Literal字段自动执行strip操作。
原单个字段验证示例:
class MyModel(BaseModel): name: str = "" description: Optional[str] = None sex: Literal["male", "female"] @field_validator("sex", mode="before") @classmethod def strip_sex(cls, v: Any, info: ValidationInfo): if isinstance(v, str): return v.strip() return v
尝试的无效写法:
def strip_literals(v: Any) -> Any: if isinstance(v, str): return v.strip() return v # doesn't work # LiteralType = TypeVar("LiteralType", bound=Literal) # LiteralStripped = Annotated[Literal, BeforeValidator(strip_literals)] class MyModel(BaseModel): name: str = "" description: Optional[str] = None sex: LiteralStripped["male", "female"]
正确实现方式
方法一:泛型Annotated封装Literal类型
通过泛型TypeVar承接Literal的具体枚举值,再用Annotated绑定验证器,这样每个StrippedLiteral字段都会自动应用strip逻辑:
from typing import Any, Literal, TypeVar, Annotated, Optional from pydantic import BaseModel, BeforeValidator def strip_literals(v: Any) -> Any: if isinstance(v, str): return v.strip() return v # 定义泛型TypeVar,绑定字符串类型(适配Literal的枚举值场景) LiteralT = TypeVar("LiteralT", bound=str) # 封装带strip验证器的Literal类型 StrippedLiteral = Annotated[Literal[LiteralT], BeforeValidator(strip_literals)] class MyModel(BaseModel): name: str = "" description: Optional[str] = None sex: StrippedLiteral["male", "female"] # 新增其他Literal字段,自动生效 role: StrippedLiteral["admin", "user", "guest"] # 测试验证效果 test_input = {"sex": " female ", "role": " admin "} model = MyModel(**test_input) print(model.sex) # 输出: female print(model.role) # 输出: admin
方法二:模型级通用验证器(批量处理所有模型的Literal字段)
如果希望所有模型的Literal字段都自动应用strip,可以定义一个基础模型,在其中添加模型级别的前置验证器,识别并处理Literal类型的字段:
from typing import Any, Literal, get_args, get_origin from pydantic import BaseModel, model_validator, ValidationInfo def strip_literals(v: Any) -> Any: if isinstance(v, str): return v.strip() return v class BaseStrippedModel(BaseModel): @model_validator(mode="before") @classmethod def strip_all_literals(cls, values: dict[str, Any], info: ValidationInfo) -> dict[str, Any]: for field_name, field in cls.model_fields.items(): # 判断字段类型是否为Literal field_type = field.annotation if get_origin(field_type) is Literal: value = values.get(field_name) if value is not None: values[field_name] = strip_literals(value) return values # 继承基础模型即可自动生效 class MyModel(BaseStrippedModel): name: str = "" description: Optional[str] = None sex: Literal["male", "female"] role: Literal["admin", "user"] # 测试 test_input = {"sex": " male ", "role": " user "} model = MyModel(**test_input) print(model.sex) # 输出: male print(model.role) # 输出: user
无效写法原因说明
之前的写法错误在于直接将Literal类型放入Annotated中,没有通过泛型传递具体的枚举参数,导致Pydantic无法识别字段的实际Literal约束,验证器也无法正确绑定到具体字段类型上。通过泛型TypeVar承接Literal的具体值,才能让Annotated封装的类型正确被Pydantic解析。
内容的提问来源于stack exchange,提问作者GopherM
相关产品推荐
相关产品推荐

