Перейти к содержанию

Запрос и ответ FastAPI: каталог кабинетов

Оглавление серии

Учебная система «Окно» помогает выбрать кабинет и занять временной интервал. В этой серии нет платежей, настоящих посетителей и действующего расписания: «Кабинет Лён» и «Кабинет Клён» придуманы для объяснения. Начнём с каталога, потому что бронирование должно ссылаться на устойчивый ресурс. К концу урока вы сможете проследить путь запроса от адреса до словаря Python и объяснить, почему отсутствие кабинета отличается от пустого списка.

Для чтения нужны функции Python, словари, списки и основные представления об HTTP. Документационный профиль курса: Python 3.14, FastAPI с поддержкой Pydantic 2, SQLAlchemy 2.0, Psycopg 3 и PostgreSQL 18. Первые состояния используют только данные процесса. Диапазоны в requirements.txt описывают предполагаемые семейства API, но зависимости не разрешались и совместимость не проверялась. Все результаты на этой странице ожидаемые: приложение автором не запускалось.

Ресурс и его представление

Ресурс — объект, о котором клиент задаёт вопрос. Представление — данные, которые сервер отдаёт сейчас. Кабинет существует как предметная сущность; JSON с номером и названием является одним способом его описать. Поэтому число 1 не означает первую строку на экране. Если позже список сортируется по имени, адрес /resources/1 продолжает обозначать тот же кабинет. Различение идентификатора и позиции пригодится и базе, и клиентскому интерфейсу.

В словаре resources внешний ключ совпадает с полем id. Это намеренное упрощение начального шага: поиск кабинета занимает одну операцию, а ответ остаётся обычным объектом. Два элемента дают достаточно данных для сравнения коллекции и карточки. Когда запрос обращается к /resources, обработчик берёт значения словаря и собирает список. Он не возвращает сам словарь с числовыми ключами, потому что контракт коллекции удобнее задать массивом одинаковых записей.

Декоратор app.get связывает функцию с методом GET и адресом. Определение функции ещё не вызывает её для читателя. Сначала сервер получает запрос, затем маршрутизатор сопоставляет путь, после чего вызывается обработчик. Такой порядок позволяет иметь одинаковую основу адреса для чтения и последующих операций создания. Имя Python-функции помогает автору кода, но клиент ориентируется на метод и путь, поэтому переименование функции не обязано менять публичный URL.

Полные файлы шага

Показанные файлы полностью совпадают с выбранным состоянием архива. Остальные согласованные файлы находятся в examples/fastapi-booking-lab/lesson-01/.

main.py

from fastapi import FastAPI, HTTPException

app = FastAPI(title="Окно")
resources = {1: {"id": 1, "name": "Кабинет Лён"},
             2: {"id": 2, "name": "Кабинет Клён"}}


@app.get("/resources")
def list_resources():
    return list(resources.values())


@app.get("/resources/{resource_id}")
def read_resource(resource_id: int):
    if resource_id not in resources:
        raise HTTPException(404, "Ресурс не найден")
    return resources[resource_id]

Ожидаемый результат

Это ожидаемый договор для самостоятельного исследования; пример не выполнялся.

GET /resources/1 → 200
{"id":1,"name":"Кабинет Лён"}
GET /resources/99 → 404

Параметр в пути

В маршруте карточки resource_id заключён в фигурные скобки. Аннотация int задаёт ожидаемый тип значения после выделения сегмента адреса. Запрос к /resources/1 должен привести к целому числу 1 в функции. Запрос к /resources/abc не означает отсутствие кабинета с буквенным номером: он нарушает форму запроса ещё до предметного поиска. Позднее приведём ошибки к единому виду, пока полезно увидеть саму границу между разбором пути и поиском данных.

Обратите внимание на проверку membership перед обращением к словарю. Если кабинет 99 не задан, сервер поднимает HTTPException со статусом 404. Возврат объекта с текстом «не найден» и обычным статусом 200 был бы другим контрактом. Клиент использует статус, чтобы отличить успешную карточку от отсутствия, а тело может показать человеку объяснение. Исключение прерывает нормальное выполнение, поэтому строка возврата кабинета для этого запроса уже не достигается.

Для пустого каталога результатом остаётся массив и успешный ответ. Такая коллекция существует, просто в ней нет записей. Карточка несуществующего кабинета — отдельный случай: у неё нет представления, которое запрошено по идентификатору. Если смешать эти ситуации, клиенту придётся угадывать смысл пустого объекта. В «Окне» заранее закрепляем, что GET коллекции возвращает массив, а GET карточки возвращает объект либо осмысленную ошибку.

От словаря к JSON

FastAPI принимает возвращённую структуру и формирует HTTP-ответ. Список Python и массив JSON похожи по назначению, но это не один объект памяти: между ними происходит сериализация. На этой ступени мы отдаём только числа и строки, поэтому не обсуждаем специальные форматы дат. Название кабинета передаётся как текст; кодировка ответа позволяет сохранить русские буквы. Клиенту не нужно знать, что серверная коллекция первоначально находилась в словаре.

В файле нет ручного чтения сокета, разбора стартовой строки HTTP и построения заголовков. Эти обязанности относятся к серверу и фреймворку. У обработчика остаётся предметная задача: выбрать данные и решить, существует ли объект. Это не отменяет необходимости понимать протокол. Например, изменение словаря в обработчике GET нарушило бы ожидание клиента, что чтение не создаёт бронирование. Поэтому каталожные операции сохраняем без побочных изменений.

Представьте два запроса подряд к кабинету 1. Оба должны вернуть одинаковые значения, если исходный словарь не менялся. Теперь мысленно удалите второй элемент из данных и повторите запрос к /resources/2: ожидается 404, но запрос к /resources всё ещё успешен и содержит один кабинет. Такой разбор показывает связь ответов с состоянием модели без предположений о наличии интерфейса. На этой ступени мы не утверждаем, что результат наблюдался в браузере.

Полный main.py приведён на этой странице. Для самостоятельного запуска читатель устанавливает зависимости в собственной учебной среде и использует uvicorn main:app --host 127.0.0.1 --port 8000 из выбранного снимка. В рамках подготовки курса эти действия не выполнялись. Локальный адрес важен: первые главы ещё не имеют входа, постоянного хранения и защиты конкурентного расписания. Получив читаемый каталог, дальше отделим внешние формы данных от внутренних объектов проекта.

Метод и адрес как самостоятельные части

Полезно сравнить адрес коллекции с адресом карточки, не меняя сами данные. В коллекции два объекта перечисляются рядом, а карточка выбирает один по устойчивому номеру. Клиент не должен выводить адрес второй карточки из позиции второй строки: порядок может измениться. Если позже добавим фильтр, кабинет 2 может оказаться единственным в результате, но его номер останется 2. Именно поэтому поле id входит и в представление списка.

Метод GET также не следует путать с вызовом функции по имени. Внешний клиент не знает Python-модуля и не вызывает list_resources напрямую. Он отправляет метод и путь, а маршрутизатор выбирает обработчик. Если по тому же пути нет POST, отправка POST не должна создавать объект случайно. Когда появится создание брони, мы назначим ему собственный адрес и явно объясним изменение состояния. Такая разница помогает увидеть, почему удобный URL сам по себе ещё не является полной спецификацией операции и почему клиентскому договору нужны обе части.

Документация и исходники

Механизмы главы сверены с первичными источниками: документация 1, документация 2. Полный архив «Окна» содержит все шестнадцать состояний. Пакеты, приложение и миграции не запускались.