📖 Теория: Pydantic — валидация и сериализация данных

🎯 Тема: типизация, валидация и работа с JSON

⚡ Краткий конспект

  • Pydantic — библиотека для валидации данных через аннотации типов Python.
  • BaseModel — базовый класс. Поля аннотируются типами, Pydantic проверяет их автоматически.
  • model_validate_json() — из JSON-строки в объект. model_dump_json() — из объекта в JSON-строку.
  • Field() — добавляет ограничения: gt=0 (больше 0), min_length=2, default=True, alias="available".
  • @field_validator — кастомная валидация поля (Pydantic v2).
  • Config внутри модели — глобальные настройки: обрезка пробелов, минимальная длина строк, кастомные JSON-энкодеры.

Что такое Pydantic

Pydantic — это библиотека для Python, которая использует аннотации типов для валидации данных. Она проверяет, что входящие данные соответствуют ожидаемым типам, автоматически приводит их к нужному формату и сообщает об ошибках.

Где используется:

  • API — валидация входящих JSON-данных в Flask и FastAPI
  • Конфигурации — проверка настроек приложения
  • Базы данных — совместимость с SQLAlchemy
  • Сериализация — преобразование объектов в JSON и обратно

Установка

pip install pydantic

Для работы с EmailStr:

pip install pydantic[email]

BaseModel — основа моделей

Все модели Pydantic наследуются от BaseModel. Поля класса аннотируются типами:

from pydantic import BaseModel

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

Что происходит:

  • При создании объекта User(id=1, name="Anna", age=25) Pydantic проверяет типы.
  • Если передать age="not_a_number" — будет ошибка ValidationError.
  • Поле is_active имеет значение по умолчанию True, его можно не передавать.

Вложенные модели

Модель может содержать другую модель как поле:

from pydantic import BaseModel

class Address(BaseModel):
    city: str
    street: str
    house_number: int

class User(BaseModel):
    id: int
    name: str
    age: int
    address: Address

Pydantic рекурсивно валидирует вложенную структуру. Если address не соответствует модели Address — ошибка.

JSON и Pydantic

JSON (JavaScript Object Notation) — универсальный формат обмена данными. Pydantic умеет преобразовывать модели в JSON и обратно.

Десериализация: JSON → модель

from pydantic import BaseModel, ValidationError

class User(BaseModel):
    name: str
    age: int

json_string = '{"name": "John", "age": 30}'

try:
    user = User.model_validate_json(json_string, strict=True)
    print(user)  # name='John' age=30
except ValidationError as e:
    print("Validation error:", e)

strict=True — строгий режим: Pydantic не будет пытаться привести типы (например, строку "30" к числу 30).

Сериализация: модель → JSON

user = User(name="John", age=30)
json_output = user.model_dump_json()
print(json_output)  # {"name":"John","age":30}

Наследование моделей

Модели Pydantic поддерживают наследование, как обычные Python-классы:

from pydantic import BaseModel, EmailStr

class User(BaseModel):
    name: str
    email: EmailStr

class AdminUser(User):
    is_superuser: bool
    access_level: int

AdminUser содержит все поля User + свои собственные.

Методы в моделях

В модель можно добавлять методы:

class User(BaseModel):
    name: str
    age: int

    def greet(self) -> str:
        return f"Hello, my name is {self.name} and I am {self.age} years old."

    def __str__(self) -> str:
        return f"{self.name}, {self.age} years old"

Field — валидация и метаданные

Field() позволяет задать ограничения и метаданные для поля:

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str
    description: str = Field(None, description="Описание продукта")
    price: float = Field(gt=0, description="Цена должна быть больше нуля")
    in_stock: bool = Field(default=True, alias="available")
ПараметрЗначениеПример
defaultЗначение по умолчаниюdefault=True
gtБольше чем (greater than)gt=0
geБольше или равноge=18
leМеньше или равноle=99
min_lengthМинимальная длина строкиmin_length=2
max_lengthМаксимальная длина строкиmax_length=50
aliasАльтернативное имя в JSONalias="available"
descriptionОписание поляdescription="Цена"
Совет: ... (Ellipsis) означает, что поле обязательное. Например: name: str = Field(..., min_length=3).

Специальные типы Pydantic

Pydantic предоставляет готовые типы для частых сценариев:

ТипОписаниеТребует
EmailStrВалидный email-адресpip install pydantic[email]
HttpUrlВалидный URL (http/https)
List[str]Список строкfrom typing import List
PositiveIntПоложительное целое числоfrom pydantic import PositiveInt

Кастомная валидация: @field_validator

Для сложных проверок используй декоратор @field_validator (Pydantic v2):

from pydantic import BaseModel, field_validator, EmailStr

class User(BaseModel):
    name: str
    age: int
    email: EmailStr

    @field_validator('email')
    def check_email_domain(cls, value: str) -> str:
        allowed = ['example.com', 'test.com']
        domain = value.split('@')[-1]
        if domain not in allowed:
            raise ValueError(f"Email должен быть из доменов: {', '.join(allowed)}")
        return value
Важно: в Pydantic v2 используется @field_validator. В v1 использовался @validator — не путай!

Валидация, зависящая от нескольких полей

Используй model_validator (Pydantic v2) для проверок, где нужно сравнить несколько полей:

from pydantic import BaseModel, model_validator

class User(BaseModel):
    age: int
    is_employed: bool

    @model_validator(mode='after')
    def check_employment_age(self):
        if self.is_employed and not (18 <= self.age <= 65):
            raise ValueError("Если занят, возраст должен быть от 18 до 65")
        return self

Настройки модели: model_config (ConfigDict)

В Pydantic v2 глобальные настройки модели задаются атрибутом model_config = ConfigDict(...):

from pydantic import BaseModel, ConfigDict

class User(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,   # Обрезать пробелы по краям
        str_min_length=1,            # Минимальная длина строки
        validate_assignment=True,    # Валидировать при присваивании
    )

    name: str
    age: int

Для кастомной сериализации даты в JSON используется декоратор @field_serializer (в v2 он заменил устаревший json_encoders):

from datetime import datetime
from pydantic import BaseModel, field_serializer

class User(BaseModel):
    signup_ts: datetime

    @field_serializer("signup_ts")
    def serialize_ts(self, v: datetime) -> str:
        return v.strftime("%d-%m-%Y %H:%M")

user = User(signup_ts=datetime.now())
print(user.model_dump_json())  # {"signup_ts":"08-06-2026 22:16"}
⚠️ В Pydantic v1 вместо этого использовался вложенный class Config: и словарь json_encoders. Сравнение v1 → v2 — на странице ⚖️ Старый vs Новый.