With Pydantic V2 and model_validate, how can I create a "computed field" from an attribute of an ORM model that IS NOT part of the Pydantic model

jsnow

This context here is that I am using FastAPI and have a response_model defined for each of the paths. The endpoint code returns a SQLAlchemy ORM instance which is then passed, I believe, to model_validate. The response_model is a Pydantic model that filters out many of the ORM model attributes (internal ids and etc...) and performs some transformations and adds some computed_fields. This all works just fine so long as all the attributes you need are part of the Pydantic model. Seems like __pydantic_context__ along with model_config = ConfigDict(from_attributes=True, extra='allow') would be a great way to hold on to some of the extra attributes from the ORM model and use them to compute new fields, however, it seems that when model_validate is used to create the instance that __pydantic_context__ remains empty. Is there some trick to getting this behavior in a clean way?

I have a way to make this work, but it involves dynamically adding new attributes to my ORM model, which leaves me with a bad feeling and a big FIXME in my code.

Here is some code to illustrate the problem. Note that the second test case fails.

from typing import Any
from pydantic import BaseModel, ConfigDict, computed_field, model_validator


class Foo:

    def __init__(self):
        self.original_thing = "foo"


class WishThisWorked(BaseModel):
    """
    __pydantic_extra__ does not pick up the additional attributes when model_validate is used to instantiate
    """
    model_config = ConfigDict(from_attributes=True, extra='allow')

    @computed_field
    @property
    def computed_thing(self) -> str:
        try:
            return self.__pydantic_extra__["original_thing"] + "_computed"
        except Exception as e:
            print(e)

        return None


model = WishThisWorked(original_thing="bar")
print(f'WishThisWorked (original_thing="bar") worked: {model.computed_thing == "bar_computed"}')

# this is the case that I actually want to work
model_orm = WishThisWorked.model_validate(Foo())
print(f'WishThisWorked model_validate(Foo()) worked: {model.computed_thing == "foo_computed"}')


class WorksButKludgy(BaseModel):
    """
    I don't like having to modify the instance passed to model_validate
    """
    model_config = ConfigDict(from_attributes=True)

    computed_thing: str

    @model_validator(mode="before")
    @classmethod
    def _set_fields(cls, values: Any) -> Any:
        if type(values) is Foo:
            # This is REALLY gross
            values.computed_thing = values.original_thing + "_computed"
        elif type(values) is dict:
            values["computed_thing"] = values["original_thing"] + "_computed"
        return values


print(f'WorksButKludgy (original_thing="bar") worked: {model.computed_thing == "bar_computed"}')
model = WorksButKludgy(original_thing="bar")

model_orm = WorksButKludgy.model_validate(Foo())
print(f'WorksButKludgy model_validate(Foo()) worked: {model_orm.computed_thing == "foo_computed"}')```
PGHE

What you could consider is having all the ORM attributes in your schema, but labelling them as excluded. Then you have access to all your ORM attributes when you want to use them in a computed field:

from pydantic import BaseModel, Field, property, computed_field
from sqlalchemy.orm import declaritive_base
from sqlalchemy import Column, Integer, String

SqlBase = declaritive_base()

class SqlModel(SqlBase):
    ID = Column(Integer)
    Name = Column(String)


class SqlSchema(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    ID: int = Field(exclude=True)
    Name: str = Field(...)

    @computed_field
    @property
    def id_name(self) -> str:
        return f'{self.ID}_{self.Name}'

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

How to make work a computed field with "store=True" when I am using fields from other model?

How to parse ObjectId in a pydantic model?

fastapi form data with pydantic model

Laravel computed field on the relation's of a model like a model attribute

pydantic generate model from dict

How do add an assembled field to a Pydantic model

Create dynamic Pydantic model with typed optional values

Python Pydantic double base model

Pydantic model does not validate on assignment with reproducable code

How to parse and read "_id" field from and to a pydantic model?

Pydantic model for array of jsons

creating a weakref to pydantic model

How do i use nested model with none value in pydantic

Generate a pydantic model from pydantic object

how can i make a key dynamic in a pydantic model

How can I transform my input data to fit my desired Pydantic model

How to use a reserved keyword in pydantic model

Pydantic how to create a model with required fields and dynamic fields?

pydantic nested model response

autodoc_pydantic: Show fields from parent model

How to model an empty dictionary in pydantic

Pydantic model dynamic field type

Pydantic create_model_from_typeddict does not play nice with mypy?

Interdependent Validation for Pydantic Model Fields

Pydantic add descriptions to dynamic model

Recursively iterate trough Pydantic Model

How does `mypy` know the signature of the pydantic model?

Recursive Pydantic model to gRPC protobuf

How to define a Pydantic model nested under a class

how to model nested object with pydantic