refactor(config): retire RuntimeSettingsCompat host usage

This commit is contained in:
jxxghp
2026-08-26 15:55:21 +08:00
parent cdab54254d
commit 9dbe424c3d
162 changed files with 1966 additions and 1745 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ def _enabled_keys() -> FrozenSet[str]:
:return: 标识集合
"""
configured = get_runtime_setting("DEPRECATION_ENABLED") or ""
configured = get_runtime_setting('DEPRECATION_ENABLED') or ""
return frozenset(item.strip() for item in str(configured).split(",") if item.strip())
@@ -13,9 +13,7 @@ from app.runtime.capabilities.model import (
SelectorSchema,
)
from app.runtime.capabilities.registry import CapabilityRegistry
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.settings import get_runtime_setting, has_runtime_setting
from app.runtime.extensions.service_config import ServiceConfigHelper
from app.schemas.types import (
DownloaderType,
@@ -63,7 +61,7 @@ class HostModuleConfigSnapshot:
def _validate_setting_selector(config: Mapping[str, Any]) -> None:
"""限制 setting selector 只能读取已声明的应用设置。"""
key = config["key"]
if not isinstance(key, str) or not key or not hasattr(settings, key):
if not isinstance(key, str) or not key or not has_runtime_setting(key):
raise ValueError(f"未知应用设置:{key!r}")
@@ -176,7 +174,7 @@ def capture_host_module_config(
service_keys.add(key)
setting_values = {
key: getattr(settings, key)
key: get_runtime_setting(key)
for key in sorted(setting_keys)
}
service_values = {
+2 -4
View File
@@ -12,9 +12,7 @@ from app.runtime.capabilities.model import (
CapabilitySpec,
)
from app.runtime.capabilities.runtime import CapabilityRuntime
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.settings import get_runtime_setting
from app.runtime.events import Event, EventHandlerBinding, eventmanager
from app.runtime.extensions.host_module_adapter import (
HOST_MODULE_KIND,
@@ -259,7 +257,7 @@ class ModuleManager(metaclass=Singleton):
if not setting:
return True
switch, value = setting
option = getattr(settings, switch)
option = get_runtime_setting(switch)
if not option:
return False
if value is True:
+15 -10
View File
@@ -7,12 +7,10 @@ from collections.abc import Callable, Mapping
from typing import Any, Optional
from app.foundation.version import compare_version
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.extensions.plugin.contracts import supports_plugin_hook
from app.runtime.extensions.plugin.storage import PluginStorage
from app.runtime.extensions.plugin.system import PluginSystemServices
from app.runtime.settings import get_runtime_setting
from app.schemas.plugin import Plugin, PluginInstance, PluginRuntimeStatus
from app.schemas.types import SystemConfigKey
@@ -56,12 +54,15 @@ class PluginCatalogFacade:
def online(self, force: bool = False) -> list[Plugin]:
"""读取所有兼容代际的在线插件目录。"""
if not settings.PLUGIN_MARKET:
plugin_market = get_runtime_setting('PLUGIN_MARKET')
if not plugin_market:
return []
markets = [item for item in settings.PLUGIN_MARKET.split(",") if item]
markets = [item for item in plugin_market.split(",") if item]
result = self._market_catalog().collect(
markets=markets,
compatible_flags=self._system().compatible_flags(settings.VERSION_FLAG),
compatible_flags=self._system().compatible_flags(
get_runtime_setting('VERSION_FLAG')
),
force=force,
loader=self._market_loader,
)
@@ -224,14 +225,17 @@ class PluginCatalogFacade:
progress_callback: Optional[Callable[..., None]] = None,
) -> list[Plugin]:
"""异步读取所有兼容代际的在线插件目录。"""
if not settings.PLUGIN_MARKET:
plugin_market = get_runtime_setting('PLUGIN_MARKET')
if not plugin_market:
if progress_callback:
progress_callback(value=100, text="未配置插件市场,跳过刷新")
return []
markets = [item for item in settings.PLUGIN_MARKET.split(",") if item]
markets = [item for item in plugin_market.split(",") if item]
result = await self._market_catalog().async_collect(
markets=markets,
compatible_flags=self._system().compatible_flags(settings.VERSION_FLAG),
compatible_flags=self._system().compatible_flags(
get_runtime_setting('VERSION_FLAG')
),
force=force,
loader=self._async_market_loader,
progress_callback=progress_callback,
@@ -254,7 +258,8 @@ class PluginCatalogFacade:
def merge(self, higher: list[Plugin], base: list[Plugin]) -> list[Plugin]:
"""合并不同代际插件目录并保留市场优先级。"""
markets = [item for item in settings.PLUGIN_MARKET.split(",") if item]
plugin_market = get_runtime_setting('PLUGIN_MARKET')
markets = [item for item in plugin_market.split(",") if item]
return self._market_catalog().merge(higher, base, markets)
def _safe_state(self, plugin_id: str, plugin: Any) -> bool:
+16 -11
View File
@@ -29,10 +29,9 @@ from app.foundation.version import compare_version
from app.runtime.execution import run_in_threadpool_to_completion
from app.runtime.log import logger
from app.runtime.observability import observe_compat_facade
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.runtime.thread import ThreadHelper
settings = RuntimeSettingsCompat()
from app.runtime.events import EventHandlerBinding, eventmanager
from app.runtime.reload import ConfigReloadMixin
from app.runtime.extensions.plugin.loader import PluginLoader
@@ -215,10 +214,10 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
self._monitor_suppression_lock = threading.Lock()
self._suppressed_monitor_plugins: Dict[str, int] = {}
self._plugin_paths = PluginPathResolver(
runtime_root=settings.ROOT_PATH / "app" / "plugins",
runtime_root=get_runtime_setting('ROOT_PATH') / "app" / "plugins",
running=lambda: self._running_plugins,
system=get_plugin_system,
strict_system_version=lambda: not settings.DEV,
strict_system_version=lambda: not get_runtime_setting('DEV'),
log=logger,
)
self._local_plugin_sync = LocalPluginSyncService(
@@ -248,7 +247,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
log=logger,
)
self._plugin_loader = PluginLoader(
plugins_root=settings.ROOT_PATH / "app" / "plugins",
plugins_root=get_runtime_setting('ROOT_PATH') / "app" / "plugins",
import_preparer=lambda **kwargs: _legacy_plugin_import_preparer(**kwargs),
import_scanner=lambda **kwargs: _legacy_import_scanner(**kwargs),
log=logger,
@@ -384,7 +383,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
with self.mutation("启动插件"):
with self._plugin_quiesce_lock:
_legacy_diagnostics_configurator(
enabled=settings.DEBUG,
enabled=get_runtime_setting('DEBUG'),
emitter=logger.warning,
)
gil_enabled_before = is_gil_enabled()
@@ -564,7 +563,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
:return: 插件类列表
"""
return PluginLoader(
plugins_root=settings.ROOT_PATH / "app" / "plugins",
plugins_root=get_runtime_setting('ROOT_PATH') / "app" / "plugins",
import_preparer=lambda **kwargs: _legacy_plugin_import_preparer(**kwargs),
import_scanner=lambda **kwargs: _legacy_import_scanner(**kwargs),
log=logger,
@@ -624,7 +623,10 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
return
if (
not self.is_plugin_settling()
and (settings.DEV or settings.PLUGIN_AUTO_RELOAD)
and (
get_runtime_setting('DEV')
or get_runtime_setting('PLUGIN_AUTO_RELOAD')
)
):
self._plugin_monitor.start()
@@ -635,7 +637,10 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
self._plugin_monitor.reload(
enabled=(
not self.is_plugin_settling()
and (settings.DEV or settings.PLUGIN_AUTO_RELOAD)
and (
get_runtime_setting('DEV')
or get_runtime_setting('PLUGIN_AUTO_RELOAD')
)
)
)
@@ -652,7 +657,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
运行 watchfiles 监视器的主循环。
"""
PluginChangeMonitor(
runtime_root=settings.ROOT_PATH / "app" / "plugins",
runtime_root=get_runtime_setting('ROOT_PATH') / "app" / "plugins",
local_roots=get_plugin_system().local_repo_paths,
stop_event=self._plugin_monitor.stop_event,
recent_sync=self._recent_local_sync,
@@ -812,7 +817,7 @@ class PluginManager(ConfigReloadMixin, metaclass=Singleton):
"""
return PluginLoader(
plugins_root=settings.ROOT_PATH / "app" / "plugins",
plugins_root=get_runtime_setting('ROOT_PATH') / "app" / "plugins",
import_preparer=lambda **kwargs: _legacy_plugin_import_preparer(**kwargs),
import_scanner=lambda **kwargs: _legacy_import_scanner(**kwargs),
log=logger,
+40 -58
View File
@@ -6,66 +6,19 @@ import importlib
from collections.abc import Callable
from typing import Any
RuntimeSettingProvider = Callable[[str], Any]
RuntimeSettingUpdater = Callable[[str, Any], tuple[Any, str]]
_provider: RuntimeSettingProvider | None = None
_runtime_settings_service: Any | None = None
_updater: RuntimeSettingUpdater | None = None
_MISSING = object()
# 测试和插件可能临时替换某个模块上的 importlib.import_module;保存原始函数,
# 让兼容代理的 legacy Settings 解析不受这类局部替身影响。
# 让启动前的 legacy Settings 回退不受这类局部替身影响。
_import_module = importlib.import_module
class RuntimeSettingsCompat:
"""为旧模块级 Settings 访问提供动态 runtime 配置代理"""
@staticmethod
def _legacy_settings() -> Any:
"""返回旧 Settings 实例,供 runtime 尚未装配时的兼容回退使用。"""
return _import_module("app.runtime.config").settings
def __getattr__(self, key: str) -> Any:
"""读取当前组合根配置;未装配时沿用旧 Settings 回退。"""
return get_runtime_setting(key)
def __setattr__(self, key: str, value: Any) -> None:
"""把旧模块级覆盖同步到 legacy Settings,保持测试和插件注入语义。"""
setattr(self._legacy_settings(), key, value)
def __delattr__(self, key: str) -> None:
"""删除旧模块级覆盖,使配置对象恢复其原有属性解析。"""
delattr(self._legacy_settings(), key)
def model_dump(
self,
*,
include: set[str] | None = None,
exclude: set[str] | None = None,
**kwargs: Any,
) -> dict[str, Any]:
"""导出当前配置快照,保留旧 Settings 的序列化入口。"""
if _runtime_settings_service is not None:
return _runtime_settings_service.snapshot(include=include, exclude=exclude)
return self._legacy_settings().model_dump(
include=include, exclude=exclude, **kwargs
)
def update_setting(self, key: str, value: Any) -> tuple[Any, str]:
"""更新单项配置,兼容插件对模块级 Settings 的公开调用。"""
if _runtime_settings_service is not None:
return _runtime_settings_service.update(key, value)
return self._legacy_settings().update_setting(key, value)
def update_settings(self, env: dict[str, Any]) -> dict[str, tuple[Any, str]]:
"""批量更新配置,兼容旧 Settings 的管理接口。"""
if _runtime_settings_service is not None:
return _runtime_settings_service.update_many(env)
return self._legacy_settings().update_settings(env=env)
def configure_runtime_settings_compat(service: Any) -> None:
"""由应用组合根注入可变配置服务,避免低层代理反向导入应用层。"""
global _runtime_settings_service
_runtime_settings_service = service
def _legacy_settings() -> Any:
"""返回启动前读取配置用的旧 Settings 实例"""
return _import_module("app.runtime.config").settings
def configure_runtime_setting_provider(provider: RuntimeSettingProvider) -> None:
@@ -74,8 +27,37 @@ def configure_runtime_setting_provider(provider: RuntimeSettingProvider) -> None
_provider = provider
def get_runtime_setting(key: str) -> Any:
"""读取单项运行配置;启动早期未装配时回退旧 Settings ABI"""
def configure_runtime_setting_updater(updater: RuntimeSettingUpdater) -> None:
"""由启动组合根登记配置写入器,避免低层调用方依赖 Application"""
global _updater
_updater = updater
def get_runtime_setting(key: str, default: Any = _MISSING) -> Any:
"""读取单项运行配置;可选默认值保留旧 `getattr` 容错语义。"""
try:
if _provider is not None:
return _provider(key)
return getattr(_legacy_settings(), key)
except AttributeError:
if default is _MISSING:
raise
return default
def update_runtime_setting(key: str, value: Any) -> tuple[Any, str]:
"""更新单项运行配置;启动早期沿用旧 Settings 的兼容写入。"""
if _updater is not None:
return _updater(key, value)
return _legacy_settings().update_setting(key, value)
def has_runtime_setting(key: str) -> bool:
"""判断运行配置是否声明指定键,供低层 manifest 校验使用。"""
if _provider is not None:
return _provider(key)
return getattr(RuntimeSettingsCompat._legacy_settings(), key)
try:
_provider(key)
except AttributeError:
return False
return True
return hasattr(_legacy_settings(), key)
+25 -12
View File
@@ -11,9 +11,7 @@ from typing import Optional, Tuple
import docker
import psutil
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.settings import get_runtime_setting
from app.runtime.log import logger
from app.runtime.reload import ConfigReloadMixin
from app.foundation.environment import is_docker
@@ -33,10 +31,18 @@ class SystemHelper(ConfigReloadMixin):
}
__system_flag_file = "/var/log/nginx/__moviepilot__"
__local_backend_runtime_file = settings.TEMP_PATH / "moviepilot.runtime.json"
__local_restart_log_file = settings.LOG_PATH / "moviepilot.restart.stdout.log"
__one_shot_dev_update_flag_file = settings.TEMP_PATH / "moviepilot.pending_dev_update"
__docker_restart_intent_file = settings.TEMP_PATH / "moviepilot.intentional_restart"
__local_backend_runtime_file = (
get_runtime_setting('TEMP_PATH') / "moviepilot.runtime.json"
)
__local_restart_log_file = (
get_runtime_setting('LOG_PATH') / "moviepilot.restart.stdout.log"
)
__one_shot_dev_update_flag_file = (
get_runtime_setting('TEMP_PATH') / "moviepilot.pending_dev_update"
)
__docker_restart_intent_file = (
get_runtime_setting('TEMP_PATH') / "moviepilot.intentional_restart"
)
__graceful_shutdown_monitor_lock = threading.Lock()
__graceful_shutdown_monitor: Optional[threading.Thread] = None
@@ -142,13 +148,14 @@ class SystemHelper(ConfigReloadMixin):
"subprocess.run(cmd, cwd=os.environ.get('MOVIEPILOT_ROOT'), env=os.environ.copy(), check=False)"
)
env = os.environ.copy()
env["MOVIEPILOT_ROOT"] = str(settings.ROOT_PATH)
root_path = get_runtime_setting('ROOT_PATH')
env["MOVIEPILOT_ROOT"] = str(root_path)
env["PYTHONUNBUFFERED"] = "1"
SystemHelper.__local_restart_log_file.parent.mkdir(parents=True, exist_ok=True)
with SystemHelper.__local_restart_log_file.open("a", encoding="utf-8") as log_handle:
kwargs = {
"cwd": str(settings.ROOT_PATH),
"cwd": str(root_path),
"stdout": log_handle,
"stderr": subprocess.STDOUT,
"stdin": subprocess.DEVNULL,
@@ -200,7 +207,9 @@ class SystemHelper(ConfigReloadMixin):
return False
# 创建 Docker 客户端
client = docker.DockerClient(base_url=settings.DOCKER_CLIENT_API)
client = docker.DockerClient(
base_url=get_runtime_setting('DOCKER_CLIENT_API')
)
# 获取容器信息
container = client.containers.get(container_id)
restart_policy = container.attrs.get('HostConfig', {}).get('RestartPolicy', {})
@@ -280,7 +289,9 @@ class SystemHelper(ConfigReloadMixin):
@staticmethod
def upgrade_dev() -> Tuple[bool, str]:
"""保留原 Dev 模式:重启后跟踪当前 v3 开发分支。"""
configured_mode = str(settings.MOVIEPILOT_AUTO_UPDATE or "").strip().lower()
configured_mode = str(
get_runtime_setting('MOVIEPILOT_AUTO_UPDATE') or ""
).strip().lower()
if configured_mode != "dev":
queued, message = SystemHelper.queue_one_shot_dev_update()
if not queued:
@@ -340,7 +351,9 @@ class SystemHelper(ConfigReloadMixin):
"""
try:
# 创建 Docker 客户端
client = docker.DockerClient(base_url=settings.DOCKER_CLIENT_API)
client = docker.DockerClient(
base_url=get_runtime_setting('DOCKER_CLIENT_API')
)
container_id = SystemHelper._get_container_id()
if not container_id:
return False, "获取容器ID失败!"
+4 -4
View File
@@ -4,9 +4,7 @@ from typing import Any, Callable, TypeVar, cast
from app.foundation.singleton import Singleton
from app.runtime.execution import OwnedThreadPoolExecutor
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.settings import get_runtime_setting
_Result = TypeVar("_Result")
_THREAD_POOL_STOP_TIMEOUT_SECONDS = 10.0
@@ -23,7 +21,9 @@ class ThreadHelper(metaclass=Singleton): # type: ignore[metaclass]
def __init__(self) -> None:
"""按系统配置创建共享后台线程池。"""
self.pool = OwnedThreadPoolExecutor(max_workers=settings.CONF.threadpool)
self.pool = OwnedThreadPoolExecutor(
max_workers=get_runtime_setting('CONF').threadpool
)
def submit(
self,
+2 -2
View File
@@ -29,13 +29,13 @@ def get_frontend_version(*, fallback_to_declared: bool = True) -> str | None:
"""返回当前部署的前端资源版本,并可关闭发布声明回退。"""
if is_frozen() and is_windows():
version_file = (
Path(get_runtime_setting("CONFIG_PATH")).parent
Path(get_runtime_setting('CONFIG_PATH')).parent
/ "nginx"
/ "html"
/ "version.txt"
)
else:
version_file = Path(get_runtime_setting("FRONTEND_PATH")) / "version.txt"
version_file = Path(get_runtime_setting('FRONTEND_PATH')) / "version.txt"
installed_version = _read_version_file(version_file)
if installed_version or not fallback_to_declared:
return installed_version