- 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>
36 lines
1.3 KiB
Python
36 lines
1.3 KiB
Python
"""Async engine and session factory."""
|
|
|
|
from sqlalchemy import event
|
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
SessionMaker = async_sessionmaker[AsyncSession]
|
|
|
|
|
|
def make_engine(url: str) -> AsyncEngine:
|
|
if url.startswith("sqlite"):
|
|
# In-memory SQLite lives per connection: share one connection so every session sees the same DB.
|
|
kwargs = (
|
|
{"poolclass": StaticPool, "connect_args": {"check_same_thread": False}}
|
|
if ":memory:" in url or url.endswith("://")
|
|
else {}
|
|
)
|
|
engine = create_async_engine(url, **kwargs)
|
|
event.listen(engine.sync_engine, "connect", _sqlite_foreign_keys)
|
|
return engine
|
|
return create_async_engine(url, pool_pre_ping=True)
|
|
|
|
|
|
def _sqlite_foreign_keys(dbapi_conn: object, _record: object) -> None:
|
|
# SQLite ignores FOREIGN KEY / ON DELETE CASCADE unless asked per connection
|
|
cursor = dbapi_conn.cursor() # type: ignore[attr-defined]
|
|
cursor.execute("PRAGMA foreign_keys=ON")
|
|
cursor.close()
|
|
|
|
|
|
def make_sessionmaker(engine: AsyncEngine) -> SessionMaker:
|
|
return async_sessionmaker(engine, expire_on_commit=False)
|
|
|
|
|
|
__all__ = ["SessionMaker", "make_engine", "make_sessionmaker"]
|