Ревью кодовой базы: docs/reviews/2026-09-27-codebase-review.md.
Корректность и согласованность, пункты 5–7 ревью (docs/changes/018):
- одиночное удаление бэкапа в UI идёт через общий delete_many: пометка
deleted_at и событие backup.deleted, как у группового удаления и API;
- единая система миграций: ручные ALTER из db._migrate перенесены в
migrations.run (при user_version < 1, до замены ID);
- групповая смена канала выполняется фоновыми задачами set_channel;
PUT /api/v1/batch/channel → 202 {"job_ids": [...]} (ломающее изменение
API), меню «Канал» в UI выводит задачи в панель «Задачи».
Тесты: 22 из 22. Стенд проверен на порту 8001 (8000 занят посторонним
процессом), боевые данные не изменены. Ручная проверка UI пользователем
на момент коммита не подтверждена.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
1.5 KiB
Python
48 lines
1.5 KiB
Python
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
|
|
|
from app.config import get_settings
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
_engine = None
|
|
_SessionLocal = None
|
|
|
|
|
|
def init_db(url: str | None = None) -> None:
|
|
"""Создаёт engine и таблицы. Вызывается при старте приложения (и в тестах)."""
|
|
global _engine, _SessionLocal
|
|
url = url or get_settings().database_url
|
|
if url.startswith("sqlite:///") and ":memory:" not in url:
|
|
Path(url.removeprefix("sqlite:///")).parent.mkdir(parents=True, exist_ok=True)
|
|
_engine = create_engine(url, connect_args={"check_same_thread": False})
|
|
_SessionLocal = sessionmaker(_engine, expire_on_commit=False)
|
|
from app import models # noqa: F401 (регистрация моделей)
|
|
|
|
Base.metadata.create_all(_engine) # у новой БД — все таблицы, у старой — только недостающие (events)
|
|
|
|
from app import migrations
|
|
|
|
db_file = Path(url.removeprefix("sqlite:///")) if url.startswith("sqlite:///") and ":memory:" not in url else None
|
|
migrations.run(_engine, db_file)
|
|
|
|
|
|
@contextmanager
|
|
def session_scope():
|
|
"""Короткая транзакция: commit при успехе, rollback при ошибке."""
|
|
session = _SessionLocal()
|
|
try:
|
|
yield session
|
|
session.commit()
|
|
except Exception:
|
|
session.rollback()
|
|
raise
|
|
finally:
|
|
session.close()
|