Sitemap
Hugo Habbema

Hugo Habbema

Engenheiro de Dados | Linkedin: https://www.linkedin.com/in/hugo-habbema/

Press enter or click to view image in full size

Apresentando o Pydantic

Simplificando a Validação de Dados em Python

7 min readOct 16, 2023

--

Introdução

A validação de dados é uma parte crucial do desenvolvimento de software. Garantir que os dados estejam corretos e consistentes é fundamental para evitar erros e vulnerabilidades em um sistema. Uma biblioteca em Python chamada Pydantic veio para simplificar a tarefa de validar dados, tornando-a mais fácil e eficaz. Neste artigo, vamos explorar o que é o Pydantic, como ele funciona e como pode ser usado em seus projetos Python.

O que é o Pydantic?

Pydantic é uma biblioteca Python de código aberto criada por Samuel Colvin que resolve um problema clássico do desenvolvimento: como garantir que os dados que chegam no seu sistema são exatamente o que você espera?

Imagine que sua API recebe um JSON com a idade do usuário, mas alguém envia a string "trinta" em vez do número 30. Sem validação, isso pode causar bugs silenciosos ou exceções inesperadas em produção. O Pydantic intercepta isso antes que o dado chegue na sua lógica de negócio.

Para que serve?

  • Validar dados de entrada em APIs REST
  • Ler e validar arquivos de configuração (.env, .json, .yaml)
  • Garantir a integridade de dados em pipelines de dados
  • Documentar automaticamente a estrutura de dados esperada
  • Serializar/desserializar objetos Python ↔ JSON

Como o Pydantic Funciona?

O Pydantic funciona definindo modelos de dados Python que descrevem a estrutura dos dados que você deseja validar. Esses modelos contêm campos que especificam os tipos de dados esperados, validações personalizadas e informações adicionais, como descrições. Aqui está um exemplo simples de um modelo Pydantic que valida dados de uma pessoa:

"""Modelo Pydantic simples — validação de dados de uma pessoa."""

from pydantic import BaseModel


class Pessoa(BaseModel):
nome: str
idade: int
email: str


def main() -> None:
dados = {
"nome": "Alice",
"idade": 30,
"email": "alice@example.com",
}
pessoa = Pessoa(**dados)
print("Instância validada:", pessoa)
print("Como dict:", pessoa.model_dump())


if __name__ == "__main__":
main()

Resultado

Principais Recursos do Pydantic

O Pydantic oferece diversos recursos poderosos para simplificar a validação de dados em Python:

1. Tipagem Estática
O Pydantic usa a anotação de tipos introduzida no Python 3.5 para definir os tipos de dados esperados. Isso ajuda a detectar erros de tipo em tempo de desenvolvimento, tornando seu código mais robusto.

2. Validações Personalizadas
Você pode adicionar validações personalizadas aos campos dos modelos. Isso permite que você defina regras específicas para cada campo, como intervalos de valores, expressões regulares ou validações complexas.

3. Geração Automática de Documentação
O Pydantic pode gerar automaticamente documentação baseada em seus modelos, tornando mais fácil compartilhar informações sobre os dados esperados em seu sistema.

4. Conversão de Dados
O Pydantic é capaz de converter dados de entrada em tipos de dados definidos no modelo. Isso simplifica a manipulação de diferentes tipos de entrada, como JSON ou formulários da web.

5. Suporte a Herança
Você pode criar hierarquias de modelos, aproveitando a herança. Isso é útil quando você tem modelos relacionados que compartilham campos comuns.

Como Usar o Pydantic em Seus Projetos

Para começar a usar o Pydantic em seus projetos Python, siga estas etapas:

Instalação: Você pode instalar o Pydantic usando o pip:

pip install pydantic
pip install pydantic[email] # para validação de e-mail

Defina Seus Modelos: Crie modelos Pydantic para descrever a estrutura dos dados que você deseja validar.

Get Hugo Habbema’s stories in your inbox

Join Medium for free to get updates from this writer.

Use Seus Modelos: Use esses modelos para validar dados de entrada em seu aplicativo ou API. O Pydantic cuidará da validação e fornecerá informações detalhadas sobre quaisquer erros.

