FastAPI dependency injection и провайдеры

10 сентября 2026
Автор

Антон Ларичев

Что такое dependency injection в FastAPI

Dependency Injection (внедрение зависимостей) — паттерн проектирования, при котором компонент получает свои зависимости извне, а не создаёт их самостоятельно. FastAPI реализует этот паттерн через механизм Depends().

Вместо того чтобы каждый обработчик маршрута самостоятельно создавал подключение к базе данных, проверял токен или читал конфигурацию, всё это выносится в отдельные функции-провайдеры. FastAPI автоматически вызывает их перед обработчиком и передаёт результат в нужный параметр.

Преимущества подхода:

  • Повторное использование логики без дублирования кода
  • Простота тестирования через замену зависимостей
  • Автоматическое управление жизненным циклом ресурсов (соединения с БД, HTTP-клиенты)
  • Явное объявление того, что нужно маршруту для работы

Курс по теме

Научиться программировать на Python

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Базовый синтаксис Depends()

Простейшая зависимость — функция, возвращающая значение, которое FastAPI передаёт в маршрут:

from fastapi import FastAPI, Depends

app = FastAPI()

def get_query_limit() -> int:
    return 100

@app.get("/items")
async def get_items(limit: int = Depends(get_query_limit)):
    return {"limit": limit, "items": []}

Depends(get_query_limit) говорит FastAPI: перед вызовом get_items вызови get_query_limit() и передай результат в параметр limit.

Зависимости с параметрами из запроса

Зависимости могут принимать параметры из запроса так же, как и сами маршруты. FastAPI анализирует сигнатуру функции-провайдера и автоматически извлекает нужные значения:

from fastapi import FastAPI, Depends, Query

app = FastAPI()

async def pagination_params(
    skip: int = Query(0, ge=0),
    limit: int = Query(10, ge=1, le=100)
) -> dict:
    return {"skip": skip, "limit": limit}

@app.get("/users")
async def get_users(pagination: dict = Depends(pagination_params)):
    return {
        "skip": pagination["skip"],
        "limit": pagination["limit"],
        "users": []
    }

@app.get("/products")
async def get_products(pagination: dict = Depends(pagination_params)):
    return {
        "skip": pagination["skip"],
        "limit": pagination["limit"],
        "products": []
    }

Функция pagination_params переиспользуется в двух маршрутах. Если один и тот же провайдер указан несколько раз в рамках одного запроса, FastAPI вызывает его только один раз и кэширует результат.

Классы как провайдеры зависимостей

FastAPI поддерживает классы в роли зависимостей. При вызове Depends(SomeClass) FastAPI создаёт экземпляр, передавая параметры запроса в __init__:

from fastapi import FastAPI, Depends

app = FastAPI()

class FilterParams:
    def __init__(
        self,
        search: str | None = None,
        category: str | None = None,
        min_price: float = 0.0,
        max_price: float = 9999.0
    ):
        self.search = search
        self.category = category
        self.min_price = min_price
        self.max_price = max_price

@app.get("/products")
async def get_products(filters: FilterParams = Depends(FilterParams)):
    return {
        "search": filters.search,
        "category": filters.category,
        "price_range": [filters.min_price, filters.max_price]
    }

Современный способ — использовать Annotated, чтобы не повторять тип класса дважды:

from typing import Annotated
from fastapi import FastAPI, Depends

app = FastAPI()

FiltersDep = Annotated[FilterParams, Depends()]

@app.get("/products")
async def get_products(filters: FiltersDep):
    return {"search": filters.search}

Когда Depends() вызывается без аргумента внутри Annotated, FastAPI использует сам тип аннотации (FilterParams) как провайдер.

Цепочки зависимостей

Зависимости могут иметь собственные зависимости — FastAPI строит граф и разрешает их в правильном порядке:

from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

async def get_token(x_token: str = Header(...)) -> str:
    if x_token != "secret-token":
        raise HTTPException(status_code=403, detail="Invalid token")
    return x_token

async def get_current_user(token: str = Depends(get_token)) -> dict:
    return {"id": 1, "name": "Alice", "token": token}

@app.get("/profile")
async def get_profile(user: dict = Depends(get_current_user)):
    return user

Цепочка выглядит так: FastAPI вызывает get_token → получает токен → передаёт в get_current_user → получает пользователя → вызывает get_profile. Ошибка на любом шаге прерывает выполнение и возвращает соответствующий HTTP-ответ.

Управление сессиями базы данных

Стандартный паттерн для работы с ORM-сессиями — генератор с yield. FastAPI гарантирует выполнение кода после yield даже при возникновении исключения:

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

DATABASE_URL = "postgresql://user:password@localhost/dbname"

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/{user_id}")
async def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

Код до yield выполняется перед обработчиком (открытие сессии), код после — после завершения запроса (закрытие сессии). Блок finally гарантирует закрытие сессии при любом исходе.

Зависимости на уровне роутера и приложения

Зависимости можно применять не только к отдельным маршрутам, но и ко всему роутеру или приложению целиком:

from fastapi import FastAPI, APIRouter, Depends, Header, HTTPException

async def verify_api_key(x_api_key: str = Header(...)):
    if x_api_key != "valid-api-key":
        raise HTTPException(status_code=401, detail="Invalid API key")

