📖 Теория: напоминание перед практикумом

Это практикум — теория здесь краткое повторение уроков 03 и 05

⚡ Кратко: что повторяем на практикуме

  • Pydantic v2: BaseModel, @field_validator (@classmethod!), Field(min_length=, gt=, pattern=), EmailStr, ConfigDict(json_schema_extra=, str_strip_whitespace=), Annotated[Decimal, Field(gt=0)]
  • SQLAlchemy 2.x: create_engine, class Base(DeclarativeBase), Mapped[тип] = mapped_column(), ForeignKey, relationship(back_populates=), with Session(engine) as session:

Полная теория — Урок 03 и Урок 05.

1. Pydantic v2: ключевые концепции

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

BaseModel и объявление полей

Каждое поле модели объявляется как аннотация типа. Pydantic автоматически проверяет типы при создании объекта.

from pydantic import BaseModel, EmailStr, Field
from typing import Annotated
from decimal import Decimal

class UserProfile(BaseModel):
    username: str
    password: str = Field(min_length=8, description="Min 8 characters")
    email: EmailStr

@field_validator (Pydantic v2)

Кастомный валидатор в Pydantic v2 объявляется через @field_validator с обязательным @classmethod:

from pydantic import BaseModel, field_validator
from datetime import datetime

class Event(BaseModel):
    title: str
    date: datetime

    @field_validator('date')
    @classmethod
    def date_must_be_future(cls, v: datetime) -> datetime:
        if v < datetime.now():
            raise ValueError("Event date must be in the future")
        return v
Важно: в Pydantic v2 декоратор @field_validator требует @classmethod. В лекции можно встретить старый @validator без @classmethod — это Pydantic v1. Подробнее — Старый vs Новый.

Field-ограничения и Annotated

Для числовых типов и строк с ограничениями в Pydantic v2 используется Annotated совместно с Field:

from typing import Annotated
from decimal import Decimal
from pydantic import BaseModel, Field

class Transaction(BaseModel):
    # Decimal > 0
    amount: Annotated[Decimal, Field(gt=0)]
    # Строка с паттерном
    transaction_type: Annotated[str, Field(pattern=r"^(debit|credit)$")]
    # Строка ровно 3 символа
    currency: Annotated[str, Field(min_length=3, max_length=3)]

ConfigDict и json_schema_extra

Конфигурация модели в Pydantic v2 задаётся через model_config = ConfigDict(...) (вместо class Config из v1):

from pydantic import BaseModel, Field, ConfigDict

class UserProfile(BaseModel):
    username: str
    password: str = Field(min_length=8)

    model_config = ConfigDict(
        str_strip_whitespace=True,
        json_schema_extra={
            "example": {
                "username": "john_doe",
                "password": "securePass123"
            }
        }
    )

2. SQLAlchemy 2.x: ключевые концепции

SQLAlchemy — ORM-библиотека для Python. Подробнее — Урок 05.

create_engine

Движок (engine) — точка входа SQLAlchemy. Строка подключения зависит от СУБД:

from sqlalchemy import create_engine

# MySQL через pymysql
engine = create_engine('mysql+pymysql://user:password@localhost/mydb')

# SQLite в памяти (для тестов)
engine = create_engine('sqlite:///:memory:', echo=True)

DeclarativeBase и Mapped/mapped_column

В SQLAlchemy 2.x базовый класс создаётся наследованием от DeclarativeBase, поля объявляются через Mapped с аннотациями типов:

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String, Integer

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = 'users'
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    age: Mapped[int] = mapped_column(Integer)

ForeignKey и relationship

Связи между таблицами задаются через ForeignKey и relationship:

from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship

class Post(Base):
    __tablename__ = 'posts'
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(255))
    user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
    user: Mapped["User"] = relationship("User", back_populates="posts")

Работа с Session

В SQLAlchemy 2.x сессия используется через контекстный менеджер — закрытие автоматическое:

from sqlalchemy.orm import Session

Base.metadata.create_all(engine)

with Session(engine) as session:
    new_user = User(name="Alice", age=30)
    session.add(new_user)
    session.commit()
    # Запрос
    users = session.query(User).filter_by(name="Alice").all()
    # Удаление
    session.delete(new_user)
    session.commit()