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.

7 min de leitura Equipe python.dev.br

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() substitui parse_obj() e parse_raw()
  • model_dump() substitui .dict() e model_dump_json() substitui .json()
  • model_config = ConfigDict(...) substitui a classe interna Config
  • @field_validator substitui @validator com sintaxe mais explícita
  • @model_validator substitui @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:

  1. Use modelos para todas as fronteiras de dados — entrada de APIs, leitura de arquivos, variáveis de ambiente, configurações
  2. Prefira frozen=True para modelos que representam dados que não devem mudar após a criação
  3. Use extra="forbid" para evitar que campos inesperados passem despercebidos
  4. Separe modelos de criação e respostaItemCriacao para input, ItemResposta para output
  5. Aproveite modelos aninhados para estruturas complexas em vez de dicionários genéricos
  6. Use Field() para adicionar metadados, valores padrão e restrições como ge=0, max_length=100
  7. 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.

E

Equipe python.dev.br

Contribuidor do Python Brasil — Aprenda Python em Português