"""SQLite 引擎、Session、Base、get_db 依赖。 自包含:无需外部 MySQL,容器内单文件 SQLite 即可。对齐 server/database.py 的接口形态, 但用 SQLite(本项目独立运行、无并发写入压力)。 """ from __future__ import annotations from pathlib import Path from typing import Generator from sqlalchemy import create_engine from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from .config import get_settings _engine = None _SessionLocal = None def _db_path() -> Path: """SQLite 文件落在 work_dir 下,跟数据一起走 volume。""" s = get_settings() p = s.work_dir() / "audio2text.db" p.parent.mkdir(parents=True, exist_ok=True) return p def get_engine(): global _engine, _SessionLocal if _engine is None: url = f"sqlite:///{_db_path()}" _engine = create_engine( url, # 后台 reaper / 缓存清理线程与请求线程并发写同一库;busy_timeout 让等待方 # 在拿锁时阻塞 5s 而非立即报 database is locked。 connect_args={"check_same_thread": False, "timeout": 30}, future=True, ) _SessionLocal = sessionmaker(bind=_engine, autoflush=False, expire_on_commit=False) return _engine def get_session_local(): get_engine() return _SessionLocal class Base(DeclarativeBase): pass def init_db_schema() -> None: """建表(幂等)+ 旧库迁移(给 task 表补新字段)。 SQLAlchemy 的 create_all 只建新表不改旧表。对已存在的 task 表, 需手动 ALTER TABLE ADD COLUMN 补 wav_path / segments_json(nullable)。 """ from .models.task import Task # noqa: F401 from .models.upload_session import UploadSession # noqa: F401 from .models.setting import Setting # noqa: F401 engine = get_engine() Base.metadata.create_all(engine) _migrate_task_columns(engine) def _migrate_task_columns(engine) -> None: """检测 task 表缺失的列并 ALTER TABLE 补上(nullable,向后兼容)。""" from sqlalchemy import inspect, text insp = inspect(engine) if "task" not in insp.get_table_names(): return # 新库,create_all 已建好完整表 existing = {c["name"] for c in insp.get_columns("task")} # 新增字段:(列名, 列定义) additions = [ ("wav_path", "VARCHAR(1024)"), ("segments_json", "TEXT"), ] with engine.begin() as conn: for col, coltype in additions: if col not in existing: conn.execute(text(f"ALTER TABLE task ADD COLUMN {col} {coltype}")) def get_db() -> Generator[Session, None, None]: """FastAPI 依赖:每请求一个 Session,结束自动关闭。""" db = get_session_local()() try: yield db finally: db.close()