mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 07:56:52 +08:00
refactor(config): retire RuntimeSettingsCompat host usage
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user