Pydantic: Inherited models as fields are reconstructed as base model

Created on 20 Sep 2018  路  4Comments  路  Source: samuelcolvin/pydantic

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.

feature request

Most helpful comment

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.

All 4 comments

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.

Was this page helpful?
0 / 5 - 0 ratings