Короткий ответ
requests.get — синхронный блокирующий вызов; выполняясь внутри async def, он останавливает весь event loop на время сетевого запроса, замораживая обработку всех остальных конкурентных запросов приложения в этом процессе; решение — использовать асинхронный HTTP-клиент (httpx.AsyncClient) с await, либо вынести вызов в threadpool.
Как это работает подробнее
Event loop asyncio однопоточно переключается между корутинами в точках await. Вызов обычной блокирующей функции без await — это обычный синхронный Python-код, который выполняется от начала до конца, не отдавая управление обратно event loop, то есть другие корутины не могут продолжить выполнение в это время.
Пример кода
example.py
import requests
from fastapi import FastAPI
app = FastAPI()
# Проблема: блокирует event loop на время сетевого запроса
@app.get("/bad-proxy")
async def bad_proxy():
r = requests.get("https://example.com/api")
return r.json()
# Исправление: асинхронный клиент
import httpx
@app.get("/good-proxy")
async def good_proxy():
async with httpx.AsyncClient() as client:
r = await client.get("https://example.com/api")
return r.json()Что использовать на практике
- Практика. Правильное исправление — заменить
requestsнаhttpx.AsyncClient(илиaiohttp) и использоватьawait client.get(...). Если библиотека принципиально синхронная и без асинхронной альтернативы, можно вызвать её черезawait run_in_threadpool(requests.get, url)(Starlette) илиloop.run_in_executor, чтобы не блокировать event loop, хотя это не даёт истинного параллелизма для CPU-bound части.
Подводные камни
- Риск. Оставляют синхронный клиент «временно», и проблема незаметна при низкой нагрузке в разработке, но проявляется под нагрузкой в production как резкий рост latency у несвязанных запросов; переходят на
async def«по инерции», не проверяя, что все вызываемые библиотеки внутри действительно асинхронные.