Guillaume
Guillaume

Reputation: 2899

Get model in pydantic type validator

How can I, in a pydantic type validator, get the actual model? The idea is to read metadata from the Field() parameter.

This is what I end up with. I tried a WrapValidator as well, but it does not help, and the model does not appear in the context.

from typing import Annotated, Any

from pydantic import BaseModel, BeforeValidator, Field, ValidationInfo


def use_default_if_none[T](f: T | None, info: ValidationInfo) -> T | Any:
    if f is None:
        return 42 # QUESTION how to get the model here?
    else:
        return f


type IntDefault = Annotated[int, BeforeValidator(use_default_if_none)]


class Data(BaseModel):
    a: IntDefault = Field(default=42)


print(Data(a=None))

Upvotes: 0

Views: 751

Answers (1)

Axel Donath
Axel Donath

Reputation: 1638

I do not think this is possible using annotated validators. They are explicitly independent of the model class they are used in. They cannot know, in which model they are used, if that makes sense.

To make this work you need validators, that are attached to the model class. One possible solution is to use a field_validator:

from pydantic import BaseModel, field_validator

class Data(BaseModel):
    a: IntDefault = Field(default=42)

    @field_validator("a", mode="before")
    @classmethod
    def validate(cls, value):
        if value is None:
            field = cls.model_fields["a"]
            return field.default

        return value

print(Data(a=None))

Which prints:

a=42

If you really want to achieve something along the lines of what you proposed, you need a far more advanced pattern using parametric types (by implementing __class_item__) to tell IntDefault, which model it is part of. See for example:

from __future__ import annotations
from typing import Annotated
from pydantic import BaseModel, BeforeValidator, Field


class IntDefault:
    def __class_getitem__(cls, item):
        def use_default_if_none(value, info):
            if value is None:
                return  item.model_fields[info.field_name].default

            return value
        
        return Annotated[int, BeforeValidator(use_default_if_none)]


class Data(BaseModel):
    a: IntDefault[Data] = Field(default=42)


print(Data(a=None))

But if possible I would really try to avoid it. I think the pattern based on the parametric type is too advanced and the simpler repetitive pattern I proposed above might be the better choice.

I hope this helps!

Upvotes: 1

Related Questions