Dependency Injection и Depends
Внедрение зависимостей (DI) в FastAPI — это способ объявить, что обработчику нужен результат другой функции; FastAPI сам вызовет её и подставит результат через Depends.
Суть DI: вы не создаёте нужные объекты внутри обработчика, а объявляете их как параметры. FastAPI выстраивает граф зависимостей и наполняет его за вас перед вызовом функции.
Во многих эндпоинтах повторяется одинаковая подготовка: проверить токен, открыть сессию БД, разобрать общие query-параметры пагинации. Копировать этот код в каждый обработчик — путь к ошибкам. FastAPI предлагает вынести его в отдельные функции-«зависимости» и подключать их через Depends. Обработчик просто говорит «мне нужен результат вот этой функции», и фреймворк его предоставляет.
from fastapi import FastAPI, Depends
from typing import Annotated
app = FastAPI()
# зависимость: общие параметры пагинации
def pagination(skip: int = 0, limit: int = 20):
return {"skip": skip, "limit": limit}
PageParams = Annotated[dict, Depends(pagination)]
@app.get("/items")
async def list_items(page: PageParams):
return {"page": page}
@app.get("/users")
async def list_users(page: PageParams):
return {"page": page}
Здесь функция pagination — зависимость; оба эндпоинта получают её результат, не дублируя разбор параметров. Современный стиль — завести псевдоним типа Annotated[dict, Depends(pagination)] и переиспользовать его. Зависимости сами могут принимать параметры запроса (как skip/limit здесь) — FastAPI разберёт их так же, как у обычного обработчика.
Как работает под капотом
FastAPI строит граф: у обработчика есть зависимости, у них — свои зависимости, и так далее. Перед вызовом обработчика фреймворк обходит граф, вычисляет каждую зависимость и кэширует её результат в пределах запроса. Смоделируем разрешение зависимостей на stdlib:
def get_db():
return "db-session"
def get_user(db): # зависит от get_db
return f"user(from {db})"
def handler(db, user): # зависит от обоих
return f"ответ: {user}, {db}"
# граф зависимостей: что от чего зависит
graph = {
"db": (get_db, []),
"user": (get_user, ["db"]),
"handler": (handler, ["db", "user"]),
}
cache = {}
def resolve(name):
if name in cache: # кэш в пределах одного запроса
return cache[name]
func, deps = graph[name]
args = [resolve(d) for d in deps] # сперва вычисляем зависимости
cache[name] = func(*args)
return cache[name]
print(resolve("handler"))
print("db вычислялся один раз:", cache["db"])
Попробуй сам ▶ Обрати внимание: db вычисляется один раз и переиспользуется обеими зависимостями — так же FastAPI кэширует зависимости внутри запроса.
Частые ошибки
Первая — создавать ресурсы (соединение с БД, клиент) прямо в обработчике вместо зависимости, теряя переиспользование и единое управление. Вторая — забывать, что результат зависимости кэшируется в пределах запроса: одна и та же зависимость не вычислится дважды (если не отключить кэш через use_cache=False). Третья — городить слишком глубокие графы зависимостей, усложняя отладку.
Best practices
- Выносите повторяющуюся подготовку (пагинация, текущий пользователь, сессия) в зависимости.
- Заводите псевдонимы типов
Annotated[T, Depends(...)]и переиспользуйте их. - Полагайтесь на кэш зависимостей в пределах запроса; отключайте его осознанно.
- Держите зависимости маленькими и сфокусированными — одну ответственность на зависимость.
Зачем DI, если можно просто вызвать функцию
Резонный вопрос новичка: зачем Depends, если общую логику можно вынести в обычную функцию и вызывать её руками в начале каждого обработчика? Отличие в четырёх вещах. Во-первых, зависимость сама участвует в разборе запроса: она может объявлять свои path/query/header-параметры, и FastAPI наполнит их. Во-вторых, результат кэшируется в пределах запроса, так что одна сессия БД не создастся пять раз. В-третьих, зависимости попадают в OpenAPI-схему: требования вроде токена видны в документации. В-четвёртых, их можно подменять в тестах через dependency_overrides, не трогая код обработчиков. Ручной вызов функции не даёт ничего из этого. Поэтому DI — не усложнение ради красоты, а механизм, который одновременно решает разбор параметров, переиспользование, документирование и тестируемость. Это инвестиция, окупающаяся на каждом эндпоинте.
Итог: DI позволяет объявлять потребности обработчика как параметры. FastAPI строит граф зависимостей, вычисляет их (с кэшем в пределах запроса) и подставляет результаты — это убирает дублирование и централизует общую логику.