Урок курса

POST, JSON-тело и первая модель запроса

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

До сих пор все маршруты в уроках были GET: браузер или /docs запрашивали данные, а сервер отвечал списком или одним объектом. Теперь нужно принять данные от клиента — JSON в теле запроса. Для этого подключается другой HTTP-метод и первая модель Pydantic.

POST-маршрут и несовпадение HTTP-метода

FastAPI сопоставляет входящий запрос сразу по двум параметрам: пути и методу. Декоратор @app.get("/api/rooms") регистрирует обработчик только для метода GET. Если клиент отправит на тот же путь POST-запрос, FastAPI вернёт 405 Method Not Allowed — маршрут найден, но метод не совпадает.

Чтобы принимать POST на /api/rooms, нужен отдельный декоратор:

@app.post("/api/rooms")
def create_room():
    return {"ok": True}

Этот и GET-маршрут могут сосуществовать на одном пути — каждый обслуживает свой метод своей функцией. FastAPI просто выбирает нужный обработчик по методу входящего запроса.

В интерфейсе /docs POST и GET отображаются отдельными секциями для одного пути. Ошибка 405 появляется именно тогда, когда путь зарегистрирован, но для другого метода: не стоит путать её с 404, которая означает, что пути вообще нет.

Этот обработчик возвращает одинаковый {"ok": true} независимо от отправленного тела: аргумента для его получения пока нет.

Обязательные поля модели BaseModel

Чтобы FastAPI умел читать JSON-тело и проверять его структуру, нужна модель — класс, наследующий BaseModel из Pydantic:

from pydantic import BaseModel

class RoomIn(BaseModel):
    name: str
    floor: int
    capacity: int

Каждое поле — просто аннотированный атрибут класса. Никаких __init__, __repr__ или ручной валидации писать не нужно: всё это берёт на себя Pydantic.

Все три поля здесь обязательны: у них нет значения по умолчанию. Если входящий JSON не содержит хотя бы одно из них или передаёт значение несовместимого типа (например, строку "шесть" вместо числа в capacity), Pydantic не сможет построить объект RoomIn. FastAPI при этом вернёт 422 Unprocessable Entity и не вызовет функцию-обработчик вообще.

Pydantic также делает мягкое приведение типов там, где это безопасно: строка "2" превратится в int 2 для поля floor. Но строку "два" привести к int не получится — это уже 422.

Важно понимать, что RoomIn описывает контракт входных данных. Всё, что не прошло проверку, до вашего кода не доходит.

Аргумент-тело в обработчике: получение объекта RoomIn и доступ к полям

Когда аргумент функции-обработчика аннотирован подклассом BaseModel, FastAPI автоматически читает его из тела запроса — не из пути и не из query-строки. Механизм вывода источника встроен в FastAPI: он смотрит на тип аннотации.

Полный рабочий файл выглядит так:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class RoomIn(BaseModel):
    name: str
    floor: int
    capacity: int

@app.post("/api/rooms")
def create_room(room: RoomIn):
    return {"received": room.name, "floor": room.floor, "capacity": room.capacity}

Внутри create_room переменная room — это уже готовый объект RoomIn с проверенными полями. Обращение к ним — обычный доступ к атрибутам: room.name, room.floor, room.capacity.

Чтобы проверить в /docs: откройте секцию POST /api/rooms, нажмите Try it out, в поле Request body введите:

{"name": "Лондон", "floor": 2, "capacity": 6}

Нажмите Execute. Ответ будет 200 и тело:

{"received": "Лондон", "floor": 2, "capacity": 6}

/docs отправляет запрос с заголовком Content-Type: application/json — он сообщает серверу формат тела. FastAPI ориентируется именно на него при разборе.

Обработчик может одновременно принимать аргумент-тело и query-параметры или path-параметры: они не конфликтуют, FastAPI разбирает каждый источник отдельно. Данные здесь не сохраняются — обработчик просто возвращает то, что получил, и это достаточно, чтобы убедиться: контракт работает.

JSON-тело POST /api/rooms содержит name Лондон, floor 2, capacity 6. FastAPI проверяет тело по модели RoomIn с типами str, int, int. Обработчик create_room получает room: RoomIn и читает room.name как Лондон.
Аннотация room: RoomIn связывает аргумент с JSON-телом. После проверки обработчик получает модель и читает её атрибуты.

Закрепите тему на задачах

Отправьте корректный JSON, пропустите обязательное поле и передайте неверный тип. Сравните статус и тело ответа: успешный запрос не проверяет все ветви валидации.

POST, JSON-тело и первая модель запроса — задача по FastAPI

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

Задача 1

Маршрут объявлен как @app.post("/api/rooms") с функцией create_room(room: RoomIn), где RoomIn имеет поля name: str и capacity: int. Клиент отправляет POST /api/rooms с телом {"name": "Лондон"} — поле capacity отсутствует. Какой статус вернёт FastAPI и выполнится ли тело функции create_room?

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

Задача 2

Клиент отправляет POST /api/rooms с JSON-телом {"name": "Берлин", "capacity": 12}. Модель и обработчик объявлены так:

class RoomIn(BaseModel):
    name: str
    capacity: int

@app.post("/api/rooms")
def create_room(room: RoomIn):
    return {"free_seats": room.capacity - 2}

Какими будут значение room.capacity внутри обработчика и JSON успешного ответа клиенту?

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

Задача 3

Объявлены модель RoomIn(BaseModel) с обязательными полями name: str, capacity: int и обработчик @app.post("/api/rooms") с аргументом room: RoomIn. Клиент отправил POST /api/rooms?name=Осло&capacity=4 без тела. Как исправить запрос, не меняя модель и обработчик?

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

Задача 4

Подтверждение описания переговорной

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

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

Roomly обрабатывает описание и, если все обязательные сведения присутствуют и поддаются разбору, возвращает подтверждение с принятыми данными о комнате. Если же что-то из необходимого отсутствует или не может быть разобрано, сервис сообщает об ошибке — вместо подтверждения. При этом речь идёт исключительно о проверке: каталог переговорных остаётся нетронутым, и никакая новая комната в результате такого запроса не появляется.

Что уже дано

Перед вами самостоятельный файл с импортами и верхнеуровневым объектом app. Дополните его моделями и маршрутом; код прежних уроков в этот файл переносить не нужно.

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

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

Создайте модель RoomIn на основе BaseModel: обязательные поля name: str, floor: int и capacity: int. Подключите её как тело маршрута POST /api/rooms.

При корректном JSON верните статус 200 и объект ровно с тремя полями: received_name — принятое название, floor — этаж, capacity — вместимость. Имена обработчиков выбирайте сами. Не добавляйте ограничения на длину или диапазон чисел: здесь проверяются типы и обязательность полей. Обычные числовые строки должны разбираться Pydantic как числа.

При отсутствии обязательного поля или значении, которое модель не может разобрать, должен срабатывать стандартный ответ FastAPI 422 до выполнения обработчика. JSON-тело обязательно: одноимённые query-параметры его не заменяют. Данные нигде не сохраняйте.

Ввод и вывод

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

POST /api/rooms с JSON {"name": "Север", "floor": 3, "capacity": 8} получает 200 и {"received_name": "Север", "floor": 3, "capacity": 8}.

РешениеPython

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

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

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

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

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

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

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