refactor: reorganize backend module boundaries

This commit is contained in:
jxxghp
2026-08-14 19:53:33 +08:00
parent fe0b444ceb
commit 369d7d6448
584 changed files with 2423 additions and 2275 deletions
+224
View File
@@ -0,0 +1,224 @@
import ast
import inspect
import threading
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Dict, List, Optional, Set, Tuple
from app.runtime.compat.manifest import MODULE_ALIASES, PACKAGE_ALIASES, ModuleAlias
WarningEmitter = Callable[[str], object]
@dataclass(frozen=True, slots=True)
class LegacyImportUsage:
"""记录一次旧导入路径命中及其调用来源。"""
consumer: str
legacy_module: str
origin: Optional[str] = None
_lock = threading.RLock()
_enabled: Optional[bool] = None
_emitter: Optional[WarningEmitter] = None
_pending: List[LegacyImportUsage] = []
_reported: Set[Tuple[str, str]] = set()
_hits: Set[Tuple[str, str]] = set()
_scan_cache: Dict[Path, Tuple[int, int, Tuple[Tuple[str, int], ...], Optional[str]]] = {}
def _find_import_consumer() -> str:
"""从导入调用栈中识别触发兼容导入的插件或主程序模块。"""
frame = inspect.currentframe()
try:
frame = frame.f_back if frame else None
while frame:
module_name = str(frame.f_globals.get("__name__") or "")
if module_name and not (
module_name.startswith("importlib")
or module_name.startswith("app.runtime.compat")
):
return module_name
frame = frame.f_back
finally:
del frame
return "unknown"
def _format_warning(usage: LegacyImportUsage, alias: ModuleAlias) -> str:
"""生成包含实际目标和推荐 SDK 路径的单行兼容警告。"""
consumer = usage.consumer
if consumer.startswith("app.plugins."):
parts = consumer.split(".")
source = f"插件 {parts[2] if len(parts) > 2 else consumer}"
else:
source = f"模块 {consumer}"
origin = f"{usage.origin}" if usage.origin else ""
return (
f"[兼容导入] {source}{origin} 使用旧路径 {usage.legacy_module}"
f"已映射到 {alias.target};请迁移到 {alias.replacement}"
)
def _emit_usage(usage: LegacyImportUsage) -> None:
"""按调用方和旧路径去重后输出兼容警告。"""
alias = MODULE_ALIASES.get(usage.legacy_module) or PACKAGE_ALIASES.get(
usage.legacy_module
)
if not alias:
return
key = (usage.consumer, usage.legacy_module)
with _lock:
_hits.add(key)
if _enabled is None:
_pending.append(usage)
return
if not _enabled or key in _reported or not _emitter:
return
_reported.add(key)
emitter = _emitter
emitter(_format_warning(usage, alias))
def record_legacy_import(legacy_module: str) -> None:
"""记录运行时 Finder 实际解析到的旧模块路径。"""
with _lock:
if _enabled is False:
return
_emit_usage(
LegacyImportUsage(
consumer=_find_import_consumer(),
legacy_module=legacy_module,
)
)
def configure_legacy_import_diagnostics(
*,
enabled: bool,
emitter: WarningEmitter,
) -> None:
"""
配置旧导入诊断,并在 DEBUG 开启时刷新安装早期暂存的命中。
:param enabled: 是否启用兼容警告
:param emitter: 接收单行警告文本的日志回调
"""
with _lock:
global _enabled, _emitter
_enabled = enabled
_emitter = emitter
pending = list(_pending) if enabled else []
_pending.clear()
for usage in pending:
_emit_usage(usage)
def _extract_legacy_imports(tree: ast.AST) -> Tuple[Tuple[str, int], ...]:
"""从插件 AST 中提取映射表已登记的静态旧导入。"""
matches: Set[Tuple[str, int]] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for imported in node.names:
if imported.name in MODULE_ALIASES or imported.name in PACKAGE_ALIASES:
matches.add((imported.name, node.lineno))
elif isinstance(node, ast.ImportFrom) and node.module:
if node.module in MODULE_ALIASES or node.module in PACKAGE_ALIASES:
matches.add((node.module, node.lineno))
for imported in node.names:
candidate = f"{node.module}.{imported.name}"
if candidate in MODULE_ALIASES or candidate in PACKAGE_ALIASES:
matches.add((candidate, node.lineno))
elif isinstance(node, ast.Call) and node.args:
function_name = ""
if isinstance(node.func, ast.Name):
function_name = node.func.id
elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name):
function_name = f"{node.func.value.id}.{node.func.attr}"
if function_name not in {"__import__", "importlib.import_module"}:
continue
argument = node.args[0]
if (
isinstance(argument, ast.Constant)
and isinstance(argument.value, str)
and (
argument.value in MODULE_ALIASES
or argument.value in PACKAGE_ALIASES
)
):
matches.add((argument.value, node.lineno))
return tuple(sorted(matches, key=lambda item: (item[1], item[0])))
def _scan_plugin_file(path: Path) -> Tuple[Tuple[Tuple[str, int], ...], Optional[str]]:
"""解析单个插件文件,并按文件状态缓存旧导入扫描结果。"""
stat = path.stat()
with _lock:
cached = _scan_cache.get(path)
if cached and cached[:2] == (stat.st_mtime_ns, stat.st_size):
return cached[2], cached[3]
try:
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
matches = _extract_legacy_imports(tree)
error = None
except (OSError, SyntaxError, UnicodeError) as err:
matches = ()
error = str(err)
with _lock:
_scan_cache[path] = (stat.st_mtime_ns, stat.st_size, matches, error)
return matches, error
def scan_plugin_legacy_imports(plugin_id: str, plugin_dir: Path) -> None:
"""
在 DEBUG 模式下扫描插件源码,补足 sys.modules 缓存导致的 Finder 漏报。
:param plugin_id: 插件 ID
:param plugin_dir: 插件源码目录
"""
with _lock:
enabled = bool(_enabled)
emitter = _emitter
if not enabled or not emitter:
return
for path in sorted(plugin_dir.rglob("*.py")):
if "__pycache__" in path.parts:
continue
matches, error = _scan_plugin_file(path)
relative_path = path.relative_to(plugin_dir)
if error:
emitter(f"[兼容导入] DEBUG 扫描插件 {plugin_id} 文件 {relative_path} 失败:{error}")
continue
for legacy_module, line_number in matches:
_emit_usage(
LegacyImportUsage(
consumer=f"app.plugins.{plugin_id.lower()}",
legacy_module=legacy_module,
origin=f"{relative_path}:{line_number}",
)
)
def get_legacy_import_diagnostics() -> Dict[str, object]:
"""返回只读诊断快照,供测试和后续 doctor 能力使用。"""
with _lock:
return {
"enabled": _enabled,
"hits": sorted(_hits),
"reported": sorted(_reported),
"pending": len(_pending),
}
def reset_legacy_import_diagnostics() -> None:
"""清空进程级诊断状态,仅供隔离测试使用。"""
with _lock:
global _enabled, _emitter
_enabled = None
_emitter = None
_pending.clear()
_reported.clear()
_hits.clear()
_scan_cache.clear()