tenacity
tenacity adiciona lógica de retentativa configurável a qualquer chamável - backoff exponencial, jitter, condições de parada e hooks - sem precisar criar loops while manualmente em chamadas de rede ou banco de dados instáveis.
Receita
from tenacity import retry, stop_after_attempt, wait_exponential_jitter
@retry(stop=stop_after_attempt(3), wait=wait_exponential_jitter(initial=1, max=10))
def fetch_status() -> dict:
response = httpx.get("https://api.example.com/health", timeout=5.0)
response.raise_for_status()
return response.json()Quando usar isso:
- Erros transitórios HTTP 502/503/timeout de APIs upstream
- Deadlocks de banco de dados ou falhas de conexão
- Limitação de taxa de APIs na nuvem (combinado com consciência de limites de taxa)
- Tarefas de worker que devem sobreviver a breves solavancos na infraestrutura
Exemplo de Trabalho
from __future__ import annotations
import logging
import httpx
from tenacity import (
before_sleep_log,
retry,
retry_if_exception_type,
stop_after_attempt,
wait_exponential_jitter,
)
logger = logging.getLogger(__name__)
class TransientError(Exception):
pass
def _raise_for_transient(response: httpx.Response) -> None:
if response.status_code >= 500:
raise TransientError(f"server error {response.status_code}")
response.raise_for_status()
@retry(
retry=retry_if_exception_type((TransientError, httpx.TimeoutException)),
stop=stop_after_attempt(5),
wait=wait_exponential_jitter(initial=0.5, max=8),
before_sleep=before_sleep_log(logger, logging.WARNING),
reraise=True,
)
def charge_customer(customer_id: str, amount_cents: int) -> str:
with httpx.Client(timeout=10.0) as client:
response = client.post(
"/charges",
json={"customer_id": customer_id, "amount_cents": amount_cents},
headers={"Idempotency-Key": f"{customer_id}:{amount_cents}"},
)
_raise_for_transient(response)
return response.json()["id"]
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
print(charge_customer("cus_1", 1999))O que isso demonstra:
- Retentativas apenas para
TransientErrore timeouts - não para erros 4xx do cliente - Backoff exponencial com jitter reduz "thundering herds" (ataques sincronizados)
before_sleep_logexibe as tentativas de retentativa nos logs- Cabeçalho de idempotência combinado com retentativas para segurança de pagamento
reraise=Truepreserva a exceção final após o esgotamento
Mergulho Profundo
Como Funciona
- Decoradores -
@retryenvolve funções síncronas; use@retryem funções assíncronas com espera/parada compatíveis com async (ou o contextoAsyncRetrying). - Políticas de parada - Combine
stop_after_attempt,stop_after_delay,stop_any. - Políticas de espera - Fixa, exponencial, aleatória ou um chamável personalizado.
- Predicados de retentativa -
retry_if_exception,retry_if_result,retry_unless_exception_type.
Matriz de Decisão de Retentativa
| Tipo de Erro | Retentar? | Notas |
|---|---|---|
| HTTP 408/429/502/503/504 | Frequentemente | Respeitar Retry-After quando presente |
| HTTP 400/401/404 | Não | Corrigir requisição ou autenticação |
| Deadlock de DB | Sim | Backoff curto, tentativas limitadas |
| ValidationError | Não | Os dados não mudarão na retentativa |
Notas de Python
from tenacity import Retrying
# Estilo imperativo dentro do corpo de um loop
for attempt in Retrying(stop=stop_after_attempt(3)):
with attempt:
do_work()Armadilhas
- Retentar POST não idempotente - Cobranças duplas ou registros duplicados. Correção: chaves de idempotência ou tabela de desduplicação.
- Retentativa infinita com configuração incorreta - URL errada retenta para sempre com um cron. Correção:
stop_after_attemptmais alerta em caso de falha final. - Capturar
Exceptiongenérico - Mascara bugs de programação. Correção: estreitarretry_if_exception_type. - Sem jitter - Clientes sincronizados retentam juntos e causam DDoS no serviço que está se recuperando. Correção:
wait_exponential_jitter. - Retentativa dentro de retentativa - Decoradores aninhados multiplicam as tentativas. Correção: um limite de retentativa por chamada de saída.
- Falhas silenciosas - Ignorar a última exceção oculta interrupções. Correção:
reraise=Truee métricas na contagem de retentativas.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Loop for manual | Scripts pontuais com 2 retentativas | Políticas compartilhadas entre serviços |
urllib3.util.retry | Apenas na pilha requests/urllib3 | Aplicativos nativos httpx |
Celery autoretry_for | Retentativas de fila de tarefas no nível do worker | Chamadas de cliente HTTP no processo |
| Circuit breaker (pybreaker) | Falha sustentada do upstream | Apenas falhas transitórias breves |
FAQs
O tenacity funciona com funções async?
Sim - use helpers de retentativa async ou AsyncRetrying dependendo da versão do tenacity; mantenha um loop de eventos por cliente.
Como faço para retentar em status HTTP sem exceções personalizadas?
Lance um TransientError de domínio após inspecionar response.status_code dentro da função envolvida.
Posso notificar o Slack em caso de falha final?
Use retry_error_callback ou envolva a chamada e trate a exceção relançada no chamador.
Quantas tentativas são suficientes?
Comece com 3-5 para HTTP; ajuste com base na latência p99 e SLOs do upstream - registre métricas de retentativa para decidir.
Os workers devem retentar ou o cliente?
Ambas as camadas são válidas - retentativas do cliente lidam com falhas de subsegundo; retentativas do Celery lidam com reinícios de processo.
Como testar a lógica de retentativa?
Simule a dependência instável para falhar duas vezes e depois ter sucesso; afirme que a contagem de chamadas é igual a três.
O tenacity respeita os cabeçalhos Retry-After?
Não automaticamente - analise o cabeçalho em before_sleep e retorne uma espera personalizada, se necessário.
Posso desativar as retentativas em testes?
Passe uma política de retentativa no-op via injeção de dependência ou defina uma flag de ambiente que retorne a função nua.
E o rollback da sessão do banco de dados?
Retente no limite da unidade de trabalho após session.rollback() - não retente no meio da transação.
O tenacity é thread-safe?
O decorador é reentrante por chamada; compartilhe nenhum estado mutável dentro de funções retentadas sem locks.
Relacionado
- httpx & requests - Clientes HTTP para envolver
- Retentativas e Backoff - padrões em nível de aplicativo
- celery - retentativas de tarefas distribuídas
- Rate Limiting - quando não retentar
Versões da Stack: Esta página foi escrita para Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.