"""Жизненный цикл партии: статус, тайминг, длительность, причина победы; place → nullable. Идемпотентна: на свежей БД (0001 создаёт схему из актуальных моделей через create_all) столбцы уже есть → no-op; на существующей БД (ревизия 0002, старая схема) — добавляет столбцы и делает place nullable. render_as_batch включён в env.py (нужен для SQLite). Revision ID: 0003_match_lifecycle Revises: 0002_seed_reference Create Date: 2026-06-15 """ from typing import Sequence, Union import sqlalchemy as sa from sqlalchemy import inspect from alembic import op revision: str = "0003_match_lifecycle" down_revision: Union[str, None] = "0002_seed_reference" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: bind = op.get_bind() insp = inspect(bind) match_cols = {c["name"] for c in insp.get_columns("matches")} add = [] if "status" not in match_cols: add.append(sa.Column("status", sa.String(16), nullable=False, server_default="finished")) if "started_at" not in match_cols: add.append(sa.Column("started_at", sa.DateTime(), nullable=True)) if "finished_at" not in match_cols: add.append(sa.Column("finished_at", sa.DateTime(), nullable=True)) if "duration_minutes" not in match_cols: add.append(sa.Column("duration_minutes", sa.Integer(), nullable=True)) if "win_reason" not in match_cols: add.append(sa.Column("win_reason", sa.String(16), nullable=True)) if add: with op.batch_alter_table("matches") as b: for col in add: b.add_column(col) # Бэкфилл для существующих (уже завершённых) партий. op.execute("UPDATE matches SET status = 'finished' WHERE status IS NULL") op.execute("UPDATE matches SET started_at = created_at WHERE started_at IS NULL") op.execute("UPDATE matches SET finished_at = created_at WHERE finished_at IS NULL") # place → nullable, если ещё NOT NULL. place_col = next(c for c in insp.get_columns("match_participants") if c["name"] == "place") if not place_col["nullable"]: with op.batch_alter_table("match_participants") as b: b.alter_column("place", existing_type=sa.Integer(), nullable=True) def downgrade() -> None: with op.batch_alter_table("matches") as b: for name in ("win_reason", "duration_minutes", "finished_at", "started_at", "status"): try: b.drop_column(name) except Exception: # noqa: BLE001 pass