Когда пишешь первый эндпоинт, кажется: просто прочитай из базы и верни. Но быстро появляются вопросы: где создавать подключение к базе? Как проверить токен? Как в тестах обойтись без реальной базы? Без ответа на эти вопросы каждый эндпоинт превращается в мешанину из разного кода.
FastAPI решает это через механизм Depends — встроенное внедрение зависимостей. Разберём, что это такое и зачем.
Зачем вообще нужен Depends
Представим простой эндпоинт: прочитать настройки из конфигурации и вернуть их.
Без Depends это выглядит так:
settings = Settings() # создаём один раз глобально
@router.get("/info")
async def info():
return {"debug": settings.debug}
Сейчас работает. Но как только нужно написать тест — приходится либо патчить глобальную переменную, либо переписывать код. А если настройки нужны в десяти эндпоинтах? А если кроме настроек — ещё сессия базы и текущий пользователь?
Depends решает это чисто: зависимость объявляется один раз как функция, FastAPI сам её вызывает и передаёт результат в эндпоинт.
Зависимость — обычная функция
Зависимость — это любая функция (или класс). FastAPI видит её в сигнатуре эндпоинта, вызывает и подставляет результат. Объявляется через Annotated[Тип, Depends(функция)]:
from typing import Annotated
from fastapi import Depends
from app.config import Settings, get_settings
@router.get("/info")
async def info(settings: Annotated[Settings, Depends(get_settings)]):
return {"debug": settings.debug}
get_settings — просто функция:
def get_settings() -> Settings:
return Settings()
Стиль с Annotated читается явно: тип — это Settings, а откуда его взять — через get_settings. Чтобы не повторять одно и то же в каждом эндпоинте, зависимость выносят в псевдоним типа:
SettingsDep = Annotated[Settings, Depends(get_settings)]
@router.get("/info")
async def info(settings: SettingsDep):
return {"debug": settings.debug}
Дерево зависимостей
Зависимость сама может зависеть от других. FastAPI строит дерево и разрешает его автоматически — сверху вниз:
def get_repository(session: SessionDep) -> ProductRepository:
return ProductRepository(session)
def get_handler(
repo: Annotated[ProductRepository, Depends(get_repository)],
) -> CreateProductHandler:
return CreateProductHandler(repo)
@router.post("/products")
async def create_product(handler: Annotated[CreateProductHandler, Depends(get_handler)]):
...
Здесь session → repository → handler — три уровня, и FastAPI разберётся со всем сам.
Важная деталь: если одна и та же зависимость нужна нескольким местам в дереве, FastAPI вызовет её один раз на запрос и передаст один и тот же объект везде. Это называют кэшированием зависимостей. Именно поэтому сессию базы удобно объявить как зависимость — она будет одна на запрос, независимо от того, сколько мест её используют.
yield-зависимости: ресурс с очисткой
Сессию базы нужно не только открыть, но и закрыть — даже если в эндпоинте произошла ошибка. Для этого существуют yield-зависимости.
Код до yield — подготовка ресурса. Код после yield — очистка. FastAPI гарантирует, что очистка выполнится после завершения запроса в любом случае:
from collections.abc import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
async def get_session(request: Request) -> AsyncGenerator[AsyncSession, None]:
session_factory = async_sessionmaker(request.app.state.engine, expire_on_commit=False)
async with session_factory() as session:
yield session
SessionDep = Annotated[AsyncSession, Depends(get_session)]
async with закрывает сессию сразу после yield — это и есть очистка. Один сеанс живёт ровно один запрос.
Такой же принцип работает для любого ресурса, который нужно освободить: файл, HTTP-клиент, соединение с очередью.
Переопределение зависимостей в тестах
Главная практическая выгода Depends — любую зависимость можно подменить в тестах, не трогая код эндпоинтов.
Делается через app.dependency_overrides: указываем, какую функцию-зависимость заменить и на что:
async def get_test_session() -> AsyncGenerator[AsyncSession, None]:
async with test_session_factory() as session:
yield session
app.dependency_overrides[get_session] = get_test_session
Теперь в тестах эндпоинты работают с тестовой базой. Реальный код не изменился ни на строчку. Именно ради этого стоит проводить через Depends всё, что обращается наружу — подменяемость встроена с самого начала.
Зависимость — это сборка, а не логика
Важная граница: Depends отвечает на вопрос «откуда взять объект», а не «что с ним делать». Логика — в том коде, который получает этот объект, а не в самой зависимости.
Если в функцию-зависимость начинает проникать ветвление по бизнес-правилам — это сигнал, что она разрослась не туда. Зависимость остаётся тонкой: создала объект, передала — и всё.
Коротко
Depends— встроенный механизм внедрения зависимостей в FastAPI; зависимость — это просто функция.- FastAPI сам вызывает зависимость перед эндпоинтом и передаёт результат аргументом.
- Зависимости образуют дерево: одна зависимость может зависеть от другой, FastAPI разрешает всё автоматически.
- Одинаковая зависимость вызывается один раз на запрос и кэшируется — это безопасно использовать в нескольких местах.
yield-зависимость гарантирует очистку ресурса после запроса, даже при ошибке.- Любую зависимость можно подменить в тестах через
app.dependency_overrides— это ключевая причина использовать Depends для всего, что работает с внешними ресурсами.
Что почитать дальше
- Работа с базой данных: SQLAlchemy — как устроена сессия и транзакции в FastAPI.
- Безопасность и аутентификация — как через Depends реализуется проверка токенов.
- Тестирование FastAPI-приложений — подробнее о dependency_overrides и тестовых фикстурах.