Pydantic já converte e valida tipos declarados; validadores personalizados devem tratar regras que o schema não expressa sozinho. field_validator atua em campos, enquanto model_validator verifica o objeto como conjunto.

from datetime import date
from typing import Self
from pydantic import BaseModel, field_validator, model_validator

class Periodo(BaseModel):
    inicio: date
    fim: date

    @field_validator("inicio", "fim", mode="before")
    @classmethod
    def limpar_data(cls, valor: object) -> object:
        return valor.strip() if isinstance(valor, str) else valor

    @model_validator(mode="after")
    def validar_ordem(self) -> Self:
        if self.fim < self.inicio:
            raise ValueError("fim deve ser posterior ao início")
        return self

O modo before recebe a entrada bruta e deve lidar com qualquer objeto. O validador de modelo no modo after recebe a instância já validada e retorna self. Uma ordem de datas é regra entre campos, portanto pertence ao modelo.

Boas práticas

Mantenha validadores determinísticos e sem chamadas de rede ou banco. Levante ValueError com mensagem útil, sem expor segredos. Evite mutar a entrada antes de lançar erro em unions, pois outro ramo pode receber o objeto alterado. Cubra entradas válidas, limites e falhas em testes.

Comece pelo guia de Pydantic e separe configuração com Pydantic Settings.

A documentação oficial de validadores do Pydantic, consultada em 22 de julho de 2026, detalha a API e seus limites.