Урок курса
Зависимости через 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
Контекст подключения: код роутера находится в 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?
Задача 2
Роутер подключён с prefix="/api/rooms". Маршрут @router.get("/{room_id}") принимает room: dict = Depends(get_room_or_404); зависимость объявлена как get_room_or_404(room_id: int). Клиент отправляет GET /api/rooms/1?room_id=99. Какое значение room_id получит зависимость и почему?
Задача 3
Просмотр каталога небольшими частями
Офис-менеджер в Roomly работает с каталогом переговорных комнат ежедневно: уточняет названия, проверяет описания, сверяет детали. Каталог может быть достаточно длинным, и просматривать его целиком за один раз неудобно — гораздо естественнее двигаться по нему небольшими частями, останавливаясь ровно там, где нужно.
Для этого менеджер указывает, с какого места в списке начать и сколько комнат показать за один раз. Если никаких уточнений не задано, просмотр начинается с самого начала в привычном порядке. Этот порядок остаётся одинаковым от запроса к запросу, а сам каталог при чтении, разумеется, не изменяется.
Помимо листания списка, менеджер может обратиться к конкретной переговорной напрямую и получить её карточку. Если такой комнаты в каталоге нет, система сообщает об этом понятно и однозначно. При этом важно, чтобы два режима работы — просмотр части списка и выбор конкретной комнаты — не смешивались: параметры листания не должны случайно подменять идентификатор комнаты, а запрос карточки не должен превращаться в настройку просмотра.
Программа должна корректно обслуживать оба сценария, сохраняя их независимость и возвращая именно те данные, которые менеджер запросил.
Что уже дано
Видимый файл — самостоятельное упражнение только на чтение, как pagination_demo из урока; оно не заменяет CRUD-проект. Даны app, router, полные Address/RoomOut, словарь rooms_db с тремя карточками и рабочая функция get_room_or_404. Поиск переписывать не нужно. Начальный каталог имеет id1,2,3; проверка может заменить rooms_db другим каталогом той же формы, включая пустой, чтобы проверить повторное чтение. Порядок записей — порядок вставки, не сортировка по id или имени.
Служебный переходник не создаёт приложение или модели. Он передаёт HTTP-запросы, может заменить входной rooms_db и вызвать get_pagination с обычными аргументами. Отдельно он передаёт безопасные метаданные регистрации Depends и диагностически подставляет результат зависимости, чтобы проверить его использование обработчиком. HTTP-запросы проверяют ответы и источник параметров. Писать переходник, подстановки или тесты не нужно.
Что нужно сделать
Реализуйте публичную функцию get_pagination(skip: int = 0, limit: int = 10) -> dict, возвращающую параметры под ключами skip и limit. GET /api/rooms должен получать этот словарь через Depends и возвращать соответствующую часть текущего каталога: пропустить skip записей и взять не более limit. Область задания — неотрицательные целые, в том числе limit=0 и skip за концом. Если query отсутствует, используются defaults функции; неконвертируемое значение даёт стандартный422 с loc query/skip или query/limit. Дополнительную политику для отрицательных значений вводить не нужно.
GET /api/rooms/{room_id} получает найденную запись через Depends от готового get_room_or_404. Источник room_id — путь, одноимённый query его не заменяет. Существующая комната даёт200, отсутствующая —404 с detail="Room not found", неверный тип path — стандартный422 с loc path/room_id. Чтение не изменяет каталог.
Оба маршрута возвращают полные публичные карточки: ровно id, name, capacity, address с city/street, comment. Служебные поля наружу не попадают. Подключите результаты обеих зависимостей как параметры обработчиков; одного ручного вызова или регистрации неиспользуемой зависимости недостаточно. Имена обработчиков и их параметров выбирайте сами.
Ввод и вывод
stdin не используется. Печатать через print в stdout ничего не нужно. После выполнения файла проверяющая система обращается к верхнеуровневому app: публичный переходник вызывает TestClient внутри процесса приложения и передаёт результат скрытой проверке. Запускать Uvicorn, сеть, клиентский скрипт или pytest не требуется.
Для начального каталога GET /api/rooms?skip=1&limit=1 возвращает список только с карточкой Бета (id2). GET /api/rooms/1?room_id=99 по-прежнему возвращает Альфу (id1).
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Зависимости через Depends — задача по FastAPI
Откуда FastAPI берёт значение skip при выполнении getpagination? Проверьте ответ в задаче из бесплатного курса FastAPI.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
