HTTPX es un cliente HTTP para Python con APIs síncrona y asíncrona. Un uso confiable depende menos de llamar a get() y más de definir timeouts, reutilizar conexiones, limitar respuestas y convertir fallos remotos en errores útiles.

Reutilizar el 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:
    respuesta = client.get("https://api.example.com/status")
    respuesta.raise_for_status()
    datos = respuesta.json()

Client mantiene un pool de conexiones y evita abrir una nueva en cada llamada. Define el timeout en el cliente y cámbialo solo cuando un endpoint lo justifique. raise_for_status() trata estados de error, pero el JSON todavía necesita validación.

En código asíncrono, usa AsyncClient dentro de async with y espera sus operaciones. No coloques una llamada bloqueante dentro del event loop. La guía de async y await en Python explica ese modelo.

Trata TimeoutException, NetworkError y HTTPStatusError por separado cuando la política de reintentos dependa de la causa. No repitas operaciones no idempotentes sin una clave de idempotencia. Limita el contenido leído si el servidor no es confiable.

La documentación oficial de HTTPX, consultada el 22 de julio de 2026, explica clientes, timeouts, límites, streaming, async y HTTP/2 opcional.

Centralizar la frontera HTTP

Configura base_url, autenticación, headers, timeouts y límites una vez. No guardes tokens en el repositorio ni en URLs.

def crear_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),
    )

Prueba la URL final porque una barra inicial cambia su combinación. Usa params= para query, json= para JSON, data= para formularios y files= para multipart. No construyas escapes o boundaries manualmente.

Distinguir timeouts y errores

HTTPX separa connect, read, write y pool. Read limita la espera por datos, no toda una descarga. Pool limita la espera por una conexión.

def buscar_usuario(client: httpx.Client, user_id: int) -> dict:
    try:
        respuesta = client.get(f"users/{user_id}")
        respuesta.raise_for_status()
    except httpx.TimeoutException as exc:
        raise RuntimeError("el servicio agotó el tiempo") from exc
    except httpx.HTTPStatusError as exc:
        if exc.response.status_code == 404:
            raise LookupError(user_id) from exc
        raise RuntimeError("el servicio rechazó la petición") from exc

    datos = respuesta.json()
    if not isinstance(datos, dict) or "id" not in datos:
        raise RuntimeError("respuesta inválida")
    return datos

No expongas cuerpos remotos. Registra estado e identificador, ocultando autorización y datos personales. Incluso un 200 requiere media type y esquema esperados.

Streaming con límite

.content carga todo. Para descargas usa stream() y cuenta bytes, pues Content-Length puede faltar:

LIMITE = 5_000_000
with httpx.Client(timeout=10.0) as client:
    with client.stream("GET", "https://example.com/a.bin") as respuesta:
        respuesta.raise_for_status()
        total = 0
        with open("archivo.bin", "wb") as salida:
            for bloque in respuesta.iter_bytes():
                total += len(bloque)
                if total > LIMITE:
                    raise ValueError("descarga demasiado grande")
                salida.write(bloque)

Elige el destino sin confiar en nombres externos. Escribe temporalmente y mueve tras validar.

Ciclo de vida y async

Crea el cliente al iniciar el componente y ciérralo al terminar. Un cliente por llamada pierde el pool; un global sin dueño complica pruebas. Las cookies persisten, así que no mezcles usuarios.

Mantén AsyncClient durante la vida del componente asíncrono. Limita tareas porque las corutinas en cola consumen memoria. No compartas cliente entre event loops. En programas síncronos usa Client.

Retry, redirects y TLS

Reintenta solo fallos transitorios y operaciones repetibles, con pocos intentos, backoff, jitter y plazo total. Respeta Retry-After. Un POST que cobra exige idempotency key aceptada por el servidor. Errores de autenticación o validación necesitan corrección.

Los redirects son deliberados. Si la URL procede del usuario, valida destino y cada salto para reducir SSRF. Mantén TLS verificado; verify=False no es solución de producción.

Uploads, logs y pruebas

Cierra archivos subidos, valida tipo y tamaño y no uses un nombre externo como destino. Al enviar JSON, distingue campo omitido de null.

