5

According to the docs, Pydantic "ORM mode" (enabled with orm_mode = True in Config) is needed to enable the from_orm method in order to create a model instance by reading attributes from another class instance. If ORM mode is not enabled, the from_orm method raises an exception.

My doubts are:

  1. Are there any other effects (in functionality, performance, etc.) in enabling ORM mode?
  2. If not, why is it an opt-in feature?
rrobby86
  • 1,356
  • 9
  • 14
  • 1
    This seems to be a question that is more appropriately addressed by the Pydantic development community. Answers provided here are most likely conjecture and/or opinion. – itprorh66 Jan 23 '23 at 16:20
  • 1
    @itprorh66 While I agree with your assessment regarding the _second_ question, I still think the _first_ question can be answered and provide value to others. The issue here is (as it often is) the fact that the OP asked more than one question at a time. Closing this would still be unwarranted in my opinion. Maybe the OP could simply rephrase the second part as an observation to put even more focus on the first part. – Daniil Fajnberg Jan 23 '23 at 22:06

1 Answers1

4

Fortunately, at least the first question can be answered fairly easily.

As of version 1.10.4, there are only two places (aside from the plugins), where orm_mode comes into play.


BaseModel.from_orm

This is basically an alternative constructor. It forgoes the regular __init__ method in favor of a marginally different setup. Not sure, why that is designed this way. But the orm_mode flag must be set for this method to not raise an error. Straightforward. I see no hidden surprises here.


BaseModel.validate

This method is the default validator for the BaseModel type. Without the orm_mode flag, the validator expects a value that is either 1) an instance of that particular model, 2) a dictionary that can be unpacked into the constructor of that model, or 3) something that can be coerced to a dictionary, then to be unpacked into the constructor of that model.

If orm_mode is True and the validator encounters something that is not an instance of the model and not a dictionary, it assumes it is an object that can be passed to the aforementioned from_orm method and calls that instead of trying the dict coercion.

Note that this method is not called during initialization and it is not called if something is assigned to a model field of any type that isn't BaseModel. It only comes into play, when you are dealing with nested models (and the objects that serve as the data input are also nested), i.e. with a model that has a field annotated with another model. Only then will the outer model call the validate method of the inner model.

Consider the following:

from __future__ import annotations
from typing import TypeVar

from pydantic import BaseModel


M = TypeVar("M", bound=BaseModel)


class Foo(BaseModel):
    x: int

    @classmethod
    def validate(cls: type[M], value: object) -> M:
        print("called `Foo.validate`")
        return super().validate(value)

    class Config:
        orm_mode = True


class A:
    x = 1


foo = Foo.from_orm(A)
print(foo.json())

The output is {"x": 1} and we see that Foo.validate was not called.

Now we extend this a bit:

...


class Bar(BaseModel):
    f: Foo

    class Config:
        orm_mode = True


class B:
    f = A


bar = Bar.from_orm(B)
print(bar.json())

The new output:

called `Foo.validate`
{"f": {"x": 1}}

Now the validator was called as expected and if we were to inject a similar print statement into Foo.from_orm we would see that it too was called, when we called Bar.from_orm right after Foo.validate was called.

This may be relevant in certain niche situations, but generally speaking I would argue that this cascading application of from_orm during validation makes sense and should accommodate the main intended use case -- database ORM objects.

If you want different behavior during validation, you can always define your own validator methods or even simply override the validate method (depending on your use case).


There are no other uses of orm_mode in the source code, so that is it in terms of functionality.

Performance is not really relevant in those contexts IMO because it is just an altogether different way of initializing an instance of the model. Unless you are interested in whether or not it is faster to first manually turn your ORM object into a dictionary and pass that to parse_obj or to just call from_orm on it. You could benchmark that fairly easily though.

No other functionality of the BaseModel is affected (performance wise) by that config setting in any way that I can see.


To your second question, I could only speculate. So I will refrain from answering. There is an issue already open for a while that suggests removing the setting altogether, which seems to be kind of in line with your reasoning that it should not be "opt-in" in any case. I am not sure if Samuel Colvin is still accepting backwards-incompatible feature requests for v2, but this issue has not gotten a lot of attention. You might want to participate there.

Daniil Fajnberg
  • 12,753
  • 2
  • 10
  • 41