"""数据库引擎、Session 与 Declarative Base。 引擎采用懒初始化:首次访问 `get_engine()` 时才创建连接池, 避免 import 时的副作用,并支持 `dispose()` 后重新加载配置。 """ from __future__ import annotations from collections.abc import Generator from sqlalchemy import create_engine from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from .config import get_settings _engine: create_engine | None = None _session_local: sessionmaker | None = None def get_engine() -> create_engine: """返回全局 Engine 实例;首次调用时懒创建。""" global _engine, _session_local if _engine is None: s = get_settings() _engine = create_engine( s.db_url(), pool_pre_ping=True, pool_size=s.database.pool_size, pool_recycle=s.database.pool_recycle, future=True, ) _session_local = sessionmaker( bind=_engine, autoflush=False, autocommit=False, future=True, ) return _engine def dispose_engine() -> None: """关闭连接池并清除缓存,下次访问时重新创建(配合 reload_settings 使用)。""" global _engine, _session_local if _engine is not None: _engine.dispose() _engine = None _session_local = None def get_session_local() -> sessionmaker: """返回全局 SessionLocal;确保 Engine 已初始化。""" get_engine() return _session_local # type: ignore[return-value] class Base(DeclarativeBase): pass def get_db() -> Generator[Session, None, None]: """FastAPI 依赖:为每个请求产出一个 Session。""" db = get_session_local()() try: yield db finally: db.close() def init_db_schema() -> None: """按需建表(幂等)并校验既有表列与模型一致(fail-fast on schema drift)。 先导入 models 注册映射;create_all 用 IF NOT EXISTS 仅补缺失的表; 随后对每张已存在的表检查模型声明的列是否齐全,缺列即抛 RuntimeError, 避免运行期才以晦涩的 OperationalError 暴露 schema 漂移。 """ from sqlalchemy import inspect from . import models # noqa: F401 engine = get_engine() Base.metadata.create_all(bind=engine) inspector = inspect(engine) missing: list[str] = [] for table, mapper in Base.registry.mappers.items(): if not inspector.has_table(table): continue db_cols = {c["name"] for c in inspector.get_columns(table)} for model_col in mapper.columns.keys(): if model_col not in db_cols: missing.append(f"{table}.{model_col}") if missing: raise RuntimeError( "数据库 schema 与模型不一致,缺少列: " + ", ".join(missing) + "。请执行 sql/schema.sql 或迁移脚本更新表结构。" )