HTTPX é um cliente HTTP para Python com APIs síncrona e assíncrona. Seu uso confiável depende menos de chamar get() e mais de definir timeout, reutilizar conexões, limitar respostas e transformar falhas remotas em erros compreensíveis.
Reutilizar o cliente
import httpx
timeout = httpx.Timeout(5.0, connect=2.0)
limits = httpx.Limits(max_connections=20, max_keepalive_connections=10)
with httpx.Client(timeout=timeout, limits=limits) as client:
resposta = client.get("https://api.example.com/status")
resposta.raise_for_status()
dados = resposta.json()
Client mantém um pool de conexões, reduzindo o custo de abrir uma conexão a cada chamada. Defina timeout no cliente e substitua-o apenas quando um endpoint justificar. raise_for_status() trata status de erro, mas o JSON ainda precisa ser validado antes de entrar no domínio da aplicação.
No modo assíncrono, use AsyncClient dentro de async with e aguarde as operações. Não misture uma chamada bloqueante dentro do event loop. O guia de async e await em Python explica esse modelo de execução.
Trate TimeoutException, NetworkError e HTTPStatusError de forma distinta quando a política de retry depender da causa. Nunca repita automaticamente operações não idempotentes sem uma chave de idempotência ou garantia equivalente. Limite o tamanho lido quando o servidor ou conteúdo não for confiável.
A documentação oficial do HTTPX, consultada em 22 de julho de 2026, descreve clientes, timeouts, limites, streaming, suporte assíncrono e HTTP/2 opcional.
Centralizar a fronteira HTTP
Configure base_url, autenticação, headers, timeouts e limites uma vez. Não versione tokens nem os coloque na URL, onde logs podem expô-los.
def criar_cliente(token: str) -> httpx.Client:
return httpx.Client(
base_url="https://api.example.com/v1/",
headers={"Authorization": f"Bearer {token}"},
timeout=httpx.Timeout(10.0, connect=2.0),
limits=httpx.Limits(max_connections=20),
)
Teste a URL final, pois uma barra inicial altera a combinação com base_url. Use params= para query, json= para JSON, data= para formulário e files= para multipart. Não monte escaping ou boundary manualmente.
Distinguir timeouts e falhas
HTTPX separa connect, read, write e pool. Read limita a espera por dados, não o download total. Pool limita a espera por conexão disponível.
def buscar_usuario(client: httpx.Client, user_id: int) -> dict:
try:
resposta = client.get(f"users/{user_id}")
resposta.raise_for_status()
except httpx.TimeoutException as exc:
raise RuntimeError("serviço excedeu o tempo") from exc
except httpx.HTTPStatusError as exc:
if exc.response.status_code == 404:
raise LookupError(user_id) from exc
raise RuntimeError("serviço rejeitou a requisição") from exc
dados = resposta.json()
if not isinstance(dados, dict) or "id" not in dados:
raise RuntimeError("resposta inválida")
return dados
Não exponha corpos remotos brutos. Registre status e identificador, removendo autorização e dados pessoais. Mesmo status 200 exige media type e schema esperados.
Streaming com limite
.content carrega tudo. Para downloads, use stream() e conte bytes, pois Content-Length pode faltar:
LIMITE = 5_000_000
with httpx.Client(timeout=10.0) as client:
with client.stream("GET", "https://example.com/a.bin") as resposta:
resposta.raise_for_status()
total = 0
with open("arquivo.bin", "wb") as saida:
for bloco in resposta.iter_bytes():
total += len(bloco)
if total > LIMITE:
raise ValueError("download excedeu o limite")
saida.write(bloco)
Escolha o destino sem confiar em nomes externos. Grave temporariamente e mova após validar para não apresentar arquivo incompleto.
Ciclo de vida e async
Crie o cliente no início do componente e feche no shutdown. Um cliente por chamada perde o pool; um global sem dono dificulta testes. Cookies persistem, portanto não misture sessões de usuários.
Mantenha AsyncClient durante a vida do componente assíncrono. Limite tarefas porque corrotinas enfileiradas também consomem memória. Não compartilhe cliente entre event loops. Em programa síncrono, use Client, sem asyncio.run() por requisição.
Retry, redirect e TLS
Use retry apenas para falhas transitórias e operações repetíveis, com poucas tentativas, backoff, jitter e prazo total. Respeite Retry-After. POST que cobra ou cria pedido exige idempotency key aceita pelo servidor. Autenticação e validação precisam de correção, não retry.
Redirects devem ser deliberados. Quando URLs vêm do usuário, valide destino e cada redirecionamento para reduzir SSRF. Mantenha TLS verificado. CA corporativa pode ser legítima; verify=False não é solução.
Upload, logs e testes
Feche arquivos de upload, valide tipo e tamanho e nunca use nome externo como destino local. Ao enviar JSON, diferencie campo omitido de null.
Hooks de tracing devem ser rápidos e remover tokens, cookies e parâmetros sensíveis. Métricas devem distinguir conexão, pool, leitura, status, decoding e schema com labels limitadas.
MockTransport testa método, URL, headers e corpo sem rede pública. Cubra JSON inválido, redirects, timeout, limite de download e status mapeados. Use servidor local em poucos testes de streaming.
Antes do deploy, confirme timeout finito, conexões limitadas, TLS, redirects, segredo protegido, resposta validada e fechamento. HTTP/2 opcional não elimina esses controles. Uma camada HTTP pequena deve devolver objetos de domínio previsíveis.
Propriedade do cliente em aplicações web
Crie o cliente durante a inicialização da aplicação e feche-o no encerramento. Em frameworks com injeção de dependência, registre esse ciclo explicitamente. Um cliente novo para cada requisição de entrada descarta conexões reutilizáveis; um cliente global criado na importação dificulta substituição em testes e shutdown.
Cookies ficam armazenados no cliente. Isso é útil numa sessão deliberada, mas perigoso quando o mesmo objeto atende usuários diferentes. Prefira credenciais por requisição ou clientes isolados quando o estado pertence a uma pessoa. Revise também headers padrão antes de encaminhar chamadas entre serviços.
Upload e corpos de requisição
Abra arquivos de upload com context manager e feche-os após a chamada. Para conteúdo grande ou gerado aos poucos, use streaming em vez de construir um único objeto de bytes. Valide tamanho, tipo e autorização localmente, pois o servidor remoto pode rejeitar o corpo apenas depois de todo o envio.
Ao serializar JSON, converta datas, decimais e objetos de domínio de forma explícita. Não envie __dict__ indiscriminadamente: ele pode incluir campos internos. Diferencie campo ausente de null quando a API atribui significados distintos.
Media type e validação
raise_for_status() verifica o status, não o contrato do corpo. Confira Content-Type quando necessário, trate corpo vazio e valide tipos e campos obrigatórios. Um proxy pode devolver uma página HTML com status 200; chamar .json() sem contexto produz uma mensagem pouco útil.
Converta o payload em um objeto de domínio numa camada pequena. O restante da aplicação não deveria conhecer headers, códigos HTTP ou nomes instáveis do fornecedor. Isso concentra compatibilidade e permite alterar a biblioteca cliente sem espalhar mudanças.
Observabilidade sem vazamento
Registre separadamente falhas de conexão, espera no pool, leitura, status, decoding e schema. Cada categoria aponta para uma ação diferente. Use nome do serviço e rota normalizada nas métricas, nunca URL completa com identificadores.
Hooks podem propagar um identificador de correlação ou medir duração, mas executam no caminho crítico. Mantenha-os rápidos e sem I/O bloqueante adicional. Remova Authorization, cookies, chaves de API, parâmetros pessoais e corpos sensíveis. Limite o tamanho de qualquer amostra de resposta.
Testes por transporte
Com MockTransport, um handler recebe a requisição e devolve uma resposta controlada. Assim é possível conferir método, URL combinada, query, headers e corpo sem abrir socket. Cubra sucesso, JSON malformado, corpo vazio, 404, 429, 500, timeout traduzido, redirect proibido e resposta acima do limite.
Mantenha alguns testes com servidor local para streaming, uploads e detalhes de protocolo. Não dependa de uma API pública na suíte normal, pois disponibilidade e rate limits mudam. Confirme também que o cliente fecha mesmo quando parsing ou escrita falha.
Política de retry documentada
Liste quais operações são idempotentes e quais aceitam uma chave de idempotência. O orçamento de retry precisa caber no prazo total da operação. Uma tentativa com dez segundos repetida três vezes não respeita um endpoint que promete responder ao chamador em quinze segundos.
Use jitter para evitar que muitas instâncias repitam simultaneamente após uma indisponibilidade. Limite o número total e exponha a contagem em logs. Se o serviço devolve Retry-After, interprete-o conforme o contrato, mas ainda respeite o prazo do chamador.
Com essas decisões explícitas, HTTPX deixa de ser uma chamada espalhada pelo código e vira uma fronteira confiável, observável e testável.