mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-30 12:36:55 +08:00
113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
"""
|
||
数据库包入口。
|
||
|
||
本模块只做符号再导出,不承载实现——具体职责分布在:
|
||
|
||
- diagnostics 驱动错误的统一分类与日志
|
||
- engine 引擎构建、连接额度核算
|
||
- session 会话获取、异步连接池与配额
|
||
- decorators 同步/异步事务装饰器
|
||
- base ORM 基类与数据访问基类
|
||
- models 表结构声明,一实体一文件
|
||
- oper 数据访问实现,与 models 同名文件一一对应
|
||
|
||
历史上这些代码全部堆在本文件里(782 行),既让包入口承担了实现职责、
|
||
使依赖图难以理清,也让「import 即建立数据库连接」这一副作用被固化下来。
|
||
"""
|
||
from typing import TYPE_CHECKING, Any
|
||
|
||
from app.db.base import Base, DbOper, execute_dml, get_id_column
|
||
from app.db.decorators import async_db_query, async_db_update, db_query, db_update
|
||
from app.db.engine import (
|
||
check_connection_budget,
|
||
connection_budget,
|
||
get_engine,
|
||
get_global_async_engine,
|
||
)
|
||
from app.db.session import (
|
||
AsyncSessionFactory,
|
||
ScopedSession,
|
||
SessionFactory,
|
||
async_session_scope,
|
||
close_database,
|
||
get_async_db,
|
||
get_async_engine,
|
||
get_async_session_factory,
|
||
get_db,
|
||
get_scoped_session,
|
||
get_session_factory,
|
||
)
|
||
|
||
# ==================== 对外契约的分层 ====================
|
||
# 下方 __all__ 是本包**对外承诺**的那一层,仓库外的插件只应依赖其中的名字:
|
||
#
|
||
# - 数据访问:继承 DbOper 子类(插件基类已备好 self.plugindata / self.systemconfig),
|
||
# 或给自己的函数套 db_query / db_update / async_db_query / async_db_update 装饰器。
|
||
# 会话的获取、提交、回滚、释放全部由装饰器收口。
|
||
# - 引擎:Engine / AsyncEngine 保留在契约内。建表、Alembic 迁移、连接诊断这些用途
|
||
# 确实需要引擎对象本身,装饰器覆盖不到,仓库外拿它是正当的。
|
||
#
|
||
# SessionFactory / AsyncSessionFactory / ScopedSession 三个名字**不在**契约内,已从
|
||
# __all__ 移除,降级为内部实现细节。它们建出来的是绕过上述装饰器的裸会话——没有提交、
|
||
# 没有回滚、没有释放,谁建谁自己兜底,本身就是误用的形状。仓库内确有几处直接
|
||
# `from app.db import SessionFactory`(scheduler、postgresql 模块、Alembic 迁移脚本),
|
||
# 那是包内部的既有用法,直接导入不受 __all__ 约束,照常可用。
|
||
# 若确实需要真正的工厂对象(而非 `X()` 取一个会话),用 get_session_factory() /
|
||
# get_scoped_session() / get_async_session_factory()——转发函数上没有 sessionmaker
|
||
# 与 scoped_session 的实例接口(.remove() / .configure() / .begin() 等)。
|
||
#
|
||
# 实现上,三个工厂名字本身就是转发函数(见 session 模块),直接再导出即可——导入它们
|
||
# 不会碰引擎。Engine / AsyncEngine 则不同:调用方拿到的必须是引擎**对象**而非函数,
|
||
# 所以只能靠模块级 __getattr__ 在取属性时才创建。
|
||
#
|
||
# 注意这意味着 `from app.db import Engine` 仍会在 import 期把引擎建出来——那是调用方
|
||
# 自己选的时机。本包自身及仓库内代码一律用 get_engine(),所以 `import app.db` 不连库。
|
||
if TYPE_CHECKING:
|
||
# 只为静态检查声明这两个名字:运行期由下方 __getattr__ 解析,模块 __dict__ 里并不存在,
|
||
# 类型检查器无从知道它们属于本模块(__all__ 里的它们会被报成 reportUnsupportedDunderAll)。
|
||
# 这里同时把类型钉准,比 __getattr__ 的 Any 更有用:调用方拿到的确实是这两类引擎。
|
||
from sqlalchemy.engine import Engine as _SyncEngine
|
||
from sqlalchemy.ext.asyncio import AsyncEngine as _SaAsyncEngine
|
||
|
||
Engine: _SyncEngine
|
||
AsyncEngine: _SaAsyncEngine
|
||
|
||
|
||
def __getattr__(name: str) -> Any:
|
||
"""
|
||
惰性解析 Engine / AsyncEngine 两个旧名字,保持仓库外插件的导入路径可用。
|
||
:param name: 属性名
|
||
:return: 对应的引擎
|
||
"""
|
||
if name == "Engine":
|
||
return get_engine()
|
||
if name == "AsyncEngine":
|
||
return get_global_async_engine()
|
||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||
|
||
|
||
__all__ = [
|
||
"AsyncEngine",
|
||
"Base",
|
||
"DbOper",
|
||
"Engine",
|
||
"async_db_query",
|
||
"async_db_update",
|
||
"async_session_scope",
|
||
"check_connection_budget",
|
||
"close_database",
|
||
"connection_budget",
|
||
"db_query",
|
||
"db_update",
|
||
"execute_dml",
|
||
"get_async_db",
|
||
"get_async_engine",
|
||
"get_async_session_factory",
|
||
"get_db",
|
||
"get_engine",
|
||
"get_global_async_engine",
|
||
"get_id_column",
|
||
"get_scoped_session",
|
||
"get_session_factory",
|
||
]
|