БэкендPython

Pydantic для новичка: проверяем JSON из API и показываем понятные ошибки

Разбираем Pydantic 2 на одном заказе: проверяем типы, обязательные поля и цену, читаем ValidationError и возвращаем понятный список ошибок.

Кодик

Автор

7 мин чтения

Pydantic превращает сырой словарь из JSON в объект с проверенными полями или в структурированный список ошибок. Соберём модель заказа на Pydantic 2, проверим пропущенное имя, отрицательную цену и неверное количество. Затем превратим технический ValidationError в сообщения, которые можно показать пользователю API.

Обычный dict принимает строку вместо числа и замечает проблему только глубже в программе. С Pydantic граница данных появляется сразу после json.loads. В этой статье вы соберёте валидатор заказа с полями name, price и quantity. Финальный результат можно проверить без догадок: правильный JSON даёт total=599.80, а три неверных поля возвращаются отдельными ошибками.

1Описываем

Типы и ограничения живут в классе Order, а не размазаны по нескольким if.

2Проверяем

model_validate принимает словарь и либо создаёт объект, либо поднимает ValidationError.

3Объясняем

errors() даёт loc, type и msg, из которых собирается понятный ответ API.

Как Pydantic проверяет JSON на границе программы

Входной JSON сначала превращается в словарь Python. Затем Order.model_validate сопоставляет ключи с аннотациями, применяет Field и дополнительный field_validator. Бизнес-код получает только Order, поэтому не повторяет проверки цены и количества. Для Pydantic держите этапы раздельно: ввод, проверка, основное действие и вывод. Такой маршрут позволяет остановиться в конкретной точке, увидеть фактические данные и исправить одну причину вместо случайной перестановки строк.

ЭтапДействиеПроверяемый результат
Разбор JSONjson.loads превращает строку в dictКлючи и значения доступны Python
МодельOrder.model_validate проверяет схемуПолучен Order или ValidationError
Ошибкиerrors() раскрывает путь и типКаждая проблема имеет поле
Расчётtotal умножает price на quantityСумма считается из валидных чисел

Ошибка останавливается у входа, а не всплывает как TypeError во время расчёта. У Pydantic каждый этап оставляет наблюдаемый след, поэтому следующий шаг начинается только после проверки предыдущего.

Путь JSON через модель Pydantic
От входных данных до видимого результата. Бизнес-код получает только проверенный объект

Не путайте проверку формата с бизнес-правилами. Тип Decimal и gt=0 отвечают за форму цены. Правило скидки или лимит склада лучше проверять отдельной функцией после создания модели.

Пишем модель заказа и выводим ошибки

Создайте проект командами uv init pydantic-lab и uv add pydantic. Сохраните код в main.py и запустите uv run python main.py. Пример использует API Pydantic 2: BaseModel, Field, field_validator и model_validate. Старый parse_obj из материалов по Pydantic 1 здесь не нужен.

import json
from decimal import Decimal

from pydantic import BaseModel, Field, ValidationError, field_validator


class Order(BaseModel):
    name: str = Field(min_length=2, max_length=40)
    price: Decimal = Field(gt=0, decimal_places=2)
    quantity: int = Field(ge=1, le=20)

    @field_validator("name")
    @classmethod
    def name_must_not_be_blank(cls, value: str) -> str:
        cleaned = value.strip()
        if not cleaned:
            raise ValueError("name cannot be blank")
        return cleaned

    @property
    def total(self) -> Decimal:
        return self.price * self.quantity


raw = '{"name":"Keyboard","price":"299.90","quantity":2}'

try:
    order = Order.model_validate(json.loads(raw))
    print(order.model_dump())
    print(f"total={order.total:.2f}")
except ValidationError as error:
    for item in error.errors():
        field = ".".join(map(str, item["loc"]))
        print(f"{field}: {item['msg']}")
Что должно произойти после запуска
  • Строка цены безопасно преобразуется в Decimal.
  • Пробелы по краям имени удаляются валидатором.
  • Количество 0 даёт ошибку greater_than_equal.
  • Валидный заказ печатает total=599.80.

Decimal выбран вместо float, чтобы учебный пример не накапливал двоичную погрешность в денежной сумме. Пример Pydantic специально ограничен одним пользовательским сценарием, чтобы назначение каждой строки оставалось видимым.

Ручные if и единый контракт данных
Что отличает устойчивый проект от случайного запуска. Контракт держит ограничения рядом с типами

Из чего состоит ValidationError в Pydantic 2

BaseModel читает аннотации один раз при создании класса и строит внутреннюю схему. Field добавляет ограничения поверх типа. При model_validate Pydantic собирает все найденные проблемы, а не останавливается на первой. Поле loc показывает путь, поэтому вложенная ошибка может выглядеть как items.0.price. Поле type удобно для машинной обработки, msg подходит для журнала, input помогает увидеть исходное значение. В ответе публичного API не стоит бездумно возвращать весь input: там может оказаться пароль или токен.

