← Назад к вопросам

Какие схемы реализовывал на pydantic?

1.0 Junior🔥 201 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Схемы на Pydantic

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

1. Базовые модели для API

from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
from typing import Optional

class UserCreate(BaseModel):
    """Схема для создания пользователя"""
    name: str = Field(..., min_length=3, max_length=50)
    email: EmailStr
    password: str = Field(..., min_length=8)
    age: Optional[int] = Field(None, ge=0, le=150)

    model_config = {
        "json_schema_extra": {
            "examples": [{
                "name": "John Doe",
                "email": "john@example.com",
                "password": "secure123",
                "age": 30
            }]
        }
    }

class UserResponse(BaseModel):
    """Схема для ответа пользователя"""
    id: int
    name: str
    email: str
    created_at: datetime
    is_active: bool = True

    model_config = {
        "from_attributes": True  # Работает с ORM моделями
    }

2. Вложенные модели и отношения

from typing import List

class AddressModel(BaseModel):
    street: str
    city: str
    postal_code: str
    country: str

class PostModel(BaseModel):
    id: int
    title: str
    content: str
    created_at: datetime

class UserWithRelations(BaseModel):
    id: int
    name: str
    email: str
    address: AddressModel  # Вложенная модель
    posts: List[PostModel]  # Список вложенных

    model_config = {"from_attributes": True}

# Использование
user_data = {
    "id": 1,
    "name": "John",
    "email": "john@example.com",
    "address": {
        "street": "123 Main St",
        "city": "New York",
        "postal_code": "10001",
        "country": "USA"
    },
    "posts": [
        {"id": 1, "title": "Post 1", "content": "...", "created_at": "2024-01-01T10:00:00"},
        {"id": 2, "title": "Post 2", "content": "...", "created_at": "2024-01-02T10:00:00"}
    ]
}

user = UserWithRelations(**user_data)
print(user)

3. Валидация с field_validator

from pydantic import BaseModel, field_validator, ValidationError

class ProductCreate(BaseModel):
    name: str
    price: float
    quantity: int
    sku: str

    @field_validator('price')
    @classmethod
    def price_must_be_positive(cls, v):
        if v <= 0:
            raise ValueError('Price must be greater than 0')
        return round(v, 2)  # Округлить до 2 знаков

    @field_validator('quantity')
    @classmethod
    def quantity_must_be_non_negative(cls, v):
        if v < 0:
            raise ValueError('Quantity cannot be negative')
        return v

    @field_validator('sku')
    @classmethod
    def sku_format(cls, v):
        if not v.isupper() or len(v) < 3:
            raise ValueError('SKU must be uppercase with at least 3 characters')
        return v

# Использование
try:
    product = ProductCreate(
        name="Laptop",
        price=-100,  # Ошибка!
        quantity=5,
        sku="abc"  # Ошибка!
    )
except ValidationError as e:
    print(e.json())

4. Кастомные типы и аннотации

from pydantic import BaseModel, BeforeValidator, Field
from typing import Annotated
import re

# Кастомный телефонный номер
def validate_phone(v):
    pattern = r'^\+?1?\d{9,15}$'
    if not re.match(pattern, v):
        raise ValueError('Invalid phone number')
    return v

PhoneNumber = Annotated[str, BeforeValidator(validate_phone)]

class ContactInfo(BaseModel):
    phone: PhoneNumber
    email: str

# Использование
contact = ContactInfo(phone="+1234567890", email="user@example.com")
print(contact.phone)  # +1234567890

5. Условная валидация с model_validator

from pydantic import BaseModel, model_validator
from datetime import datetime

class DateRange(BaseModel):
    start_date: datetime
    end_date: datetime

    @model_validator(mode='after')
    def validate_date_range(self):
        if self.start_date >= self.end_date:
            raise ValueError('start_date must be before end_date')
        return self

class EventCreate(BaseModel):
    name: str
    date_range: DateRange
    max_attendees: int
    current_attendees: int = 0

    @model_validator(mode='after')
    def validate_attendees(self):
        if self.current_attendees > self.max_attendees:
            raise ValueError('current_attendees cannot exceed max_attendees')
        return self

6. Динамические поля и Optional

from pydantic import BaseModel, Field
from typing import Optional, Dict, Any

class FilterOptions(BaseModel):
    category: Optional[str] = None
    min_price: Optional[float] = None
    max_price: Optional[float] = None
    in_stock: Optional[bool] = None

    model_config = {
        "json_schema_extra": {
            "example": {
                "category": "electronics",
                "min_price": 100,
                "max_price": 1000
            }
        }
    }

class ProductFilter(BaseModel):
    filters: FilterOptions
    sort_by: str = "created_at"
    limit: int = Field(20, ge=1, le=100)
    offset: int = Field(0, ge=0)

# Использование
filter_data = {
    "filters": {"category": "electronics", "min_price": 100},
    "sort_by": "price",
    "limit": 50
}
product_filter = ProductFilter(**filter_data)

7. Наследование и переиспользование

class TimestampModel(BaseModel):
    """Базовая модель с временными метками"""
    created_at: datetime
    updated_at: datetime

    model_config = {"from_attributes": True}

class BaseEntity(TimestampModel):
    """Базовая сущность"""
    id: int

