← назад к разделу

Когда пишешь первый эндпоинт, кажется: просто прочитай из базы и верни. Но быстро появляются вопросы: где создавать подключение к базе? Как проверить токен? Как в тестах обойтись без реальной базы? Без ответа на эти вопросы каждый эндпоинт превращается в мешанину из разного кода.

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)]):
    ...

Здесь sessionrepositoryhandler — три уровня, и 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 и тестовых фикстурах.