Pydantic в Python: Глубокое Погружение и Интересные Практики

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.