Los hooks deben ser rápidos y ocultar tokens, cookies y parámetros sensibles. Las métricas deben separar conexión, pool, lectura, estado, decoding y esquema con etiquetas limitadas.

MockTransport comprueba método, URL, headers y cuerpo sin red pública. Cubre JSON inválido, redirects, timeout, límite de descarga y estados de dominio. Usa servidor local en algunas pruebas de streaming.

Antes del despliegue confirma timeout finito, conexiones limitadas, TLS, redirects, secretos protegidos, respuesta validada y cierre. HTTP/2 opcional no elimina controles. Una capa HTTP pequeña debe devolver valores de dominio predecibles.

Propiedad en una aplicación web

Crea el cliente durante startup y ciérralo durante shutdown. En un framework con inyección de dependencias, registra ese ciclo. Un cliente por petición entrante pierde conexiones reutilizables; un global creado al importar dificulta pruebas y cierre.

Las cookies persisten dentro del cliente. Esto ayuda en una sesión deliberada, pero es peligroso si un objeto compartido mezcla usuarios. Usa autorización por petición o clientes aislados cuando el estado pertenece a una persona. Revisa headers predeterminados antes de reenviar llamadas.

Cuerpos y uploads

Abre archivos con context manager y ciérralos después. Para cuerpos grandes o generados gradualmente, usa streaming. Valida tamaño, tipo y permisos antes de transmitir, pues el remoto puede rechazar solo después de recibir todo.

Convierte fechas, decimales y objetos explícitamente al producir JSON. No envíes __dict__ sin revisar: puede incluir atributos internos. Distingue campo omitido de null según el contrato.

Validar más que el estado

raise_for_status() no valida el cuerpo. Comprueba media type, cuerpo vacío, campos y tipos. Un proxy puede devolver HTML con 200. Traduce el payload a objetos de dominio en una capa pequeña para que el resto no dependa de headers o nombres inestables.

Cada clase de error necesita acción distinta: conexión puede justificar retry; espera de pool sugiere saturación; decoding señala respuesta inesperada; schema indica incompatibilidad. Conserva la causa original al traducir excepciones.

Observabilidad segura

Registra servicio, ruta normalizada, duración, estado e identificador de correlación. Nunca incluyas tokens, cookies, claves, query sensible o cuerpos personales. Limita muestras y usa etiquetas acotadas.

Los hooks forman parte del camino crítico. Úsalos para tracing ligero, no para I/O adicional. En concurrencia, evita mutar headers globales; construye valores específicos por petición.

Estrategia de pruebas

MockTransport permite que un handler inspeccione la petición y devuelva una respuesta controlada. Comprueba URL combinada, query repetida, JSON, autorización redactada y traducción de errores. Cubre JSON malformado, cuerpo vacío, 404, 429, 500, timeout, redirect bloqueado y descarga demasiado grande.

Añade pocos tests con servidor local para streaming, uploads y protocolo. No hagas depender la suite de una API pública. Comprueba que respuesta, stream, archivo y cliente se cierran aunque falle parsing o escritura.

Presupuesto de retry

Documenta operaciones idempotentes y aquellas protegidas por idempotency key. El tiempo de todas las tentativas debe caber en el plazo del llamador. Tres intentos de diez segundos no sirven si la operación completa dispone de quince.

Aplica backoff exponencial y jitter para que muchas instancias no repitan a la vez. Respeta Retry-After sin superar el plazo total. Expón la cantidad de intentos en logs y métricas. Nunca repitas indefinidamente errores permanentes.

Proxy, HTTP/2 y límites

Configura proxies deliberadamente cuando variables del entorno serían sorprendentes. HTTP/2 puede multiplexar conexiones, pero requiere extras compatibles y pruebas con el servidor. No sustituye límites de tareas, tamaños ni timeouts.

Revisa max_connections junto con concurrencia async y capacidad del servicio remoto. Una cola enorme delante de un pool pequeño puede consumir memoria y vencer plazos antes de comenzar. Limita trabajo en el nivel de aplicación.

Con estas decisiones, HTTPX queda concentrado en una frontera mantenible: la red puede ser lenta, fallar o devolver datos hostiles, mientras el resto del programa recibe valores validados y errores comprensibles.