refactor: 推进后端分层架构治理

This commit is contained in:
jxxghp
2026-08-18 00:29:14 +08:00
parent 5d0bacabd5
commit 5128ae9e1e
363 changed files with 34179 additions and 6019 deletions
+54 -78
View File
@@ -1,89 +1,65 @@
"""数据库包的惰性兼容导出入口。
具体实现位于 ``base``、``decorators``、``engine`` 与 ``session``。包入口只维护
公开符号到所有者模块的映射,避免数据库子模块为了导入同包实现而回流到一个会主动
导入全部实现的根模块。旧的 ``from app.db import X`` 路径继续可用。
"""
数据库包入口。
本模块只做符号再导出,不承载实现——具体职责分布在:
from importlib import import_module
from typing import Any
- 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
_EXPORTS = {
"AsyncSessionFactory": ("app.db.session", "AsyncSessionFactory"),
"Base": ("app.db.base", "Base"),
"DbOper": ("app.db.base", "DbOper"),
"ScopedSession": ("app.db.session", "ScopedSession"),
"SessionFactory": ("app.db.session", "SessionFactory"),
"async_db_query": ("app.db.decorators", "async_db_query"),
"async_db_update": ("app.db.decorators", "async_db_update"),
"async_session_scope": ("app.db.session", "async_session_scope"),
"check_connection_budget": ("app.db.engine", "check_connection_budget"),
"close_database": ("app.db.session", "close_database"),
"connection_budget": ("app.db.engine", "connection_budget"),
"db_query": ("app.db.decorators", "db_query"),
"db_update": ("app.db.decorators", "db_update"),
"execute_dml": ("app.db.base", "execute_dml"),
"get_async_db": ("app.db.session", "get_async_db"),
"get_async_engine": ("app.db.session", "get_async_engine"),
"get_async_session_factory": (
"app.db.session",
"get_async_session_factory",
),
"get_db": ("app.db.session", "get_db"),
"get_engine": ("app.db.engine", "get_engine"),
"get_global_async_engine": ("app.db.engine", "get_global_async_engine"),
"get_id_column": ("app.db.base", "get_id_column"),
"get_scoped_session": ("app.db.session", "get_scoped_session"),
"get_session_factory": ("app.db.session", "get_session_factory"),
}
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}")
return getattr(import_module("app.db.engine"), "get_engine")()
elif name == "AsyncEngine":
return getattr(
import_module("app.db.engine"),
"get_global_async_engine",
)()
elif name in _EXPORTS:
module_name, symbol_name = _EXPORTS[name]
value = getattr(import_module(module_name), symbol_name)
else:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
globals()[name] = value
return value
def __dir__() -> list[str]:
"""向交互式工具暴露兼容符号,同时保持实现模块惰性。"""
return sorted({*globals(), *_EXPORTS, "Engine", "AsyncEngine"})
__all__ = [