You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:47:11