ЧастьЗа что отвечаетЧто проверить
BaseModelОписывает форму объектаВсе обязательные поля перечислены
FieldЗадаёт диапазон и длинуГраницы совпадают с задачей
field_validatorПроверяет одно поле дополнительноВозвращает очищенное значение
ValidationErrorХранит список проблемerrors() не теряет loc
model_dumpСериализует готовую модельDecimal обработан осознанно

Pydantic может преобразовать строку "299.90" в Decimal, но это не значит, что нужно принимать любые преобразования. Для строгой границы используйте StrictInt, StrictStr или model_config. У значения по умолчанию есть отдельная ловушка: оно не всегда валидируется так же, как вход, пока не включена соответствующая настройка. Модели также умеют вложенные списки и JSON Schema, однако новичку полезнее сначала научиться читать errors(). Если ошибка не объяснима по loc и type, усложнять модель рано.

Не скрывайте ValidationError общим except Exception. Перехватывайте ValidationError отдельно, логируйте безопасные детали и возвращайте клиенту код 422 или собственный структурированный ответ. Остальные исключения должны остаться видимыми разработчику.

Ломаем входные данные и проверяем правила

Запустите валидный заказ. Затем по очереди удалите name, передайте price=-1, quantity="many" и имя из пробелов. После каждого запуска выпишите loc и type. Добавьте необязательное поле coupon: str | None = None и запретите код длиннее 12 символов. Последний шаг: создайте Cart с полем items: list[Order] и проверьте ошибку во втором элементе. Не исправляйте данные внутри except, сначала добейтесь точного сообщения.

Проверка своими руками
  1. Запустите валидный JSON и проверьте total.
  2. Удалите name и найдите тип missing.
  3. Передайте отрицательную price и найдите поле в loc.
  4. Проверьте quantity=21 и верхнюю границу.
  5. Добавьте coupon с ограничением длины.
  6. Создайте список из двух Order и сломайте второй.
Готово, если выполняются все пункты
  • Валидный словарь создаёт Order.
  • Три неверных поля видны отдельно.
  • Денежный расчёт использует Decimal.
  • Новая проверка добавляется в одном месте.

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

Четыре проверки модели заказа
Четыре проверки перед следующим шагом. Ошибку можно воспроизвести отдельным входом

Как применять Pydantic в настоящем API

В FastAPI модели Pydantic подключаются к телу запроса автоматически, но смысл остаётся тем же: недоверенные данные проходят через контракт до бизнес-логики. Разделяйте модели создания, ответа и обновления, если у них разные обязательные поля. Не храните секреты в repr и не отдавайте внутренний input без фильтра. Для больших проектов вынесите модели в отдельный модуль, но оставьте рядом примеры входа и тесты на границы.

Как изучить тему в Кодике

Начните со словарей, функций и исключений Python. Затем сравните ручные if с декларативной моделью. В Кодике сначала восстановите чистую логику без фреймворка, затем верните библиотеку и интерфейс Pydantic.

ШагЧто разобратьМини-проверка
1Словари и JSONПолучить price по ключу
2Аннотации типовОбъяснить int и Decimal
3ИсключенияПерехватить только ValidationError
4Классы и свойстваВычислить total
5Контракты APIСобрать вложенный Cart

Сначала напишите одну ручную проверку, потом замените её Field и сравните сообщения. Финальная проверка для Pydantic: закрыть статью, объяснить путь данных и добавить одну функцию без копирования готового блока.

Используют float для денег

Decimal делает ожидаемой сумму 299.90 × 2 и форматирование до копеек.

Проверяют поле после расчёта

Валидация должна завершиться до вызова бизнес-логики.

Возвращают str(error) клиенту

Соберите безопасный ответ из loc, type и собственного текста.

Копируют синтаксис Pydantic 1

Для нового проекта используйте model_validate, model_dump и field_validator из Pydantic 2.

Сверяйтесь с первичной документацией. Команды и ограничения примера проверены по официальной документации Pydantic 2. Если версия инструмента изменилась, официальный источник важнее старого скриншота.

Что получится в итоге

Готовый результат проекта: Pydantic для новичка: проверяем JSON из API и показываем понятные ошибки
Модель принимает корректный заказ, считает сумму и превращает несколько нарушений контракта в отдельные сообщения с путём к полю.

Короткие ответы
Что возвращает Order.model_validate?

Order.model_validate возвращает экземпляр Order или поднимает ValidationError со списком проблем.

Зачем нужен loc в ошибке Pydantic?

loc показывает путь к полю, включая индекс элемента во вложенном списке.

Когда нужен field_validator?

field_validator нужен для правила одного поля, которого нельзя выразить только типом и Field.

Как понять, что учебный проект Pydantic действительно работает?

Проверьте основной сценарий Pydantic, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.

Можно ли начать с готового кода Pydantic из статьи?

Да. Сначала добейтесь результата Pydantic, затем измените одно правило и восстановите ключевой файл без копирования.

Закрепите контракт данных на маленьком API

Базовые типы и исключения разберите в курсе Python. Затем подключите модель к первому API на FastAPI.

Сравните ошибки модели с разбором TypeError между строкой и числом и ValueError при преобразовании int.