I have a model which has a field of a another base-class model. I want this field to be able to contain any sub-class of this model. But when I assign to this field it gets reconstructed to the base model.
from pydantic import BaseModel, validator, PyObject, ValidationError, BaseConfig
class Foo(BaseModel):
a: int
class FooSub(Foo):
b: str
class Container(BaseModel):
foo: Foo
f = Foo(a=1)
c1 = Container(foo=f)
# print(f'c1: {c1}')
fs = FooSub(a=2, b='hello')
c2 = Container(foo=fs)
# print(f'c2: {c2}')
print(f'fs: {fs} (id: {id(fs)})')
print(f'c2.foo: {c2.foo} ({id(c2.foo)})')
this prints
fs: FooSub a=2 b='hello' (id: 4566162824)
c2.foo: Foo a=2 (4566162696)
What I want is c2.foo to be the FooSub instance I created and assigned. How can I achieve this? I have tried using custom validators, but without success.
I understand that current behaviour may be desired in many cases, but I really need to have the subclass members intact.
Note that I can not explicitly list the set of supported subclasses, I do not know in advance which once will be available.
I can set the field to Any, but then all validation is lost.
Could this be solved with some custom decorator perhaps? That would allow a field to be defined with this behaviour. Or a validator with some special flag/configuration.
There might be some construct for this using Generic and/or TypeVar? I am not sure how to specify this correctly. Looking a bit at
Here's a workaround:
from pydantic import BaseModel
from pydantic.validators import dict_validator
class Foo(BaseModel):
a: int
@classmethod
def get_validators(cls):
# yield dict_validator
yield cls.validate
@classmethod
def validate(cls, value):
if isinstance(value, cls):
return value
else:
return cls(**dict_validator(value))
class FooSub(Foo):
b: str
class Container(BaseModel):
foo: Foo
fs = FooSub(a=2, b='hello')
c2 = Container(foo=fs)
# print(f'c2: {c2}')
print(f'fs: {fs} (id: {id(fs)})')
print(f'c2.foo: {c2.foo} ({id(c2.foo)})')
You can even take get_validators and validate out and make your own "MyBaseModel" to reuse.
It would be possible to change pydantics behavior completely to work like this but I'm not yet sure if that's a good idea.
I'll leave this issue open and continue to think about it.
Thank you! This seem to work for me.
But yes, it would be nice to have this feature packaged as some utility decorator/type declaration, and as part of the main field-API with documented usage.
Most helpful comment
Here's a workaround:
You can even take
get_validatorsandvalidateout and make your own "MyBaseModel" to reuse.It would be possible to change pydantics behavior completely to work like this but I'm not yet sure if that's a good idea.
I'll leave this issue open and continue to think about it.