如何创建保留字段别名的全可选Pydantic模型?
保留字段别名的全可选Pydantic模型创建方案
问题背景
通过自定义get_annotations函数获取基类注解,再用create_model生成全字段可选的OptionalClientModel时,发现id字段丢失了原有的alias="_id"和serialization_alias="id"配置,导致别名功能失效。
原实现代码:
def get_annotations(main_cls): ret_val = main_cls.__annotations__ for base_cls in main_cls.__bases__: if base_cls != BaseModel: ret_val.update(get_annotations(base_cls)) return ret_val OptionalClientModel = create_model( "OptionalClientModel", **{k: (Optional[v], None) for k, v in get_annotations(ClientModel).items()})
原始模型定义(包含带别名的id字段):
from typing import Annotated from bson import ObjectId from pydantic import Field from pydantic import BaseModel from pydantic import BeforeValidator from pydantic import ConfigDict PyObjectId = Annotated[str, BeforeValidator(str)] class DBTableBase(BaseModel): id: PyObjectId | None = Field(alias="_id", serialization_alias="id", default=None) model_config = ConfigDict( json_encoders={ObjectId: str}, json_schema_extra={ "example": { "id": "BSON_ID" } }, ) class ClientModel(DBTableBase): first_name: str last_name: str
问题原因
__annotations__仅存储字段的类型信息,不包含Field中定义的别名、验证器等元数据。直接基于注解生成新模型时,这些元数据会被丢失。
解决方案
方案1:遍历模型的__fields__获取完整字段配置
Pydantic模型的__fields__属性存储了每个字段的完整元数据(包括别名、默认值、验证规则等),我们可以直接遍历这个属性来构建新模型的字段参数:
from typing import Optional, TypeVar from pydantic import create_model T = TypeVar('T', bound=BaseModel) def create_optional_model(model_cls: type[T]) -> type[T]: # 遍历原模型的所有字段(包括基类字段) fields = {} for field_name, field in model_cls.__fields__.items(): # 将字段类型改为Optional,保留原字段的所有元数据,默认值设为None fields[field_name] = ( Optional[field.annotation], field.field_info or None # 保留原Field的配置(别名、描述等) ) # 创建新模型,同时继承原模型的model_config return create_model( f"Optional{model_cls.__name__}", __config__=model_cls.model_config, **fields ) # 使用方式 OptionalClientModel = create_optional_model(ClientModel)
方案2:使用Pydantic V2的model_copy(更简洁)
如果使用Pydantic V2,可以直接利用model_copy方法结合字段属性修改实现需求:
from pydantic import BaseModel def create_optional_model(model_cls: type[BaseModel]) -> type[BaseModel]: # 复制原模型的字段定义,将所有字段设为非必填,默认值None optional_fields = {} for field_name, field in model_cls.model_fields.items(): optional_fields[field_name] = field.model_copy( update={ 'required': False, 'default': None } ) # 创建新模型 return model_cls.model_copy( name=f"Optional{model_cls.__name__}", fields=optional_fields ) # 使用方式 OptionalClientModel = create_optional_model(ClientModel)
验证效果
生成的OptionalClientModel会完整保留id字段的alias="_id"和serialization_alias="id"配置,同时所有字段变为可选且默认值为None。
测试序列化示例:
client = OptionalClientModel(id="650000000000000000000000") print(client.model_dump(by_alias=True)) # 输出: {'_id': '650000000000000000000000', 'first_name': None, 'last_name': None} print(client.model_dump()) # 输出: {'id': '650000000000000000000000', 'first_name': None, 'last_name': None}
内容的提问来源于stack exchange,提问作者NPatel
相关产品推荐
相关产品推荐

