refactor: unify module runtime settings

This commit is contained in:
jxxghp
2026-08-24 00:19:57 +08:00
parent 25ad924aac
commit 6b65dc5d12
11 changed files with 148 additions and 30 deletions
+7 -1
View File
@@ -1,10 +1,12 @@
from typing import Tuple, Union
from app.runtime.config import settings
from app.application.database import get_database_governance
from app.modules import _ModuleBase
from app.runtime.settings import RuntimeSettingsCompat
from app.schemas.types import ModuleType, OtherModulesType
settings = RuntimeSettingsCompat()
class PostgreSQLModule(_ModuleBase):
"""
@@ -12,10 +14,12 @@ class PostgreSQLModule(_ModuleBase):
"""
def init_module(self) -> None:
"""PostgreSQL 连接由数据库 adapter 管理,无需模块级初始化。"""
pass
@staticmethod
def get_name() -> str:
"""返回模块展示名称。"""
return "PostgreSQL"
@staticmethod
@@ -40,9 +44,11 @@ class PostgreSQLModule(_ModuleBase):
return 0
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""数据库类型由部署配置决定,不声明独立模块开关。"""
pass
def stop(self) -> None:
"""连接池由数据库生命周期释放,本模块无独立资源。"""
pass
def test(self):
+3 -1
View File
@@ -4,9 +4,9 @@ from typing import Set, Tuple, Optional, Union, List, Dict
from qbittorrentapi import TorrentFilesList
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
from app.runtime.config import settings
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.modules._base import _DownloaderModuleBase
from app.modules.qbittorrent.qbittorrent import Qbittorrent
from app.schemas.transfer import DownloaderTorrent
@@ -21,6 +21,8 @@ from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
from app.foundation import text as text_tools
settings = RuntimeSettingsCompat()
_QBITTORRENT_DOWNLOADING_STATES = {
"allocating",
"checkingdl",
+7 -1
View File
@@ -1,10 +1,12 @@
from typing import Tuple, Union
from app.runtime.config import settings
from app.adapters.cache.redis import RedisHelper
from app.modules import _ModuleBase
from app.runtime.settings import RuntimeSettingsCompat
from app.schemas.types import ModuleType, OtherModulesType
settings = RuntimeSettingsCompat()
class RedisModule(_ModuleBase):
"""
@@ -12,10 +14,12 @@ class RedisModule(_ModuleBase):
"""
def init_module(self) -> None:
"""Redis 客户端由缓存 adapter 惰性管理,无需模块级初始化。"""
pass
@staticmethod
def get_name() -> str:
"""返回模块展示名称。"""
return "Redis缓存"
@staticmethod
@@ -40,9 +44,11 @@ class RedisModule(_ModuleBase):
return 0
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""缓存后端由部署配置决定,不声明独立模块开关。"""
pass
def stop(self) -> None:
"""缓存 adapter 负责连接释放,本模块无独立资源。"""
pass
def test(self):
+8 -1
View File
@@ -2,9 +2,9 @@ from pathlib import Path
from typing import Set, Tuple, Optional, Union, List, Dict
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
from app.runtime.config import settings
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.modules._base import _DownloaderModuleBase
from app.modules.rtorrent.rtorrent import Rtorrent
from app.schemas.transfer import DownloaderTorrent
@@ -19,8 +19,12 @@ from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
from app.foundation import text as text_tools
settings = RuntimeSettingsCompat()
class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
"""rTorrent 下载器模块,负责任务添加、标签和文件状态转换。"""
def init_module(self) -> None:
"""
初始化模块
@@ -31,6 +35,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
@staticmethod
def get_name() -> str:
"""返回模块展示名称。"""
return "Rtorrent"
@staticmethod
@@ -55,9 +60,11 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
return 3
def stop(self):
"""下载器客户端由服务基类管理,本模块无额外停止动作。"""
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""下载器实例由系统配置管理,不声明独立模块开关。"""
pass
def download(
+7 -1
View File
@@ -4,9 +4,9 @@ from typing import Set, Tuple, Optional, Union, List, Dict
from transmission_rpc import File
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
from app.runtime.config import settings
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.modules._base import _DownloaderModuleBase
from app.modules.transmission.transmission import Transmission
from app.schemas.transfer import DownloaderTorrent
@@ -20,6 +20,8 @@ from app.schemas.types import (
from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
settings = RuntimeSettingsCompat()
_TRANSMISSION_DOWNLOADING_STATES = {
"download_pending",
"downloading",
@@ -30,6 +32,7 @@ _TRANSMISSION_PAUSED_STATES = {
class TransmissionModule(_DownloaderModuleBase[Transmission]):
"""Transmission 下载器模块,负责任务添加、标签和文件选择。"""
def init_module(self) -> None:
"""
@@ -40,6 +43,7 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]):
@staticmethod
def get_name() -> str:
"""返回模块展示名称。"""
return "Transmission"
@staticmethod
@@ -64,9 +68,11 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]):
return 2
def stop(self):
"""下载器客户端由服务基类管理,本模块无额外停止动作。"""
pass
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""下载器实例由系统配置管理,不声明独立模块开关。"""
pass
def download(self, content: Union[Path, str, bytes], download_dir: Path, cookie: str,
@@ -96,6 +96,9 @@
concrete `PluginManager`,不改变 V1/V2/V3 插件加载与自由响应 API。
Command 的 API 消费点也已完成原计划迁移:WebAgent 查询和站点认证后的刷新统一使用
`app.application.commands`,只有 startup 组合根注册 concrete Command;门面保留原命令对象与插件命令语义。
宿主 Module 的部署配置读取也已统一到 `RuntimeSettingsCompat`PostgreSQL、Redis、qBittorrent、
rTorrent 和 Transmission 不再直接导入全局 Settings,模块目录由架构测试保持零直连;兼容代理仍保留
启动早期与旧插件/测试的动态 Settings 注入语义。
### P2:中长期可演进性债务
@@ -10,16 +10,11 @@
"root": "app"
},
"settings_imports": {
"count": 8,
"count": 3,
"files": [
"app/db/base.py",
"app/db/engine.py",
"app/db/session.py",
"app/modules/postgresql/__init__.py",
"app/modules/qbittorrent/__init__.py",
"app/modules/redis/__init__.py",
"app/modules/rtorrent/__init__.py",
"app/modules/transmission/__init__.py"
"app/db/session.py"
]
},
"system_config_oper_constructions": {
+6 -6
View File
@@ -14,7 +14,7 @@
"workflow_to_db": []
},
"edge_count": 6499,
"edge_sha256": "f21eea0878e60d4b955a87da15cf0653eaecf07d147dc43e9eabc9f9573bb793",
"edge_sha256": "54ddd2d6cd5669354a2d1303b24c95e573368e145fdd74e4240b01bb8ef1f687",
"edges": [
"app -> app.runtime",
"app -> app.runtime.compat",
@@ -4808,7 +4808,7 @@
"app.modules.postgresql -> app.application.database",
"app.modules.postgresql -> app.modules",
"app.modules.postgresql -> app.runtime",
"app.modules.postgresql -> app.runtime.config",
"app.modules.postgresql -> app.runtime.settings",
"app.modules.postgresql -> app.schemas",
"app.modules.postgresql -> app.schemas.types",
"app.modules.qbittorrent -> app.domain",
@@ -4821,8 +4821,8 @@
"app.modules.qbittorrent -> app.modules._base",
"app.modules.qbittorrent -> app.modules.qbittorrent.qbittorrent",
"app.modules.qbittorrent -> app.runtime",
"app.modules.qbittorrent -> app.runtime.config",
"app.modules.qbittorrent -> app.runtime.log",
"app.modules.qbittorrent -> app.runtime.settings",
"app.modules.qbittorrent -> app.schemas",
"app.modules.qbittorrent -> app.schemas.dashboard",
"app.modules.qbittorrent -> app.schemas.transfer",
@@ -4879,7 +4879,7 @@
"app.modules.redis -> app.adapters.cache.redis",
"app.modules.redis -> app.modules",
"app.modules.redis -> app.runtime",
"app.modules.redis -> app.runtime.config",
"app.modules.redis -> app.runtime.settings",
"app.modules.redis -> app.schemas",
"app.modules.redis -> app.schemas.types",
"app.modules.rtorrent -> app.domain",
@@ -4892,8 +4892,8 @@
"app.modules.rtorrent -> app.modules._base",
"app.modules.rtorrent -> app.modules.rtorrent.rtorrent",
"app.modules.rtorrent -> app.runtime",
"app.modules.rtorrent -> app.runtime.config",
"app.modules.rtorrent -> app.runtime.log",
"app.modules.rtorrent -> app.runtime.settings",
"app.modules.rtorrent -> app.schemas",
"app.modules.rtorrent -> app.schemas.dashboard",
"app.modules.rtorrent -> app.schemas.transfer",
@@ -5248,8 +5248,8 @@
"app.modules.transmission -> app.modules._base",
"app.modules.transmission -> app.modules.transmission.transmission",
"app.modules.transmission -> app.runtime",
"app.modules.transmission -> app.runtime.config",
"app.modules.transmission -> app.runtime.log",
"app.modules.transmission -> app.runtime.settings",
"app.modules.transmission -> app.schemas",
"app.modules.transmission -> app.schemas.dashboard",
"app.modules.transmission -> app.schemas.transfer",
+15
View File
@@ -926,6 +926,21 @@ def test_runtime_consumers_use_command_application_facade():
assert violations == {}
def test_modules_read_deployment_settings_through_runtime_port():
"""宿主 Module 不得绕过 runtime 配置端口直接依赖 Settings 实例。"""
violations: list[str] = []
for path in (APP_ROOT / "modules").rglob("*.py"):
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
for node in ast.walk(tree):
if not isinstance(node, ast.ImportFrom) or node.module != "app.runtime.config":
continue
if any(alias.name == "settings" for alias in node.names):
violations.append(path.relative_to(PROJECT_ROOT).as_posix())
break
assert violations == []
def test_api_does_not_import_factory():
"""装配器(factory)只允许 app.main 使用,HTTP 端点不得回引。"""
violations: dict[str, set[str]] = {}
+61 -8
View File
@@ -13,8 +13,16 @@ def _load_downloader_base():
app_module.__path__ = []
helper_module = types.ModuleType("app.helper")
helper_module.__path__ = []
runtime_module = types.ModuleType("app.runtime")
runtime_module.__path__ = []
runtime_extensions_module = types.ModuleType("app.runtime.extensions")
runtime_extensions_module.__path__ = []
service_module = types.ModuleType("app.runtime.extensions.service_config")
log_module = types.ModuleType("app.runtime.log")
schemas_module = types.ModuleType("app.schemas")
schemas_module.__path__ = []
schema_message_module = types.ModuleType("app.schemas.message")
schema_system_module = types.ModuleType("app.schemas.system")
schema_types_module = types.ModuleType("app.schemas.types")
utils_module = types.ModuleType("app.utils")
utils_module.__path__ = []
@@ -27,6 +35,13 @@ def _load_downloader_base():
class _ConfigReloadMixin:
pass
class _Logger:
"""隔离模块基类加载时使用的无副作用日志桩。"""
def error(self, *_args, **_kwargs):
"""忽略测试范围外的错误日志输出。"""
pass
class _ServiceConfigHelper:
@staticmethod
def get_downloader_configs():
@@ -59,24 +74,37 @@ def _load_downloader_base():
)
service_module.ServiceConfigHelper = _ServiceConfigHelper
log_module.logger = _Logger()
mixins_module.ConfigReloadMixin = _ConfigReloadMixin
schemas_module.Message = object
schemas_module.NotificationConf = object
schemas_module.MediaServerConf = object
schemas_module.DownloaderConf = object
schema_message_module.Message = object
schema_system_module.NotificationConf = object
schema_system_module.MediaServerConf = object
schema_system_module.DownloaderConf = object
app_module.helper = helper_module
app_module.runtime = runtime_module
app_module.schemas = schemas_module
app_module.utils = utils_module
helper_module.service = service_module
runtime_module.extensions = runtime_extensions_module
runtime_module.log = log_module
runtime_module.reload = mixins_module
runtime_extensions_module.service_config = service_module
schemas_module.message = schema_message_module
schemas_module.system = schema_system_module
schemas_module.types = schema_types_module
utils_module.mixins = mixins_module
stub_modules = {
"app": app_module,
"app.helper": helper_module,
"app.runtime": runtime_module,
"app.runtime.extensions": runtime_extensions_module,
"app.runtime.extensions.service_config": service_module,
"app.runtime.log": log_module,
"app.schemas": schemas_module,
"app.schemas.message": schema_message_module,
"app.schemas.system": schema_system_module,
"app.schemas.types": schema_types_module,
"app.utils": utils_module,
"app.runtime.reload": mixins_module,
@@ -108,7 +136,10 @@ def _load_transmission_module():
torrent_rules_module = types.ModuleType("app.domain.torrent")
size_tools_module = types.ModuleType("app.foundation.size")
temporal_tools_module = types.ModuleType("app.foundation.temporal")
runtime_module = types.ModuleType("app.runtime")
runtime_module.__path__ = []
cache_module = types.ModuleType("app.runtime.cache")
runtime_settings_module = types.ModuleType("app.runtime.settings")
base_module = types.ModuleType("app.modules._base")
modules_module = types.ModuleType("app.modules")
modules_module.__path__ = []
@@ -116,6 +147,9 @@ def _load_transmission_module():
transmission_package_module.__path__ = []
transmission_client_module = types.ModuleType("app.modules.transmission.transmission")
schemas_module = types.ModuleType("app.schemas")
schemas_module.__path__ = []
schema_dashboard_module = types.ModuleType("app.schemas.dashboard")
schema_transfer_module = types.ModuleType("app.schemas.transfer")
schema_types_module = types.ModuleType("app.schemas.types")
config_module = types.ModuleType("app.runtime.config")
metainfo_module = types.ModuleType("app.domain.metainfo")
@@ -225,12 +259,19 @@ def _load_transmission_module():
def get(self, *_args, **_kwargs):
return None
class _RuntimeSettingsCompat:
"""隔离测试用动态配置代理,保持生产模块的兼容读取语义。"""
def __getattr__(self, key):
"""从测试提供的旧 Settings 桩读取配置项。"""
return getattr(config_module.settings, key)
transmission_client_module.Transmission = object
cache_module.FileCache = _FileCache
schemas_module.TransferTorrent = _TransferTorrent
schemas_module.DownloadingTorrent = _DownloadingTorrent
schemas_module.DownloaderTorrent = _DownloaderTorrent
schemas_module.DownloaderInfo = object
schema_transfer_module.TransferTorrent = _TransferTorrent
schema_transfer_module.DownloadingTorrent = _DownloadingTorrent
schema_transfer_module.DownloaderTorrent = _DownloaderTorrent
schema_dashboard_module.DownloaderInfo = object
schema_types_module.TorrentStatus = TorrentStatus
schema_types_module.TorrentQueryStatus = TorrentQueryStatus
schema_types_module.DownloadTaskState = DownloadTaskState
@@ -239,6 +280,7 @@ def _load_transmission_module():
"DownloaderType", {"Transmission": "Transmission"}
)
config_module.settings = SimpleNamespace(TORRENT_TAG="moviepilot-tag")
runtime_settings_module.RuntimeSettingsCompat = _RuntimeSettingsCompat
metainfo_module.MetaInfo = _MetaInfo
log_module.logger = _Logger()
modules_module._ModuleBase = _ModuleBase
@@ -257,6 +299,7 @@ def _load_transmission_module():
app_module.domain = domain_module
app_module.foundation = foundation_module
app_module.modules = modules_module
app_module.runtime = runtime_module
app_module.schemas = schemas_module
domain_module.torrent = torrent_rules_module
foundation_module.size = size_tools_module
@@ -264,8 +307,14 @@ def _load_transmission_module():
core_module.cache = cache_module
core_module.config = config_module
core_module.metainfo = metainfo_module
runtime_module.cache = cache_module
runtime_module.config = config_module
runtime_module.log = log_module
runtime_module.settings = runtime_settings_module
modules_module.transmission = transmission_package_module
transmission_package_module.transmission = transmission_client_module
schemas_module.dashboard = schema_dashboard_module
schemas_module.transfer = schema_transfer_module
schemas_module.types = schema_types_module
torrentool_module.torrent = torrentool_torrent_module
@@ -279,13 +328,17 @@ def _load_transmission_module():
"app.foundation.temporal": temporal_tools_module,
"app.runtime.cache": cache_module,
"app.runtime.config": config_module,
"app.runtime": runtime_module,
"app.domain.metainfo": metainfo_module,
"app.runtime.log": log_module,
"app.runtime.settings": runtime_settings_module,
"app.modules": modules_module,
"app.modules._base": base_module,
"app.modules.transmission": transmission_package_module,
"app.modules.transmission.transmission": transmission_client_module,
"app.schemas": schemas_module,
"app.schemas.dashboard": schema_dashboard_module,
"app.schemas.transfer": schema_transfer_module,
"app.schemas.types": schema_types_module,
"transmission_rpc": transmission_rpc_module,
"torrentool": torrentool_module,
+29 -4
View File
@@ -26,11 +26,17 @@ def _load_qbittorrent_modules():
modules_module.__path__ = []
qbittorrent_package_module = types.ModuleType("app.modules.qbittorrent")
qbittorrent_package_module.__path__ = []
runtime_module = types.ModuleType("app.runtime")
runtime_module.__path__ = []
log_module = types.ModuleType("app.runtime.log")
cache_module = types.ModuleType("app.runtime.cache")
config_module = types.ModuleType("app.runtime.config")
runtime_settings_module = types.ModuleType("app.runtime.settings")
metainfo_module = types.ModuleType("app.domain.metainfo")
schemas_module = types.ModuleType("app.schemas")
schemas_module.__path__ = []
schema_dashboard_module = types.ModuleType("app.schemas.dashboard")
schema_transfer_module = types.ModuleType("app.schemas.transfer")
schema_types_module = types.ModuleType("app.schemas.types")
torrentool_module = types.ModuleType("torrentool")
torrentool_module.__path__ = []
@@ -78,6 +84,13 @@ def _load_qbittorrent_modules():
def get(self, *_args, **_kwargs):
return None
class _RuntimeSettingsCompat:
"""隔离测试用动态配置代理,保持生产模块的兼容读取语义。"""
def __getattr__(self, key):
"""从测试提供的旧 Settings 桩读取配置项。"""
return getattr(config_module.settings, key)
class _MetaInfo:
def __init__(self, name):
self.name = name
@@ -160,11 +173,12 @@ def _load_qbittorrent_modules():
log_module.logger = _Logger()
cache_module.FileCache = _FileCache
config_module.settings = types.SimpleNamespace(TORRENT_TAG="moviepilot-tag")
runtime_settings_module.RuntimeSettingsCompat = _RuntimeSettingsCompat
metainfo_module.MetaInfo = _MetaInfo
schemas_module.DownloaderInfo = object
schemas_module.TransferTorrent = object
schemas_module.DownloadingTorrent = object
schemas_module.DownloaderTorrent = _DownloaderTorrent
schema_dashboard_module.DownloaderInfo = object
schema_transfer_module.TransferTorrent = object
schema_transfer_module.DownloadingTorrent = object
schema_transfer_module.DownloaderTorrent = _DownloaderTorrent
schema_types_module.TorrentStatus = TorrentStatus
schema_types_module.TorrentQueryStatus = TorrentQueryStatus
schema_types_module.DownloadTaskState = DownloadTaskState
@@ -195,6 +209,7 @@ def _load_qbittorrent_modules():
app_module.foundation = foundation_module
app_module.log = log_module
app_module.modules = modules_module
app_module.runtime = runtime_module
app_module.schemas = schemas_module
domain_module.torrent = torrent_rules_module
foundation_module.size = size_tools_module
@@ -204,6 +219,12 @@ def _load_qbittorrent_modules():
core_module.cache = cache_module
core_module.config = config_module
core_module.metainfo = metainfo_module
runtime_module.cache = cache_module
runtime_module.config = config_module
runtime_module.log = log_module
runtime_module.settings = runtime_settings_module
schemas_module.dashboard = schema_dashboard_module
schemas_module.transfer = schema_transfer_module
schemas_module.types = schema_types_module
modules_module.qbittorrent = qbittorrent_package_module
torrentool_module.torrent = torrentool_torrent_module
@@ -220,12 +241,16 @@ def _load_qbittorrent_modules():
"app.foundation.url": url_tools_module,
"app.runtime.cache": cache_module,
"app.runtime.config": config_module,
"app.runtime": runtime_module,
"app.domain.metainfo": metainfo_module,
"app.runtime.log": log_module,
"app.runtime.settings": runtime_settings_module,
"app.modules": modules_module,
"app.modules._base": base_module,
"app.modules.qbittorrent": qbittorrent_package_module,
"app.schemas": schemas_module,
"app.schemas.dashboard": schema_dashboard_module,
"app.schemas.transfer": schema_transfer_module,
"app.schemas.types": schema_types_module,
"qbittorrentapi": qbittorrentapi_module,
"qbittorrentapi.client": qbittorrentapi_client_module,