CRUD как HTTP-контракт

Полное и частичное обновление

Содержание курса

Новое название без потери сведений

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

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

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

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

Что уже дано

Это самостоятельный файл main.py для браузерного редактора: код ваших прежних решений не переносится автоматически. Модели Address, RoomIn, RoomOut, верхнеуровневый app, пустой rooms_db и начальный next_id уже в файле. Это отдельное упражнение на полную карточку из урока11, а не объединение всех прежних каталогов и фильтров. POST, GET, helper и полный PUT уже работают. Готовая RoomPatch содержит только name/capacity. Дополните PATCH; готовые действия переписывать не нужно.

Служебный переходник выполняется перед файлом, но не создаёт app или данные. Он только передаёт запросы и результаты между приложением и скрытой проверкой; менять его не нужно.

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

Реализуйте PATCH /api/rooms/{room_id}: ответ200 с полной RoomOut, а GET затем возвращает сохранённую карточку. RoomPatch допускает любое подмножество name и capacity; {} оставляет комнату без изменений. Используйте model_fields_set, чтобы отличать пропуск от явного null. До любых изменений записи проверьте все присланные значения: явный null любого из этих двух полей даёт422 с {"detail":"Fields cannot be null"}. Смешанный запрос с допустимым значением и null также не меняет ничего, независимо от порядка полей.

Непереданные name/capacity, серверный id, address и comment остаются прежними. address и comment не входят в эту PATCH-модель; как и другие лишние поля, они игнорируются. Для переданных ненулевых значений сохраняются ограничения name2–50 и capacity1–50: недопустимый тип или граница дают стандартный422 до изменения записи. Отказ не изменяет ни выбранную комнату, ни соседние записи.

Сначала используйте get_room_or_404(room_id): корректный PATCH по отсутствующему целочисленному id возвращает404/{"detail":"Room not found"}. Нечисловой path-параметр даёт стандартный422. Готовые POST/GET/PUT сохраняют свои контракты. Готовая RoomIn принимает обязательные name (строка длиной2–50), capacity (целое1–50) и address (объект с обязательными строками city, street). comment — необязательная строка или null, default — None. Сохраните эту валидацию: неверное тело получает стандартный422, включая путь вложенного поля. Публичная карточка содержит ровно id, name, capacity, address, comment; internal_note наружу не попадает. Лишние входные поля игнорируются; клиентские id и internal_note не подменяют серверные данные.

Работа последовательна в одном запуске; DELETE, база данных, изменение адреса через PATCH и сохранность после перезапуска не нужны.

Ввод и вывод

stdin не используется. Печатать через print в stdout ничего не нужно. После выполнения файла проверяющая система обращается к верхнеуровневому app: публичный переходник вызывает TestClient внутри процесса приложения и передаёт результат скрытой проверке. Запускать Uvicorn, сеть, клиентский скрипт или pytest не требуется.

Если созданная карточка с id=1 содержит name: "Липа", capacity: 4, PATCH /api/rooms/1 с {"capacity":8} возвращает200: вместимость стала8, остальные поля сохранены. Тело {"name":"Кедр","capacity":null} получает422; оба прежних значения остаются в карточке.

О данных в ответах

Используйте учебные данные. Не вставляйте пароли, токены, ключи доступа, паспортные и банковские данные, а также персональные данные других людей. Политика обработки данных.

РешениеPython · Python 3.12 (web: FastAPI, SQLAlchemy, pytest)
Как проверяется решение

Проверяются функции и их результаты. Собственный запуск выполняет ваш код без авторских тестов. Интерактивный запуск не влияет на оценку. Лимит сессии — 5 минут, процессорного времени — 10 секунд.

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