HTTPX в FastAPI: один AsyncClient на время работы приложения

API магазина запрашивает остатки у складского сервиса. Сначала вызов один, потом появляется список товаров, и обработчик делает десятки запросов. Если создавать новый HTTP-клиент для каждого товара, приложение снова и снова готовит соединения вместо их переиспользования.

У клиента есть собственный пул соединений. Поэтому удобно создавать его при запуске FastAPI, отдавать обработчикам и закрывать при остановке. При этом ответы и данные пользователей остаются локальными для запроса: общим становится транспорт, а не корзина покупателя.

Клиент живёт вместе с приложением

Установите python -m pip install fastapi uvicorn httpx. Сохраните пример в main.py. Адрес ниже — учебная замена внешнего сервиса: укажите свой API, прежде чем вызывать маршрут.

from contextlib import asynccontextmanager
import httpx
from fastapi import FastAPI, HTTPException, Request

@asynccontextmanager
async def lifespan(app: FastAPI):
    timeout = httpx.Timeout(5.0, connect=2.0, pool=1.0)
    limits = httpx.Limits(max_connections=20, max_keepalive_connections=10)
    async with httpx.AsyncClient(
        base_url="https://inventory.example.com",
        timeout=timeout, limits=limits,
    ) as client:
        app.state.inventory = client
        yield

app = FastAPI(lifespan=lifespan)

@app.get("/stock/{sku}")
async def stock(sku: int, request: Request):
    try:
        response = await request.app.state.inventory.get(f"/stock/{sku}")
        response.raise_for_status()
    except httpx.TimeoutException:
        raise HTTPException(504, "Склад не ответил вовремя") from None
    except httpx.HTTPError:
        raise HTTPException(502, "Не удалось получить остаток") from None
    return response.json()

Код после yield выполнится при штатном завершении lifespan; выход из async with закроет клиент. Один экземпляр используется внутри процесса. При нескольких серверных воркерах у каждого будет свой клиент и свои лимиты.

Лимит соединений не равен лимиту покупателей

max_connections ограничивает транспортный пул. Когда свободного соединения нет, запрос может ждать его и получить PoolTimeout. Это не сообщение «склад медленно считает»: до склада новый вызов мог вообще не дойти. Поэтому в диагностике полезно различать ожидание пула, установление соединения и чтение ответа.

В примере таймауты означают пределы отдельных операций, а не общий секундомер всей бизнес-операции. Если обработчик делает три последовательных вызова, итоговое ожидание может оказаться больше пяти секунд. Для общего бюджета времени нужен отдельный deadline.

Не переносите состояние пользователя в общий клиент

Токен конкретного покупателя передавайте в headers отдельного вызова. Изменять общие client.headers перед await опасно: следующий запрос другого пользователя может увидеть новое значение. То же относится к общей cookie-сессии, если внешний сервис авторизует клиентов через cookies.

Проверяйте успешный ответ, timeout, ошибочный статус и некорректный JSON отдельно. В этом примере JSON предполагается частью контракта склада; для реальной интеграции добавьте проверку структуры. Подробнее — разбор отказов внешнего API.

Собрать следующий этап проекта

Хотите собрать API целиком, а не останавливаться на отдельном обработчике? На бесплатном курсе FastAPI вы сделаете сервис с базой, регистрацией, проверкой прав и тестами. Это проект, который можно развивать дальше и показывать вместе с исходниками.

Посмотреть программу: FastAPI для начинающих: API с базой данных и тестами

Проверить детали: HTTPX: клиенты и пул, HTTPX: асинхронные клиенты.

Продолжить на практике

  • FastAPI для начинающих: API с базой данных и тестами ↗

    Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.