Skip to content

Access to orm_mode object in root validator #821

@dmontagu

Description

@dmontagu

Feature Request

It would be nice if you could access the "source" object in a root_validator (coming soon; see #817) when the model was being initialized via a call to from_orm. This would enable almost any form of custom loading from an orm object possible by use of a root validator.

I think this can be accomplished now by making changes only to the GetterDict class:

class GetterDict:
    """
    Hack to make object's smell just enough like dicts for validate_model.
    """

    __slots__ = ('_obj', '_overrides')

    def __init__(self, obj: Any):
        self._obj = obj
        self._overrides: Optional[Dict] = None  # NEWLY ADDED

    # NEWLY ADDED
    def source_object(self):
        return self._obj

    # NEWLY ADDED
    def __setitem__(self, key, value):
        if self._overrides is None:
            self._overrides = {}
        self._overrides.__setitem__(key, value)

    def get(self, item: Any, default: Any) -> Any:
        # SLIGHTLY MODIFIED
        if self._overrides is None:
            # This check could be removed if we make the _overrides non-optional
            # but I think that would come with a *slightly* larger performance penalty if not used
            return getattr(self._obj, item, default)
        return self._overrides.get(item) or getattr(self._obj, item, default)

    def keys(self) -> Set[Any]:
        """
        We don't want to get any other attributes of obj if the model didn't explicitly ask for them
        """
        return set()

The only performance sacrifice if not making use of the feature is the check if self._overrides is None (it seems to me like the impact would be miniscule in practice).

This would allow things like:

from pydantic import BaseModel, Schema, root_validator
from pydantic.utils import GetterDict


class MyModelORM:
    def __init__(self, x):
        self.x = x

    @property
    def orm_property(self) -> str:
        return "hello"


class MyModel(BaseModel):
    x: int
    y: str = Schema(None)

    @root_validator(pre=True)
    def get_y(cls, values):
        if isinstance(values, GetterDict):
            # This is how you know it got called via from_orm
            values['y'] = values.source_object().orm_property
        return values

    class Config:
        orm_mode = True


print(MyModel.from_orm(MyModelORM(x=1)))
# MyModel x=1 y='hello'

@samuelcolvin I can't think of any breaking changes this would introduce, can you? Would this be interesting to you?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions