Зависимости в FastAPI: сессии, транзакции и тесты
Автор: Кирилл Сермягин
Зависимости — главная фишка FastAPI и главный источник утечек соединений. Если сессия базы создаётся в зависимости, важно, кто и когда её закрывает, в какой момент коммитится транзакция и как подменить всё это в тестах.
Сессия на запрос
Зависимость с yield работает как контекстный менеджер: код после yield выполнится, когда ответ уже сформирован.
from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db() -> AsyncIterator[AsyncSession]:
async with SessionLocal() as session:
async with session.begin():
yield session # commit при успехе, rollback при исключении
DB = Annotated[AsyncSession, Depends(get_db)]
@app.post("/orders")
async def create_order(payload: OrderIn, db: DB) -> OrderOut:
order = Order(**payload.model_dump())
db.add(order)
await db.flush()
return OrderOut.model_validate(order)
Когда закрывается сессия
С FastAPI 0.121 у зависимости с yield можно указать scope="request" — тогда выход выполняется после отправки ответа, а по умолчанию ("function") — сразу после обработчика. Для сессии базы нужен второй вариант: соединение возвращается в пул раньше.
Подмена в тестах
В тестах подменяем зависимость одной строкой — приложение даже не узнает, что работает с тестовой базой.
@pytest.fixture
async def client(session):
app.dependency_overrides[get_db] = lambda: session
async with AsyncClient(transport=ASGITransport(app), base_url="http://test") as c:
yield c
app.dependency_overrides.clear()
- Не создавайте движок базы внутри зависимости — только сессию.
- Не коммитьте в обработчике, если транзакцией управляет зависимость.
- Очищайте
dependency_overridesпосле теста.