Pydantic v2: модели и валидация данных

Pydantic-модель — это класс-наследник BaseModel с типизированными полями; он принимает «сырые» данные, валидирует их, конвертирует типы и даёт строго типизированный объект.

Модель — это одновременно описание формы данных, валидатор и сериализатор. В версии 2 ядро Pydantic переписали на Rust, поэтому валидация стала в разы быстрее.

Pydantic — это вторая опора FastAPI. Именно он стоит за тем, что тело запроса проверяется, а ответ корректно сериализуется. Вы описываете модель как обычный класс с аннотациями полей, а Pydantic берёт на себя всё остальное: проверит, что строка не пришла вместо числа, подставит значения по умолчанию, отбросит лишнее или пожалуется на недостающее.

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    age: int = 0
    is_active: bool = True

# создание из словаря (Pydantic v2)
user = User.model_validate({"id": "7", "name": "Аня", "age": "30"})
print(user.id, type(user.id))   # 7 <class 'int'> — строка сконвертирована
print(user.model_dump())        # сериализация в обычный dict

Ключевые методы Pydantic v2, которые заменили старые: model_validate(data) вместо parse_obj создаёт и проверяет модель из словаря, model_validate_json(bytes) — прямо из JSON-байтов (быстрее, чем сначала парсить, потом валидировать), model_dump() возвращает словарь, model_dump_json() — JSON-строку. FastAPI вызывает эти методы за вас.

Как работает под капотом

Pydantic для каждого поля знает целевой тип и пытается привести значение к нему по понятным правилам, а при провале накапливает ошибки и сообщает их разом. Смоделируем суть валидатора модели на stdlib:

def validate(schema, data):
    result, errors = {}, []
    for field, (typ, required, default) in schema.items():
        if field not in data:
            if required:
                errors.append(f"{field}: обязательное поле отсутствует")
            else:
                result[field] = default       # подстановка дефолта
            continue
        try:
            result[field] = typ(data[field])  # конвертация в целевой тип
        except (TypeError, ValueError):
            errors.append(f"{field}: ожидался {typ.__name__}")
    return result, errors

schema = {
    "id":   (int,  True,  None),
    "name": (str,  True,  None),
    "age":  (int,  False, 0),
}
print(validate(schema, {"id": "7", "name": "Аня", "age": "30"}))
print(validate(schema, {"name": "Боб"}))               # нет id
print(validate(schema, {"id": "x", "name": "В"}))      # id не число

Попробуй сам ▶ Видно три исхода: успешная конвертация, отсутствие обязательного поля, ошибка типа — это упрощённая модель того, что делает Pydantic, только быстрее и подробнее.

Частые ошибки

Первая — путать API v1 и v2: parse_obj, .dict(), .json() устарели, в v2 это model_validate, model_dump, model_dump_json. Вторая — полагаться на неявную конвертацию там, где она нежелательна; иногда нужен строгий режим (strict=True), чтобы строка "7" не превращалась молча в число. Третья — складывать в модель «сырые» данные без аннотаций типов, теряя смысл инструмента.

Best practices

  • Используйте методы v2: model_validate, model_validate_json, model_dump, model_dump_json.
  • Берите model_validate_json, если у вас уже есть JSON-байты, — это быстрее.
  • Описывайте поля точными типами; для строгости применяйте strict-режим там, где важна точность.
  • Держите модели маленькими и осмысленными; одна модель — одна форма данных.

model_config и поведение модели

В Pydantic v2 поведение модели настраивается через атрибут model_config (на смену устаревшему вложенному классу Config из v1). Через него включают полезные режимы: from_attributes=True позволяет строить модель прямо из ORM-объекта, читая его атрибуты, что незаменимо при отдаче данных из базы; str_strip_whitespace=True автоматически обрезает пробелы у строк; frozen=True делает модель неизменяемой. Особо стоит выделить from_attributes: именно он позволяет вернуть из обработчика SQLAlchemy-объект, а FastAPI через response_model аккуратно спроецирует его в выходную модель, не требуя ручного перекладывания полей. Знание model_config отличает поверхностное использование Pydantic от осознанного: вы не боретесь с библиотекой, а настраиваете её под свой сценарий.

Итог: Pydantic-модель валидирует, конвертирует и сериализует данные на основе аннотаций. В v2 ядро на Rust и новые методы (model_*); именно этот слой делает данные в FastAPI надёжными.

Проверьте себя
1. Какой метод Pydantic v2 создаёт и валидирует модель из словаря?
A.dict()
Bparse_obj() — он актуален в v2
Cmodel_validate(data)
Dvalidate_python()
2. Что вернёт User.model_validate({'id': '7', ...}), если поле id объявлено как int?
AОшибку, так как '7' — строка
BМодель, где id равен числу 7 (строка сконвертирована в int)
CМодель, где id остался строкой '7'
DNone