Pydantic构建含动态枚举的JSON Schema时报类型声明错误
问题原因
你遇到报错的核心原因是字段类型标注错误:EnumMeta是Python枚举类的元类,用于构造枚举类本身,并非实际的枚举值类型,Pydantic无法将元类映射为JSON Schema支持的合法类型,因此抛出声明失败的错误。
另外你原有代码里的根校验器逻辑存在缺陷:直接统计values的长度会把值为None的默认字段计入,会导致单字段传值时也误触发"选择超过一个"的报错。
解决思路
因为枚举值从外部API动态获取,无法提前定义固定的枚举类和模型类,采用动态类工厂的方式实现即可:拿到API返回的枚举值后,先动态生成对应枚举类,再动态生成绑定了这些枚举类型的Pydantic模型,后续用生成的模型做校验、导出JSON Schema都可以正常运行。
可直接运行的实现代码
from enum import Enum from typing import Optional, Dict, Any from pydantic import BaseModel, root_validator import json def _build_dynamic_enum(enum_members: Dict[str, str]) -> type[Enum]: """根据API返回的枚举键值对动态生成Enum类""" return Enum("DynamicLangEnum", enum_members) def _build_language_model(lang_enum_map: Dict[str, Dict[str, str]]) -> type[BaseModel]: """动态生成包含三个语言字段的Language模型""" # 为三个语言字段分别生成对应枚举 audio_enum = _build_dynamic_enum(lang_enum_map.get("language_audio", {})) booklet_enum = _build_dynamic_enum(lang_enum_map.get("language_booklet", {})) live_enum = _build_dynamic_enum(lang_enum_map.get("language_live", {})) class Language(BaseModel): language_audio: Optional[audio_enum] = None language_booklet: Optional[booklet_enum] = None language_live: Optional[live_enum] = None @root_validator(pre=True) @classmethod def check_single_selection(cls, values): # 仅统计值非None的选中项 selected_fields = [k for k, v in values.items() if v is not None] if len(selected_fields) > 1: raise ValueError("仅可选择一种语言类型选项") if len(selected_fields) == 0: raise ValueError("必须选择一种语言类型选项") return values return Language def build_customer_info_model(lang_enum_map: Dict[str, Dict[str, str]]) -> type[BaseModel]: """对外暴露的模型生成方法,传入API返回的枚举配置即可得到可用的Pydantic模型""" LanguageModel = _build_language_model(lang_enum_map) class GetYourGuideCustomerInfo(BaseModel): """客户信息校验类""" language: Optional[LanguageModel] = None hotel: Optional[str] = None supplier_requested_question: Optional[str] = None return GetYourGuideCustomerInfo # ------------------- 使用示例 ------------------- if __name__ == "__main__": # 模拟从外部API获取的枚举配置 api_enum_config = { "language_audio": {"zh": "中文", "en": "英语", "ja": "日语"}, "language_booklet": {"zh": "中文", "en": "英语"}, "language_live": {"zh": "中文", "en": "英语", "ko": "韩语"} } # 生成绑定了动态枚举的模型类 CustomerInfoModel = build_customer_info_model(api_enum_config) # 正常生成JSON Schema,不会报错 schema = json.loads(CustomerInfoModel.schema_json(indent=2)) # 数据校验 valid_instance = CustomerInfoModel(language={"language_audio": "zh"}, hotel="测试酒店地址")
注意事项
- 每次从外部API拉取到最新的枚举值后,重新调用
build_customer_info_model生成新的模型类即可,不需要修改代码里的固定定义 - 不要使用
EnumMeta作为Pydantic字段的类型标注,该类型不属于可被序列化为JSON Schema的合法值类型 - 根校验器统计选中字段时,必须先过滤掉值为
None的字段,否则会误判选中数量
内容的提问来源于stack exchange,提问作者Oz Cohen
相关产品推荐
相关产品推荐

