Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pydantic - apply validator on all fields of specific type

In my project, all pydantic models inherit from a custom "base model" called GeneralModel.

This enables to configure the same behavior for the entire project in one place.

Let's assume the following implementation:

from pydantic import BaseModel


class GeneralModel(BaseModel):
    class Config:
        use_enum_values = True
        exclude_none = True

One particularly desired behavior is to perform a validation on all fields of a specific type.

Any ideas how to achieve this using my GeneralModel? Different approaches are blessed as well.

like image 643
localhost Avatar asked Dec 16 '25 12:12

localhost


2 Answers

Quote from the Pydantic validators documentation:

a single validator can also be called on all fields by passing the special value '*'

and:

you can also add any subset of the following arguments to the signature (the names must match):

[...]

  • field: the field being validated. Type of object is pydantic.fields.ModelField.

So you can write a catch-all validator and pass it the ModleField instance as an argument. Then check the type of the field and apply whatever validation you want.

Very simple example:

from typing import Any

from pydantic import BaseModel, validator
from pydantic.fields import ModelField


class GeneralModel(BaseModel):
    @validator("*")
    def ensure_non_negative(cls, v: Any, field: ModelField) -> Any:
        if field.type_ is float and v < 0:
            raise ValueError(f"`{field.name}` value must not be negative")
        return v

    class Config:
        ...

Usage:

from pydantic import ValidationError

# ... import GeneralModel


class ConcreteModel(GeneralModel):
    x: float
    y: str = "foo"


print(ConcreteModel(x=3.14))
try:
    ConcreteModel(x=-1)
except ValidationError as err:
    print(err.json(indent=4))

Output:

x=3.14 y='foo'
[
    {
        "loc": [
            "x"
        ],
        "msg": "`x` value must not be negative",
        "type": "value_error"
    }
]

Note: When defining a field as for example list[str], Pydantic internally considers the field type to be str, but its shape to be pydantic.fields.SHAPE_LIST. Luckily, shape is also a public attribute of ModelField. See this answer for an example, where this is important.

like image 151
Daniil Fajnberg Avatar answered Dec 19 '25 01:12

Daniil Fajnberg


One of the options is to use Annotated Validators

Then you can perform a validation on the specific type as you request

on all fields of a specific type

So in your case it would be something like this

def my_validator(v: Any) -> Any:
    assert v > 0, f'{v} should not be negative'
    return v


MyFloat = Annotated[float, AfterValidator(my_validator)]


class ConcreteModel(GeneralModel):
    x: MyFloat
    y: str = "foo"
like image 32
GopherM Avatar answered Dec 19 '25 00:12

GopherM



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!