---
title: "Pydantic em Python: Validação de Dados"
url: "https://python.dev.br/blog/pydantic-validacao-dados-python/"
markdown_url: "https://python.dev.br/blog/pydantic-validacao-dados-python.MD"
description: "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."
date: "2026-03-23"
author: "Equipe python.dev.br"
---

# 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](/blog/apis-rest-com-fastapi/)

Se você já trabalha com [tipagem estática em Python](/blog/tipagem-estatica-python-mypy/), vai se sentir em casa com o Pydantic.

## Instalação

O Pydantic v2 é a versão mais recente e recomendada. Para instalar:

```python
pip install pydantic
```

Para verificar a versão instalada:

```python
import pydantic
print(pydantic.__version__)  # 2.x.x
```

Se você usa o [uv como gerenciador de pacotes](/blog/uv-gerenciador-pacotes-python/), 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:

```python
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:

```python
# 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:

```python
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`:

```python
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`:

```python
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:

```python
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:

```python
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](/blog/trabalhando-com-json-python/) vindos de APIs externas ou arquivos de configuração.

## Integração com FastAPI

O Pydantic é a base de validação do [FastAPI](/blog/apis-rest-com-fastapi/). Quando você define um endpoint com um modelo Pydantic, o FastAPI valida automaticamente o body da requisição:

```python
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](/blog/boas-praticas-python-2026/):

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 resposta** — `ItemCriacao` 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 <a href="https://rustlang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust</a>, 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.