"""Validação de dados de formulário com try/except."""

from pydantic import BaseModel


class FormularioPydantic(BaseModel):
nome: str
idade: int
email: str


def main() -> None:
dados_do_formulario = {
"nome": "Alice",
"idade": "trinta",
"email": "alice@example.com",
}
try:
formulario_validado = FormularioPydantic(**dados_do_formulario)
print("Dados válidos:", formulario_validado.model_dump())
except Exception as e:
print("Erro na validação:", e)


if __name__ == "__main__":
main()

Se algum dado não atender aos critérios especificados no modelo, o Pydantic lançará uma exceção, fornecendo informações detalhadas sobre quais campos falharam na validação.

Press enter or click to view image in full size

Mais exemplos Práticos

Validação de Cadastro de Usuário com Regras

"""Cadastro de usuário com Field, EmailStr e validadores personalizados."""

from pydantic import BaseModel, EmailStr, Field, field_validator


class CadastroUsuario(BaseModel):
nome: str = Field(min_length=2, max_length=100)
idade: int = Field(ge=18, le=120)
email: EmailStr
senha: str = Field(min_length=8)
cpf: str

@field_validator("cpf")
@classmethod
def validar_cpf(cls, v: str) -> str:
numeros = "".join(filter(str.isdigit, v))
if len(numeros) != 11:
raise ValueError("CPF deve conter 11 dígitos")
return numeros

@field_validator("senha")
@classmethod
def senha_forte(cls, v: str) -> str:
if not any(c.isupper() for c in v):
raise ValueError("Senha deve ter ao menos uma letra maiúscula")
if not any(c.isdigit() for c in v):
raise ValueError("Senha deve ter ao menos um número")
return v


def main() -> None:
try:
user = CadastroUsuario(
nome="Alice",
idade=25,
email="alice@exemplo.com",
senha="Senha123",
cpf="123.456.789-09",
)
print(user.model_dump())
except Exception as e:
print(e)


if __name__ == "__main__":
main()

Resultado

Press enter or click to view image in full size

Validação de Pedido de E-commerce

"""Pedido de e-commerce com Enum, listas aninhadas e model_validator."""

from __future__ import annotations

from enum import Enum
from typing import List

from pydantic import BaseModel, Field, model_validator


class StatusPedido(str, Enum):
PENDENTE = "pendente"
CONFIRMADO = "confirmado"
ENVIADO = "enviado"
ENTREGUE = "entregue"


class ItemPedido(BaseModel):
produto_id: int
nome: str
quantidade: int = Field(gt=0)
preco_unitario: float = Field(gt=0)

@property
def subtotal(self) -> float:
return self.quantidade * self.preco_unitario


class Pedido(BaseModel):
pedido_id: str
cliente_email: str
itens: List[ItemPedido] = Field(min_length=1)
status: StatusPedido = StatusPedido.PENDENTE
desconto: float = Field(default=0, ge=0, le=100)

@model_validator(mode="after")
def validar_pedido(self) -> Pedido:
if not self.itens:
raise ValueError("Pedido deve ter ao menos um item")
return self

@property
def total(self) -> float:
subtotal = sum(item.subtotal for item in self.itens)
return subtotal * (1 - self.desconto / 100)


def main() -> None:
pedido = Pedido(
pedido_id="PED-001",
cliente_email="cliente@loja.com",
itens=[
{
"produto_id": 1,
"nome": "Camiseta",
"quantidade": 2,
"preco_unitario": 49.90,
},
{
"produto_id": 2,
"nome": "Calça",
"quantidade": 1,
"preco_unitario": 129.90,
},
],
desconto=10,
)
print(f"Total do pedido: R$ {pedido.total:.2f}")


if __name__ == "__main__":
main()

Resultado

Leitura de Configurações com BaseSettings

Um dos usos mais comuns do Pydantic em produção é ler variáveis de ambiente com segurança

"""Leitura de configurações a partir de variáveis de ambiente e .env."""

import os
from pathlib import Path

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Configuracoes(BaseSettings):
model_config = SettingsConfigDict(
env_file=str(Path(__file__).resolve().parent.parent / ".env"),
env_file_encoding="utf-8",
extra="ignore",
)

