search

Python Tipado: Type Hints, mypy e Validação com Pydantic na prática

Python Tipado: Type Hints, mypy e Validação com Pydantic na prática

Python Tipado deixou de ser um “extra” e passou a fazer parte do cotidiano de equipes que buscam confiabilidade, manutenção mais simples e menos incidentes em produção. Com type hints (anotações de tipo), ferramentas de análise estática como mypy e validação de dados em tempo de execução com Pydantic, é possível criar uma camada robusta de prevenção de erros: alguns são detectados antes de rodar o sistema, outros são bloqueados quando dados externos chegam com formato inesperado.

Este artigo explica o que cada peça resolve, onde entram no fluxo de desenvolvimento e como combinar tudo em um pipeline coerente.

O que é “Python Tipado” (e o que não é)

Python é uma linguagem dinamicamente tipada: variáveis podem apontar para diferentes tipos ao longo do tempo, e o interpretador não exige declaração de tipo para executar. O “Python Tipado” normalmente se refere a:

  • Type hints: anotações opcionais que descrevem tipos esperados.
  • Checagem estática: ferramentas externas analisam o código e tentam provar consistência de tipos sem executar.
  • Validação em runtime: bibliotecas verificam e convertem dados quando o programa está rodando (especialmente em entradas externas).

Ponto importante: type hints não mudam o runtime do Python por padrão. Eles orientam editores, linters e checkers. Para segurança e confiabilidade na fronteira com dados externos (APIs, arquivos, filas), você ainda precisa de validação em runtime.

Type hints: a base do Python Tipado

Type hints entram no código como anotações em funções, métodos, variáveis e estruturas. O objetivo é documentar intenções e permitir análise automática.

Exemplo básico

from typing import Iterable

def total(itens: Iterable[int]) -> int:
    return sum(itens)

Aqui, a função declara que recebe um iterável de inteiros e retorna um inteiro. O interpretador não impede total(["1", "2"]), mas ferramentas como mypy podem sinalizar o problema.

Tipos comuns e úteis

  • list[int], dict[str, int], tuple[int, str] (Python 3.9+)
  • Optional[T] (ou T | None no Python 3.10+)
  • Literal["a", "b"] para valores específicos
  • TypedDict para dicionários com chaves conhecidas
  • Protocol para “interfaces” estruturais
  • NewType para diferenciar conceitos que são o mesmo tipo em runtime (ex.: UserId e OrderId ambos int)

Exemplo com Optional e união:

def buscar_usuario(email: str) -> dict[str, str] | None:
    ...

mypy: checagem estática para encontrar erros antes de rodar

O mypy é um verificador estático de tipos para Python. Ele lê as anotações e tenta identificar inconsistências: retornos errados, parâmetros incorretos, branches que produzem tipos incompatíveis e muito mais.

Por que mypy importa na prática

  • Reduz bugs “silenciosos” que só aparecem em produção.
  • Ajuda refatorações: se você muda a assinatura de uma função, os pontos quebrados aparecem na análise.
  • Fortalece o contrato entre módulos e equipes.

Exemplo de erro típico detectado por mypy

def normalizar_id(valor: str) -> int:
    return valor  # erro: retornando str onde se espera int

O Python executa até alguém usar esse id como número. O mypy acusa na análise.

Passo a passo: adicionando mypy ao projeto

Mypy Ao Projeto
  1. Instale o mypy (em geral no ambiente de desenvolvimento).
  2. Comece com um alvo pequeno: rode mypy em um pacote específico, não necessariamente no projeto inteiro.
  3. Aperte gradualmente as regras: conforme o time corrige alertas, aumente o rigor.

Um arquivo de configuração típico (mypy.ini) pode incluir:

