refactor: add dynamic runtime settings compatibility proxy

This commit is contained in:
jxxghp
2026-08-23 02:16:03 +08:00
parent de85ae3ef9
commit ef6a613eb3
7 changed files with 41 additions and 12 deletions
+3 -1
View File
@@ -30,7 +30,7 @@ from importlib.metadata import distributions
from requests import Response from requests import Response
from app.runtime.cache import cached, is_fresh from app.runtime.cache import cached, is_fresh
from app.runtime.config import settings from app.runtime.settings import RuntimeSettingsCompat
from app.adapters.system.package import ( from app.adapters.system.package import (
PackageInstallRequest, PackageInstallRequest,
build_package_install_strategies, build_package_install_strategies,
@@ -52,6 +52,8 @@ from app.adapters.system.host import SystemUtils
from app.foundation.url import UrlUtils from app.foundation.url import UrlUtils
from version import APP_VERSION from version import APP_VERSION
# 保留模块级可替换入口,代理默认读取组合根的最新 runtime 配置。
settings = RuntimeSettingsCompat()
PLUGIN_DIR = Path(settings.ROOT_PATH) / "app" / "plugins" PLUGIN_DIR = Path(settings.ROOT_PATH) / "app" / "plugins"
LOCAL_REPO_PREFIX = "local://" LOCAL_REPO_PREFIX = "local://"
PLUGIN_SYSTEM_VERSION_FIELD = "system_version" PLUGIN_SYSTEM_VERSION_FIELD = "system_version"
+5 -1
View File
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.parse import parse_qs, quote, urlparse, urlsplit from urllib.parse import parse_qs, quote, urlparse, urlsplit
from app.runtime.cache import cached from app.runtime.cache import cached
from app.runtime.config import settings from app.runtime.settings import RuntimeSettingsCompat
from app.domain.context import MediaInfo, MusicInfo from app.domain.context import MediaInfo, MusicInfo
from app.domain.meta.metabase import MetaBase from app.domain.meta.metabase import MetaBase
from app.runtime.log import logger from app.runtime.log import logger
@@ -24,6 +24,10 @@ from app.adapters.system.host import SystemUtils
from version import APP_VERSION, FRONTEND_VERSION from version import APP_VERSION, FRONTEND_VERSION
# 保留旧插件可覆盖的模块级入口,默认通过 runtime 代理动态读取配置。
settings = RuntimeSettingsCompat()
_server_report_service: Any = None _server_report_service: Any = None
_server_sharing_service: Any = None _server_sharing_service: Any = None
+7 -3
View File
@@ -12,7 +12,11 @@ from typing import Optional
from app.adapters.external.market import PluginHelper as _PluginHelper from app.adapters.external.market import PluginHelper as _PluginHelper
from app.runtime.log import logger from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting from app.runtime.settings import RuntimeSettingsCompat
# 保留旧模块级入口,插件本地同步测试和旧扩展仍可能覆盖这些设置。
settings = RuntimeSettingsCompat()
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
@@ -38,7 +42,7 @@ class PluginPackageManager:
def _plugin_dir(plugin_id: str) -> Path: def _plugin_dir(plugin_id: str) -> Path:
"""解析插件运行目录并拒绝越出宿主插件根目录的标识。""" """解析插件运行目录并拒绝越出宿主插件根目录的标识。"""
plugins_root = ( plugins_root = (
Path(get_runtime_setting("ROOT_PATH")) / "app" / "plugins" Path(settings.ROOT_PATH) / "app" / "plugins"
).resolve() ).resolve()
plugin_dir = (plugins_root / plugin_id.lower()).resolve() plugin_dir = (plugins_root / plugin_id.lower()).resolve()
if plugin_dir == plugins_root or not plugin_dir.is_relative_to(plugins_root): if plugin_dir == plugins_root or not plugin_dir.is_relative_to(plugins_root):
@@ -49,7 +53,7 @@ class PluginPackageManager:
"""在包变更前创建独立快照,供后续提交或补偿恢复。""" """在包变更前创建独立快照,供后续提交或补偿恢复。"""
plugin_dir = self._plugin_dir(plugin_id) plugin_dir = self._plugin_dir(plugin_id)
transaction_dir = ( transaction_dir = (
Path(get_runtime_setting("TEMP_PATH")) Path(settings.TEMP_PATH)
/ "plugin_transactions" / "plugin_transactions"
/ f"{plugin_id.lower()}-{uuid.uuid4().hex}" / f"{plugin_id.lower()}-{uuid.uuid4().hex}"
) )
+18
View File
@@ -11,6 +11,24 @@ RuntimeSettingProvider = Callable[[str], Any]
_provider: RuntimeSettingProvider | None = None _provider: RuntimeSettingProvider | None = None
class RuntimeSettingsCompat:
"""为旧模块级 Settings 访问提供动态 runtime 配置代理。"""
def __getattr__(self, key: str) -> Any:
"""读取当前组合根配置;未装配时沿用旧 Settings 回退。"""
return get_runtime_setting(key)
def __setattr__(self, key: str, value: Any) -> None:
"""把旧模块级覆盖同步到 legacy Settings,保持测试和插件注入语义。"""
legacy_settings = importlib.import_module("app.runtime.config").settings
setattr(legacy_settings, key, value)
def __delattr__(self, key: str) -> None:
"""删除旧模块级覆盖,使配置对象恢复其原有属性解析。"""
legacy_settings = importlib.import_module("app.runtime.config").settings
delattr(legacy_settings, key)
def configure_runtime_setting_provider(provider: RuntimeSettingProvider) -> None: def configure_runtime_setting_provider(provider: RuntimeSettingProvider) -> None:
"""由启动组合根登记配置读取器,保持适配器只依赖 runtime 端口。""" """由启动组合根登记配置读取器,保持适配器只依赖 runtime 端口。"""
global _provider global _provider
@@ -70,7 +70,7 @@ MoviePilot V3 当前不是“目录混乱、必须推倒重来”的状态。第
| legacy 默认模块契约 | 0 个宿主观察方法;未知动态方法保留 fallback | 所有静态宿主方法已有显式 V2 spec;真实 fallback 命中由 `module.contract.legacy_hit` 观测 | | legacy 默认模块契约 | 0 个宿主观察方法;未知动态方法保留 fallback | 所有静态宿主方法已有显式 V2 spec;真实 fallback 命中由 `module.contract.legacy_hit` 观测 |
| 事件枚举 | 53 | 66 个静态 producer、15 个静态 consumer | | 事件枚举 | 53 | 66 个静态 producer、15 个静态 consumer |
| 专用 EventData model | 53 | Event Contract Registry 已为全部事件登记 typed payload/fallback 原因 | | 专用 EventData model | 53 | Event Contract Registry 已为全部事件登记 typed payload/fallback 原因 |
| 直接读取 `settings` 的文件 | 107 | 仍按模块族迁移,动态协议和安全端口暂保留 | | 直接读取 `settings` 的文件 | 105 | 仍按模块族迁移,动态协议和安全端口暂保留 |
| `SystemConfigOper()` | 1 个 | 仅组合根创建 `SystemConfigService` 时保留 | | `SystemConfigOper()` | 1 个 | 仅组合根创建 `SystemConfigService` 时保留 |
| Model 上的 DB 查询装饰器 | 117 | `db_update`/`async_db_update` 为 0;查询 ABI 继续按 canonical 用例迁移 | | Model 上的 DB 查询装饰器 | 117 | `db_update`/`async_db_update` 为 0;查询 ABI 继续按 canonical 用例迁移 |
| 路由端点 | 335 | 11 个已装饰端点超过 80 行,最大 400 行 | | 路由端点 | 335 | 11 个已装饰端点超过 80 行,最大 400 行 |
@@ -1031,6 +1031,9 @@ MFA/Passkey 专项测试与架构门禁通过,密钥类配置仍保留在安
Pylint 与架构基线通过,配置债务由 112 个文件降至 109 个文件。 Pylint 与架构基线通过,配置债务由 112 个文件降至 109 个文件。
缓存 Redis 连接池、内存限制和文件缓存工厂随后改用 runtime 配置端口,保留旧模块级 Settings 覆盖入口; 缓存 Redis 连接池、内存限制和文件缓存工厂随后改用 runtime 配置端口,保留旧模块级 Settings 覆盖入口;
缓存专项 41 项测试与 Pylint 通过,配置债务由 109 个文件降至 107 个文件。 缓存专项 41 项测试与 Pylint 通过,配置债务由 109 个文件降至 107 个文件。
同时为兼容市场、服务端和插件包适配器增加 `RuntimeSettingsCompat` 动态代理:组合根已装配时读取
runtime provider,旧插件或测试替换模块级 `settings` 时仍保持原覆盖语义。相关插件市场、插件本地同步、
服务端和评分专项 184 项测试与 Pylint 通过,配置债务由 107 个文件降至 105 个文件。
用户模型的 `get_by_name``get_by_id` 同步查询改为显式 Session 执行,并以一次性短会话保留旧插件 用户模型的 `get_by_name``get_by_id` 同步查询改为显式 Session 执行,并以一次性短会话保留旧插件
无 Session ABI;用户查询与兼容专项 75 项测试、Pylint 及架构基线通过,查询装饰器由 119 个降至 117 个。 无 Session ABI;用户查询与兼容专项 75 项测试、Pylint 及架构基线通过,查询装饰器由 119 个降至 117 个。
@@ -9,10 +9,8 @@
"root": "app" "root": "app"
}, },
"settings_imports": { "settings_imports": {
"count": 107, "count": 105,
"files": [ "files": [
"app/adapters/external/market.py",
"app/adapters/external/server.py",
"app/adapters/network/browser.py", "app/adapters/network/browser.py",
"app/adapters/system/fsproxy.py", "app/adapters/system/fsproxy.py",
"app/adapters/web/security/access.py", "app/adapters/web/security/access.py",
+3 -3
View File
@@ -14,7 +14,7 @@
"workflow_to_db": [] "workflow_to_db": []
}, },
"edge_count": 6421, "edge_count": 6421,
"edge_sha256": "694818834db43d9b556226bb11ddfc5420d2475f03e83125b64b7bd41e4a0771", "edge_sha256": "0bbd08d44dc572b174f49e5a08ad2191e8b256ecceb0f7a765e82d3cddf10853",
"edges": [ "edges": [
"app -> app.runtime", "app -> app.runtime",
"app -> app.runtime.compat", "app -> app.runtime.compat",
@@ -62,9 +62,9 @@
"app.adapters.external.market -> app.foundation.version", "app.adapters.external.market -> app.foundation.version",
"app.adapters.external.market -> app.runtime", "app.adapters.external.market -> app.runtime",
"app.adapters.external.market -> app.runtime.cache", "app.adapters.external.market -> app.runtime.cache",
"app.adapters.external.market -> app.runtime.config",
"app.adapters.external.market -> app.runtime.log", "app.adapters.external.market -> app.runtime.log",
"app.adapters.external.market -> app.runtime.observability", "app.adapters.external.market -> app.runtime.observability",
"app.adapters.external.market -> app.runtime.settings",
"app.adapters.external.ocr -> app.adapters", "app.adapters.external.ocr -> app.adapters",
"app.adapters.external.ocr -> app.adapters.network", "app.adapters.external.ocr -> app.adapters.network",
"app.adapters.external.ocr -> app.adapters.network.http", "app.adapters.external.ocr -> app.adapters.network.http",
@@ -87,9 +87,9 @@
"app.adapters.external.server -> app.domain.meta.metabase", "app.adapters.external.server -> app.domain.meta.metabase",
"app.adapters.external.server -> app.runtime", "app.adapters.external.server -> app.runtime",
"app.adapters.external.server -> app.runtime.cache", "app.adapters.external.server -> app.runtime.cache",
"app.adapters.external.server -> app.runtime.config",
"app.adapters.external.server -> app.runtime.log", "app.adapters.external.server -> app.runtime.log",
"app.adapters.external.server -> app.runtime.observability", "app.adapters.external.server -> app.runtime.observability",
"app.adapters.external.server -> app.runtime.settings",
"app.adapters.external.server -> app.schemas", "app.adapters.external.server -> app.schemas",
"app.adapters.external.server -> app.schemas.media", "app.adapters.external.server -> app.schemas.media",
"app.adapters.external.server -> app.schemas.types", "app.adapters.external.server -> app.schemas.types",