Валидация FastAPI: как запретить лишние поля и найти опечатку клиента

Фронтенд отправил в заказ поле quantitty вместо quantity. API ответил 200, но сохранил количество по умолчанию — одну штуку. Опечатка прошла незаметно, потому что неизвестные поля Pydantic по умолчанию игнорирует. Для команды создания заказа это опасный договор: успешный ответ ещё не означает, что сервер понял всё отправленное.

Определите политику входных данных

У Pydantic есть три способа обращения с дополнительными полями: игнорировать, разрешать и запрещать. Выбор зависит от назначения документа. Команда, которая меняет состояние, обычно выигрывает от строгого списка ключей: клиент сразу узнаёт, что прислал неподдерживаемое поле. Событие внешнего поставщика иногда требует более мягкой совместимости, чтобы новое служебное поле не остановило приём.

В модели команды установим ConfigDict(extra="forbid"). Это правило касается названий полей. Оно не включает строгую проверку типов и не задаёт ограничения количества: для этого по-прежнему нужен Field. Полезно разделять эти решения, чтобы понять причину отказа: неизвестный ключ, неверный тип или значение вне допустимых границ.

Проверяем форму команды на HTTP-границе

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

Пример рассчитан на Python 3.11+ с FastAPI, Pydantic v2 и HTTPX. Установите пакеты fastapi и httpx в отдельное окружение, сохраните блок в файл и запустите его Python.

from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict, Field

app = FastAPI()
class OrderIn(BaseModel):
    model_config = ConfigDict(extra="forbid")
    product: str = Field(min_length=1)
    quantity: int = Field(default=1, ge=1)

@app.post("/orders")
def create_order(data: OrderIn):
    return data

with TestClient(app) as client:
    assert client.post("/orders", json={"product": "Book"}).json()["quantity"] == 1
    bad = client.post("/orders", json={"product": "Book", "quantitty": 3})
    assert bad.status_code == 422
    error = bad.json()["detail"][0]
    assert error["type"] == "extra_forbidden"
    assert error["loc"] == ["body", "quantitty"]
    assert client.post("/orders", json={"product": "Book", "quantity": 0}).status_code == 422
    assert client.post("/orders", json={"product": "Book", "quantity": 3}).json()["quantity"] == 3
print("unknown field rejected; valid quantity preserved")

Что сообщить разработчику клиента

В ошибке loc показывает путь до проблемного значения. Для опечатки это body, quantitty, а type равен extra_forbidden. Отдельно зафиксируйте такой ответ в тесте интеграции: интерфейс должен показать ошибку ввода или отправить исправленное имя поля, а не повторять тот же запрос бесконечно. Полный текст сообщения может меняться между версиями, поэтому устойчивее проверять тип и путь.

Не возвращайте клиенту все внутренние исключения через str(exc). FastAPI уже формирует структурированный ответ для ошибки входной модели. Если продукту нужен собственный формат, преобразуйте только нужные элементы в обработчике RequestValidationError. Значения input в такой структуре могут содержать пользовательские данные; учитывайте это перед записью ошибок в журнал.

Как внедрить правило в работающий API

У включения запрета есть цена совместимости. Клиенты, которые годами присылали лишний служебный ключ, начнут получать 422. Перед изменением выясните фактическую форму запросов в тестовых примерах и договорах с клиентами. Согласуйте удаление лишних полей или версию контракта. Делать правило общим базовым классом удобно только после этого решения.

Вложенная модель имеет собственную конфигурацию. Если заказ содержит адрес, extra="forbid" во внешней модели не заменяет аналогичное правило для Address. Проверьте опечатку и снаружи, и внутри вложенного объекта. А публичный ответ настраивается отдельно: существующий разбор фильтрации response_model отвечает за утечку служебных данных, тогда как эта статья защищает смысл входящей команды.

Следующая практика

Продолжите с открытым уроком о POST и первой Pydantic-модели: опишите JSON-команду, получите её в обработчике и проверьте ответ API. Так политика лишних ключей останется частью понятного контракта создания ресурса. Откройте бесплатный урок и выполните практику.

В бесплатном курсе FastAPI вы затем соедините маршруты, базу, регистрацию и тесты в одном приложении. Когда понадобится воспроизводимо запускать этот backend на другом компьютере, откройте программу Docker для разработчика: в ней есть образы, контейнеры и Compose для приложения с базой.

Продолжить на практике