Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set Pydantic BaseModel field type dynamically when instantiating an instance?

With Pydantic is it possible to build a model where the type of one of its fields is set via an argument when creating an instance?

For example:

class Animal(BaseModel):
    name: str

class Dog(Animal):
    # more custom fields...

class Cat(Animal):
    # more custom fields...

class Person(BaseModel):
    name: str
    pet: Cat | Dog
 
person_args = {
    "name": "Dan",
    # set which type to use via some kind of argument?
    "_pet_type": Dog,
    "pet": { 
        "name": "Fido",
        # ...
    },
} 
    
person = Person(**person_args)

I'm aware that typically you could use a discriminated union to resolve which type to use. However in this particular case the information for which type I should validate against exists outside of this set of data in another, related model.

Alternately could I use some kind of private field on the types being discriminated that I set but isn't included in validated output? Something like:

person_args = {
    "name": "Dan",
    "pet": { 
        "_pet_type": Dog, # exclude from output
        "name": "Fido",
        # ...
    },
} 
    
person = Person(**person_args)

I'd like to avoid use of a custom validator as I'm using FastAPI and I want the potential types to be reflected properly in the OpenAPI schema.

I'm currently on Pydantic v1.x, but would consider upgrading to v2 if it would help me solve this issue.

like image 873
Matt Sanders Avatar asked Oct 20 '25 11:10

Matt Sanders


1 Answers

Consider doing something like this:

class PetType(str, Enum):
    DOG = "dog"
    CAT = "cat"


class Animal(BaseModel):
    name: str

    @classmethod
    def from_dict(cls, animal: dict) -> "Animal":
        """Build animal based on dict provided."""

        type_ = PetType(animal["type"])
        pet_mapper: dict[PetType, Type[Animal]] = {
            PetType.DOG: Dog,
            PetType.CAT: Cat,
        }

        return pet_mapper[type_].model_validate(obj=animal)


class Dog(Animal):
    pass


class Cat(Animal):
    pass


class Person(BaseModel):
    name: str
    pet: Cat | Dog

    @classmethod
    def from_dict(cls, person: dict) -> "Person":
        """Build person based on dict provided."""

        return cls(
            name=person["name"],
            pet=Animal.from_dict(animal=person["pet"])
        )


person_args = {
    "name": "Dan",
    "pet": {
        "type": "cat",
        "name": "Fido",
    },
}

person = Person.from_dict(person=person_args)
like image 163
Victor Egiazarian Avatar answered Oct 22 '25 01:10

Victor Egiazarian



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!