Introdução ao Pydantic: Validação de Dados em Python
Vamos aprender o Pydantic, uma biblioteca Python para validação e análise de dados usando type hints. Aprenda como definir modelos, validar dados, lidar com estruturas aninhadas e integrá-lo com frameworks web como o FastAPI.
Progresso do Tutorial
1 Bem-vindo ao Pydantic!
Já se encontrou a lidar com dados de entrada — talvez de uma API, base de dados ou alguma entrada do utilizador — e a pensar: "Isto tem a forma certa?" O Pydantic entra para organizar essa confusão. É uma biblioteca Python que, francamente, torna a validação e o parsing de dados quase prazerosos.
Pense nele como um rigoroso porteiro para os seus dados. Você diz ao Pydantic como os seus dados devem parecer usando anotações de tipo Python padrão, e ele verifica rigorosamente tudo o que chega ao portão. Se algo não corresponder, ele lança um erro educado, mas firme. Isto não é apenas para apanhar erros cedo; é para tornar o seu código mais robusto, legível e, geralmente, menos propenso a acessos de raiva inesperados mais tarde.
- Segurança de Tipo: Reforça os tipos de dados esperados, reduzindo erros em tempo de execução.
- Validação Automática: Converte dados brutos em objetos validados sem esforço.
- Serialização: Converta facilmente modelos de volta para dicionários ou JSON.
- Ótimo para APIs: Um pilar para frameworks como FastAPI, lidando com modelos de requisição e resposta.
Pronto para fazer os seus dados se comportarem? Clique em "Próximo" para instalar o Pydantic.
2 Instalar o Pydantic
Antes que o Pydantic possa começar o seu bom trabalho, você precisa de o trazer para o seu projeto. Se você tem trabalhado com FastAPI, as chances são de que o Pydantic já esteja em algum lugar nas suas dependências, já que o FastAPI confia nele bastante internamente. No entanto, para um projeto independente, um simples pip install resolve.
Primeiro, certifique-se de que está dentro do ambiente virtual do seu projeto. Se precisar de um lembrete sobre como configurar um, volte um passo nos seus tutoriais habituais do FastAPI — é sempre um bom hábito. Assim que o seu ambiente estiver ativo, execute este comando:
pip install pydantic
Assim que isso estiver feito, o Pydantic está pronto para ir. Você não verá um desfile, mas acredite em mim, ele está lá, esperando pacientemente para trazer ordem aos seus objetos Python.
3 O Seu Primeiro Modelo - É Como Uma Classe, Mas Melhor
Na sua essência, o Pydantic estende as classes de dados do Python adicionando uma camada de validação. Você define uma classe que herda de pydantic.BaseModel, e depois usa anotações de tipo Python padrão para cada atributo. O Pydantic pega essas anotações e as transforma em regras rigorosas.
Vamos criar um modelo User simples. Este modelo esperará um name (uma string) e uma age (um inteiro). Se você tentar dar-lhe algo mais, o Pydantic irá educadamente — ou nem tão educadamente, dependendo da sua perspectiva — dizer-lhe que é um não.
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
# Agora você tem um blueprint para dados de User.
Viu? Parece exatamente como uma classe Python normal. A magia realmente acontece quando você tenta criar uma instância dela.
4 Validação em Ação - Capturando as Partes Feias
Definir um modelo é apenas metade da diversão. O verdadeiro espetáculo começa quando você o alimenta com alguns dados. O Pydantic vai devorá-lo, verificá-lo contra os seus tipos definidos, e ou entregar-lhe um objeto impecável ou, bem, cuspi-lo com uma ValidationError.
Vamos ver o nosso modelo User a lidar tanto com dados bons quanto maus. Começaremos com um utilizador perfeitamente válido, depois tentaremos inserir uma idade inválida — digamos, uma string em vez de um inteiro. O Pydantic não será enganado.
from pydantic import BaseModel, ValidationError
class User(BaseModel):
name: str
age: int
# Isto funcionará perfeitamente
valid_user = User(name="Alice", age=30)
print(f"Utilizador Válido: {valid_user}")
# Saída: Utilizador Válido: name='Alice' age=30
print("\n--- Tentando dados inválidos ---")
try:
invalid_user = User(name="Bob", age="vinte")
except ValidationError as e:
print(f"Erro capturado! \n{e}")
# A saída mostrará uma ValidationError indicando que 'o valor não é um inteiro válido'
Note como o Pydantic não só detecta a incompatibilidade de tipos, mas também lhe dá uma mensagem de erro bastante útil. Este ciclo de feedback é de ouro para depuração e construção de aplicações robustas.
5 Anotações de Tipo - O Presente do Python para o Pydantic
Todo o sistema de validação do Pydantic depende das anotações de tipo do Python. Estas não são apenas para deixar o seu IDE feliz; o Pydantic as usa ativamente para reforçar esquemas. Além de tipos básicos como str e int, o módulo typing do Python oferece um tesouro de anotações mais complexas.
Uma anotação incrivelmente útil é Optional. Quando um campo pode ou não estar presente, ou pode ser None, Optional (do módulo typing) é seu amigo. Ele sinaliza ao Pydantic que, embora um valor seja esperado, None também é uma entrada válida.
from pydantic import BaseModel
from typing import Optional # Não se esqueça desta importação!
class Product(BaseModel):
name: str
price: float
description: Optional[str] # Este campo pode ser uma string ou None
# Produtos válidos
product1 = Product(name="Laptop", price=1200.50, description="Potente computação em movimento.")
product2 = Product(name="Mouse", price=25.00, description=None) # Explicitamente None é bom
product3 = Product(name="Teclado", price=75.99) # Omitir também resulta em None por padrão
print(f"Produto 1: {product1}")
print(f"Produto 2: {product2}")
print(f"Produto 3: {product3}")
Usar Optional[str] é açúcar sintático para Union[str, None], tornando as suas intenções cristalinas. É um pequeno detalhe, mas evita muitas confusões e erros inesperados relacionados com None.
6 Valores Padrão e Campos Opcionais - Tornando as Coisas Flexíveis
Às vezes, um campo não é estritamente necessário, ou talvez deva apenas ter um valor padrão sensato se nenhum valor for fornecido. O Pydantic funciona bem com os valores de argumento padrão do Python, permitindo que você os defina diretamente na definição do seu modelo. Isso torna seus modelos robustos contra dados ausentes, mantendo as coisas previsíveis.
Atualizaremos o nosso modelo Product para incluir uma quantidade padrão e uma lista de tags opcional, que por padrão é uma lista vazia. Isso significa que você não precisa sempre fornecer esses valores, o que é bom para uma entrada de dados menos verbosa.
from pydantic import BaseModel
from typing import Optional, List # Adicionado List para anotação de tipo de listas
class Product(BaseModel):
name: str
price: float
description: Optional[str] = None # Definindo description como None por padrão
quantity: int = 1 # Quantidade padrão para 1 se não fornecida
tags: List[str] = [] # Padrão para uma lista vazia de strings
# Cria produtos, omitindo alguns campos
product_default_quantity = Product(name="Fones de ouvido", price=99.99)
product_with_tags = Product(name="Smartwatch", price=250.00, tags=["wearable", "tech"])
print(f"Produto com quantidade padrão: {product_default_quantity}")
print(f"Produto com tags: {product_with_tags}")
# O que acontece se fornecermos uma não-lista para tags?
try:
bad_tags_product = Product(name="Caneca", price=10.0, tags="cerâmica")
except Exception as e:
print(f"\nErro capturado com tags inválidas: \n{e}")
Ao definir valores padrão para tipos mutáveis como listas ou dicionários, use sempre `List[str] = []` diretamente na atribuição, ou considere `Field(default_factory=list)` se estiver preocupado com estado mutável compartilhado (embora o Pydantic muitas vezes lide com isso graciosamente internamente para atribuições simples). O Pydantic sabe validar os tipos dentro da lista também, provando que é mais do que apenas uma verificação superficial.
7 Modelos Aninhados - Quando as Coisas Ficam Complicadas
Dados do mundo real raramente vêm em estruturas planas e simples. Frequentemente, você terá objetos que contêm outros objetos — um utilizador pode ter um endereço, que por si só tem uma rua, cidade e código postal. O Pydantic lida com estas estruturas aninhadas lindamente, permitindo-lhe incorporar um BaseModel dentro de outro.
Vamos refinar o nosso modelo User. Introduziremos primeiro um modelo Address, depois incluiremos uma instância desse modelo Address como um campo no nosso User. O Pydantic validará automaticamente o modelo aninhado quando você instanciar o pai.
from pydantic import BaseModel, ValidationError
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
age: int
address: Address # Este campo espera um modelo Address!
# Um utilizador válido com um endereço aninhado
user_with_address = User(
name="Charlie",
age=45,
address={
"street": "123 Main St",
"city": "Anytown",
"zip_code": "12345"
}
)
print(f"Utilizador com endereço: {user_with_address}")
# O que acontece se o endereço aninhado for inválido?
try:
invalid_address_user = User(
name="Diana",
age=28,
address={
"street": "456 Oak Ave",
"city": "Otherville",
"zip_code": 98765 # Código postal como int em vez de str!
}
)
except ValidationError as e:
print(f"\nErro capturado para endereço aninhado inválido: \n{e}")
O Pydantic lida graciosamente com a recursão, garantindo que mesmo dados profundamente aninhados aderem aos tipos especificados. É uma maneira fantástica de construir estruturas de dados complexas e auto-descritivas.
8 Listas e Dicionários - Lidando com Coleções
Além de modelos aninhados únicos, os dados geralmente vêm em coleções — listas de itens ou dicionários mapeando chaves para valores. O Pydantic, usando o módulo typing do Python, estende seus poderes de validação a essas estruturas de dados comuns também.
Vamos dar ao nosso modelo User alguns amigos (uma lista de outros modelos User, talvez?) e uma lista de hobbies. Também adicionaremos um dicionário para metadados arbitrários, apenas para mostrar como o Pydantic lida com esses tipos flexíveis.
from pydantic import BaseModel
from typing import List, Dict, Optional # Precisamos destes para listas e dicts
# Reutilizando nosso modelo User para aninhamento
class User(BaseModel):
name: str
age: int
# Sem Endereço para simplicidade neste exemplo, mas poderia estar aqui!
hobbies: List[str] = []
friends: List['User'] = [] # Referência direta para modelos auto-referenciais
metadata: Dict[str, str] = {} # Um dicionário de chaves de string para valores de string
# Cria alguns utilizadores
friend1 = User(name="Eve", age=25, hobbies=["leitura"])
friend2 = User(name="Frank", age=27, hobbies=["jogos", "programação"])
# Um utilizador principal com uma lista de amigos e hobbies
main_user = User(
name="Grace",
age=32,
hobbies=["caminhada", "fotografia", "culinária"],
friends=[friend1, friend2],
metadata={"status": "ativo", "level": "sênior"}
)
print(f"Utilizador Principal com amigos e hobbies:\n{main_user.model_dump_json(indent=2)}")
# E se um amigo não for um modelo User?
try:
bad_friends_user = User(
name="Harry",
age=40,
friends=[{"name": "Amigo Inválido", "age": "não-é-int"}] # Dados de amigo inválidos
)
except Exception as e:
print(f"\nErro capturado com dados de amigo inválidos: \n{e}")
O Pydantic mergulha em coleções, validando cada item contra o seu tipo especificado. Para modelos auto-referenciais (como um User ter uma lista de amigos User), você pode usar uma referência direta, uma string literal do nome do modelo, como 'User', que o Pydantic resolve mais tarde. É bastante inteligente como ele lida com essas danças recursivas.
9 Pydantic e FastAPI - Uma Combinação Perfeita
Se você tem trabalhado com FastAPI, você provavelmente já encontrou o Pydantic sem mesmo perceber. FastAPI usa BaseModels do Pydantic extensivamente para definir a estrutura dos corpos de requisição de entrada, parâmetros de consulta e modelos de resposta de saída. Essa integração estreita é um dos superpoderes do FastAPI.
Quando você define uma rota no FastAPI e usa um modelo Pydantic como tipo de parâmetro, o FastAPI automaticamente faz algumas coisas:
- Ele analisa o corpo da requisição JSON de entrada para o seu modelo Pydantic.
- Ele valida os dados contra as anotações de tipo e restrições do seu modelo.
- Se a validação falhar, ele retorna automaticamente uma resposta de erro clara (tipicamente, uma entidade não processável 422).
- Ele gera documentação da API (OpenAPI/Swagger UI) com base nos seus modelos Pydantic.
Você declara a sua estrutura de dados uma vez com Pydantic, e o FastAPI utiliza essa declaração para validação, serialização e documentação — um fluxo de trabalho verdadeiramente eficiente. Aqui está um pequeno vislumbre, embora a configuração do FastAPI seja um tutorial totalmente à parte:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel): # Um modelo Pydantic para os nossos dados de item
name: str
description: str | None = None # Sintaxe Python 3.10+ para Optional
price: float
tax: float | None = None
@app.post("/items/")
async def create_item(item: Item): # FastAPI espera automaticamente um modelo Pydantic 'Item'
return item.model_dump() # Converte o modelo Pydantic de volta para um dict
Essa forte ligação remove uma montanha de código repetitivo que você teria de escrever para validação e transformação de dados. É um pouco de mágica, não é?
10 Exportando Seus Dados - `model_dump` e `model_dump_json`
Depois de ter seus dados felizmente validados dentro de um modelo Pydantic, você frequentemente precisará enviá-los de volta para o mundo. Talvez você esteja retornando uma resposta JSON de uma API, salvando em um banco de dados, ou apenas depurando. Pydantic torna essa conversão de volta para dicionários Python simples ou strings JSON direta.
Para Pydantic V2 (a versão estável atual), você usará model_dump() para obter uma representação de dicionário Python e model_dump_json() para uma string JSON. Esses métodos lidam automaticamente com todos os aninhamentos e conversões de tipo, garantindo que seus dados exportados pareçam tão bons quanto quando entraram.
from pydantic import BaseModel
from typing import List
class Tag(BaseModel):
name: str
color: str
class Article(BaseModel):
title: str
content: str
tags: List[Tag] = []
is_published: bool = False
# Cria um artigo
article = Article(
title="Poder Pydantic",
content="Um mergulho profundo na validação de dados com Pydantic.",
tags=[Tag(name="python", color="blue"), Tag(name="fastapi", color="green")],
is_published=True
)
# Converte para um dicionário Python
article_dict = article.model_dump()
print(f"Artigo como um dicionário:\n{article_dict}")
# Saída: {'title': 'Poder Pydantic', 'content': 'Um mergulho profundo na validação de dados com Pydantic.', 'tags': [{'name': 'python', 'color': 'blue'}, {'name': 'fastapi', 'color': 'green'}], 'is_published': True}
# Converte para uma string JSON
article_json = article.model_dump_json(indent=2) # indent para formatação bonita
print(f"\nArtigo como uma string JSON:\n{article_json}")
Esses métodos são seus principais recursos para fazer com que objetos Pydantic funcionem bem com sistemas externos. Eles garantem consistência na saída, simplificando drasticamente sua lógica de serialização.
11 E Agora? - Além do Básico
Agora você teve uma introdução adequada ao Pydantic, viu seus principais poderes em ação e até vislumbrou seu melhor amigo, o FastAPI. O que cobrimos mal arranha a superfície, mas é mais do que suficiente para você começar a construir modelos de dados robustos e seguros em termos de tipo.
A partir daqui, você pode mergulhar em alguns dos recursos mais avançados do Pydantic:
- Validadores Personalizados: Use
@field_validatorpara criar sua própria lógica de validação para campos específicos, garantindo que os dados atendam até mesmo às regras de negócios mais arcanas. - Configurações Pydantic: Gerencie as configurações da aplicação e variáveis de ambiente com facilidade, aproveitando a validação do Pydantic para configuração.
- Tipos Mais Complexos: Explore
Enums,UUIDs, objetosdatetimee até modelos genéricos para estruturas de dados verdadeiramente flexíveis. Fieldcom Restrições Adicionais: Adicione regras de validação mais granulares, como comprimentos mínimo/máximo para strings, ou maior/menor que para números.
Você tem uma compreensão firme dos conceitos fundamentais do Pydantic. Em seguida, você pode refatorar imediatamente um projeto existente para usar modelos Pydantic para uma melhor higiene de dados, ou mergulhar diretamente na construção de uma aplicação FastAPI, onde o Pydantic realmente brilha. Vá em frente, experimente!
Explore a documentação oficial do Pydantic para um mergulho mais profundo.