Урок курса

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-телом. После проверки обработчик получает модель и читает её атрибуты.

Попробуйте решить

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

Продолжить с проверкой и прогрессом

Откройте интерактивный раннер с заданиями урока.

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