Home > Net >  Inherit only a Subset of Fields of Pydantic Model
Inherit only a Subset of Fields of Pydantic Model

Time:10-19

I would like to generate a Pydantic model that inherits from a parent class, but only has a subset of that parent model's fields.

E.g. ModelB should inherit only field_b from ModelA:

from pydantic import BaseModel

class ModelA(BaseModel):
    field_a: str
    field_b: str

class ModelB(ModelA):
    pass

CodePudding user response:

As far as I know, there is no built-in mechanism for this in Pydantic.

Difficult solutions

You could start messing around with the internals like __fields__ and __fields_set__, but I would strongly advise against it. I think this may be less than trivial because you need to take into account validators that are already registered and maybe a bunch of other stuff that happens internally, one a field is defined on a model.

You could also go the route of e.g. defining your own __init_subclass__ on ModelA or even subclassing ModelMetaclass, but this will likely lead to the same difficulties. Unless you are very familiar with the intricacies of Pydantic models and are prepared to rework your code, if something fundamentally changes on their end, I would not recommend this.

I can think of a few workarounds though.


Potential workarounds

The simplest one in my opinion is simply factoring out the fields that you want to share into their own model:

from pydantic import BaseModel


class ModelWithB(BaseModel):
    field_b: str


class ModelA(ModelWithB):
    field_a: str


class ModelB(ModelWithB):
    pass

This obviously doesn't work, if you have no control over ModelA. It also may mess up the order of the fields on ModelA (in this case field_b would come before field_a), which may or may not be important to you. Validation for example depends on the order in which fields were defined.

Another possible workaround would be to override the unneeded field in ModelB and make it optional with a None default and exclude it from dict and json exports:

from pydantic import BaseModel, Field


class ModelA(BaseModel):
    field_a: str
    field_b: str


class ModelB(ModelA):
    field_a: str | None = Field(default=None, exclude=True)


b = ModelB(field_b="foo")
print(b.json())

Output:

{"field_b": "foo"}

Note that this does not actually get rid of the field. It is still there and by default still visible in the model's string representation for example, as well as in the model schema. But at least you never need to pass a value for field_a and it is not present, when calling dict or json by default.

Note also that you may run into addtional problems, if you have custom validators for field_a that don't work with a None value.


If you provide more details, I might amend this answer, but so far I hope this helps a little.

CodePudding user response:

It was enough for me to hard copy the field and to adjust the extras I had defined. Here is a snippet from my code:

import copy

from pydantic import BaseModel


def copy_primary_field(
        model_from: BaseModel,
        model_to: BaseModel,
        primary_key: str,
) -> BaseModel:
    new_field_name = f"{model_from.__name__}"   "_"   primary_key
    model_to.__fields__[new_field_name] = copy.deepcopy(
        model_from.__fields__[primary_key]
    )
    model_to.__fields__[new_field_name].name = new_field_name
    model_to.__fields__[new_field_name].field_info.extra["references"] = (
            f"{model_from.__name__}"   ":"   primary_key
    )

    return model_to
  • Related