Урок курса

Зависимости через Depends

FastAPI для начинающих: API с базой данных и тестами

Обработчики в роутере пока вызывают вспомогательные функции вручную. Depends поручает FastAPI вызов функции и передаёт её результат в параметр обработчика; поиск записи или подготовка параметров остаются в самой зависимости.

Depends: функция и её результат в аргументе обработчика

FastAPI импортируется вместе с Depends из того же пакета:

from fastapi import APIRouter, Depends

Когда FastAPI видит параметр обработчика с Depends(...), он не ждёт, что значение придёт снаружи — из пути или query. Вместо этого FastAPI сам вызывает переданную функцию, берёт то, что она вернула, и подставляет результат в этот параметр.

Простой пример с функцией пагинации:

def get_pagination(skip: int = 0, limit: int = 10) -> dict:
    return {"skip": skip, "limit": limit}

@router.get("")
def list_rooms(pagination: dict = Depends(get_pagination)):
    rooms = list(rooms_db.values())
    return rooms[pagination["skip"]: pagination["skip"] + pagination["limit"]]

Здесь pagination — не query-параметр и не path-параметр. FastAPI вызывает get_pagination, получает словарь {"skip": ..., "limit": ...} и передаёт его в list_rooms как значение pagination. Обработчик вообще не видит skip и limit напрямую.

Функция без скобок. В Depends(get_pagination) передаётся сама функция — объект, который FastAPI сможет вызвать при обработке каждого запроса. Если написать Depends(get_pagination()) со скобками, Python вызовет get_pagination немедленно, в момент загрузки модуля, и передаст в Depends обычный словарь {"skip": 0, "limit": 10}. Словарь — не вызываемый объект, FastAPI не сможет его использовать как зависимость, и при старте приложения маршрут не зарегистрируется. Декоратор выполняется при импорте модуля, поэтому этот импорт тоже прервётся ошибкой регистрации.

Для показанной зависимости передайте get_pagination, а не результат вызова get_pagination(): функция возвращает словарь, который нельзя вызвать как зависимость.

get_pagination: откуда берутся skip и limit

Функция-зависимость — обычная Python-функция, и FastAPI применяет к её параметрам те же правила, что и к параметрам обработчика.

Для get_pagination(skip: int = 0, limit: int = 10) ни skip, ни limit не совпадают ни с одной переменной пути в маршруте GET /api/rooms. Поэтому FastAPI читает их из query-строки. Если параметр отсутствует в запросе, берётся default. Если значение не конвертируется в int, FastAPI возвращает 422 до вызова обработчика — прямо на этапе разбора зависимости.

Для самостоятельного примера пагинации создайте отдельный каталог pagination_demo, а в нём два файла ниже и пустой routers/__init__.py. Это сокращённый пример с тремя готовыми записями: не заменяйте им рабочий CRUD-проект из урока16.

routers/rooms.py

from fastapi import APIRouter, Depends

router = APIRouter()

rooms_db: dict[int, dict] = {
    1: {"id": 1, "name": "Альфа", "capacity": 6},
    2: {"id": 2, "name": "Бета",  "capacity": 12},
    3: {"id": 3, "name": "Гамма", "capacity": 4},
}

def get_pagination(skip: int = 0, limit: int = 10) -> dict:
    return {"skip": skip, "limit": limit}

@router.get("")
def list_rooms(pagination: dict = Depends(get_pagination)):
    rooms = list(rooms_db.values())
    return rooms[pagination["skip"]: pagination["skip"] + pagination["limit"]]

main.py

from fastapi import FastAPI
from routers import rooms

app = FastAPI()
app.include_router(rooms.router, prefix="/api/rooms")

Запустите из каталога рядом с main.py:

uvicorn main:app --reload

В запущенном pagination_demo доступны следующие запросы:

  • GET /api/rooms — нет query, оба defaults вступают в силу, возвращаются все три записи.
  • GET /api/rooms?skip=1&limit=2 — FastAPI передаёт skip=1, limit=2 в get_pagination; та возвращает словарь; срез rooms[1:3] даёт записи с id=2 и id=3.
  • GET /api/rooms?skip=abc — строку abc нельзя привести к int, FastAPI отвечает 422; один из элементов списка detail в JSON-ответе содержит "loc": ["query", "skip"]. До list_rooms выполнение не доходит.

Все значения по умолчанию и логика сборки словаря сосредоточены в get_pagination. Если завтра появится второй маршрут с пагинацией, он подключит ту же функцию и получит те же параметры без копирования кода.

get_room_or_404 как зависимость: room_id берётся из пути

Вернитесь к рабочему CRUD-проекту из урока16. В его routers/rooms.py уже есть RoomOut, get_room_or_404 и нужные импорты; сокращённый pagination_demo из предыдущего раздела здесь не используется. Добавьте Depends к импорту из fastapi. get_room_or_404 ищет переговорную или выбрасывает HTTPException(404). Прежний обработчик вызывал её вручную:

@router.get("/{room_id}", response_model=RoomOut)
def get_room(room_id: int):
    return get_room_or_404(room_id)

Замените прежний GET по id вместе с декоратором на фрагмент ниже; второй маршрут на тот же путь не добавляйте. С Depends вызов выполняет FastAPI:

@router.get("/{room_id}", response_model=RoomOut)
def get_room(room: dict = Depends(get_room_or_404)):
    return room

Сигнатура get_room_or_404(room_id: int) не меняется. FastAPI видит параметр room_id в зависимости и смотрит, есть ли в маршруте переменная пути с таким именем. Маршрут /{room_id} — есть. Значит, room_id читается из пути, а не из query.

Это проявляется в интересной ситуации: запрос GET /api/rooms/1?room_id=99 возвращает переговорную с id=1. Значение из query игнорируется, потому что имя совпало с переменной пути. Аннотация int задаёт только преобразование типа — она не меняет источник.

Если room_id=1 есть в rooms_db, зависимость возвращает найденный словарь, и FastAPI подставляет его в room. Обработчик просто возвращает room — отдельный аргумент room_id в нём больше не нужен.

Если room_id=99 нет в rooms_db, get_room_or_404 выбрасывает HTTPException(404). FastAPI перехватывает исключение и отправляет ответ 404 — тело обработчика не выполняется.

Изменился только способ вызова функции: раньше его выполнял обработчик, теперь FastAPI. Логика поиска в rooms_db остаётся той же.

Для существующей комнаты1 запрос GET /api/rooms/1?room_id=99 передаёт get_room_or_404 значение1 из пути. FastAPI передаёт найденную запись обработчику; query room_id99 не подменяет путь. В диаграмме обработчик назван read_room, в тексте — get_room.
Если комната1 существует, путь даёт зависимости room_id=1, а её результат становится room в обработчике. Query room_id=99 не используется. На схеме обработчик назван read_room; в коде — get_room.

Попробуйте решить

Контекст подключения: код роутера находится в routers/rooms.py; main.py создаёт app = FastAPI() и после импорта from routers import rooms вызывает app.include_router(rooms.router, prefix="/api/rooms"). В декораторах пишите только часть после prefix; путь списка — пустая строка.

Объявлена зависимость:

def get_pagination(skip: int = 0, limit: int = 10) -> dict:
    return {"skip": skip, "limit": limit}

И маршрут:

@router.get("")
def list_rooms(pagination: dict = Depends(get_pagination)):
    ...

Клиент отправляет GET /api/rooms?limit=5. Откуда FastAPI берёт значение skip при выполнении get_pagination?

Продолжить с проверкой и прогрессом

Откройте интерактивный раннер с заданиями урока.

Перейти к интерактивному уроку