database_url: str
secret_key: str
debug: bool = False
porta: int = Field(default=8000, ge=1024, le=65535)
max_conexoes: int = 10


def main() -> None:
# Garante variáveis mínimas se não houver .env (demo sem arquivo)
os.environ.setdefault("DATABASE_URL", "postgresql://user:pass@localhost/db")
os.environ.setdefault("SECRET_KEY", "minha-chave-secreta-demo")
os.environ.setdefault("DEBUG", "true")

config = Configuracoes()
print(config.database_url)
print(config.debug)


if __name__ == "__main__":
main()

Resultado

Hierarquia de Modelos (Herança)

"""Hierarquia de modelos — PessoaFisica e PessoaJuridica herdando PessoaBase."""

from datetime import datetime
from typing import Optional

from pydantic import BaseModel


class PessoaBase(BaseModel):
nome: str
email: str


class PessoaFisica(PessoaBase):
cpf: str
data_nascimento: datetime


class PessoaJuridica(PessoaBase):
cnpj: str
razao_social: str
inscricao_estadual: Optional[str] = None


def main() -> None:
pf = PessoaFisica(
nome="João",
email="joao@email.com",
cpf="12345678900",
data_nascimento="1990-05-15",
)
pj = PessoaJuridica(
nome="Empresa XYZ",
email="contato@xyz.com",
cnpj="12345678000100",
razao_social="XYZ Comércio Ltda",
)
print("PF:", pf.model_dump())
print("PJ:", pj.model_dump())


if __name__ == "__main__":
main()

Resultado

Press enter or click to view image in full size

Serialização e Desserialização de JSON

"""Serialização model_dump_json e desserialização model_validate_json."""

from datetime import date

from pydantic import BaseModel


class Produto(BaseModel):
id: int
nome: str
preco: float
disponivel: bool = True
criado_em: date


def main() -> None:
produto = Produto(
id=1,
nome="Notebook",
preco=3499.99,
criado_em="2024-01-15",
)
json_str = produto.model_dump_json()
print(json_str)

json_data = '{"id":2,"nome":"Mouse","preco":89.90,"criado_em":"2024-03-01"}'
produto2 = Produto.model_validate_json(json_data)
print(produto2.nome)
print(type(produto2.criado_em))


if __name__ == "__main__":
main()

Resultado

Press enter or click to view image in full size

Tratamento Detalhado de Erros

"""Tratamento detalhado de erros com ValidationError."""

from pydantic import BaseModel, Field, ValidationError


class Endereco(BaseModel):
rua: str = Field(min_length=3)
numero: int = Field(gt=0)
cep: str = Field(pattern=r"^\d{5}-\d{3}$")
cidade: str


def main() -> None:
try:
Endereco(
rua="AB",
numero=-5,
cep="1234567",
cidade="São Paulo",
)
except ValidationError as e:
print(f"Encontrados {e.error_count()} erro(s):\n")
for erro in e.errors():
campo = " > ".join(str(loc) for loc in erro["loc"])
print(f" Campo : {campo}")
print(f" Erro : {erro['msg']}")
print(f" Valor : {erro['input']}\n")


if __name__ == "__main__":
main()

Resultado

Aproveite os Recursos Avançados: Explore os recursos avançados do Pydantic, como validações personalizadas, geração de documentação e conversão de dados, para tornar seu código ainda mais poderoso.

Conclusão

O Pydantic é uma ferramenta poderosa para simplificar a validação de dados em projetos Python. Com sua sintaxe limpa, tipagem estática e suporte a validações personalizadas, ele se tornou uma escolha popular entre desenvolvedores Python. Se você deseja garantir a integridade dos dados em seu aplicativo e evitar erros devido a dados incorretos, o Pydantic é uma biblioteca que vale a pena explorar. Considerando o cenário atual de desenvolvimento de software, onde a qualidade e a segurança dos dados são essenciais, o Pydantic se destaca como uma ferramenta valiosa para a comunidade Python. Experimente e simplifique a validação de dados em seus projetos Python hoje mesmo!

Até a próxima!

--

--