Pydantic em Python: Validação de Dados
Aprenda a usar Pydantic v2 para validação e serialização de dados em Python. Tutorial completo com exemplos práticos de modelos, validadores e integração.
Trabalhar com dados externos — APIs, formulários, arquivos JSON, bancos de dados — é uma das tarefas mais comuns no dia a dia de quem programa em Python. O problema é que dados externos são imprevisíveis: campos faltando, tipos errados, valores fora do esperado. É aí que o Pydantic entra em cena, oferecendo validação e serialização de dados com uma API elegante e performática.
O Que É Pydantic e Por Que Usar
Pydantic é uma biblioteca Python para validação de dados usando type hints. Em vez de escrever dezenas de if e isinstance para verificar se seus dados estão corretos, você define um modelo com tipos e o Pydantic cuida de toda a validação automaticamente.
As principais vantagens do Pydantic incluem:
- Validação automática baseada em type hints — sem boilerplate
- Mensagens de erro detalhadas que facilitam o debugging
- Serialização e deserialização de JSON, dicts e outros formatos
- Performance excepcional no Pydantic v2, com o core escrito em Rust
- Integração nativa com frameworks como FastAPI
Se você já trabalha com tipagem estática em Python, vai se sentir em casa com o Pydantic.
Instalação
O Pydantic v2 é a versão mais recente e recomendada. Para instalar:
pip install pydantic
Para verificar a versão instalada:
import pydantic
print(pydantic.__version__) # 2.x.x
Se você usa o uv como gerenciador de pacotes, basta rodar uv add pydantic.
Modelos Básicos com BaseModel
O conceito fundamental do Pydantic é o modelo — uma classe que herda de BaseModel e define os campos com seus tipos:
from pydantic import BaseModel
from datetime import datetime
from typing import Optional
class Usuario(BaseModel):
nome: str
email: str
idade: int
ativo: bool = True
criado_em: datetime = datetime.now()
bio: Optional[str] = None
# Criando uma instância — dados válidos
usuario = Usuario(
nome="Maria Silva",
email="maria@exemplo.com",
idade=28
)
print(usuario.nome) # Maria Silva
print(usuario.ativo) # True (valor padrão)
print(usuario.bio) # None (campo opcional)
O Pydantic faz a validação automaticamente na criação do objeto. Se você passar um tipo errado, recebe um erro claro:
# Isso gera ValidationError — idade precisa ser int
try:
usuario = Usuario(
nome="João",
email="joao@exemplo.com",
idade="não é número"
)
except Exception as e:
print(e)
# 1 validation error for Usuario
# idade
# Input should be a valid integer (type=int_parsing)
Note que o Pydantic tenta fazer coerção de tipos quando possível. Por exemplo, idade="28" seria convertido automaticamente para 28 (int). Esse comportamento é configurável.
Tipos de Campo Suportados
O Pydantic suporta uma ampla variedade de tipos nativos do Python e tipos especiais:
from pydantic import BaseModel, EmailStr, HttpUrl
from typing import Optional
from datetime import datetime, date
from enum import Enum
class StatusPedido(str, Enum):
PENDENTE = "pendente"
PAGO = "pago"
ENVIADO = "enviado"
ENTREGUE = "entregue"
class Pedido(BaseModel):
id: int
cliente: str
valor: float
itens: list[str]
tags: set[str] = set()
status: StatusPedido = StatusPedido.PENDENTE
observacao: Optional[str] = None
data_criacao: datetime
data_entrega: Optional[date] = None
metadata: dict[str, str] = {}
pedido = Pedido(
id=1001,
cliente="Ana Costa",
valor=199.90,
itens=["Camiseta", "Calça"],
data_criacao="2026-03-23T10:30:00", # string convertida para datetime
tags=["urgente", "frete-grátis"]
)
print(pedido.status) # StatusPedido.PENDENTE
print(pedido.data_criacao) # 2026-03-23 10:30:00
print(pedido.itens) # ['Camiseta', 'Calça']
Para validação de email, instale pydantic[email] e use EmailStr. Para URLs, use HttpUrl. O Pydantic oferece dezenas de tipos especializados para cenários comuns.
Validadores Customizados
Quando a validação de tipos não é suficiente, você pode criar validadores customizados com o decorador @field_validator:
from pydantic import BaseModel, field_validator
class Produto(BaseModel):
nome: str
preco: float
estoque: int
sku: str
@field_validator("preco")
@classmethod
def preco_deve_ser_positivo(cls, v: float) -> float:
if v <= 0:
raise ValueError("O preço deve ser maior que zero")
return round(v, 2)
@field_validator("sku")
@classmethod
def sku_deve_ser_maiusculo(cls, v: str) -> str:
if not v.isupper():
raise ValueError("SKU deve estar em letras maiúsculas")
return v
@field_validator("estoque")
@classmethod
def estoque_nao_negativo(cls, v: int) -> int:
if v < 0:
raise ValueError("Estoque não pode ser negativo")
return v
# Funciona — preço arredondado automaticamente
produto = Produto(nome="Mouse", preco=49.999, estoque=100, sku="MSE001")
print(produto.preco) # 50.0
# Falha — preço negativo
try:
Produto(nome="Teclado", preco=-10, estoque=50, sku="TEC001")
except Exception as e:
print(e) # O preço deve ser maior que zero
Para validações que dependem de múltiplos campos, use @model_validator:
from pydantic import BaseModel, model_validator
class Intervalo(BaseModel):
inicio: int
fim: int
@model_validator(mode="after")
def verificar_intervalo(self):
if self.inicio >= self.fim:
raise ValueError("O início deve ser menor que o fim")
return self
Configurações com model_config
O Pydantic v2 usa model_config para configurar o comportamento do modelo:
from pydantic import BaseModel, ConfigDict
class Contato(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True, # remove espaços nas pontas
str_min_length=1, # strings não podem ser vazias
frozen=True, # modelo imutável após criação
extra="forbid", # não permite campos extras
)
nome: str
telefone: str
# Espaços removidos automaticamente
contato = Contato(nome=" João Silva ", telefone="11999887766")
print(contato.nome) # "João Silva"
# Erro — campo extra não permitido
try:
Contato(nome="Maria", telefone="11888", cpf="123")
except Exception as e:
print(e) # Extra inputs are not permitted
A opção frozen=True torna o modelo imutável, o que é útil para garantir que dados validados não sejam alterados acidentalmente.
Parsing de JSON e Dicts
O Pydantic facilita a conversão entre modelos, dicionários e JSON:
from pydantic import BaseModel
class Endereco(BaseModel):
rua: str
cidade: str
estado: str
cep: str
class Cliente(BaseModel):
nome: str
idade: int
endereco: Endereco
# A partir de um dicionário
dados = {
"nome": "Carlos Souza",
"idade": 35,
"endereco": {
"rua": "Rua das Flores, 123",
"cidade": "São Paulo",
"estado": "SP",
"cep": "01234-567"
}
}
cliente = Cliente.model_validate(dados)
print(cliente.endereco.cidade) # São Paulo
# A partir de JSON string
json_str = '{"nome": "Ana", "idade": 25, "endereco": {"rua": "Av. Brasil", "cidade": "Rio", "estado": "RJ", "cep": "20000-000"}}'
cliente2 = Cliente.model_validate_json(json_str)
print(cliente2.nome) # Ana
# Exportando para dict e JSON
print(cliente.model_dump()) # dicionário Python
print(cliente.model_dump_json(indent=2)) # string JSON formatada
Esse fluxo de parsing é essencial quando você trabalha com dados JSON em Python vindos de APIs externas ou arquivos de configuração.
Integração com FastAPI
O Pydantic é a base de validação do FastAPI. Quando você define um endpoint com um modelo Pydantic, o FastAPI valida automaticamente o body da requisição:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemCriacao(BaseModel):
nome: str
preco: float
quantidade: int = 1
@app.post("/itens/")
async def criar_item(item: ItemCriacao):
return {"mensagem": f"Item {item.nome} criado", "total": item.preco * item.quantidade}
Se o cliente enviar dados inválidos, o FastAPI retorna automaticamente um erro 422 com detalhes da validação. Isso elimina a necessidade de validação manual nos endpoints.
Pydantic v2 vs v1: Principais Mudanças
O Pydantic v2 trouxe mudanças significativas em relação à v1:
- Performance: o core de validação foi reescrito em Rust, tornando a v2 entre 5x e 50x mais rápida
model_validate()substituiparse_obj()eparse_raw()model_dump()substitui.dict()emodel_dump_json()substitui.json()model_config = ConfigDict(...)substitui a classe internaConfig@field_validatorsubstitui@validatorcom sintaxe mais explícita@model_validatorsubstitui@root_validator- Strict mode permite desabilitar coerção automática de tipos
Se você está migrando da v1, o Pydantic oferece o pacote bump-pydantic para automatizar boa parte da migração.
Boas Práticas e Casos de Uso
Para aproveitar ao máximo o Pydantic no seu projeto, siga estas boas práticas:
- Use modelos para todas as fronteiras de dados — entrada de APIs, leitura de arquivos, variáveis de ambiente, configurações
- Prefira
frozen=Truepara modelos que representam dados que não devem mudar após a criação - Use
extra="forbid"para evitar que campos inesperados passem despercebidos - Separe modelos de criação e resposta —
ItemCriacaopara input,ItemRespostapara output - Aproveite modelos aninhados para estruturas complexas em vez de dicionários genéricos
- Use
Field()para adicionar metadados, valores padrão e restrições comoge=0,max_length=100 - Combine com type hints — quanto mais preciso o tipo, melhor a validação automática
O Pydantic é usado em produção por empresas como Microsoft, Amazon, Google e Netflix. É a escolha natural para qualquer projeto Python que lida com dados estruturados, desde pequenos scripts até grandes sistemas distribuídos.
Conclusão
O Pydantic transforma a validação de dados em Python de uma tarefa tediosa e propensa a erros em algo declarativo e confiável. Com modelos baseados em type hints, validadores customizados e integração nativa com FastAPI, ele se tornou uma ferramenta indispensável no ecossistema Python moderno. Se você ainda valida dados manualmente com if e isinstance, experimente o Pydantic — seus bugs de dados vão agradecer.
O motor por trás da performance: o core do Pydantic v2 é escrito em Rust, o que explica os ganhos de 5x a 50x na velocidade de validação. Rust oferece um sistema de tipos rigoroso em tempo de compilação — a mesma filosofia de segurança de tipos que o Pydantic traz para o Python em tempo de execução.
Equipe python.dev.br
Contribuidor do Python Brasil — Aprenda Python em Português