feat(bot): модель данных, миграции Alembic, FSM в БД, TTL-чистка
- app/db/models.py: 13 таблиц (ARCHITECTURE §2.10); перечисления — VARCHAR + CHECK, JSON → JSONB на Postgres, BIGINT/INTEGER-ключи для SQLite - миграция 0001 (автогенерация + ручная чистка: импорт sa.Text, дубли CHECK) - миграции применяются при старте бота программно (app.db.migrate), script_location из пакета - SqlStorage: FSM aiogram поверх fsm_state — состояние переживает рестарт (перенесено из #4) - cleanup_loop: истёкшие черновики, снимки контейнеров и попытки входа старше суток - тесты: upgrade/downgrade, дрейф моделей и миграций, CHECK, CASCADE, FSM, чистка Refs #5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""Apply Alembic migrations programmatically at startup — no separate step in the container.
|
||||
|
||||
`python -m app.db.migrate [revision]` upgrades the DB from DATABASE_URL (default: head).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||
|
||||
from app.config import DatabaseSettings
|
||||
from app.db.session import make_engine
|
||||
|
||||
SCRIPT_LOCATION = "app.db:migrations"
|
||||
|
||||
|
||||
def alembic_config(connection: Connection | None = None) -> Config:
|
||||
cfg = Config()
|
||||
cfg.set_main_option("script_location", SCRIPT_LOCATION)
|
||||
# env.py reuses this connection instead of opening its own (async engine, one transaction)
|
||||
cfg.attributes["connection"] = connection
|
||||
return cfg
|
||||
|
||||
|
||||
async def upgrade(engine: AsyncEngine, revision: str = "head") -> None:
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(lambda sync_conn: command.upgrade(alembic_config(sync_conn), revision))
|
||||
|
||||
|
||||
async def downgrade(engine: AsyncEngine, revision: str) -> None:
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(lambda sync_conn: command.downgrade(alembic_config(sync_conn), revision))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
revision = sys.argv[1] if len(sys.argv) > 1 else "head"
|
||||
|
||||
async def _run() -> None:
|
||||
engine = make_engine(DatabaseSettings().url)
|
||||
try:
|
||||
await upgrade(engine, revision)
|
||||
finally:
|
||||
await engine.dispose()
|
||||
|
||||
asyncio.run(_run())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user