"""数据库:SQLite(aiosqlite)+ SQLAlchemy async。""" from __future__ import annotations from collections.abc import AsyncGenerator from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase from config import get_settings class Base(DeclarativeBase): pass _engine = None _session_factory: async_sessionmaker[AsyncSession] | None = None def get_engine(): global _engine, _session_factory if _engine is None: settings = get_settings() db_path = f"{settings.data_dir}/app.db" _engine = create_async_engine(f"sqlite+aiosqlite:///{db_path}", echo=False) _session_factory = async_sessionmaker(_engine, expire_on_commit=False) return _engine def get_session_factory() -> async_sessionmaker[AsyncSession]: get_engine() assert _session_factory is not None return _session_factory async def get_db() -> AsyncGenerator[AsyncSession, None]: async with get_session_factory()() as session: yield session async def _migrate(conn) -> None: """给旧库补充新增列(create_all 不会修改已存在的表)。""" rows = await conn.execute(text("PRAGMA table_info(suites)")) cols = {row[1] for row in rows} if "model" not in cols: await conn.execute(text("ALTER TABLE suites ADD COLUMN model VARCHAR(64)")) if "requirements" not in cols: await conn.execute(text("ALTER TABLE suites ADD COLUMN requirements TEXT")) async def init_db() -> None: """启动时建表 + 轻量列迁移(MVP 不引 Alembic)。""" import models # noqa: F401 确保模型注册 engine = get_engine() async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) await _migrate(conn)