router = APIRouter(
    prefix="/admin",
    dependencies=[Depends(verify_api_key)]
)

@router.get("/stats")
async def get_stats():
    return {"total_users": 1000}

@router.delete("/cache")
async def clear_cache():
    return {"message": "Cache cleared"}

app = FastAPI()
app.include_router(router)

Оба маршрута /admin/stats и /admin/cache автоматически требуют валидный API-ключ. Для защиты всего приложения:

app = FastAPI(dependencies=[Depends(verify_api_key)])

Кэширование зависимостей

По умолчанию FastAPI кэширует результат зависимости в рамках одного запроса. Если одна зависимость используется в нескольких местах, функция-провайдер вызывается только один раз:

from fastapi import FastAPI, Depends
import uuid

app = FastAPI()

def get_request_id() -> str:
    return str(uuid.uuid4())

def get_logger(request_id: str = Depends(get_request_id)) -> dict:
    return {"logger": f"logger-{request_id}"}

@app.get("/test")
async def test_endpoint(
    request_id: str = Depends(get_request_id),
    logger: dict = Depends(get_logger)
):
    # request_id и logger["logger"] содержат один и тот же UUID
    return {"request_id": request_id, "logger": logger}

Если нужно отключить кэширование — передайте use_cache=False:

@app.get("/test")
async def test_endpoint(
    id1: str = Depends(get_request_id, use_cache=False),
    id2: str = Depends(get_request_id, use_cache=False)
):
    # id1 и id2 будут разными UUID
    return {"id1": id1, "id2": id2}

Async-зависимости

Зависимости могут быть синхронными и асинхронными. FastAPI обрабатывает оба варианта корректно:

import httpx
from fastapi import FastAPI, Depends

app = FastAPI()

async def get_remote_config() -> dict:
    async with httpx.AsyncClient() as client:
        response = await client.get("https://config-service/api/config")
        return response.json()

@app.get("/settings")
async def get_settings(config: dict = Depends(get_remote_config)):
    return config

Синхронные зависимости FastAPI выполняет в thread pool, не блокируя event loop. Асинхронные зависимости с yield тоже поддерживаются — механизм тот же, что и в примере с базой данных.

Тестирование с переопределением зависимостей

Одно из главных преимуществ DI — простота тестирования. FastAPI предоставляет app.dependency_overrides для замены реальных зависимостей на тестовые заглушки:

from fastapi.testclient import TestClient
from myapp.main import app
from myapp.database import get_db
from myapp.auth import get_current_user

TestingSessionLocal = sessionmaker(bind=test_engine)

def get_db_override():
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()

def get_current_user_override():
    return {"id": 1, "name": "Test User", "role": "user"}

app.dependency_overrides[get_db] = get_db_override
app.dependency_overrides[get_current_user] = get_current_user_override

client = TestClient(app)

def test_get_profile():
    response = client.get("/profile")
    assert response.status_code == 200
    assert response.json()["name"] == "Test User"

Переопределения применяются глобально для данного экземпляра приложения. После тестов их стоит очищать:

def teardown_module():
    app.dependency_overrides.clear()

Практический пример: многоуровневая авторизация

Собираем типичную систему авторизации с разделением ролей:

from typing import Annotated
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
import jwt

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

async def get_current_user(
    token: str = Depends(oauth2_scheme)
) -> dict:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid token payload"
            )
        return {"id": int(user_id), "role": payload.get("role", "user")}
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token expired"
        )
    except jwt.JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token"
        )

def require_admin(
    user: Annotated[dict, Depends(get_current_user)]
) -> dict:
    if user["role"] != "admin":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Admin access required"
        )
    return user

CurrentUser = Annotated[dict, Depends(get_current_user)]
AdminUser = Annotated[dict, Depends(require_admin)]

@app.get("/profile")
async def get_profile(user: CurrentUser):
    return user

@app.delete("/users/{user_id}")
async def delete_user(user_id: int, admin: AdminUser):
    return {"message": f"User {user_id} deleted by admin {admin['id']}"}

@app.get("/admin/stats")
async def get_admin_stats(admin: AdminUser):
    return {"total_users": 1000, "requested_by": admin["id"]}

Здесь require_admin зависит от get_current_user, а маршруты используют готовые типы CurrentUser и AdminUser. Добавить новый защищённый маршрут — одна строка.

Когда использовать DI, а когда нет

Dependency injection в FastAPI уместен для:

  • Подключения к базе данных или внешнему сервису (одно соединение на запрос)
  • Извлечения и валидации JWT-токена
  • Общих параметров пагинации, фильтрации, сортировки
  • Проверки прав доступа
  • Получения конфигурации из переменных окружения

Не стоит оборачивать в Depends() простую бизнес-логику, которая используется только в одном месте, или синхронные вычисления без побочных эффектов — это усложняет код без реальной пользы.

Изучить FastAPI и Python глубже, включая работу с базами данных, аутентификацию и развёртывание, можно на курсе Python на PurpleSchool.

Стрелочка влевоPython functools — встроенные инструменты для функций

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonКак работает команда print в PythonПочему Python выводит значение без команды printВозможности Python для автоматизации задачРабота с JSON в Python на примерахPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаКоманда python print - полное руководство по выводу данныхПравила именования переменных в PythonПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Загрузка данных PythonУправление проектами на GitHub с PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий