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.
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 ispydantic.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.