Pydantic в Python: Глубокое Погружение и Интересные Практики
Pydantic в Python: Глубокое Погружение и Интересные Практики
Pydantic — одна из ключевых библиотек в современном Python-стеке для работы с данными. Она обеспечивает валидацию, парсинг и сериализацию с использованием аннотаций типов. Вот как извлечь из неё максимум пользы.
1. Валидация с Кастомными Правилами
Используйте @validator для сложной логики:
from pydantic import BaseModel, validator
class User(BaseModel):
username: str
password: str
@validator("password")
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("Пароль слишком короткий")
if not any(char.isdigit() for char in v):
raise ValueError("Пароль должен содержать цифру")
return v
user = User(username="john", password="secure123") # Корректно
2. Динамические Значения по Умолчанию
Используйте default_factory для генерации значений:
from uuid import uuid4
from pydantic import BaseModel, Field
class Document(BaseModel):
id: str = Field(default_factory=lambda: uuid4().hex)
content: str
doc = Document(content="Hello")
print(doc.id) # Например: "e4eaaaf2d3a74d3b8b0e7b8d7"
3. Модели для Ошибок и Ответов API
Создайте унифицированные модели для API:
class ErrorResponse(BaseModel):
error: str
details: dict = {}
class SuccessResponse(BaseModel):
data: dict
message: str = "Success"
# Использование в FastAPI:
from fastapi import status, HTTPException
def handle_error(error: str, status_code: status):
raise HTTPException(
status_code=status_code,
detail=ErrorResponse(error=error).dict()
)
4. Работа с Наследованием Моделей
Комбинируйте модели через наследование:
class TimestampMixin(BaseModel):
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class Post(TimestampMixin):
title: str
content: str
post = Post(title="Hello", content="World")
print(post.created_at) # 2025-05-15 12:00:00
5. Парсинг Данных из Неизвестных Источников
Используйте parse_obj для сырых данных:
raw_data = '{"name": "Alice", "age": "30"}' # Возможно из внешнего API
data = User.parse_raw(raw_data)
print(data) # name='Alice' age=30
6. Валидация Данных с Условиями
@root_validator для проверки всей модели:
class Order(BaseModel):
items: list[str]
discount_code: str = None
@root_validator
def validate_discount(cls, values):
items, code = values.get("items"), values.get("discount_code")
if code and len(items) < 3:
raise ValueError("Скидка только для 3+ товаров")
return values
7. Интеграция с ORM (SQLAlchemy, Django)
Используйте from_orm для преобразования:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class UserDB(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
class UserPydantic(BaseModel):
id: int
name: str
class Config:
orm_mode = True
db_user = UserDB(name="John")
pydantic_user = UserPydantic.from_orm(db_user)
8. Работа с Конфигурацией
Удобное управление настройками через BaseSettings:
from pydantic import BaseSettings, PostgresDsn
class AppSettings(BaseSettings):
db_url: PostgresDsn
debug: bool = False
class Config:
env_file = ".env"
env_prefix = "APP_"
settings = AppSettings() # Автоматически загружает APP_DB_URL из .env
9. Кастомные Типы Данных
Создавайте свои типы для специфичных данных:
from pydantic import BaseModel
class RGBColor(BaseModel):
r: int = Field(0, ge=0, le=255)
g: int = Field(0, ge=0, le=255)
b: int = Field(0, ge=0, le=255)
class Design(BaseModel):
background: RGBColor = RGBColor(r=255, g=255, b=255)
10. Эволюция Моделей с Field
Плавное изменение схемы:
class Product(BaseModel):
id: int
name: str
# Старое поле, будет удалено в будущем
old_field: str = Field(
default=None,
deprecated=True,
description="Используйте `new_field` вместо этого"
)
11. Валидация Путей и Файлов
FilePath и DirectoryPath для работы с ФС:
from pydantic import FilePath, DirectoryPath
class Config(BaseModel):
config_path: FilePath
log_dir: DirectoryPath
cfg = Config(config_path="/app/config.yaml", log_dir="/logs")
12. Модели для Тестирования
Используйте Pydantic в тестах:
def test_user_creation():
data = {"username": "test", "password": "secret123"}
user = User(**data)
assert user.username == "test"
with pytest.raises(ValueError):
User(username="test", password="short") # Невалидный пароль
13. Расширение через Плагины
Интеграция с другими инструментами:
pydantic-settings: Для расширенных конфигураций.pydantic-extra-types: Дополнительные типы данных (IBAN, MAC-адреса).pydantic-django: Интеграция с Django ORM.
14. Оптимизация Производительности
- Pydantic V2: Написана на Rust (до 50x быстрее V1).
- Используйте
model_construct()для создания объектов без валидации, когда уверены в данных:user = User.model_construct(username="admin", password="x"*8) # Без валидации
15. Генерация Схем для Документации
Автоматическая генерация OpenAPI-схем в FastAPI:
from fastapi import FastAPI
app = FastAPI()
@app.post("/users/", response_model=User)
def create_user(user: User):
return user
Схема эндпоинта будет доступна в /docs.
Заключение
Pydantic — это не просто валидатор данных, а мощный инструмент для построения надёжных и поддерживаемых приложений. Его ключевые преимущества:
- Типизация: Статический анализ с mypy.
- Безопасность: Защита от инъекций через кастомные типы.
- Интеграция: Работа с ORM, веб-фреймворками, конфигами.
- Производительность: Оптимизированные алгоритмы в V2.
Пример полной системы:
# Модель -> Настройки -> API
class DatabaseConfig(BaseSettings):
url: PostgresDsn
class AppConfig(BaseSettings):
db: DatabaseConfig
debug: bool
config = AppConfig()
class User(BaseModel):
id: UUID
name: str
# FastAPI эндпоинт
@app.post("/users", response_model=User)
def create_user(user: User):
db_user = UserDB(**user.dict())
session.add(db_user)
session.commit()
return User.from_orm(db_user)
Совет: Всегда обновляйтесь до последней версии Pydantic для использования современных возможностей и оптимизаций. Библитория активно развивается и становится стандартом де-факто для работы с данными в Python.