Учебное пособие по работе с Python Pydantic
1. Назначение и введение
1.1 Что такое Pydantic?
Pydantic — это самая популярная библиотека для валидации данных в Python. Она предназначена для:
- Валидации данных — автоматической проверки соответствия данных заданным типам и правилам
- Парсинга и сериализации — преобразования данных между различными форматами (JSON, dict, Python-объекты)
- Управления настройками — работы с конфигурациями приложений
1.2 Зачем нужен Pydantic?
В реальных проектах данные часто приходят из внешних источников — API, пользовательского ввода, файлов конфигурации. Без надёжной валидации это приводит к:
- Ошибкам типов во время выполнения
- Разрастанию проверочного кода (if-ы, try-except) по всему проекту
- Смешиванию бизнес-логики с проверочной логикой
Pydantic решает эти проблемы, позволяя описывать структуру данных декларативно с помощью аннотаций типов Python.
1.3 Ключевые преимущества
| Преимущество | Описание |
|---|---|
| Работает на type hints | Валидация управляется аннотациями типов — меньше кода, лучше интеграция с IDE |
| Высокая скорость | Ядро валидации написано на Rust |
| Автоматическое приведение типов | Строка "25" автоматически становится int 25 |
| Понятные ошибки | Вместо cryptic-исключений — чёткие сообщения о том, что пошло не так |
| Генерация JSON Schema | Модели могут экспортировать JSON-схему для интеграции с другими инструментами |
| Огромная экосистема | ~8000 пакетов на PyPI используют Pydantic: FastAPI, LangChain, SQLModel, HuggingFace и другие |
1.4 Установка
pip install pydantic
Или через conda:
conda install pydantic -c conda-forge
2. Теория: основные концепции
2.1 BaseModel — основа всего
Центральная концепция Pydantic — модель, создаваемая наследованием от BaseModel. Поля модели объявляются с помощью аннотаций типов:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
email: str
Важные правила:
- Поля без значения по умолчанию — обязательные
- Поля с = None или = значение — опциональные
- Pydantic автоматически проверяет аннотации при создании класса и строит схему валидации
2.2 Типы данных
Pydantic поддерживает все стандартные типы Python, а также добавляет свои:
from pydantic import BaseModel
from typing import Optional, List, Dict
from datetime import datetime
class Example(BaseModel):
# Стандартные типы
id: int
name: str
price: float
is_active: bool
# Контейнеры
tags: List[str]
metadata: Dict[str, str]
# Опциональные поля
description: Optional[str] = None
# Дата и время
created_at: datetime
Pydantic умеет автоматически преобразовывать строки в datetime, числа в строки и т.д..
2.3 Field — расширенные ограничения
Когда простой аннотации типа недостаточно, используется Field для задания дополнительных ограничений:
from pydantic import BaseModel, Field
from typing import Optional
class Product(BaseModel):
name: str = Field(..., min_length=3, max_length=100, description="Название товара")
price: float = Field(gt=0, le=9999.99, description="Цена должна быть положительной")
quantity: int = Field(ge=0, le=1000, description="Количество на складе")
sku: Optional[str] = Field(None, pattern=r'^[A-Z]{2}-\d{4}$', description="Артикул")
Основные параметры Field:
| Параметр | Назначение |
|---|---|
| ... (Ellipsis) | Поле обязательно (не может быть None) |
| default | Значение по умолчанию |
| min_length / max_length | Минимальная/максимальная длина строки |
| gt / ge / lt / le | Больше/больше или равно/меньше/меньше или равно |
| pattern | Регулярное выражение для строки |
| description | Описание поля (для документации) |
| alias | Альтернативное имя поля |
2.4 Валидация при создании экземпляра
# Корректные данные — создание успешно
user = User(name="Alice", age=25, email="alice@example.com")
# Автоматическое приведение типов
user = User(name="Bob", age="30", email="bob@example.com")
print(type(user.age)) # <class 'int'>
# Некорректные данные — ValidationError
from pydantic import ValidationError
try:
user = User(name="A", age="abc", email="not-an-email")
except ValidationError as e:
print(e.json()) # Подробная информация об ошибках
3. Практические примеры
3.1 Пример 1: Модель пользователя с валидацией
from pydantic import BaseModel, Field, ValidationError, EmailStr
from typing import Optional
class User(BaseModel):
username: str = Field(..., min_length=3, max_length=20, description="Имя пользователя")
email: EmailStr = Field(..., description="Email-адрес")
age: int = Field(gt=0, lt=150, description="Возраст (1-149 лет)")
is_active: bool = True
phone: Optional[str] = Field(None, pattern=r'^\+?\d{10,15}$')
# Валидные данные
user = User(
username="alex_dev",
email="alex@example.com",
age=28
)
print(user.model_dump())
# {'username': 'alex_dev', 'email': 'alex@example.com', 'age': 28, 'is_active': True, 'phone': None}
# Невалидные данные
try:
user = User(
username="a", # Слишком короткое
email="invalid", # Невалидный email
age=200 # Слишком большой возраст
)
except ValidationError as e:
print(e)
3.2 Пример 2: Работа с JSON
Pydantic отлично работает с JSON-данными — например, из API или файлов:
import json
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip_code: str
class Person(BaseModel):
name: str
age: int
address: Address
# Парсинг из JSON-строки
json_data = '''
{
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Boston",
"zip_code": "02101"
}
}
'''
# Метод model_validate_json для парсинга JSON
person = Person.model_validate_json(json_data)
print(person.name) # John Doe
print(person.address.city) # Boston
# Сериализация в JSON
print(person.model_dump_json(indent=2))
3.3 Пример 3: Опциональные поля и значения по умолчанию
В реальных данных часто встречаются пропущенные поля:
from pydantic import BaseModel
from typing import Optional
class Product(BaseModel):
name: str # Обязательное поле
price: float # Обязательное поле
description: Optional[str] = None # Опциональное
discount: float = 0.0 # Значение по умолчанию
in_stock: bool = True # Значение по умолчанию
# Можно не указывать опциональные поля
product = Product(name="Laptop", price=999.99)
print(product.discount) # 0.0
print(product.in_stock) # True
print(product.description) # None
3.4 Пример 4: Вложенные модели
Pydantic поддерживает вложенные структуры любой глубины:
from pydantic import BaseModel
from typing import List
class Item(BaseModel):
name: str
price: float
quantity: int = 1
class Address(BaseModel):
street: str
city: str
country: str = "US"
class Order(BaseModel):
order_id: str
customer_name: str
shipping_address: Address # Вложенная модель
items: List[Item] # Список вложенных моделей
total: float
data = {
"order_id": "ORD-001",
"customer_name": "Alice",
"shipping_address": {
"street": "456 Oak Ave",
"city": "Chicago"
},
"items": [
{"name": "Book", "price": 19.99, "quantity": 2},
{"name": "Pen", "price": 2.50, "quantity": 5}
],
"total": 52.48
}
order = Order(**data)
print(order.items[0].name) # Book
print(order.shipping_address.country) # US
3.5 Пример 5: Пользовательские валидаторы
Для сложной логики валидации используются декораторы @field_validator и @model_validator:
from pydantic import BaseModel, field_validator, model_validator, ValidationError
class PasswordUser(BaseModel):
username: str
password: str
confirm_password: str
age: int
# Валидатор отдельного поля
@field_validator('password')
@classmethod
def validate_password(cls, v: str) -> str:
if len(v) < 8:
raise ValueError('Пароль должен содержать минимум 8 символов')
if not any(c.isdigit() for c in v):
raise ValueError('Пароль должен содержать хотя бы одну цифру')
if not any(c.isupper() for c in v):
raise ValueError('Пароль должен содержать хотя бы одну заглавную букву')
return v
@field_validator('age')
@classmethod
def validate_age(cls, v: int) -> int:
if v < 18:
raise ValueError('Возраст должен быть не менее 18 лет')
return v
# Валидатор всей модели (между полями)
@model_validator(mode='after')
def check_passwords_match(self) -> 'PasswordUser':
if self.password != self.confirm_password:
raise ValueError('Пароли не совпадают')
return self
# Проверка
try:
user = PasswordUser(
username="alex",
password="weak",
confirm_password="weak",
age=20
)
except ValidationError as e:
print(e)
# Пароль должен содержать минимум 8 символов
4. Продвинутые возможности
4.1 Strict Mode (Строгий режим)
По умолчанию Pydantic работает в свободном режиме — пытается автоматически приводить типы. Например, строка "25" становится числом 25.
В строгом режиме Pydantic требует точного соответствия типов:
from pydantic import BaseModel, ConfigDict
class StrictUser(BaseModel):
model_config = ConfigDict(strict=True)
id: int
name: str
# В строгом режиме это вызовет ошибку
try:
user = StrictUser(id="123", name="Alice") # Ошибка: ожидается int
except ValidationError as e:
print(e)
4.2 Alias (Псевдонимы полей)
Псевдонимы полезны при работе с API, где имена полей отличаются от Python-стиля:
from pydantic import BaseModel, Field
class ApiUser(BaseModel):
user_id: int = Field(alias="userId")
first_name: str = Field(alias="firstName")
last_name: str = Field(alias="lastName")
data = {
"userId": 123,
"firstName": "John",
"lastName": "Doe"
}
# model_validate с by_alias=True для работы с алиасами
user = ApiUser.model_validate(data)
print(user.user_id) # 123
print(user.model_dump(by_alias=True)) # {'userId': 123, 'firstName': 'John', 'lastName': 'Doe'}
4.3 TypeAdapter для ad-hoc валидации
TypeAdapter позволяет применять логику валидации Pydantic к произвольным типам, не создавая модель:
from pydantic import TypeAdapter from typing import List # Валидация списка чисел adapter = TypeAdapter(List[int]) data = adapter.validate_python(["1", "2", "3"]) print(data) # [1, 2, 3] # Валидация JSON json_str = '["10", "20", "30"]' result = adapter.validate_json(json_str) print(result) # [10, 20, 30]
5. Миграция с Pydantic V1 на V2
Pydantic V2 — это текущая production-версия с существенными улучшениями и некоторыми breaking changes.
5.1 Ключевые изменения
| V1 | V2 |
|---|---|
| parse_obj() | model_validate() |
| parse_raw() | model_validate_json() |
| dict() | model_dump() |
| json() | model_dump_json() |
| @validator | @field_validator |
| @root_validator | @model_validator |
5.2 Пример миграции
V1:
from pydantic import BaseModel, validator
class User(BaseModel):
name: str
age: int
@validator('age')
def check_age(cls, v):
if v < 0:
raise ValueError('Age must be positive')
return v
user = User.parse_obj({"name": "Alice", "age": 25})
print(user.json())
V2:
from pydantic import BaseModel, field_validator
class User(BaseModel):
name: str
age: int
@field_validator('age')
@classmethod
def check_age(cls, v: int) -> int:
if v < 0:
raise ValueError('Age must be positive')
return v
user = User.model_validate({"name": "Alice", "age": 25})
print(user.model_dump_json())
5.3 Инструмент для автоматической миграции
Для автоматического перевода кода с V1 на V2 существует утилита bump-pydantic:
pip install bump-pydantic cd /path/to/your/project bump-pydantic my_package
5.4 Совместное использование V1 и V2
Если нужно временно использовать V1 в проекте с V2:
# Использование V1 API в проекте с V2
from pydantic.v1 import BaseModel, validator
class User(BaseModel):
name: str
6. Заключение и лучшие практики
6.1 Когда использовать Pydantic
- Веб-приложения (FastAPI, Django Ninja) — валидация запросов и ответов
- Работа с API — парсинг и валидация внешних данных
- Конфигурация приложений — управление настройками из env-файлов
- Обработка пользовательского ввода — формы, JSON-данные
- Проекты с LLM — LangChain и другие AI-инструменты
6.2 Лучшие практики
- Держите модели в отдельном модуле (models.py или schemas/)
- Используйте Field для документации — параметр description помогает генерировать качественную документацию
- Не злоупотребляйте сложными валидаторами — выносите сложную логику в отдельные функции
- Используйте model_dump() вместо dict() в Pydantic V2
- При работе с API используйте алиасы (alias) для соответствия стилю API
6.3 Пример хорошо структурированного проекта
project/
├── src/
│ ├── models/
│ │ ├── __init__.py
│ │ ├── user.py # Модели пользователей
│ │ ├── product.py # Модели товаров
│ │ └── order.py # Модели заказов
│ ├── services/ # Бизнес-логика
│ └── main.py
└── tests/
└── test_models.py # Тесты моделей
6.4 Резюме
Pydantic — это must-have инструмент для любого Python-разработчика, работающего с данными. Он:
- Устраняет необходимость в ручной валидации
- Делает код чище и самодокументируемым
- Предоставляет понятные сообщения об ошибках
- Интегрируется с экосистемой Python (FastAPI, LangChain и др.)
Начните с малого — добавьте Pydantic в свой следующий проект, и вы заметите, как упростится работа с данными.