如何用Pydantic V2的model_validate从ORM非模型属性生成计算字段?
问题解决:FastAPI中基于ORM未定义属性生成Pydantic计算字段
问题背景
使用FastAPI时,接口返回SQLAlchemy ORM实例,通过model_validate转换为定义了response_model的Pydantic模型。需要基于ORM实例中未在Pydantic模型里声明的属性生成计算字段,但直接结合__pydantic_extra__与extra='allow'的方案在处理ORM实例时失效,当前动态修改ORM实例的实现方式不够优雅。
根本原因
当用model_validate处理ORM实例时,from_attributes=True仅让Pydantic提取模型显式定义的字段,extra='allow'默认仅对字典格式的输入生效,不会自动捕获ORM实例的所有额外属性,导致__pydantic_extra__为空。
简洁解决方案
方案一:前置验证器转ORM为字典,激活extra机制
通过前置验证器将ORM实例转换为包含所有属性的字典,让extra='allow'正常捕获额外属性:
from typing import Any from pydantic import BaseModel, ConfigDict, computed_field, model_validator class Foo: def __init__(self): self.original_thing = "foo" class CleanSolution1(BaseModel): model_config = ConfigDict(from_attributes=True, extra='allow') @model_validator(mode="before") @classmethod def convert_orm_to_dict(cls, values: Any) -> Any: # 实际场景可替换为判断SQLAlchemy基类,比如 isinstance(values, Base) if isinstance(values, Foo): return vars(values) return values @computed_field @property def computed_thing(self) -> str: return self.__pydantic_extra__["original_thing"] + "_computed" # 测试用例 model = CleanSolution1(original_thing="bar") print(f'CleanSolution1 (original_thing="bar") worked: {model.computed_thing == "bar_computed"}') model_orm = CleanSolution1.model_validate(Foo()) print(f'CleanSolution1 model_validate(Foo()) worked: {model_orm.computed_thing == "foo_computed"}')
方案二:私有属性存储原始ORM实例,直接访问计算
通过Pydantic私有属性保留原始ORM实例,绕开extra限制直接访问所需属性:
from typing import Any from pydantic import BaseModel, ConfigDict, computed_field, model_validator, PrivateAttr class Foo: def __init__(self): self.original_thing = "foo" class CleanSolution2(BaseModel): model_config = ConfigDict(from_attributes=True) # 私有属性存储原始ORM实例 _orm_instance: Any = PrivateAttr() @model_validator(mode="after") def capture_orm_instance(self) -> "CleanSolution2": # 从Pydantic验证器上下文获取原始ORM实例 self._orm_instance = self.__pydantic_validator__.values.obj return self @computed_field @property def computed_thing(self) -> str: return self._orm_instance.original_thing + "_computed" # 测试用例 model_orm = CleanSolution2.model_validate(Foo()) print(f'CleanSolution2 model_validate(Foo()) worked: {model_orm.computed_thing == "foo_computed"}')
内容的提问来源于stack exchange,提问作者jsnow
相关产品推荐
相关产品推荐

