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

如何创建保留字段别名的全可选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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 00:55:01