Урок курса
Ограничения полей и ошибки валидации
FastAPI для начинающих: API с базой данных и тестамиВ прошлом уроке модель RoomIn объявляла поля через простые аннотации: name: str и capacity: int. FastAPI проверял тип, но ничего не знал о допустимых значениях — вместимость 0 или имя из одного символа проходили без ошибки. Сейчас добавим правила прямо в объявление поля.
Field(...) как контракт: ge, le, min_length, max_length
Чтобы задать числовые или строковые ограничения, используют Field из pydantic. Выражение с Field записывают справа от =: оно описывает ограничения поля и, при необходимости, его значение по умолчанию. В показанном ниже Field(...) многоточие оставляет поле обязательным без значения по умолчанию.
Первый аргумент Field — это либо многоточие ..., либо конкретное значение по умолчанию. Многоточие означает «поле обязательно»: если клиент не передаст его в теле запроса, FastAPI вернёт 422 не выполняя обработчик.
Для числовых полей:
ge— значение должно быть больше или равно указанному;le— меньше или равно;gtиlt— строго больше и строго меньше.
Для строковых:
min_length— минимальная длина в символах Unicode;max_length— максимальная.
Модель для переговорной выглядит так:
from pydantic import BaseModel, Field
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(..., ge=1, le=50)
Теперь name обязана содержать от 2 до 50 символов, а capacity — целое число от 1 до 50 включительно. Правила проверяются до того, как управление дойдёт до функции-обработчика: если хотя бы одно нарушено, FastAPI отвечает 422.
Field(0, ge=1) против Field(..., ge=1): что происходит при пропуске поля
Представьте, что кто-то написал модель чуть иначе:
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(0, ge=1) # default = 0
Теперь у capacity есть значение по умолчанию — 0. Если клиент отправит тело без этого поля:
{"name": "Большой зал"}
Pydantic v2 в стандартной конфигурации подставит 0 и не будет проверять, удовлетворяет ли сам default ограничению ge=1. Обработчик получит capacity=0 и вернёт 200.
При этом если клиент явно передаст "capacity": 0, ограничение сработает и ответ будет 422. Итог: одно и то же значение 0 ведёт себя по-разному в зависимости от того, пришло ли оно из запроса или было подставлено как default.
Чтобы этого не было, используйте Field(..., ge=1). Тогда пропущенное поле даёт 422, а default, нарушающий правило, в принципе не возникает. Значение по умолчанию стоит задавать только тогда, когда оно само по себе допустимо по контракту.
Как прочитать ответ 422 и найти неверное поле
Возьмём полный маршрут с моделью, которую объявили выше:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(..., ge=1, le=50)
@app.post("/api/rooms")
def create_room(room: RoomIn):
return {"received_name": room.name, "capacity": room.capacity}
Отправим запрос с намеренно неверными значениями:
{"name": "А", "capacity": 0}
FastAPI вернёт статус 422. Ниже сокращённое тело ответа: у каждой ошибки оставлены loc, msg и type.
{
"detail": [
{
"loc": ["body", "name"],
"msg": "String should have at least 2 characters",
"type": "string_too_short"
},
{
"loc": ["body", "capacity"],
"msg": "Input should be greater than or equal to 1",
"type": "greater_than_equal"
}
]
}
detail — массив: каждый объект описывает одно нарушение. Для диагностики рассмотрим три показанных поля; в полном ответе могут быть и другие, например input и ctx.
loc — путь к проблемному месту. Первый элемент "body" говорит, что ошибка в JSON-теле запроса. Второй — имя конкретного поля модели. По этому пути сразу понятно, что исправлять.
msg — человекочитаемое описание: что именно нарушено. Видно, что name слишком короткое, а capacity меньше 1.
type — машинный код ошибки: удобен, если нужно обрабатывать ответ программно.
В данном случае оба поля нарушены одновременно, поэтому detail содержит два объекта. Чтобы исправить запрос, достаточно передать name длиной от 2 до 50 символов и capacity от 1 до 50 — например, {"name": "Малый зал", "capacity": 10}.
![Поле capacity: int ограничено Field(..., ge=1, le=50). Переданное capacity 0 даёт 422 до вызова обработчика. В одном элементе detail loc равен [body, capacity], а msg сообщает Input should be greater than or equal to 1.](/uploads/lesson-media/field_error-1788107190599-0f86e12a-670e-4204-9275-e61ad785ba98.png)
Задача 1
Модель объявлена так:
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(..., ge=1, le=50)
Клиент отправляет {"name": "Переговорная номер один в офисе Москва длинное название которое точно превышает лимит", "capacity": 10}. Каков статус ответа и какое поле укажет loc?
Задача 2
POST /api/rooms принимает тело по модели:
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(..., ge=1, le=50)
В ответе 422 есть ровно одна ошибка. Ниже показаны её основные поля:
{"loc": ["body", "capacity"], "msg": "Input should be greater than or equal to 1", "type": "greater_than_equal"}
Какое из этих JSON-тел могло вызвать такой ответ?
Задача 3
Разработчик задал значение по умолчанию, которое само нарушает ограничение:
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(default=0, ge=1, le=50)
@app.post("/api/rooms")
def create_room(room: RoomIn):
return {"capacity": room.capacity}
Используется Pydantic v2 без validate_default. Первый POST содержит {"name": "Рига"}, второй — {"name": "Рига", "capacity": 0}. Какие статусы вернут эти два запроса?
Задача 4
Проверка карточки переговорной
Roomly — система для управления рабочими пространствами, которой пользуются офис-менеджеры, чтобы поддерживать актуальный каталог переговорных комнат. При проверке описания комнаты название и вместимость должны отвечать определённым правилам, заложенным в систему заранее. Эти правила одинаковы для всех комнат и не зависят от того, кто и когда заполняет карточку.
Офис-менеджер передаёт описание переговорной и рассчитывает получить чёткий ответ: принято ли оно или нет. Ошибки при ручном заполнении неизбежны — можно случайно указать недопустимое название, ввести некорректное значение вместимости или сразу допустить оба промаха. Поэтому важно, чтобы система не просто отвергла описание, а точно указала, что именно и по какой причине не прошло проверку: менеджер должен понимать, какое поле нужно скорректировать и какое правило оно нарушает.
Если описание удовлетворяет всем правилам, система подтверждает его допустимость. Если нет — возвращает информативный отказ, охватывающий все найденные нарушения сразу, а не только первое из них. При этом проверка не вносит никаких изменений в каталог: она лишь сообщает об итоге контроля, оставляя исправление и повторную передачу данных на стороне менеджера.
Что уже дано
В самостоятельном файле уже есть app, модель RoomIn и работающий POST-обработчик. Но сейчас модель принимает имя из одного символа, нулевую вместимость и запрос без вместимости. Исправьте этот файл, сохранив форму успешного ответа.
Служебный переходник проверки запускается перед вашим файлом, но не создаёт приложение или данные. Менять его не нужно.
Что нужно сделать
Исправьте входную модель RoomIn с помощью Field, чтобы оба поля были обязательными: name — строка длиной от 2 до 50 символов включительно, capacity — целое число от 1 до 50 включительно.
Маршрут POST /api/rooms получает эту модель из JSON-тела. Для допустимого описания ответ — статус 200 и объект ровно с полями accepted_name и capacity, содержащими принятые значения.
Неверное описание должно получать стандартный ответ FastAPI 422. Каждый элемент detail содержит путь loc к полю тела, машинный type и объяснение msg; если нарушены оба поля, сообщаются оба нарушения. Не исправляйте входные значения и не заменяйте отсутствующие поля значениями по умолчанию. Каталог и хранилище не нужны.
Ввод и вывод
stdin не используется. Печатать через print в stdout ничего не нужно. После выполнения файла проверяющая система обращается к верхнеуровневому объекту app: служебный переходник отправляет HTTP-запросы через TestClient внутри процесса приложения и передаёт результаты скрытой проверке. Запускать Uvicorn или сетевой сервер не нужно.
POST /api/rooms с {"name": "Кедр", "capacity": 6} получает 200 и {"accepted_name": "Кедр", "capacity": 6}. Для {"name": "К", "capacity": 0} нужен 422 с ошибками по loc: ["body", "name"] и loc: ["body", "capacity"].
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Ограничения полей и ошибки валидации — задача по FastAPI
Какие статусы вернут эти два запроса? Проверьте ответ в задаче из бесплатного курса FastAPI.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