class BlogPost(BaseEntity):
    """Пост в блоге с наследованием"""
    title: str
    content: str
    author_id: int
    is_published: bool = False
    tags: List[str] = Field(default_factory=list)

class Comment(BaseEntity):
    """Комментарий с наследованием"""
    post_id: int
    author_id: int
    text: str
    is_approved: bool = False

8. Сложные типы и Generic

from pydantic import BaseModel
from typing import Generic, TypeVar, List, Dict

T = TypeVar('T')

class PaginatedResponse(BaseModel, Generic[T]):
    """Обёртка для пагинированных ответов"""
    data: List[T]
    total: int
    page: int
    page_size: int

    @property
    def total_pages(self) -> int:
        return (self.total + self.page_size - 1) // self.page_size

# Использование
class UserInfo(BaseModel):
    id: int
    name: str
    email: str

paginated_users = PaginatedResponse[UserInfo](
    data=[
        {"id": 1, "name": "John", "email": "john@example.com"},
        {"id": 2, "name": "Jane", "email": "jane@example.com"}
    ],
    total=100,
    page=1,
    page_size=2
)

9. Конфигурация и сериализация

from pydantic import BaseModel, Field, ConfigDict
from datetime import datetime

class ProductResponse(BaseModel):
    model_config = ConfigDict(
        from_attributes=True,
        populate_by_name=True,  # Использовать alias
        str_strip_whitespace=True,
        json_encoders={  # Кастомное кодирование
            datetime: lambda v: v.isoformat()
        }
    )

    id: int
    name: str = Field(alias="product_name")
    price: float
    created_at: datetime
    description: str | None = None

    def to_dict(self, **kwargs):
        """Кастомная сериализация"""
        return self.model_dump(**kwargs)

    def to_json(self, **kwargs):
        """JSON сериализация"""
        return self.model_dump_json(**kwargs)

# Использование
product = ProductResponse(
    id=1,
    product_name="Laptop",  # Используем alias
    price=999.99,
    created_at=datetime.now()
)

print(product.model_dump(by_alias=True))
print(product.model_dump_json(indent=2))

10. Перечисления и Literal типы

from enum import Enum
from pydantic import BaseModel
from typing import Literal

class OrderStatus(str, Enum):
    PENDING = "pending"
    PROCESSING = "processing"
    COMPLETED = "completed"
    CANCELLED = "cancelled"

class OrderCreate(BaseModel):
    items: List[int]  # Product IDs
    status: OrderStatus = OrderStatus.PENDING
    priority: Literal["low", "medium", "high"] = "medium"
    payment_method: Literal["credit_card", "paypal", "bank_transfer"]

# Использование
order = OrderCreate(
    items=[1, 2, 3],
    status=OrderStatus.PENDING,
    payment_method="credit_card"
)

print(order.status.value)  # "pending"
print(order.model_dump())  # {'items': [1, 2, 3], 'status': 'pending', ...}

11. Кастомные вычисляемые поля

from pydantic import BaseModel, computed_field
from datetime import datetime

class UserProfile(BaseModel):
    first_name: str
    last_name: str
    birth_date: datetime

    @computed_field  # type: ignore[misc]
    @property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}"

    @computed_field  # type: ignore[misc]
    @property
    def age(self) -> int:
        return (datetime.now() - self.birth_date).days // 365

# Использование
user = UserProfile(
    first_name="John",
    last_name="Doe",
    birth_date=datetime(1990, 1, 1)
)

print(user.full_name)  # "John Doe"
print(user.age)  # ~34
print(user.model_dump())  # Включает вычисляемые поля

12. Коллекции и Dict

from pydantic import BaseModel
from typing import Dict, List, Set

class AppConfig(BaseModel):
    settings: Dict[str, str]  # Ключ-значение
    features: Dict[str, bool]  # Feature flags
    tags: Set[str]  # Уникальные значения
    metadata: Dict[str, int]  # Метаданные

# Использование
config = AppConfig(
    settings={"api_url": "https://api.example.com", "timeout": "30"},
    features={"new_ui": True, "beta_api": False},
    tags={"production", "critical", "monitored"},
    metadata={"version": 1, "revision": 42}
)

print(config.features)

Лучшие практики

# 1. Разные модели для разных операций
class UserCreate(BaseModel):  # Для POST
    name: str
    email: str

class UserUpdate(BaseModel):  # Для PATCH
    name: Optional[str] = None
    email: Optional[str] = None

class UserResponse(BaseModel):  # Для GET
    id: int
    name: str
    email: str
    created_at: datetime

# 2. Использование Field для документации
class BlogPost(BaseModel):
    title: str = Field(
        ...,
        min_length=5,
        max_length=200,
        description="Title of the blog post"
    )
    content: str = Field(..., description="Main content")

# 3. Группировка по бизнес-логике
class PaymentRequest(BaseModel):
    amount: float = Field(..., gt=0)
    currency: Literal["USD", "EUR", "GBP"]
    card_token: str
    customer_id: int

# 4. Кастомные исключения
from pydantic import ValidationError

try:
    user = UserCreate(name="", email="invalid")
except ValidationError as e:
    # Обработка ошибок валидации
    for error in e.errors():
        print(f"Field: {error['loc'][0]}, Error: {error['msg']}")

Pydantic позволяет создавать надёжные, хорошо задокументированные API с минимальным усилием. Главное — использовать типизацию и правильно структурировать модели для разных операций.