如何实现单个Pydantic字段的动态Float Enum名称校验及OpenAPI支持
解决方案:为特定Pydantic字段实现按名称校验的动态Enum
核心思路
要实现单个/多个字段按Enum名称校验,同时不影响全局Enum的默认行为,我们可以通过自定义校验器或扩展Enum类的方式,针对目标字段单独处理解析逻辑,同时满足动态生成Enum、序列化输出名称、OpenAPI Schema生成这三个核心需求。
方案一:使用BeforeValidator针对单个字段处理
适用于仅需给少数字段单独配置的场景,无需修改Enum本身。
步骤1:动态生成Enum
从运行时数据源(如配置、数据库)生成Enum:
from enum import Enum from pydantic import BaseModel, BeforeValidator, ValidationInfo from typing import Annotated # 实际场景可替换为从外部加载的动态数据 factor_data = {"single": 1.0, "half": 0.4, "quarter": 0.1} Factor = Enum("Factor", factor_data)
步骤2:编写名称解析校验器
创建校验器函数,将输入的名称转换为对应Enum实例:
def parse_enum_by_name(value, info: ValidationInfo): enum_type = info.field.annotation if isinstance(value, str): try: return enum_type[value] except KeyError: allowed_names = [member.name for member in enum_type] raise ValueError(f"无效名称: {value},可选值: {allowed_names}") # 若输入已是Enum实例,直接返回 return value
步骤3:在模型中绑定校验器
用Annotated为目标字段单独配置校验逻辑,其他字段保持默认行为:
class Sex(str, Enum): MALE = "M" FEMALE = "F" class Model(BaseModel): sex: Sex # 保持默认按值校验逻辑 # 为factor字段绑定按名称解析的校验器 factor: Annotated[Factor, BeforeValidator(parse_enum_by_name)] class Config: # 序列化时输出Enum名称 json_encoders = {Factor: lambda x: x.name}
测试验证
# 用名称传入可正常实例化 model = Model(sex="M", factor="half") print(model.factor.value) # 输出: 0.4 print(model.json()) # 输出: {"sex": "M", "factor": "half"} # 传入错误名称会抛出明确提示 try: Model(sex="M", factor="invalid") except ValueError as e: print(e) # 输出: 无效名称: invalid,可选值: ['single', 'half', 'quarter']
方案二:自定义支持名称解析的Enum子类
若多个字段需要使用按名称校验的Enum,可自定义Enum基类,便于复用。
步骤1:自定义Enum基类
重写校验逻辑和Schema生成逻辑,确保支持名称解析且OpenAPI显示正确:
from enum import Enum from pydantic import GetCoreSchemaHandler, ValidationInfo from pydantic_core import core_schema class NameResolvableEnum(Enum): @classmethod def __get_validators__(cls): yield cls.validate @classmethod def validate(cls, value, info: ValidationInfo): if isinstance(value, str): try: return cls[value] except KeyError: allowed_names = [member.name for member in cls] raise ValueError(f"无效名称: {value},可选值: {allowed_names}") return super(cls, cls)._missing_(value) # 确保OpenAPI Schema显示Enum名称而非值 @classmethod def __get_pydantic_core_schema__(cls, source_type, handler: GetCoreSchemaHandler): schema = handler(source_type) schema['enum'] = [member.name for member in cls] return schema
步骤2:动态生成Enum并继承基类
factor_data = {"single": 1.0, "half": 0.4, "quarter": 0.1} # 动态生成Factor Enum,继承自定义基类 Factor = NameResolvableEnum("Factor", factor_data)
步骤3:在模型中直接使用
无需额外校验器,直接声明字段类型即可:
class Sex(str, Enum): MALE = "M" FEMALE = "F" class Model(BaseModel): sex: Sex # 保持默认行为 factor: Factor # 自动按名称校验 class Config: json_encoders = {NameResolvableEnum: lambda x: x.name}
测试验证
效果与方案一一致,且OpenAPI Schema会自动生成["single", "half", "quarter"]作为枚举选项。
关键注意事项
- 动态Enum扩展:两种方案都支持从外部数据源加载Enum键值对,只需替换
factor_data即可。 - Decimal兼容:后续若需将float改为Decimal,仅需修改
factor_data中的值类型为Decimal("0.4"),逻辑无需额外调整。 - Schema优化:方案一中若需生成正确的OpenAPI Schema,可给字段添加
Field(enum=[member.name for member in Factor])。
内容的提问来源于stack exchange,提问作者NeilG
相关产品推荐
相关产品推荐

