Retry é uma política de resiliência, não uma forma de esconder qualquer exceção. Uma chamada pode ser repetida quando a falha é transitória e a operação é segura para repetição. Sem limite, timeout e observabilidade, novas tentativas prolongam incidentes e podem duplicar efeitos.
Definir uma política explícita
python -m pip install tenacity
from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_random_exponential,
)
class ServicoIndisponivel(Exception):
pass
@retry(
retry=retry_if_exception_type(ServicoIndisponivel),
stop=stop_after_attempt(4),
wait=wait_random_exponential(multiplier=0.5, max=8),
reraise=True,
)
def consultar() -> dict[str, object]:
return chamar_servico(timeout=3)
A política limita tentativas, seleciona uma exceção transitória e adiciona backoff exponencial com jitter. reraise=True preserva a última exceção como resultado final, o que costuma tornar o tratamento mais direto.
Não repita automaticamente POST que cria cobrança, pedido ou usuário. A API precisa oferecer idempotência, por exemplo com uma chave única, antes que a repetição seja segura. O guia de segurança de APIs Python discute limites e validação no perímetro.
Combinar timeout, logs e métricas
O timeout pertence ao cliente HTTP ou ao driver usado dentro da função. O limite do Tenacity controla o conjunto de tentativas. Registre número da tentativa, operação e duração, mas nunca tokens ou dados pessoais. O conteúdo sobre logging em Python mostra uma base para eventos operacionais.
Testar sem esperar
Separe a operação da política ou substitua a espera em testes. Simule duas falhas transitórias seguidas de sucesso e confirme três chamadas. Teste também uma exceção permanente e verifique que ocorreu apenas uma tentativa. Não faça o teste depender de uma API externa instável.
A documentação oficial do Tenacity, consultada em 22 de julho de 2026, descreve condições de parada, espera, seleção de exceções, callbacks e suporte assíncrono. Uma política adequada responde quatro perguntas: o que pode ser repetido, quantas vezes, por quanto tempo e como a falha final será observada.
Classificar falhas antes do retry
Falhas transitórias incluem uma interrupção curta de rede, reset de conexão, limite de requisições e indisponibilidade temporária. Credenciais inválidas, entrada malformada, acesso proibido e recurso definitivamente ausente são falhas permanentes. Repetir o segundo grupo desperdiça capacidade e atrasa o diagnóstico.
Evite selecionar Exception. Traduza erros no adaptador: timeout e resposta 503 podem virar ServicoIndisponivel, enquanto autenticação preserva outra exceção que não recebe retry. Assim a política fica revisável.
Um resultado também pode representar indisponibilidade, mas só use predicados de resultado quando o domínio garantir essa interpretação. Lista vazia, por exemplo, normalmente é um resultado válido.
Combinar limite de tentativas e tempo
from tenacity import stop_after_attempt, stop_after_delay
parada = stop_after_attempt(5) | stop_after_delay(20)
O orçamento total não substitui timeout por chamada. Se uma requisição trava por 60 segundos, a política de 20 segundos não entrega prazo real de 20 segundos. Configure timeout de conexão e leitura no cliente.
Backoff permite recuperação. Espera fixa serve para recurso local controlado; espera exponencial combina com serviço compartilhado. Jitter evita que muitos clientes retomem juntos. Limite a espera máxima para uma tentativa não consumir todo o prazo.
Respeite Retry-After quando o protocolo oferecer esse dado, com limite seguro e fallback aleatório. Valide o cabeçalho antes de transformá-lo em duração.
Idempotência e efeitos colaterais
Leituras costumam aceitar repetição. Escritas exigem projeto explícito. Um timeout após o envio não prova que o servidor recusou a operação; talvez apenas a resposta tenha se perdido.
Use a mesma chave de idempotência em todas as tentativas da mesma operação lógica. Para uma nova operação, gere outra chave. Em banco, combine transação e invariantes como restrição única. Não coloque envio de email, cobrança ou publicação de evento dentro do bloco repetido sem impedir duplicação.
Decore a menor função possível. Se parsing e persistência acontecem depois da chamada remota, aplique retry apenas à chamada. Um defeito local não deve repetir uma solicitação remota bem-sucedida.
Observar as tentativas
import logging
from tenacity import before_sleep_log
logger = logging.getLogger(__name__)
@retry(
retry=retry_if_exception_type(ServicoIndisponivel),
stop=stop_after_attempt(4),
wait=wait_random_exponential(multiplier=0.5, max=8),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
def buscar() -> dict[str, object]:
return chamar_servico(timeout=3)
Registre operação, tentativa, espera e categoria do erro. Não inclua tokens, corpo completo ou dados pessoais. Métricas devem contar retries, esgotamentos e sucessos posteriores. Muitos sucessos após retry ainda indicam dependência degradada.
Evite registrar a mesma exceção final em todas as camadas. O componente de retry registra tentativas; a borda da aplicação registra o impacto final com identificador da requisição.
Testar sem esperar
def test_sucesso_na_terceira_tentativa() -> None:
chamadas = 0
@retry(
stop=stop_after_attempt(3),
wait=lambda estado: 0,
retry=retry_if_exception_type(ServicoIndisponivel),
reraise=True,
)
def operacao() -> str:
nonlocal chamadas
chamadas += 1
if chamadas < 3:
raise ServicoIndisponivel
return "ok"
assert operacao() == "ok"
assert chamadas == 3
Teste também exceção permanente com uma chamada e esgotamento com o erro final. Para escrita, confirme que todas as tentativas carregam a mesma chave de idempotência. Controle a dependência e a espera, sem mockar a própria Tenacity.
Código assíncrono e cancelamento
Tenacity aceita corrotinas. Use cliente HTTP assíncrono e operações aguardáveis; I/O bloqueante dentro de async def continua bloqueando o event loop. Permita que cancelamento se propague para respeitar desligamento e prazo do chamador. Um predicado amplo não deve capturar sinais de cancelamento.
Checklist operacional
Documente exceções elegíveis, máximo de tentativas, atraso aproximado, timeout por chamada e garantia de idempotência. Confirme que o prazo do chamador comporta a política. Exponha esgotamento em logs e métricas e valide o mapeamento real de erros em integração.
Retry é uma defesa estreita para falhas transitórias conhecidas. Ele não corrige requisições inválidas, não substitui capacidade e não torna efeitos inseguros automaticamente repetíveis. Classificação e orçamento explícitos evitam carga escondida.
Revisar a política em produção
Uma política de retry é uma hipótese operacional. Depois da entrega, compare sucesso na primeira tentativa, sucesso posterior, esgotamento e latência total. Se quase todo retry falha, o predicado pode incluir erros permanentes. Se muitos funcionam com latência inaceitável, a dependência ou os timeouts precisam de correção.
Crie alertas sobre operações lógicas esgotadas, não apenas sobre tentativas. Uma indisponibilidade multiplica chamadas, então painéis devem separar demanda original de tráfego de retry. Durante sobrecarga, circuit breaker, limite de concorrência ou rejeição controlada podem proteger melhor que novas tentativas.
Revise a política quando a API mudar status, orientação de rate limit ou idempotência. Mantenha o mapeamento de erros do adaptador coberto por teste de integração. Uma leitura repetível pode deixar de ser segura se o endpoint passar a iniciar trabalho.
Em processamento em lote, decida se um item falho interrompe tudo, segue para fila de reparo ou gera relatório. Não decore o lote inteiro, pois itens já concluídos seriam repetidos. Aplique retry à menor unidade recuperável.
Guarde contexto suficiente para reparo seguro, como identificador da operação, último tipo de erro e chave de idempotência, sem registrar dados sensíveis. Compare periodicamente o orçamento configurado com o prazo real do chamador; tentativas que continuam depois do abandono apenas aumentam carga.