[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_ignores = True
disallow_untyped_defs = True
no_implicit_optional = True
strict_optional = True

Em projetos existentes, disallow_untyped_defs = True pode ser agressivo demais no início. Uma estratégia comum é habilitar o estrito por módulos, priorizando áreas críticas.

Limites do mypy (e por que isso é relevante para segurança)

mypy não executa o código nem valida dados externos. Ele garante consistência dentro do que o código promete. Se uma API externa manda "idade": "dez", o mypy não impede. É aqui que entra Pydantic.

Pydantic: validação (e parsing) de dados em tempo de execução

Pydantic cria modelos de dados com validação automática, conversão de tipos e mensagens de erro detalhadas. É especialmente útil na fronteira do sistema:

  • Requests/Responses de APIs
  • Mensagens de filas (Kafka, RabbitMQ)
  • Arquivos JSON/YAML
  • Variáveis de ambiente e configurações

Exemplo: modelando entrada de uma API

from pydantic import BaseModel, EmailStr, Field

class UsuarioIn(BaseModel):
    email: EmailStr
    idade: int = Field(ge=0, le=150)
    nome: str = Field(min_length=1, max_length=120)

Ao instanciar:

payload = {"email": "ana@exemplo.com", "idade": "34", "nome": "Ana"}
usuario = UsuarioIn(**payload)

Pydantic pode converter "34" para int (dependendo das regras e versão/configuração), validar limites e garantir formato de e-mail. Se algo falha, levanta erro com detalhes.

Por que validação em runtime é uma prática de cibersegurança

Entradas externas são uma superfície de ataque. Falhas de validação podem levar a:

  • Quebras de serviço por exceções não tratadas (DoS acidental ou induzido)
  • Injeção lógica (dados em formato inesperado quebrando invariantes)
  • Persistência de dados inválidos (corrupção de base, inconsistência de auditoria)

Pydantic ajuda a centralizar e padronizar validações, reduzindo a chance de “validar parcialmente” em cada endpoint e esquecer algum campo.

Como combinar: type hints + mypy + Pydantic sem duplicar trabalho

A confusão mais comum é achar que Pydantic “substitui” type hints. Na prática, eles se complementam:

  • Type hints: documentam e estruturam o código.
  • mypy: garante coerência interna e reduz regressões.
  • Pydantic: controla a fronteira com o mundo externo (dados não confiáveis).

Fluxo recomendado

  1. Funções e serviços internos bem tipados com type hints.
  2. Modelos Pydantic nas bordas (entrada/saída), convertendo dados “soltos” em objetos validados.
  3. Após a validação, o restante do código opera com tipos confiáveis — e o mypy passa a ter alto valor, porque os objetos agora respeitam invariantes.

Exemplo prático: pipeline de entrada

from pydantic import BaseModel, Field

class EventoCompra(BaseModel):
    user_id: int = Field(gt=0)
    total_centavos: int = Field(ge=0)

def processar_compra(evento: EventoCompra) -> int:
    # retorna um código de status interno
    if evento.total_centavos == 0:
        return 204
    return 200
  • Pydantic garante user_id > 0 e total_centavos >= 0.
  • mypy garante que processar_compra sempre recebe EventoCompra e retorna int.

Armadilhas frequentes (e como evitar)

1) “Anotar tipos” sem rodar mypy

Sem checagem, type hints viram documentação não verificada. Integre mypy ao CI e trate alertas como dívida técnica controlada.

2) Confiar em type hints para validar dados externos

Type hints não impedem payloads errados. Use Pydantic (ou validação equivalente) em qualquer entrada não confiável.

3) Tipagem fraca com Any

Any é útil para migração gradual, mas pode “desligar” garantias. Quando possível, substitua Any por tipos mais específicos (ou Protocol, TypedDict, Mapping[str, object], etc.).

4) Modelos Pydantic vazando para todo o domínio

Modelos Pydantic são excelentes na borda, mas nem sempre são ideais como entidades centrais do domínio. Uma prática comum é:

  • Pydantic na camada de I/O (API, eventos, config)
  • dataclasses (ou classes de domínio) internamente, com type hints e invariantes claras

Isso mantém o domínio mais independente de biblioteca.

Checklist para adotar Python Tipado com segurança

  • Padronize type hints no código novo.
  • Rode mypy no CI e defina um nível de rigor progressivo.
  • Modele entradas externas com Pydantic e valide invariantes.
  • Trate mensagens de erro de validação como parte do contrato da API.
  • Monitore mudanças em schemas (versionamento) para evitar que dados antigos quebrem o sistema.
  • Evite Any como “solução permanente”.

Conclusão

Python Tipado funciona melhor como um trio: type hints definem contratos, mypy verifica esses contratos antes da execução e Pydantic garante que dados do mundo real entrem no sistema de forma validada e previsível. Para engenharia de software e para cibersegurança, a combinação reduz incerteza, melhora a rastreabilidade de falhas e fortalece a fronteira contra dados malformados — um dos vetores mais comuns de incidentes operacionais.

Quando aplicado de forma incremental (começando por módulos críticos e pontos de entrada), o ganho costuma aparecer rápido: menos bugs difíceis de reproduzir e mais confiança para evoluir o código sem regressões.

Compartilhar este artigo: