✅ Artigo atualizado em Setembro 2026 O artigo dizia ser de Pydantic V2, mas vários exemplos usavam a API do V1 (
@validator,@root_validator,regex=,class Config,example=), que no V2 gera avisos ou erro. Todos foram migrados para a API atual e executados no Pydantic 2.13, com as saídas reais.
Salve salve Pythonista ![]()
A validação de dados é essencial no desenvolvimento de aplicações robustas em Python.
Com o Pydantic, é possível personalizar a validação de dados utilizando tipos avançados e mecanismos poderosos (se está começando agora, leia antes a introdução ao Pydantic).
Neste artigo, abordaremos como trabalhar com tipos complexos como Decimal, UUID e DateTime, utilizar campos opcionais e valores padrão, implementar validações personalizadas com field_validator e model_validator, e introduzir o uso de Field para configurar validações e metadados.
Dominar esses tópicos é crucial para garantir a integridade e a consistência dos dados em suas aplicações Python.
Vá Direto ao Assunto…
- Trabalhando com Tipos Complexos: Decimal, UUID e DateTime
- Utilizando Campos Opcionais (Optional) e Valores Padrão
- Validação Personalizada com Validator
- Introdução ao Field para Configurar Validações e Metadados
- Casos Práticos com Tipos Avançados
- Tipos Avançados: Comparação
- Validators: Níveis de Validação
- Quando Usar Validators Customizados
- Quando NÃO Usar
Trabalhando com Tipos Complexos: Decimal, UUID e DateTime
O Pydantic oferece suporte a diversos tipos complexos que facilitam a manipulação de dados específicos.
Vamos explorar como utilizar Decimal, UUID e DateTime em seus modelos.
Utilizando Decimal
O tipo Decimal é útil para representar números com precisão decimal, evitando problemas de arredondamento comuns com floats.
1
2
3
4
5
6
from decimal import Decimal
from pydantic import BaseModel
class Produto(BaseModel):
nome: str
preco: Decimal
Explicação do código:
-
Importação: Importamos
Decimaldo módulodecimaleBaseModeldo Pydantic. -
Definição da Classe: Criamos a classe
Produtoherdando deBaseModel. -
Atributos: Definimos
nomecomo string eprecocomoDecimal.
Falando em Pydantic: O Ebookr.ai usa Pydantic extensivamente para validar e estruturar conteúdo gerado por IA. É uma plataforma que criei para gerar ebooks profissionais sobre qualquer tema - com infográficos automáticos, capa gerada por IA e exportação em PDF. Conhece lá!
Utilizando UUID
Para identificar objetos de forma única, o UUID (Universally Unique Identifier) é uma excelente escolha.
1
2
3
4
5
6
import uuid
from pydantic import BaseModel
class Usuario(BaseModel):
id: uuid.UUID
nome: str
Explicação do código:
-
Importação: Importamos
uuideBaseModel. -
Definição da Classe: Criamos a classe
Usuariocomiddo tipoUUIDenomecomo string.
Antes de continuar… Está curtindo esse conteúdo? ![]()
Que tal receber 30 dias de conteúdo direto na sua Caixa de Entrada?
Utilizando DateTime
O tipo DateTime permite trabalhar com data e hora de forma eficiente (veja mais sobre o módulo no post de manipulação de datas e horários no Python).
1
2
3
4
5
6
from datetime import datetime
from pydantic import BaseModel
class Evento(BaseModel):
nome: str
data_hora: datetime
Explicação do código:
-
Importação: Importamos
datetimedo módulodatetimeeBaseModel. -
Definição da Classe: Criamos a classe
Eventocomnomeedata_horacomoDateTime.
Utilizando Campos Opcionais (Optional) e Valores Padrão
Nem todos os campos dos modelos precisam ser obrigatórios. O Pydantic facilita a definição de campos opcionais e a atribuição de valores padrão.
Campos Opcionais
Usamos Optional para indicar que um campo pode ser ignorado ou ter valor None.
1
2
3
4
5
6
from typing import Optional
from pydantic import BaseModel
class Cliente(BaseModel):
nome: str
email: Optional[str] = None
Explicação do código:
-
Importação: Importamos
OptionaldetypingeBaseModel. -
Definição da Classe: Criamos
Clientecomnomeobrigatório eemailopcional, com valor padrãoNone.
Valores Padrão
Atribuímos valores padrão diretamente nos campos para garantir que tenham um valor caso não sejam fornecidos.
1
2
3
4
5
from pydantic import BaseModel
class Configuracao(BaseModel):
ativo: bool = True
nivel_acesso: int = 1
Explicação do código:
-
Definição da Classe: Criamos
ConfiguracaocomativocomoTrueenivel_acessocomo1por padrão.
Validação Personalizada com Validator
Embora o Pydantic valide automaticamente muitos tipos, às vezes é necessário implementar validações específicas. No Pydantic V2, o decorador @field_validator (usado junto com @classmethod) facilita essa customização. O antigo @validator do V1 ainda existe só por compatibilidade: está depreciado e será removido no V3.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pydantic import BaseModel, field_validator
from decimal import Decimal
class Pedido(BaseModel):
quantidade: int
preco_unitario: Decimal
@field_validator('quantidade')
@classmethod
def quantidade_positiva(cls, v: int) -> int:
if v <= 0:
raise ValueError('A quantidade deve ser positiva')
return v
@field_validator('preco_unitario')
@classmethod
def preco_valido(cls, v: Decimal) -> Decimal:
if v <= Decimal('0.00'):
raise ValueError('O preço unitário deve ser maior que zero')
return v
Explicação do código:
-
Definição da Classe: Criamos
Pedidocomquantidadeepreco_unitario. -
Validador de Quantidade:
- O método
quantidade_positivaverifica sequantidadeé maior que zero. - Se não, lança um
ValueError.
- O método
-
Validador de Preço Unitário:
- O método
preco_validoassegura quepreco_unitarioseja maior que zero. - Se não, lança um
ValueError.
- O método
Testando com uma quantidade inválida:
1
2
3
4
5
6
7
8
from pydantic import ValidationError
print(Pedido(quantidade=3, preco_unitario="19.90"))
try:
Pedido(quantidade=0, preco_unitario="19.90")
except ValidationError as e:
print(e)
E a saída será:
1
2
3
4
5
quantidade=3 preco_unitario=Decimal('19.90')
1 validation error for Pedido
quantidade
Value error, A quantidade deve ser positiva [type=value_error, input_value=0, input_type=int]
For further information visit https://errors.pydantic.dev/2.13/v/value_error
Repare que a string "19.90" foi convertida para Decimal antes de chegar ao validador, e que o ValueError levantado vira um ValidationError com o tipo value_error. O número no link (2.13) é a versão do Pydantic instalada.
Esses validadores garantem que os dados atendam às regras de negócio definidas.
Introdução ao Field para Configurar Validações e Metadados
O Field do Pydantic permite configurar validações adicionais e adicionar metadados aos campos dos modelos, como descrições, títulos e exemplos.
1
2
3
4
5
6
7
8
from decimal import Decimal
from uuid import UUID
from pydantic import BaseModel, Field
class Produto(BaseModel):
id: UUID = Field(..., title="ID do Produto", description="Identificador único do produto")
nome: str = Field(..., min_length=3, max_length=50, examples=["Caneca"])
preco: Decimal = Field(..., gt=0, description="Preço do produto em reais")
Explicação do código:
-
Importação: Importamos
Fielddepydantic, além de outros tipos necessários. -
Definição da Classe: Criamos
Produtocomid,nomeepreco. -
Uso de Field:
-
id: Define título e descrição para documentação. -
nome: Define comprimento mínimo e máximo, além de exemplos (examples). No V2,Fieldnão aceita mais argumentos soltos comoexample=: useexamples=[...]ou, para qualquer outro dado extra no schema,json_schema_extra={...}. -
preco: Define que o valor deve ser maior que zero e adiciona uma descrição.
-
O Field é especialmente útil para gerar documentação automática e garantir que os campos atendam a critérios específicos além da validação básica de tipos.
Casos Práticos com Tipos Avançados
1. Sistema de Pagamentos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pydantic import BaseModel, ConfigDict, Field, field_validator
from decimal import Decimal
from datetime import datetime
from typing import Optional
from uuid import UUID, uuid4
from enum import Enum
class StatusPagamento(str, Enum):
PENDENTE = "pendente"
PROCESSANDO = "processando"
APROVADO = "aprovado"
REJEITADO = "rejeitado"
class Pagamento(BaseModel):
model_config = ConfigDict(use_enum_values=True)
id: UUID = Field(default_factory=uuid4)
valor: Decimal = Field(..., gt=0, decimal_places=2)
moeda: str = Field(default="BRL", pattern=r"^[A-Z]{3}$")
status: StatusPagamento = StatusPagamento.PENDENTE
created_at: datetime = Field(default_factory=datetime.now)
processado_em: Optional[datetime] = None
@field_validator('valor')
@classmethod
def validar_valor_maximo(cls, v: Decimal) -> Decimal:
if v > Decimal('999999.99'):
raise ValueError('Valor excede limite de R$ 999.999,99')
return v
# Uso
pagamento = Pagamento(valor=Decimal('150.50'))
print(pagamento.model_dump_json(indent=2))
E a saída será algo como (o id e o created_at mudam a cada execução):
1
2
3
4
5
6
7
8
{
"id": "492d022a-a6b6-48f0-ba12-a1b81095a4be",
"valor": "150.50",
"moeda": "BRL",
"status": "pendente",
"created_at": "2026-09-27T10:54:40.693557",
"processado_em": null
}
O que mudou em relação à API do V1: regex= virou pattern= (o regex= agora gera erro ao definir a classe), @validator virou @field_validator e a class Config interna virou o atributo model_config = ConfigDict(...). Como use_enum_values=True, o status é guardado como a string "pendente", e o Decimal é serializado como string no JSON para não perder precisão.
Uma moeda fora do padrão de três letras maiúsculas é recusada pelo pattern:
1
2
3
4
5
6
from pydantic import ValidationError
try:
Pagamento(valor=Decimal('10.00'), moeda="real")
except ValidationError as e:
print(e)
E a saída será:
1
2
3
4
1 validation error for Pagamento
moeda
String should match pattern '^[A-Z]{3}$' [type=string_pattern_mismatch, input_value='real', input_type=str]
For further information visit https://errors.pydantic.dev/2.13/v/string_pattern_mismatch
2. Agendamento de Eventos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator
from datetime import datetime, timedelta
class Evento(BaseModel):
titulo: str = Field(..., min_length=5, max_length=100)
inicio: datetime
fim: datetime
timezone: str = Field(default="America/Sao_Paulo")
participantes_min: int = Field(default=1, ge=1)
participantes_max: int = Field(..., ge=1, le=1000)
@field_validator('fim')
@classmethod
def fim_depois_inicio(cls, v: datetime, info: ValidationInfo) -> datetime:
inicio = info.data.get('inicio')
if inicio and v <= inicio:
raise ValueError('Fim deve ser após início')
return v
@field_validator('participantes_max')
@classmethod
def max_maior_que_min(cls, v: int, info: ValidationInfo) -> int:
minimo = info.data.get('participantes_min')
if minimo is not None and v < minimo:
raise ValueError('Máximo deve ser >= mínimo')
return v
@model_validator(mode='after')
def validar_duracao(self) -> 'Evento':
if self.fim - self.inicio > timedelta(hours=24):
raise ValueError('Evento não pode durar mais de 24h')
return self
No V2, o validador de campo que depende de outro campo recebe um ValidationInfo e lê os campos já validados em info.data (no V1 era o argumento values). Os campos são validados na ordem em que foram declarados, por isso fim consegue ver inicio. Já o antigo @root_validator virou @model_validator(mode='after'), que recebe a instância pronta (self) e deve devolvê-la. Sem migrar, o @root_validator do exemplo original nem chega a funcionar no V2: a classe falha ao ser definida, exigindo skip_on_failure=True.
Testando com um evento de dois dias:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pydantic import ValidationError
dados = {
"titulo": "Python Summit",
"inicio": "2026-10-10T09:00:00",
"fim": "2026-10-12T18:00:00", # dois dias depois
"participantes_min": 10,
}
for maximo in (5, 50):
try:
Evento(**dados, participantes_max=maximo)
except ValidationError as e:
print(e)
E a saída será:
1
2
3
4
5
6
7
1 validation error for Evento
participantes_max
Value error, Máximo deve ser >= mínimo [type=value_error, input_value=5, input_type=int]
For further information visit https://errors.pydantic.dev/2.13/v/value_error
1 validation error for Evento
Value error, Evento não pode durar mais de 24h [type=value_error, input_value={'titulo': 'Python Summit...'participantes_max': 50}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.13/v/value_error
Na primeira tentativa, só aparece o erro de participantes_max: o model_validator(mode='after') roda apenas quando todos os campos passaram na validação. Com o máximo corrigido, a regra de duração entra em ação, e o erro aparece sem nome de campo, porque pertence ao modelo inteiro.
Documentar uma API bem é resultado de fundamentos sólidos em Python; é essa base, do zero ao Django e às APIs REST, que a Jornada Python constrói com você:
3. Documentação de API com Field
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import json
from pydantic import BaseModel, ConfigDict, Field, EmailStr, HttpUrl
from typing import List, Optional
class UsuarioAPI(BaseModel):
"""Schema para criação de usuário via API"""
model_config = ConfigDict(
json_schema_extra={
"examples": [
{
"nome": "João Silva",
"email": "[email protected]",
"idade": 25,
"site": "https://joao.com",
"tags": ["python", "dev"]
}
]
}
)
nome: str = Field(
...,
min_length=2,
max_length=100,
title="Nome Completo",
description="Nome completo do usuário",
examples=["João Silva"]
)
email: EmailStr = Field(
...,
title="E-mail",
description="E-mail válido do usuário",
examples=["[email protected]"]
)
idade: int = Field(
...,
ge=18,
le=120,
title="Idade",
description="Idade do usuário (18-120)",
examples=[25]
)
site: Optional[HttpUrl] = Field(
None,
title="Website",
description="URL do site pessoal",
examples=["https://joao.com"]
)
tags: List[str] = Field(
default_factory=list,
max_length=10,
title="Tags",
description="Lista de tags (máx 10)",
examples=[["python", "dev"]]
)
# Gerar schema JSON para documentação OpenAPI
print(json.dumps(UsuarioAPI.model_json_schema(), indent=2, ensure_ascii=False))
No V2, schema_json() virou model_json_schema() (que devolve um dicionário, por isso o json.dumps), max_items virou max_length, example= virou examples=[...] e schema_extra na class Config virou json_schema_extra no model_config. O início da saída é este (o schema completo tem 87 linhas):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"description": "Schema para criação de usuário via API",
"examples": [
{
"email": "[email protected]",
"idade": 25,
"nome": "João Silva",
"site": "https://joao.com",
"tags": [
"python",
"dev"
]
}
],
"properties": {
"nome": {
"description": "Nome completo do usuário",
"examples": [
"João Silva"
],
"maxLength": 100,
"minLength": 2,
"title": "Nome Completo",
"type": "string"
},
Como usa EmailStr, este exemplo precisa do pacote email-validator (pip install "pydantic[email]").
Tipos Avançados: Comparação
| Tipo | Quando Usar | Validação | Exemplo |
|---|---|---|---|
| Decimal | Valores monetários, precisão | Precisão decimal exata | Preços, salários |
| UUID | IDs únicos, chaves primárias | Formato UUID válido | IDs de banco |
| DateTime | Timestamps, agendamentos | Formato ISO 8601 | created_at |
| EmailStr | E-mails | Formato de e-mail | Cadastros |
| HttpUrl | URLs | URL válida | Links externos |
| IPvAnyAddress | Endereços IP | IPv4/IPv6 | Logs, segurança |
| Json | JSON em string | JSON válido | Configs dinâmicas |
| SecretStr | Senhas, tokens | - | Credenciais |
Validators: Níveis de Validação
Os trechos abaixo ficam dentro da classe do modelo e usam a API do Pydantic V2 (from pydantic import field_validator, model_validator).
Field-level validator
1
2
3
4
5
@field_validator('campo')
@classmethod
def validar_campo(cls, v):
# Valida um campo específico
return v
Root validator (validar múltiplos campos)
“Root validator” é o nome do V1. No V2, @root_validator está depreciado e o equivalente é @model_validator. Com mode='after', ele recebe a instância já validada:
1
2
3
4
@model_validator(mode='after')
def validar_modelo_completo(self):
# Valida relações entre campos
return self
Validator com pre=True (antes da validação de tipo)
pre=True era a opção do @validator do V1. No V2, o equivalente é mode='before' no @field_validator: o validador recebe o valor bruto, antes da conversão de tipo.
1
2
3
4
5
@field_validator('campo', mode='before')
@classmethod
def processar_antes(cls, v):
# Processa antes da conversão de tipo
return v
Quando Usar Validators Customizados
✅ Regras de negócio complexas Ex: CEP deve ser válido para o estado
✅ Validações com dependências Ex: data_fim > data_inicio
✅ Normalização de dados Ex: converter telefone para formato padrão
✅ Integração com APIs externas Ex: validar CPF consultando serviço
Quando NÃO Usar
❌ Validações simples de tipo Type hints já resolvem
❌ Performance crítica Validators adicionam overhead
❌ Lógica de negócio pesada Melhor em camada de serviço
Conclusão
Neste artigo, exploramos como o Pydantic permite personalizar a validação de dados utilizando tipos avançados como Decimal, UUID e DateTime.
Aprendemos a definir campos opcionais e atribuir valores padrão, implementar validações personalizadas com os decoradores @field_validator e @model_validator, e utilizar o Field para configurar validações adicionais e metadados.
Essas ferramentas tornam o gerenciamento de dados em Python mais eficiente e seguro, fortalecendo a integridade das suas aplicações.
Com esses conhecimentos, você está pronto para criar modelos robustos e confiáveis em seus projetos Python. Para combinar vários modelos entre si, veja também o post sobre modelos complexos no Pydantic.
Quer aprofundar a base de Python e tipagem em projetos guiados? Conheça a Jornada Python, nosso curso completo de Python.
"Porque o Senhor dá a sabedoria, e da sua boca vem a inteligência e o entendimento" Pv 2:6