🏠 Закрепляющая практика

⏱️ Время: ~40 мин 🎯 Сложность: Средняя
Важно: это практика для самостоятельного закрепления по материалу практикума, а не отдельное домашнее задание из LMS. Урок 06 — занятие-практикум без отдельного LMS-ДЗ.

⚡ Задание кратко

Реализуй в одном файле models.py:

  1. Pydantic v2: модели Event (field_validator, дата в будущем) и UserProfile (EmailStr, Field min_length=8, ConfigDict)
  2. SQLAlchemy 2.x: модели User и Address (DeclarativeBase, Mapped, relationship, SQLite :memory:)
  3. Сессия: создать пользователя с адресом, commit, удалить, проверить

Пошаговое решение — в детальном режиме.

Текст задания

Объедини несколько задач практикума в один связный файл models.py. Цель — убедиться, что ты умеешь:

  1. Создать Pydantic v2 модели с кастомным валидатором и Field-ограничениями.
  2. Построить SQLAlchemy 2.x схему с двумя связанными таблицами.
  3. Провести полный цикл работы с сессией: add → commit → query → delete → commit.

Подзадача 1: Pydantic-модели

  • Модель Event(title, date, location) — поле date должно быть в будущем (@field_validator).
  • Модель UserProfile(username, password, email) — пароль не короче 8 символов, email через EmailStr, пример в json_schema_extra.

Подзадача 2: SQLAlchemy-модели

  • Класс Base(DeclarativeBase).
  • Модель User(id, name, age) с relationship на Address.
  • Модель Address(id, street, user_id) с ForeignKey на users.id.
  • Engine: sqlite:///:memory: с echo=True.

Подзадача 3: Сессия

  • Создать нового пользователя и адрес, добавить в БД, сделать commit.
  • Найти пользователя запросом, вывести имя и адрес.
  • Удалить пользователя, сделать commit, проверить что вернулся None.

Подготовка окружения

1. Создать виртуальное окружение

# PowerShell / cmd
python -m venv venv
venv\Scripts\activate   # Windows
# source venv/bin/activate  # macOS/Linux

2. Установить зависимости

pip install "pydantic[email]" sqlalchemy

3. Инициализировать Git-репозиторий

git init
git add models.py
git commit -m "feat: lesson 06 practice — Pydantic v2 + SQLAlchemy 2.x models"
# или создать ветку:
git checkout -b lesson/06-sqlalchemy-practice

Пошаговое решение

Шаг 1: импорты

Собираем все нужные импорты в начале файла:

# models.py
from pydantic import BaseModel, EmailStr, Field, ConfigDict, field_validator
from typing import Annotated, List, Optional
from decimal import Decimal
from datetime import datetime, timedelta

from sqlalchemy import create_engine, String, Integer, ForeignKey
from sqlalchemy.orm import (
    DeclarativeBase, Mapped, mapped_column,
    relationship, Session
)

Шаг 2: Pydantic-модели

Для Event используем @field_validator с @classmethod. Для UserProfileField(min_length=8), EmailStr и ConfigDict:

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

    @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


class UserProfile(BaseModel):
    username: str
    password: str = Field(
        min_length=8,
        description="Password must be at least 8 characters long"
    )
    email: EmailStr

    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "username": "john_doe",
                "password": "securePassword123",
                "email": "john.doe@example.com"
            }
        }
    )

Шаг 3: SQLAlchemy-схема

Базовый класс через наследование (SQLAlchemy 2.x). Поля через Mapped[тип] = mapped_column(...). Связь через relationship с back_populates на обеих сторонах:

engine = create_engine('sqlite:///:memory:', echo=True)


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)
    addresses: Mapped[List["Address"]] = relationship(
        "Address", back_populates="user"
    )


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


# Создать таблицы в БД
Base.metadata.create_all(engine)

Шаг 4: работа с сессией

Используем контекстный менеджер with Session(engine) as session: — закрытие автоматическое. После commit() транзакция зафиксирована:

with Session(engine) as session:
    # Создание
    new_user = User(name="John Doe", age=28)
    new_addr = Address(street="123 Elm Street", user=new_user)
    session.add(new_user)
    session.add(new_addr)
    session.commit()
    print(f"Created user id={new_user.id}, address={new_addr.street}")

with Session(engine) as session:
    # Запрос
    user = session.query(User).filter_by(name="John Doe").first()
    print(f"Found: {user.name}, age={user.age}")

    # Удаление
    session.delete(user)
    session.commit()

    # Проверка
    result = session.query(User).filter_by(name="John Doe").first()
    print(f"After delete: {result}")  # None

Шаг 5: тестирование Pydantic-моделей

if __name__ == "__main__":
    # Тест Event
    try:
        evt = Event(
            title="Conference",
            date=datetime.now() + timedelta(days=7),
            location="Moscow"
        )
        print(f"Event: {evt.title}, {evt.date}")
    except Exception as e:
        print(f"Event error: {e}")

    # Тест UserProfile
    try:
        user = UserProfile(
            username="alice",
            password="strongPass1",
            email="alice@example.com"
        )
        print(f"UserProfile: {user.username}, {user.email}")
    except Exception as e:
        print(f"UserProfile error: {e}")

Проверка в VS Code

Открыть проект и запустить файл

  1. Открыть папку в VS Code: File → Open Folder
  2. Убедиться, что выбран нужный Python-интерпретатор (из venv): Ctrl+Shift+P → Python: Select Interpreter
  3. Открыть терминал: Ctrl+`, активировать venv: venv\Scripts\activate
  4. Запустить файл в терминале: python models.py
  5. Или запустить через кнопку ▶️ в правом верхнем углу редактора.

Запуск через F5 с launch.json

Создайте файл .vscode/launch.json для запуска текущего файла:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Current File",
            "type": "debugpy",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal",
            "justMyCode": true
        }
    ]
}

После создания файла нажмите F5 для запуска с отладчиком.

Точки останова (breakpoints)

  1. Кликните слева от номера строки — появится красная точка (breakpoint).
  2. Нажмите F5 — выполнение остановится на этой строке.
  3. В панели Variables (слева) видны значения всех переменных.
  4. Используйте F10 (Step Over) и F11 (Step Into) для пошагового выполнения.
  5. В Debug Console (снизу) можно вводить Python-выражения в контексте текущей точки останова.

Рекомендуемые расширения VS Code

  • Python (ms-python.python) — основное расширение
  • Pylance (ms-python.vscode-pylance) — статический анализ, автодополнение с типами
  • SQLTools — просмотр и редактирование баз данных прямо в VS Code