Урок курса

Path-параметры и типы

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

В прошлом уроке мы зарегистрировали маршрут GET /api/rooms и получили список комнат одним декоратором. Теперь добавим второй маршрут, который принимает конкретный идентификатор прямо в URL.

Значение из пути и аннотация int

Когда нужно обратиться к конкретному ресурсу — не ко всему списку, а к одной комнате — принято помещать идентификатор прямо в путь: /api/rooms/3. Чтобы FastAPI понял, что 3 — это переменная, а не фиксированный текст, соответствующий сегмент пути оборачивают в фигурные скобки.

@app.get("/api/rooms/{room_id}")
def get_room(room_id):
    return {"room_id": room_id}

Имя внутри скобок — room_id — должно совпадать с именем аргумента функции. Для GET /api/rooms/3 FastAPI извлечёт строку "3" из URL и передаст её в room_id. Именно строку: без аннотации типа никакого преобразования нет, и обработчик вернёт {"room_id": "3"}.

Добавим аннотацию:

@app.get("/api/rooms/{room_id}")
def get_room(room_id: int):
    return {"room_id": room_id}

Теперь тот же запрос GET /api/rooms/3 вернёт {"room_id": 3} — уже число. FastAPI прочитал строку "3" из пути, преобразовал её в int и только затем вызвал функцию. Сам Python этого не делает: обычная аннотация типа не превращает аргумент автоматически. Преобразование — работа FastAPI, выполняется до входа в тело функции.

Неверный тип: ответ 422 до обработчика

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

Отправьте GET /api/rooms/abc к маршруту с room_id: int. Строка "abc" не превращается в целое число, и функция get_room вообще не запускается. В ответ придёт статус 422 Unprocessable Entity и JSON с описанием проблемы:

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["path", "room_id"],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "abc"
    }
  ]
}

Поле loc показывает источник: "path" — значит, ошибка в сегменте пути, "room_id" — конкретный параметр. Это удобно, когда параметров несколько и нужно быстро найти виновника.

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

При room_id: int FastAPI преобразует строку 3 из пути в число 3 и вызывает get_room(3). Строка abc не проходит преобразование: возвращается 422, get_room не вызывается.
FastAPI проверяет и преобразует значение до вызова обработчика. Неверный тип даёт 422 без выполнения функции.

Поиск по id: существующий ключ и KeyError

Допустим, у нас есть каталог из двух переговорных комнат, хранящийся в словаре с целочисленными ключами:

from fastapi import FastAPI

app = FastAPI()

rooms_by_id = {
    1: {"id": 1, "name": "Лондон", "floor": 2},
    2: {"id": 2, "name": "Берлин", "floor": 3},
}

@app.get("/api/rooms")
def list_rooms():
    return list(rooms_by_id.values())

@app.get("/api/rooms/{room_id}")
def get_room(room_id: int):
    return rooms_by_id[room_id]

GET /api/rooms/1 вернёт {"id": 1, "name": "Лондон", "floor": 2}. FastAPI преобразует "1" в 1, функция обращается к словарю по ключу 1 — всё работает.

GET /api/rooms/abc — 422 ещё до словаря, как разобрали выше.

GET /api/rooms/999 — интереснее. Строка "999" успешно преобразуется в int, проверка типа пройдена. Но ключа 999 в rooms_by_id нет. Python выбросит KeyError, FastAPI не поймает его автоматически, и клиент получит 500 Internal Server Error.

Вот принципиальное различие: аннотация int гарантирует только форму значения — что оно целое число. Она ничего не знает о содержимом словаря. Проверка типа и проверка существования записи — два независимых шага. Первый делает FastAPI, второй — ваш код.

Задача 1

Маршрут объявлен как @app.get("/api/rooms/{room_id}") с функцией def get_room(room_id: int). Клиент отправляет запрос GET /api/rooms/5. Какое значение и какого типа получит функция-обработчик в аргументе room_id?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 2

Маршрут объявлен как GET /api/rooms/{room_id}, а аргумент — room_id: int. Что произойдёт при запросе GET /api/rooms/abc?

Выберите один вариант
Без регистрации · результат не сохраняется

Задача 3

Переговорная по идентификатору

Roomly — небольшой сервис для управления переговорными комнатами в офисе. Все переговорные уже загружены в единый словарь rooms_by_id, и общий список доступен любому желающему через уже работающий маршрут каталога. Однако просматривать каждый раз весь список ради одной комнаты неудобно: нужен способ обратиться к конкретной переговорной напрямую, зная только её числовой идентификатор.

Именно для этого требуется отдельный маршрут, который принимает идентификатор как часть адреса и возвращает сведения об одной выбранной переговорной. Оба маршрута — и каталог, и запрос по идентификатору — работают с одним и тем же rooms_by_id, никакого дополнительного хранилища или дублирующего списка в системе нет.

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

Что уже дано

В редакторе открыт самодостаточный main.py с объектом верхнего уровня app, единым словарём rooms_by_id и рабочим маршрутом списка GET /api/rooms. Оба маршрута этой лабораторной должны читать только rooms_by_id; второго списка или хранилища нет. Перед файлом выполняется видимый служебный переходник проверки: он определяет функции для запросов и чтения состояния, но не создаёт приложение или данные каталога. Менять переходник не нужно.

Что нужно сделать

Самостоятельно объявите декоратор и функцию-обработчик для GET /api/rooms/{room_id}. Имя переменного сегмента должно совпадать с именем аргумента, а room_id должен быть аннотирован как int. Для существующего идентификатора верните соответствующий словарь из rooms_by_id. В лабораторной проверяются только существующие целочисленные id; поведение для отсутствующего целочисленного id не задаётся.

Ввод и вывод

stdin не используется. Печатать через print в stdout ничего не нужно. После вашего кода проверяющая система читает объекты верхнего уровня app и rooms_by_id и отправляет запросы через TestClient. Она проверяет статусы и JSON-ответы; нечисловое значение в path должно быть автоматически отклонено FastAPI статусом 422 до вызова обработчика.

РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

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