I have a complex configuration tree hierarchy, with pydantic Models nested several layers deep and I noticed a strange behaviour I cannot really explain.
Let's take this example based on the documentation:
from pydantic import BaseModel, validator
from datetime import datetime
from time import sleep
class DemoModel(BaseModel):
ts: datetime = None
@validator('ts', pre=True, always=True)
def set_ts_now(cls, v):
return v or datetime.now()
def __init__(self, **data):
super().__init__(**data)
print('Creating new DemoModel with ts =', self.ts)
This works as expected:
d1 = DemoModel()
sleep(1)
d2 = DemoModel()
assert d1.ts < d2.ts
Now when I nest this into a recursive model with a default value like this:
class CompositeModel(BaseModel):
demo: DemoModel = DemoModel()
Then DemoModel only gets instantiated once when the CompositeModel class is defined.
Therefore:
c1 = CompositeModel()
sleep(1)
c2 = CompositeModel()
assert c1 == c2
This is a bit unexpected and I haven't seen it mentioned in the documentation, but it makes some sense as we're dealing with class variables which in normal python are instantiated per class and not per object.
However, wouldn't therefore c1.demo and c2.demo be the same object? Thus if I change one I would expect the "other" to change too, but this is not the case:
c1.demo.ts = datetime(2042,1,1)
assert c1.demo.ts == c2.demo.ts # fails!
So clearly they are different instances. But the constructor (__init__) was only called once! What is going on here?
If I define the composite model with a validator that dynamically instantiates the sub-model I get the original behaviour back, where every instance of the parent also gets a fresh instance of the child:
class CompositeModel2(BaseModel):
demo: DemoModel = None
@validator('demo', pre=True, always=True)
def set_demo_dynamically(cls, v):
return v or DemoModel()
Maybe using a Field with a dynamic_factory would be another way to get this behaviour?