Зависимости с yield и управление ресурсами
Зависимость с yield отдаёт ресурс до yield и гарантированно прибирает его после — код после yield выполняется, когда запрос завершён.
Это паттерн «открыть — отдать — закрыть»: до
yieldвы создаёте сессию БД, отдаёте её обработчику черезyield, а после — закрываете, даже если внутри произошла ошибка.
Ресурсы нужно не только создавать, но и надёжно освобождать: соединение с базой закрыть, файл закрыть, транзакцию завершить. Если делать это вручную в каждом обработчике, рано или поздно где-то забудешь — и получишь утечку соединений. FastAPI решает это зависимостями с yield: то, что до yield, — подготовка, то, что после, — гарантированная уборка.
from fastapi import Depends
from typing import Annotated
def get_db():
db = SessionLocal() # открываем сессию
try:
yield db # отдаём её обработчику
finally:
db.close() # закрываем ВСЕГДА, даже при ошибке
DBSession = Annotated[Session, Depends(get_db)]
@app.get("/items")
async def list_items(db: DBSession):
return db.query(Item).all()
Ключевой момент — try/finally: блок finally выполнится в любом случае, поэтому сессия закроется и при успехе, и при исключении в обработчике. По сути зависимость с yield ведёт себя как контекстный менеджер: вход — до yield, выход — после.
Как работает под капотом
FastAPI продвигает генератор до yield, забирает отданное значение, выполняет обработчик, а затем «дотягивает» генератор до конца, исполняя уборку. Смоделируем этот жизненный цикл на stdlib:
def get_db():
print("1) открываем сессию БД")
db = {"connected": True}
try:
yield db
finally:
db["connected"] = False
print("4) закрываем сессию БД (finally)")
# то, как FastAPI «прокручивает» зависимость вокруг обработчика
gen = get_db()
db = next(gen) # доходим до yield -> получаем ресурс
print("2) обработчик использует сессию, connected =", db["connected"])
print("3) обработчик вернул результат")
try:
next(gen) # дотягиваем генератор -> срабатывает finally
except StopIteration:
pass
print("после запроса connected =", db["connected"])
Попробуй сам ▶ Порядок 1-2-3-4 показывает: уборка (4) гарантированно следует за работой обработчика. Попробуй кинуть исключение между шагами — finally всё равно сработает.
Порядок при нескольких зависимостях с yield — стековый: уборка идёт в обратном порядке создания (как в матрёшке контекстных менеджеров).
Частые ошибки
Первая и критичная — забыть try/finally и закрывать ресурс только при успехе; при исключении он утечёт. Вторая — закрывать сессию вручную внутри обработчика, дублируя логику и ломая контракт зависимости. Третья — выполнять тяжёлую работу после yield (уборка должна быть быстрой). Четвёртая — путать, что код после yield выполняется не сразу, а по завершении запроса.
Best practices
- Всегда оборачивайте уборку в
try/finally, чтобы она срабатывала и при ошибках. - Открывайте ресурс до
yield, отдавайте черезyield, закрывайте после. - Держите код после
yieldлёгким — это уборка, а не бизнес-логика. - Для async-ресурсов используйте
async defзависимость сyieldиawait-закрытием.
Async-зависимости и порядок уборки
Для асинхронных ресурсов зависимость с yield объявляют как async def и закрывают ресурс через await — например, асинхронную сессию SQLAlchemy. Важно понимать порядок уборки при нескольких зависимостях с yield: он стековый, обратный порядку создания, как у вложенных контекстных менеджеров. Если зависимость A открылась первой, а B — второй, то при завершении запроса сначала уберётся B, потом A. Это гарантирует корректность, когда B зависит от A (например, транзакция внутри соединения): нельзя закрыть соединение раньше, чем завершить транзакцию. FastAPI соблюдает этот порядок автоматически. Ещё нюанс: если исключение возникло в обработчике, оно «прокидывается» в зависимости с yield, и вы можете перехватить его в блоке вокруг yield, чтобы, скажем, откатить транзакцию — именно так и строят транзакционные сессии.
Итог: зависимость с yield — это контекстный менеджер для ресурсов: подготовка до yield, гарантированная уборка после, защищённая try/finally. Это стандартный способ управлять сессиями БД в FastAPI.