← Назад к курсу
Валидация данных в Python при помощи Pydantic
Введение в Pydantic
Pydantic - это библиотека для валидации данных и управления настройками с использованием аннотаций типов Python. Она позволяет определять структуры данных с проверкой типов во время выполнения.
Установка
pip install pydantic # Для последней версии (Pydantic V2) pip install pydantic[email]
Основы Pydantic
1. Создание базовой модели
from pydantic import BaseModel
from typing import Optional, List
# Определяем модель
class User(BaseModel):
id: int
username: str
email: str
age: Optional[int] = None # Необязательное поле со значением по умолчанию
is_active: bool = True # Поле со значением по умолчанию
# Создаем экземпляр модели
user1 = User(
id=1,
username="john_doe",
email="john@example.com",
age=25
)
print(user1)
print(user1.username)
print(user1.dict()) # Преобразование в словарь
print(user1.json()) # Преобразование в JSON
2. Валидация данных
Pydantic автоматически проверяет типы данных:
from pydantic import ValidationError
try:
user2 = User(
id="not_a_number", # Будет ошибка!
username="test",
email="test@example.com"
)
except ValidationError as e:
print("Ошибка валидации:")
print(e.json())
3. Модели с вложенными структурами
class Address(BaseModel):
street: str
city: str
country: str
class FullUser(BaseModel):
id: int
username: str
address: Address # Вложенная модель
friends: List[int] = [] # Список чисел
# Создаем вложенную структуру
address = Address(street="Main St", city="New York", country="USA")
user3 = FullUser(
id=1,
username="alice",
address=address,
friends=[2, 3, 4]
)
print(user3.dict())
Валидаторы (Validators)
Pydantic позволяет создавать собственные валидаторы:
from pydantic import validator, Field
import re
class Product(BaseModel):
name: str = Field(..., min_length=3, max_length=50)
price: float = Field(..., gt=0) # Больше 0
sku: str # Артикул товара
@validator('sku')
def validate_sku(cls, value):
# Проверяем формат артикула (3 буквы, 6 цифр)
if not re.match(r'^[A-Z]{3}\d{6}$', value):
raise ValueError('SKU должен быть в формате ABC123456')
return value
@validator('name')
def name_must_contain_space(cls, value):
if ' ' not in value:
raise ValueError('Название должно содержать пробел')
return value.title()
# Использование
try:
product = Product(name="test product", price=19.99, sku="ABC123456")
print(product)
except ValidationError as e:
print(e)
Конфигурация моделей
from pydantic import ConfigDict
class ConfigExample(BaseModel):
# Конфигурация модели
model_config = ConfigDict(
frozen=True, # Объекты нельзя изменять после создания
str_strip_whitespace=True, # Автоматически удаляет пробелы в строках
extra='forbid' # Запрещает дополнительные поля
)
name: str
value: int
obj = ConfigExample(name=" test ", value=10)
print(obj.name) # "test" (без пробелов)
# obj.value = 20 # Ошибка! Объект frozen
Работа с датами и временем
from datetime import datetime, date
from pydantic import field_validator # В Pydantic V2
class Event(BaseModel):
title: str
start_date: date
end_date: date
created_at: datetime = Field(default_factory=datetime.now)
@field_validator('end_date')
def validate_dates(cls, end_date, info):
start_date = info.data.get('start_date')
if start_date and end_date < start_date:
raise ValueError('Дата окончания не может быть раньше даты начала')
return end_date
event = Event(
title="Конференция",
start_date=date(2024, 6, 1),
end_date=date(2024, 6, 3)
)
print(event)
Парсинг из различных источников
Pydantic умеет парсить данные из разных форматов:
class Item(BaseModel):
name: str
price: float
quantity: int = 1
# Из словаря
data_dict = {"name": "Book", "price": 29.99}
item1 = Item(**data_dict)
# Из JSON
import json
json_data = '{"name": "Laptop", "price": 999.99, "quantity": 2}'
item2 = Item.parse_raw(json_data)
# Из файла
with open('item.json', 'w') as f:
f.write(json_data)
item3 = Item.parse_file('item.json')
Generic модели (обобщенные модели)
from typing import Generic, TypeVar, List
from pydantic import BaseModel
T = TypeVar('T')
class Response(BaseModel, Generic[T]):
success: bool
data: T
message: str = ""
# Использование с разными типами
user_response = Response[User](
success=True,
data=user1,
message="User retrieved successfully"
)
list_response = Response[List[User]](
success=True,
data=[user1],
message="Users retrieved successfully"
)
Работа с API (FastAPI пример)
Pydantic отлично интегрируется с FastAPI:
from fastapi import FastAPI, HTTPException
from typing import List
app = FastAPI()
# Модели для API
class ItemCreate(BaseModel):
name: str
description: str = None
price: float
tax: float = None
class ItemResponse(ItemCreate):
id: int
# "База данных"
items_db = []
@app.post("/items/", response_model=ItemResponse)
async def create_item(item: ItemCreate):
# Pydantic автоматически валидирует входящие данные
item_dict = item.dict()
item_dict['id'] = len(items_db) + 1
items_db.append(item_dict)
return item_dict
@app.get("/items/", response_model=List[ItemResponse])
async def read_items():
return items_db
Полезные декораторы и методы
class SmartModel(BaseModel):
name: str
value: int
# Метод вызывается после валидации
def model_post_init(self, __context):
print(f"Создан объект {self.name}")
# Кастомный метод
def double_value(self):
return self.value * 2
# Копирование с обновлением
obj1 = SmartModel(name="Test", value=10)
obj2 = obj1.model_copy(update={'value': 20})
print(obj2.double_value()) # 40
Практический пример: система заказов
from enum import Enum
from typing import Dict
class OrderStatus(str, Enum):
PENDING = "pending"
PROCESSING = "processing"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
class OrderItem(BaseModel):
product_id: int
product_name: str
quantity: int
price_per_unit: float
@property
def total_price(self):
return self.quantity * self.price_per_unit
class Order(BaseModel):
order_id: int
customer_id: int
items: List[OrderItem]
status: OrderStatus = OrderStatus.PENDING
metadata: Dict[str, str] = {}
@property
def order_total(self):
return sum(item.total_price for item in self.items)
def add_item(self, product_id: int, product_name: str,
quantity: int, price_per_unit: float):
self.items.append(
OrderItem(
product_id=product_id,
product_name=product_name,
quantity=quantity,
price_per_unit=price_per_unit
)
)
def update_status(self, new_status: OrderStatus):
valid_transitions = {
OrderStatus.PENDING: [OrderStatus.PROCESSING, OrderStatus.CANCELLED],
OrderStatus.PROCESSING: [OrderStatus.SHIPPED, OrderStatus.CANCELLED],
OrderStatus.SHIPPED: [OrderStatus.DELIVERED],
}
if new_status in valid_transitions.get(self.status, []):
self.status = new_status
else:
raise ValueError(f"Нельзя перейти из {self.status} в {new_status}")
# Использование
order = Order(
order_id=1,
customer_id=123,
items=[
OrderItem(
product_id=1,
product_name="Python Book",
quantity=2,
price_per_unit=29.99
)
]
)
order.add_item(2, "Pydantic Guide", 1, 15.99)
print(f"Total: ${order.order_total:.2f}")
order.update_status(OrderStatus.PROCESSING)
Советы и лучшие практики
- Всегда используйте аннотации типов
- Используйте Field для дополнительных ограничений
- Разделяйте модели для ввода и вывода (например, UserCreate и UserResponse)
- Используйте ConfigDict для настройки поведения моделей
- Проверяйте данные как можно раньше
Заключение
Pydantic - мощный инструмент для валидации данных в Python. Он помогает:
- Обеспечить целостность данных
- Уменьшить количество ошибок
- Улучшить читаемость кода
- Легко интегрироваться с другими инструментами (FastAPI, SQLAlchemy и др.)
Начните использовать Pydantic в своих проектах для более надежной работы с данными!