Reusing Pydantic validators and serializers

Posted on 2025-04-21 in Trucs et astuces

I’ll give some tips to reuse field and model validators and serializers. All my examples will use validators, but it works exactly the same for serializers.

Field validators

Assign the validator to a variable like so:

def _is_above_ten(value: int):
    if value > 10:
        return value

    raise ValueError(f"{value} is not above 10")


field_validator = AfterValidator(_is_above_ten)


class MyModel(BaseModel):
    my_field: Annotated[int, field_validator]

Model validators

With inheritance

This is specially useful if all the models requiring the validator can inherit from the same base model.

class BaseModelWithValidators(BaseModel):
    my_field: int
    my_other_field: int

    @model_validator(mode="after")
    def my_validator(self) -> Self:
        if self.my_field > self.my_other_field:
            raise ValueError("my_field must be lower or equal to my_other_field")

        return self


class MyChildModel(BaseModelWithValidators):
    third_field: int

With factories

You can achieve the same result with factories that will build the validator. It’s useful to avoid a type hierarchy since this gives you smaller pieces easier to combine. It also allows for the validator to have some configurations passed to it when the model is defined.

def validator_factory(min_value: int):
    @model_validator(mode="after")
    def my_validator(self) -> Self:
        if self.my_field > self.my_other_field:
            raise ValueError("my_field must be lower or equal to my_other_field")

        if self.my_field <= min_value:
            raise ValueError(f"my_field must be higher than {min_value}")

        return self

    return my_validator

class ModelWithValidatorFactory(BaseModel):
    my_validator = validator_factory(80)

    my_field: int
    my_other_field: int