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
+5 -10
View File
@@ -9,19 +9,14 @@ from anyio import Path as AsyncPath
from app.adapters.cache.redis import AsyncRedisHelper, RedisHelper
from app.runtime.cache import (
DEFAULT_CACHE_REGION,
AsyncCacheBackend,
CacheBackend,
DEFAULT_CACHE_REGION,
configure_cache_factories,
)
from app.runtime import config as _runtime_config
from app.runtime.settings import get_runtime_setting
# 兼容旧插件对模块级 Settings 的覆盖,工厂实际读取统一经过 runtime 端口。
settings = _runtime_config.settings
class RedisBackend(CacheBackend):
"""通过同步 Redis 客户端实现缓存后端。"""
@@ -335,14 +330,14 @@ class AsyncFileBackend(AsyncCacheBackend):
def configure_platform_cache() -> None:
"""把配置感知的 Redis 与文件适配器注册到平台缓存工厂。"""
configure_cache_factories(
backend_type_provider=lambda: get_runtime_setting("CACHE_BACKEND_TYPE"),
backend_type_provider=lambda: get_runtime_setting('CACHE_BACKEND_TYPE'),
redis_factory=lambda ttl: RedisBackend(ttl=ttl),
async_redis_factory=lambda ttl: AsyncRedisBackend(ttl=ttl),
file_factory=lambda base: FileBackend(
base=base or get_runtime_setting("TEMP_PATH")
base=base or get_runtime_setting('TEMP_PATH')
),
async_file_factory=lambda base: AsyncFileBackend(
base=base or get_runtime_setting("TEMP_PATH")
base=base or get_runtime_setting('TEMP_PATH')
),
file_ttl_provider=lambda: get_runtime_setting("TEMP_FILE_DAYS") * 24 * 3600,
file_ttl_provider=lambda: get_runtime_setting('TEMP_FILE_DAYS') * 24 * 3600,
)
+16 -21
View File
@@ -2,22 +2,17 @@ import asyncio
import json
import pickle
import threading
from typing import Any, Optional, Generator, Tuple, AsyncGenerator, Union
from typing import Any, AsyncGenerator, Generator, Optional, Tuple, Union
from urllib.parse import quote, unquote
import redis
from redis.asyncio import BlockingConnectionPool as AsyncBlockingConnectionPool
from redis.asyncio import Redis
from app.runtime import config as _runtime_config
from app.foundation.singleton import Singleton
from app.runtime.log import logger
from app.runtime.reload import ConfigReloadMixin
from app.runtime.settings import get_runtime_setting
from app.foundation.singleton import Singleton
# 兼容旧插件和测试对模块级 Settings 的覆盖,Redis 连接逻辑统一读取 runtime 端口。
settings = _runtime_config.settings
# 类型缓存集合,针对非容器简单类型
_complex_serializable_types = set()
@@ -102,7 +97,7 @@ class RedisHelper(ConfigReloadMixin, metaclass=Singleton):
"""
初始化Redis助手实例
"""
self.redis_url = get_runtime_setting("CACHE_BACKEND_URL")
self.redis_url = get_runtime_setting('CACHE_BACKEND_URL')
self.client = None
self._connect_lock = threading.RLock()
@@ -117,15 +112,15 @@ class RedisHelper(ConfigReloadMixin, metaclass=Singleton):
with self._connect_lock:
if self.client is not None:
return
self.redis_url = get_runtime_setting("CACHE_BACKEND_URL")
self.redis_url = get_runtime_setting('CACHE_BACKEND_URL')
connection_pool = redis.BlockingConnectionPool.from_url(
self.redis_url,
decode_responses=False,
socket_timeout=_socket_timeout,
socket_connect_timeout=_socket_connect_timeout,
health_check_interval=_health_check_interval,
max_connections=get_runtime_setting("CACHE_REDIS_MAX_CONNECTIONS"),
timeout=get_runtime_setting("CACHE_REDIS_POOL_TIMEOUT"),
max_connections=get_runtime_setting('CACHE_REDIS_MAX_CONNECTIONS'),
timeout=get_runtime_setting('CACHE_REDIS_POOL_TIMEOUT'),
)
client = redis.Redis(connection_pool=connection_pool)
# 测试连接,确保Redis可用
@@ -143,7 +138,7 @@ class RedisHelper(ConfigReloadMixin, metaclass=Singleton):
def on_config_changed(self):
"""缓存配置变化后重建同步 Redis 连接。"""
with self._connect_lock:
self.redis_url = get_runtime_setting("CACHE_BACKEND_URL")
self.redis_url = get_runtime_setting('CACHE_BACKEND_URL')
self.close()
self._connect()
@@ -159,8 +154,8 @@ class RedisHelper(ConfigReloadMixin, metaclass=Singleton):
"""
try:
# 如果有显式值,则直接使用,为0时说明不限制,如果未配置,开启BIG_MEMORY_MODE时为"1024mb",未开启时为"256mb"
maxmemory = get_runtime_setting("CACHE_REDIS_MAXMEMORY") or (
"1024mb" if get_runtime_setting("BIG_MEMORY_MODE") else "256mb"
maxmemory = get_runtime_setting('CACHE_REDIS_MAXMEMORY') or (
"1024mb" if get_runtime_setting('BIG_MEMORY_MODE') else "256mb"
)
self.client.config_set("maxmemory", maxmemory)
self.client.config_set("maxmemory-policy", policy)
@@ -371,7 +366,7 @@ class AsyncRedisHelper(ConfigReloadMixin, metaclass=Singleton):
"""
初始化异步Redis助手实例
"""
self.redis_url = get_runtime_setting("CACHE_BACKEND_URL")
self.redis_url = get_runtime_setting('CACHE_BACKEND_URL')
self.client: Optional[Redis] = None
self._loop: Optional[asyncio.AbstractEventLoop] = None
self._connect_lock: Optional[asyncio.Lock] = None
@@ -401,15 +396,15 @@ class AsyncRedisHelper(ConfigReloadMixin, metaclass=Singleton):
await self._close_client()
if self.client is not None:
return
self.redis_url = get_runtime_setting("CACHE_BACKEND_URL")
self.redis_url = get_runtime_setting('CACHE_BACKEND_URL')
connection_pool = AsyncBlockingConnectionPool.from_url(
self.redis_url,
decode_responses=False,
socket_timeout=_socket_timeout,
socket_connect_timeout=_socket_connect_timeout,
health_check_interval=_health_check_interval,
max_connections=get_runtime_setting("CACHE_REDIS_MAX_CONNECTIONS"),
timeout=get_runtime_setting("CACHE_REDIS_POOL_TIMEOUT"),
max_connections=get_runtime_setting('CACHE_REDIS_MAX_CONNECTIONS'),
timeout=get_runtime_setting('CACHE_REDIS_POOL_TIMEOUT'),
)
client = Redis(connection_pool=connection_pool)
self._loop = current_loop
@@ -440,7 +435,7 @@ class AsyncRedisHelper(ConfigReloadMixin, metaclass=Singleton):
async def on_config_changed(self):
"""缓存配置变化后异步重建 Redis 连接。"""
self.redis_url = get_runtime_setting("CACHE_BACKEND_URL")
self.redis_url = get_runtime_setting('CACHE_BACKEND_URL')
await self._close_client()
await self._connect()
@@ -456,8 +451,8 @@ class AsyncRedisHelper(ConfigReloadMixin, metaclass=Singleton):
"""
try:
# 如果有显式值,则直接使用,为0时说明不限制,如果未配置,开启BIG_MEMORY_MODE时为"1024mb",未开启时为"256mb"
maxmemory = get_runtime_setting("CACHE_REDIS_MAXMEMORY") or (
"1024mb" if get_runtime_setting("BIG_MEMORY_MODE") else "256mb"
maxmemory = get_runtime_setting('CACHE_REDIS_MAXMEMORY') or (
"1024mb" if get_runtime_setting('BIG_MEMORY_MODE') else "256mb"
)
await self.client.config_set("maxmemory", maxmemory)
await self.client.config_set("maxmemory-policy", policy)
+62 -63
View File
@@ -37,7 +37,7 @@ from app.runtime.dependencies import (
iter_runtime_requirement_strings,
runtime_excluded_dependency_pairs,
)
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.adapters.system.package import (
PackageInstallRequest,
build_package_install_strategies,
@@ -64,9 +64,8 @@ from app.adapters.system.host import SystemUtils
from app.foundation.url import UrlUtils
from app.runtime.version import get_app_version
# 保留模块级可替换入口,代理默认读取组合根的最新 runtime 配置。
settings = RuntimeSettingsCompat()
PLUGIN_DIR = Path(settings.ROOT_PATH) / "app" / "plugins"
# 插件市场只通过 runtime 读取端口消费组合根的最新配置。
PLUGIN_DIR = Path(get_runtime_setting('ROOT_PATH')) / "app" / "plugins"
LOCAL_REPO_PREFIX = "local://"
PLUGIN_SYSTEM_VERSION_FIELD = "system_version"
PLUGIN_MARKET_WIKI_START = "<!-- plugin-market-repos:start -->"
@@ -317,7 +316,7 @@ class PluginHelper(metaclass=WeakSingleton):
return None
path = Path(values[0]).expanduser()
if not path.is_absolute():
path = settings.ROOT_PATH / path
path = get_runtime_setting('ROOT_PATH') / path
return path.resolve()
except Exception:
return None
@@ -355,9 +354,9 @@ class PluginHelper(metaclass=WeakSingleton):
未启用 VERSION_FLAG(v1)时返回空列表,表示仅使用 package.json 基础索引。
"""
flags: List[str] = []
if settings.VERSION_FLAG:
flags.append(settings.VERSION_FLAG)
flags.extend(VERSION_BACKWARD_COMPATIBLE_FLAGS.get(settings.VERSION_FLAG, []))
if get_runtime_setting('VERSION_FLAG'):
flags.append(get_runtime_setting('VERSION_FLAG'))
flags.extend(VERSION_BACKWARD_COMPATIBLE_FLAGS.get(get_runtime_setting('VERSION_FLAG'), []))
return flags
@classmethod
@@ -370,9 +369,9 @@ class PluginHelper(metaclass=WeakSingleton):
"""
if not isinstance(plugin_info, dict):
return False
if not settings.VERSION_FLAG:
if not get_runtime_setting('VERSION_FLAG'):
return True
current_flag = settings.VERSION_FLAG
current_flag = get_runtime_setting('VERSION_FLAG')
if plugin_info.get(current_flag) is False:
return False
if plugin_info.get(current_flag) is True:
@@ -398,7 +397,7 @@ class PluginHelper(metaclass=WeakSingleton):
"""
if not isinstance(plugin_info, dict):
return False
current_flag = settings.VERSION_FLAG
current_flag = get_runtime_setting('VERSION_FLAG')
if not current_flag:
return not package_version
if package_version == current_flag:
@@ -416,7 +415,7 @@ class PluginHelper(metaclass=WeakSingleton):
package_version: Optional[str],
) -> Tuple[str, ...]:
"""返回插件安装唯一的代际候选顺序,并去除重复的基础索引。"""
preferred_version = package_version or settings.VERSION_FLAG
preferred_version = package_version or get_runtime_setting('VERSION_FLAG')
candidates = [preferred_version]
candidates.extend(
VERSION_BACKWARD_COMPATIBLE_FLAGS.get(preferred_version, [])
@@ -491,16 +490,16 @@ class PluginHelper(metaclass=WeakSingleton):
"""
获取本地插件仓库目录列表
"""
if not settings.PLUGIN_LOCAL_REPO_PATHS:
if not get_runtime_setting('PLUGIN_LOCAL_REPO_PATHS'):
return []
paths = []
for item in settings.PLUGIN_LOCAL_REPO_PATHS.split(","):
for item in get_runtime_setting('PLUGIN_LOCAL_REPO_PATHS').split(","):
local_repo_path = item.strip()
if not local_repo_path:
continue
path = Path(local_repo_path).expanduser()
if not path.is_absolute():
path = settings.ROOT_PATH / path
path = get_runtime_setting('ROOT_PATH') / path
paths.append(path.resolve())
return paths
@@ -542,11 +541,11 @@ class PluginHelper(metaclass=WeakSingleton):
continue
package_candidates = []
if settings.VERSION_FLAG:
package_candidates.append((settings.VERSION_FLAG, self.__get_local_package(repo_path,
settings.VERSION_FLAG)))
if get_runtime_setting('VERSION_FLAG'):
package_candidates.append((get_runtime_setting('VERSION_FLAG'), self.__get_local_package(repo_path,
get_runtime_setting('VERSION_FLAG'))))
# 向后兼容:补充扫描更低版本的 package 文件,便于本地仓库复用历史版本插件。
for backward_flag in VERSION_BACKWARD_COMPATIBLE_FLAGS.get(settings.VERSION_FLAG, []):
for backward_flag in VERSION_BACKWARD_COMPATIBLE_FLAGS.get(get_runtime_setting('VERSION_FLAG'), []):
package_candidates.append((backward_flag, self.__get_local_package(repo_path, backward_flag)))
package_candidates.append(("", self.__get_local_package(repo_path)))
@@ -611,9 +610,9 @@ class PluginHelper(metaclass=WeakSingleton):
repo_paths = [repo_path.resolve()] if repo_path else self.get_local_repo_paths()
package_versions = [package_version] if package_version is not None else []
if package_version is None:
if settings.VERSION_FLAG:
package_versions.append(settings.VERSION_FLAG)
package_versions.extend(VERSION_BACKWARD_COMPATIBLE_FLAGS.get(settings.VERSION_FLAG, []))
if get_runtime_setting('VERSION_FLAG'):
package_versions.append(get_runtime_setting('VERSION_FLAG'))
package_versions.extend(VERSION_BACKWARD_COMPATIBLE_FLAGS.get(get_runtime_setting('VERSION_FLAG'), []))
package_versions.append("")
selected_candidate = None
for repo_order, local_repo_path in enumerate(self.get_local_repo_paths()):
@@ -650,7 +649,7 @@ class PluginHelper(metaclass=WeakSingleton):
if not is_compatible:
candidate["compatible"] = False
candidate["skip_reason"] = (
f"插件索引条目不兼容 {settings.VERSION_FLAG}"
f"插件索引条目不兼容 {get_runtime_setting('VERSION_FLAG')}"
)
self.annotate_plugin_system_version(candidate)
if strict_system_version and candidate.get("system_version_compatible") is False:
@@ -729,7 +728,7 @@ class PluginHelper(metaclass=WeakSingleton):
else "package.json"
)
package_url = cls.__append_cache_buster(f"{raw_url}{package_file}")
headers = settings.REPO_GITHUB_HEADERS(repo=f"{user}/{repo}")
headers = get_runtime_setting('REPO_GITHUB_HEADERS')(repo=f"{user}/{repo}")
return package_url, headers
@classmethod
@@ -829,7 +828,7 @@ class PluginHelper(metaclass=WeakSingleton):
return
user_repo = f"{user}/{repo}"
headers = settings.REPO_GITHUB_HEADERS(repo=user_repo)
headers = get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo)
for page in range(1, 11):
release_api = (
f"https://api.github.com/repos/{user_repo}/releases"
@@ -998,7 +997,7 @@ class PluginHelper(metaclass=WeakSingleton):
package_version: Optional[str] = None) -> Optional[str]:
"""
检查并获取指定插件的可用版本,支持多版本优先级加载和版本兼容性检测
1. 如果未指定版本,则使用系统配置的默认版本(通过 settings.VERSION_FLAG 设置)
1. 如果未指定版本,则使用系统配置的默认版本(通过 get_runtime_setting('VERSION_FLAG') 设置)
2. 优先检查指定版本的插件(如 `package.v2.json`
3. 检查更低版本的 package 文件,并应用版本兼容标志
4. 检查 `package.json` 文件,并应用共享实现兼容标志
@@ -1084,7 +1083,7 @@ class PluginHelper(metaclass=WeakSingleton):
user_repo = f"{user}/{repo}"
if not package_version:
package_version = settings.VERSION_FLAG
package_version = get_runtime_setting('VERSION_FLAG')
# 1. 优先检查指定版本的插件
package_version = self.get_plugin_package_version(pid, repo_url, package_version)
@@ -1232,7 +1231,7 @@ class PluginHelper(metaclass=WeakSingleton):
file_api += f"/{pid.lower()}"
res = self.__request_with_fallback(file_api,
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo),
headers=get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo),
is_api=True,
timeout=30)
if res is None:
@@ -1273,7 +1272,7 @@ class PluginHelper(metaclass=WeakSingleton):
if item.get("download_url"):
logger.debug(f"正在下载文件:{item.get('path')}")
res = self.__request_with_fallback(item.get('download_url'),
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo))
headers=get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo))
if not res:
return False, f"文件 {item.get('path')} 下载失败!"
elif res.status_code != 200:
@@ -1285,7 +1284,7 @@ class PluginHelper(metaclass=WeakSingleton):
relative_path = relative_path.replace(f"plugins.{package_version}", "plugins", 1)
# 创建插件文件夹并写入文件
file_path = Path(settings.ROOT_PATH) / "app" / relative_path
file_path = Path(get_runtime_setting('ROOT_PATH')) / "app" / relative_path
file_path.parent.mkdir(parents=True, exist_ok=True)
with open(file_path, "w", encoding="utf-8") as f:
f.write(res.text)
@@ -1327,7 +1326,7 @@ class PluginHelper(metaclass=WeakSingleton):
:return: 备份目录路径
"""
plugin_dir = PLUGIN_DIR / pid.lower()
backup_dir = Path(settings.TEMP_PATH) / "plugin_backup" / pid.lower()
backup_dir = Path(get_runtime_setting('TEMP_PATH')) / "plugin_backup" / pid.lower()
if plugin_dir.exists():
# 备份时清理已有的备份目录,防止残留文件影响
@@ -1381,7 +1380,7 @@ class PluginHelper(metaclass=WeakSingleton):
logger.warn(f"{pid} 插件目录不存在,跳过刷新插件备份")
return False
backup_root = settings.CONFIG_PATH / "plugins_backup"
backup_root = get_runtime_setting('CONFIG_PATH') / "plugins_backup"
backup_dir = backup_root / pid.lower()
staging_dir = backup_root / f".{pid.lower()}.tmp-{uuid.uuid4().hex}"
previous_dir = backup_root / f".{pid.lower()}.old-{uuid.uuid4().hex}"
@@ -1546,7 +1545,7 @@ class PluginHelper(metaclass=WeakSingleton):
if package_name in cls._protected_runtime_packages
}
project_file = settings.ROOT_PATH / "pyproject.toml"
project_file = get_runtime_setting('ROOT_PATH') / "pyproject.toml"
root_requirements = cls.__parse_project_requirement_roots(project_file)
if not root_requirements:
return protected_packages
@@ -1595,7 +1594,7 @@ class PluginHelper(metaclass=WeakSingleton):
def __get_strict_runtime_packages(cls) -> Set[str]:
"""返回核心包及当前 ABI profile 中不得被插件改写的根包。"""
packages = set(cls._protected_runtime_packages)
project_file = settings.ROOT_PATH / "pyproject.toml"
project_file = get_runtime_setting('ROOT_PATH') / "pyproject.toml"
try:
for raw_requirement in iter_runtime_profile_requirement_strings(project_file):
requirement = Requirement(raw_requirement)
@@ -1717,7 +1716,7 @@ class PluginHelper(metaclass=WeakSingleton):
"""
以主程序依赖的当前已安装版本生成临时约束文件,确保插件安装不会改写主程序依赖。
"""
temp_dir = Path(settings.TEMP_PATH) / "plugin_dependencies"
temp_dir = Path(get_runtime_setting('TEMP_PATH')) / "plugin_dependencies"
temp_dir.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(
mode="w",
@@ -1794,10 +1793,10 @@ class PluginHelper(metaclass=WeakSingleton):
python_bin=Path(sys.executable),
find_links_dirs=find_links_dirs or [],
constraints_file=constraints_file,
config_dir=settings.CONFIG_PATH,
package_cache_root=settings.PACKAGE_CACHE_PATH,
package_index_url=settings.PIP_PROXY or None,
proxy_url=settings.PROXY_HOST or None,
config_dir=get_runtime_setting('CONFIG_PATH'),
package_cache_root=get_runtime_setting('PACKAGE_CACHE_PATH'),
package_index_url=get_runtime_setting('PIP_PROXY') or None,
proxy_url=get_runtime_setting('PROXY_HOST') or None,
purpose=purpose,
)
@@ -1868,7 +1867,7 @@ class PluginHelper(metaclass=WeakSingleton):
return lines
excluded_pairs = runtime_excluded_dependency_pairs(
Path(settings.ROOT_PATH) / "pyproject.toml"
Path(get_runtime_setting('ROOT_PATH')) / "pyproject.toml"
)
package_errors = set()
for match in matches:
@@ -1930,12 +1929,12 @@ class PluginHelper(metaclass=WeakSingleton):
if repair_target and not repair_target.exists():
repair_target = None
if repair_target is None:
repair_target = settings.ROOT_PATH / "pyproject.toml"
repair_target = get_runtime_setting('ROOT_PATH') / "pyproject.toml"
repair_desc = "主程序 uv.lock"
if not repair_target.exists():
return False, f"恢复依赖文件不存在:{repair_target}"
if snapshot_file is None and not (settings.ROOT_PATH / "uv.lock").exists():
return False, f"恢复依赖文件不存在:{settings.ROOT_PATH / 'uv.lock'}"
if snapshot_file is None and not (get_runtime_setting('ROOT_PATH') / "uv.lock").exists():
return False, f"恢复依赖文件不存在:{get_runtime_setting('ROOT_PATH') / 'uv.lock'}"
last_error = ""
request = cls.__build_package_install_request(repair_target, purpose="runtime-repair")
@@ -2127,21 +2126,21 @@ class PluginHelper(metaclass=WeakSingleton):
) -> List[Tuple[str, str, dict]]:
"""构造同步与异步 GitHub 请求共用的镜像、代理和直连顺序。"""
strategies: List[Tuple[str, str, dict]] = []
if not is_api and settings.GITHUB_PROXY:
if not is_api and get_runtime_setting('GITHUB_PROXY'):
proxy_url = (
f"{UrlUtils.standardize_base_url(settings.GITHUB_PROXY)}{url}"
f"{UrlUtils.standardize_base_url(get_runtime_setting('GITHUB_PROXY'))}{url}"
)
strategies.append(
("镜像站", proxy_url, {"headers": headers, "timeout": timeout})
)
if settings.PROXY_HOST:
if get_runtime_setting('PROXY_HOST'):
strategies.append(
(
"代理",
url,
{
"headers": headers,
"proxies": settings.PROXY,
"proxies": get_runtime_setting('PROXY'),
"timeout": timeout,
},
)
@@ -2218,7 +2217,7 @@ class PluginHelper(metaclass=WeakSingleton):
compatible, message = self.check_plugin_system_version(candidate)
return None if compatible else message
package_version = self.get_plugin_package_version(pid, repo_url, settings.VERSION_FLAG)
package_version = self.get_plugin_package_version(pid, repo_url, get_runtime_setting('VERSION_FLAG'))
if package_version is None:
return None
meta = self.__get_plugin_meta(pid, repo_url, package_version)
@@ -2235,7 +2234,7 @@ class PluginHelper(metaclass=WeakSingleton):
if self.is_local_repo_url(repo_url):
return await asyncio.to_thread(self.get_plugin_system_version_check_message, pid, repo_url)
package_version = await self.async_get_plugin_package_version(pid, repo_url, settings.VERSION_FLAG)
package_version = await self.async_get_plugin_package_version(pid, repo_url, get_runtime_setting('VERSION_FLAG'))
if package_version is None:
return None
meta = await self.__async_get_plugin_meta(pid, repo_url, package_version)
@@ -2382,7 +2381,7 @@ class PluginHelper(metaclass=WeakSingleton):
release_api = f"https://api.github.com/repos/{user_repo}/releases/tags/{release_tag}"
rel_res = self.__request_with_fallback(
release_api,
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo),
headers=get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo),
timeout=30,
is_api=True,
)
@@ -2405,7 +2404,7 @@ class PluginHelper(metaclass=WeakSingleton):
return False, f"解析 Release 信息失败:{e}"
# 使用资产的API端点下载,需要设置Accept头为application/octet-stream
headers = settings.REPO_GITHUB_HEADERS(repo=user_repo).copy()
headers = get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo).copy()
headers["Accept"] = "application/octet-stream"
res = self.__request_with_fallback(download_url, headers=headers, is_api=True)
if res is None or res.status_code != 200:
@@ -2416,7 +2415,7 @@ class PluginHelper(metaclass=WeakSingleton):
infos = zf.infolist()
if not infos:
return False, "压缩包内容为空"
dest_base = Path(settings.ROOT_PATH) / "app" / "plugins" / pid.lower()
dest_base = Path(get_runtime_setting('ROOT_PATH')) / "app" / "plugins" / pid.lower()
targets = self.__iter_release_zip_targets(zf, dest_base)
wrote_any = False
for info, dest_path, is_dir in targets:
@@ -2721,7 +2720,7 @@ class PluginHelper(metaclass=WeakSingleton):
file_api += f"/{pid.lower()}"
res = await self.__async_request_with_fallback(file_api,
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo),
headers=get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo),
is_api=True,
timeout=30)
if res is None:
@@ -2762,7 +2761,7 @@ class PluginHelper(metaclass=WeakSingleton):
if item.get("download_url"):
logger.debug(f"正在下载文件:{item.get('path')}")
res = await self.__async_request_with_fallback(item.get('download_url'),
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo))
headers=get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo))
if not res:
return False, f"文件 {item.get('path')} 下载失败!"
elif res.status_code != 200:
@@ -2774,7 +2773,7 @@ class PluginHelper(metaclass=WeakSingleton):
relative_path = relative_path.replace(f"plugins.{package_version}", "plugins", 1)
# 创建插件文件夹并写入文件
file_path = AsyncPath(settings.ROOT_PATH) / "app" / relative_path
file_path = AsyncPath(get_runtime_setting('ROOT_PATH')) / "app" / relative_path
await file_path.parent.mkdir(parents=True, exist_ok=True)
async with aiofiles.open(file_path, "w", encoding="utf-8") as f:
await f.write(res.text)
@@ -2825,13 +2824,13 @@ class PluginHelper(metaclass=WeakSingleton):
if repair_target and not await _await_thread_operation(repair_target.exists):
repair_target = None
if repair_target is None:
repair_target = settings.ROOT_PATH / "pyproject.toml"
repair_target = get_runtime_setting('ROOT_PATH') / "pyproject.toml"
repair_desc = "主程序 uv.lock"
if not await _await_thread_operation(repair_target.exists):
return False, f"恢复依赖文件不存在:{repair_target}"
lock_file = settings.ROOT_PATH / "uv.lock"
lock_file = get_runtime_setting('ROOT_PATH') / "uv.lock"
if snapshot_file is None and not await _await_thread_operation(lock_file.exists):
return False, f"恢复依赖文件不存在:{settings.ROOT_PATH / 'uv.lock'}"
return False, f"恢复依赖文件不存在:{get_runtime_setting('ROOT_PATH') / 'uv.lock'}"
request = cls.__build_package_install_request(
repair_target,
@@ -3076,7 +3075,7 @@ class PluginHelper(metaclass=WeakSingleton):
:return: 备份目录路径
"""
plugin_dir = AsyncPath(PLUGIN_DIR) / pid.lower()
backup_dir = AsyncPath(settings.TEMP_PATH) / "plugin_backup" / pid.lower()
backup_dir = AsyncPath(get_runtime_setting('TEMP_PATH')) / "plugin_backup" / pid.lower()
if await plugin_dir.exists():
try:
@@ -3234,7 +3233,7 @@ class PluginHelper(metaclass=WeakSingleton):
user_repo = f"{user}/{repo}"
if not package_version:
package_version = settings.VERSION_FLAG
package_version = get_runtime_setting('VERSION_FLAG')
# 1. 优先检查指定版本的插件
package_version = await self.async_get_plugin_package_version(pid, repo_url, package_version)
@@ -3396,7 +3395,7 @@ class PluginHelper(metaclass=WeakSingleton):
release_api = f"https://api.github.com/repos/{user_repo}/releases/tags/{release_tag}"
rel_res = await self.__async_request_with_fallback(
release_api,
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo),
headers=get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo),
timeout=30,
is_api=True,
)
@@ -3419,7 +3418,7 @@ class PluginHelper(metaclass=WeakSingleton):
return False, f"解析 Release 信息失败:{e}"
# 使用资产的API端点下载,需要设置Accept头为application/octet-stream
headers = settings.REPO_GITHUB_HEADERS(repo=user_repo).copy()
headers = get_runtime_setting('REPO_GITHUB_HEADERS')(repo=user_repo).copy()
headers["Accept"] = "application/octet-stream"
res = await self.__async_request_with_fallback(download_url,
headers=headers,
@@ -3432,7 +3431,7 @@ class PluginHelper(metaclass=WeakSingleton):
infos = zf.infolist()
if not infos:
return False, "压缩包内容为空"
dest_base = Path(settings.ROOT_PATH) / "app" / "plugins" / pid.lower()
dest_base = Path(get_runtime_setting('ROOT_PATH')) / "app" / "plugins" / pid.lower()
targets = self.__iter_release_zip_targets(zf, dest_base)
wrote_any = False
for info, dest_path, is_dir in targets:
+1 -1
View File
@@ -13,7 +13,7 @@ class OcrHelper:
def __init__(self, ocr_base_url: Optional[str] = None) -> None:
"""初始化 OCR 服务地址,优先使用组合根设置快照。"""
if ocr_base_url is None:
ocr_base_url = get_runtime_setting("OCR_HOST")
ocr_base_url = get_runtime_setting('OCR_HOST')
self._ocr_b64_url = f"{str(ocr_base_url).rstrip('/')}/captcha/base64"
def get_captcha_text(
+67 -68
View File
@@ -7,7 +7,7 @@ from urllib.parse import parse_qs, quote, urlparse, urlsplit
from app.runtime.cache import cached
from app.runtime.config import global_vars
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.runtime.tasks import get_task_registry
from app.domain.context import MediaInfo, MusicInfo
from app.domain.meta.metabase import MetaBase
@@ -26,8 +26,7 @@ from app.adapters.system.host import SystemUtils
from app.runtime.version import get_app_version, get_frontend_version
# 保留旧插件可覆盖的模块级入口,默认通过 runtime 代理动态读取配置。
settings = RuntimeSettingsCompat()
# 中心服务适配器只通过 runtime 读取端口消费组合根的最新配置。
_server_report_service: Any = None
@@ -102,7 +101,7 @@ class MoviePilotServerHelper:
"""
判断请求地址是否指向配置中的 MoviePilot 服务端。
"""
server_host = (settings.MP_SERVER_HOST or "").strip().rstrip("/")
server_host = (get_runtime_setting('MP_SERVER_HOST') or "").strip().rstrip("/")
if not server_host or not url:
return False
@@ -144,7 +143,7 @@ class MoviePilotServerHelper:
user_uid = cls.get_user_uid()
if user_uid:
request_headers[cls.USER_UID_HEADER] = user_uid
request_headers["User-Agent"] = settings.USER_AGENT
request_headers["User-Agent"] = get_runtime_setting('USER_AGENT')
return request_headers
@classmethod
@@ -162,10 +161,10 @@ class MoviePilotServerHelper:
"""
获取当前 GitHub 用户名。
"""
if cls._github_user is None and settings.GITHUB_HEADERS:
if cls._github_user is None and get_runtime_setting('GITHUB_HEADERS'):
res = RequestUtils(
headers=settings.GITHUB_HEADERS,
proxies=settings.PROXY,
headers=get_runtime_setting('GITHUB_HEADERS'),
proxies=get_runtime_setting('PROXY'),
timeout=15,
).get_res("https://api.github.com/user")
if res:
@@ -178,10 +177,10 @@ class MoviePilotServerHelper:
"""
异步获取当前 GitHub 用户名。
"""
if cls._github_user is None and settings.GITHUB_HEADERS:
if cls._github_user is None and get_runtime_setting('GITHUB_HEADERS'):
res = await AsyncRequestUtils(
headers=settings.GITHUB_HEADERS,
proxies=settings.PROXY,
headers=get_runtime_setting('GITHUB_HEADERS'),
proxies=get_runtime_setting('PROXY'),
timeout=15,
).get_res("https://api.github.com/user")
if res:
@@ -278,7 +277,7 @@ class MoviePilotServerHelper:
"user_uid": cls.get_user_uid(),
"backend_version": get_app_version(),
"frontend_version": get_frontend_version(),
"version_flag": settings.VERSION_FLAG,
"version_flag": get_runtime_setting('VERSION_FLAG'),
"platform": f"{platform.system()} {platform.release()}".strip(),
"arch": SystemUtils.cpu_arch(),
}
@@ -288,7 +287,7 @@ class MoviePilotServerHelper:
"""
上报当前安装实例的版本统计。
"""
if not settings.USAGE_STATISTIC_SHARE:
if not get_runtime_setting('USAGE_STATISTIC_SHARE'):
return False
payload = cls.build_usage_payload()
if not payload.get("user_uid"):
@@ -305,7 +304,7 @@ class MoviePilotServerHelper:
"""
异步上报当前安装实例的版本统计。
"""
if not settings.USAGE_STATISTIC_SHARE:
if not get_runtime_setting('USAGE_STATISTIC_SHARE'):
return False
payload = cls.build_usage_payload()
if not payload.get("user_uid"):
@@ -322,7 +321,7 @@ class MoviePilotServerHelper:
"""
异步获取安装版本统计报表。
"""
if not settings.USAGE_STATISTIC_SHARE:
if not get_runtime_setting('USAGE_STATISTIC_SHARE'):
return {}
try:
res = await cls.async_usage_statistic()
@@ -338,7 +337,7 @@ class MoviePilotServerHelper:
初始化订阅统计上报状态。
"""
cls._report_service().init_report(
enabled=settings.SUBSCRIBE_STATISTIC_SHARE,
enabled=get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'),
state_key=SystemConfigKey.SubscribeReport,
reporter=cls.sub_report,
)
@@ -347,7 +346,7 @@ class MoviePilotServerHelper:
async def async_init_subscribe_report(cls) -> None:
"""异步初始化订阅统计标记。"""
await cls._report_service().async_init_report(
enabled=settings.SUBSCRIBE_STATISTIC_SHARE,
enabled=get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'),
state_key=SystemConfigKey.SubscribeReport,
reporter=cls.async_sub_report,
)
@@ -358,7 +357,7 @@ class MoviePilotServerHelper:
初始化插件安装统计上报状态。
"""
cls._report_service().init_report(
enabled=settings.PLUGIN_STATISTIC_SHARE,
enabled=get_runtime_setting('PLUGIN_STATISTIC_SHARE'),
state_key=SystemConfigKey.PluginInstallReport,
reporter=cls.install_plugin_report,
)
@@ -367,7 +366,7 @@ class MoviePilotServerHelper:
async def async_init_plugin_report(cls) -> None:
"""异步初始化插件统计标记。"""
await cls._report_service().async_init_report(
enabled=settings.PLUGIN_STATISTIC_SHARE,
enabled=get_runtime_setting('PLUGIN_STATISTIC_SHARE'),
state_key=SystemConfigKey.PluginInstallReport,
reporter=cls.async_install_plugin_report,
)
@@ -514,7 +513,7 @@ class MoviePilotServerHelper:
"""
获取插件安装统计。
"""
if not settings.PLUGIN_STATISTIC_SHARE:
if not get_runtime_setting('PLUGIN_STATISTIC_SHARE'):
return {}
res = cls.plugin_statistic()
if res is not None and res.status_code == 200:
@@ -526,7 +525,7 @@ class MoviePilotServerHelper:
"""
异步获取插件安装统计。
"""
if not settings.PLUGIN_STATISTIC_SHARE:
if not get_runtime_setting('PLUGIN_STATISTIC_SHARE'):
return {}
res = await cls.async_plugin_statistic()
if res is not None and res.status_code == 200:
@@ -590,7 +589,7 @@ class MoviePilotServerHelper:
"""
上报单个插件安装统计。
"""
if not settings.PLUGIN_STATISTIC_SHARE:
if not get_runtime_setting('PLUGIN_STATISTIC_SHARE'):
return False
if not plugin_id:
return False
@@ -605,7 +604,7 @@ class MoviePilotServerHelper:
"""
异步上报单个插件安装统计。
"""
if not settings.PLUGIN_STATISTIC_SHARE:
if not get_runtime_setting('PLUGIN_STATISTIC_SHARE'):
return False
if not plugin_id:
return False
@@ -621,7 +620,7 @@ class MoviePilotServerHelper:
批量上报存量插件安装统计。
"""
return cls._report_service().report_plugins(
enabled=settings.PLUGIN_STATISTIC_SHARE,
enabled=get_runtime_setting('PLUGIN_STATISTIC_SHARE'),
items=items,
)
@@ -631,7 +630,7 @@ class MoviePilotServerHelper:
异步批量上报存量插件安装统计。
"""
return await cls._report_service().async_report_plugins(
enabled=settings.PLUGIN_STATISTIC_SHARE,
enabled=get_runtime_setting('PLUGIN_STATISTIC_SHARE'),
items=items,
)
@@ -820,7 +819,7 @@ class MoviePilotServerHelper:
"""
获取订阅统计数据。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return []
params = cls._build_subscribe_query_params(
page=page,
@@ -848,7 +847,7 @@ class MoviePilotServerHelper:
"""
异步获取订阅统计数据。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return []
params = cls._build_subscribe_query_params(
page=page,
@@ -866,7 +865,7 @@ class MoviePilotServerHelper:
"""
新增订阅统计。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False
payload = cls._build_subscribe_statistic_payload(sub)
if not payload:
@@ -879,7 +878,7 @@ class MoviePilotServerHelper:
"""
异步新增订阅统计。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False
payload = cls._build_subscribe_statistic_payload(sub)
if not payload:
@@ -890,14 +889,14 @@ class MoviePilotServerHelper:
@classmethod
def sub_reg_durable(cls, sub: dict) -> bool:
"""同步上报新增统计;明确禁用时视为无需投递。"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return True
return cls.sub_reg(sub)
@classmethod
async def async_sub_reg_durable(cls, sub: dict) -> bool:
"""异步上报新增统计;明确禁用时视为无需投递。"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return True
return await cls.async_sub_reg(sub)
@@ -906,7 +905,7 @@ class MoviePilotServerHelper:
"""
完成订阅统计。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False
payload = cls._build_subscribe_statistic_payload(sub)
if not payload:
@@ -917,7 +916,7 @@ class MoviePilotServerHelper:
@classmethod
async def async_sub_done(cls, sub: dict) -> bool:
"""异步完成订阅统计,并仅在服务端确认成功时返回 True。"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False
payload = cls._build_subscribe_statistic_payload(sub)
if not payload:
@@ -928,14 +927,14 @@ class MoviePilotServerHelper:
@classmethod
def sub_done_durable(cls, sub: dict) -> bool:
"""同步上报完成统计;明确禁用时视为无需投递。"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return True
return cls.sub_done(sub)
@classmethod
async def async_sub_done_durable(cls, sub: dict) -> bool:
"""异步上报完成统计;明确禁用时视为无需投递。"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return True
return await cls.async_sub_done(sub)
@@ -989,14 +988,14 @@ class MoviePilotServerHelper:
上报存量订阅统计。
"""
return cls._report_service().report_subscribes(
enabled=settings.SUBSCRIBE_STATISTIC_SHARE,
enabled=get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'),
)
@classmethod
async def async_sub_report(cls) -> bool:
"""异步上报存量订阅统计。"""
return await cls._report_service().async_report_subscribes(
enabled=settings.SUBSCRIBE_STATISTIC_SHARE,
enabled=get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'),
)
@classmethod
@@ -1011,7 +1010,7 @@ class MoviePilotServerHelper:
分享订阅。
"""
return cls._sharing_service().share_subscribe(
enabled=settings.SUBSCRIBE_STATISTIC_SHARE,
enabled=get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'),
subscribe_id=subscribe_id,
share_title=share_title,
share_comment=share_comment,
@@ -1030,7 +1029,7 @@ class MoviePilotServerHelper:
异步分享订阅。
"""
return await cls._sharing_service().async_share_subscribe(
enabled=settings.SUBSCRIBE_STATISTIC_SHARE,
enabled=get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'),
subscribe_id=subscribe_id,
share_title=share_title,
share_comment=share_comment,
@@ -1056,7 +1055,7 @@ class MoviePilotServerHelper:
"""
删除订阅分享。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False, "当前没有开启订阅数据共享功能"
return cls._handle_response(
cls.subscribe_share_delete(share_id, cls.get_user_uuid()),
@@ -1068,7 +1067,7 @@ class MoviePilotServerHelper:
"""
异步删除订阅分享。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False, "当前没有开启订阅数据共享功能"
return cls._handle_response(
await cls.async_subscribe_share_delete(share_id, cls.get_user_uuid()),
@@ -1080,7 +1079,7 @@ class MoviePilotServerHelper:
"""
复用订阅分享。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False, "当前没有开启订阅数据共享功能"
return cls._handle_response(cls.subscribe_fork(share_id))
@@ -1089,7 +1088,7 @@ class MoviePilotServerHelper:
"""
异步复用订阅分享。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return False, "当前没有开启订阅数据共享功能"
return cls._handle_response(await cls.async_subscribe_fork(share_id))
@@ -1108,7 +1107,7 @@ class MoviePilotServerHelper:
"""
获取订阅分享数据。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return []
params = cls._build_subscribe_query_params(
page=page,
@@ -1136,7 +1135,7 @@ class MoviePilotServerHelper:
"""
异步获取订阅分享数据。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return []
params = cls._build_subscribe_query_params(
page=page,
@@ -1155,7 +1154,7 @@ class MoviePilotServerHelper:
"""
获取订阅分享统计数据。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return []
return cls._handle_list_response(cls.subscribe_share_statistics())
@@ -1165,7 +1164,7 @@ class MoviePilotServerHelper:
"""
异步获取订阅分享统计数据。
"""
if not settings.SUBSCRIBE_STATISTIC_SHARE:
if not get_runtime_setting('SUBSCRIBE_STATISTIC_SHARE'):
return []
return cls._handle_list_response(await cls.async_subscribe_share_statistics())
@@ -1252,7 +1251,7 @@ class MoviePilotServerHelper:
分享工作流。
"""
return cls._sharing_service().share_workflow(
enabled=settings.WORKFLOW_STATISTIC_SHARE,
enabled=get_runtime_setting('WORKFLOW_STATISTIC_SHARE'),
workflow_id=workflow_id,
share_title=share_title,
share_comment=share_comment,
@@ -1271,7 +1270,7 @@ class MoviePilotServerHelper:
异步分享工作流。
"""
return await cls._sharing_service().async_share_workflow(
enabled=settings.WORKFLOW_STATISTIC_SHARE,
enabled=get_runtime_setting('WORKFLOW_STATISTIC_SHARE'),
workflow_id=workflow_id,
share_title=share_title,
share_comment=share_comment,
@@ -1283,7 +1282,7 @@ class MoviePilotServerHelper:
"""
删除工作流分享。
"""
if not settings.WORKFLOW_STATISTIC_SHARE:
if not get_runtime_setting('WORKFLOW_STATISTIC_SHARE'):
return False, "当前没有开启工作流数据共享功能"
return cls._handle_response(
cls.workflow_share_delete(share_id, cls.get_user_uuid()),
@@ -1295,7 +1294,7 @@ class MoviePilotServerHelper:
"""
异步删除工作流分享。
"""
if not settings.WORKFLOW_STATISTIC_SHARE:
if not get_runtime_setting('WORKFLOW_STATISTIC_SHARE'):
return False, "当前没有开启工作流数据共享功能"
return cls._handle_response(
await cls.async_workflow_share_delete(share_id, cls.get_user_uuid()),
@@ -1307,7 +1306,7 @@ class MoviePilotServerHelper:
"""
复用工作流分享。
"""
if not settings.WORKFLOW_STATISTIC_SHARE:
if not get_runtime_setting('WORKFLOW_STATISTIC_SHARE'):
return False, "当前没有开启工作流数据共享功能"
return cls._handle_response(cls.workflow_fork(share_id))
@@ -1316,7 +1315,7 @@ class MoviePilotServerHelper:
"""
异步复用工作流分享。
"""
if not settings.WORKFLOW_STATISTIC_SHARE:
if not get_runtime_setting('WORKFLOW_STATISTIC_SHARE'):
return False, "当前没有开启工作流数据共享功能"
return cls._handle_response(await cls.async_workflow_fork(share_id))
@@ -1331,7 +1330,7 @@ class MoviePilotServerHelper:
"""
获取工作流分享数据。
"""
if not settings.WORKFLOW_STATISTIC_SHARE:
if not get_runtime_setting('WORKFLOW_STATISTIC_SHARE'):
return []
return cls._handle_list_response(cls.workflow_shares({
"name": name,
@@ -1350,7 +1349,7 @@ class MoviePilotServerHelper:
"""
异步获取工作流分享数据。
"""
if not settings.WORKFLOW_STATISTIC_SHARE:
if not get_runtime_setting('WORKFLOW_STATISTIC_SHARE'):
return []
return cls._handle_list_response(await cls.async_workflow_shares({
"name": name,
@@ -1370,10 +1369,10 @@ class MoviePilotServerHelper:
"""
获取共享识别服务端地址。
"""
custom_api = (settings.MEDIA_RECOGNIZE_SHARE_API or "").strip()
custom_api = (get_runtime_setting('MEDIA_RECOGNIZE_SHARE_API') or "").strip()
if custom_api:
return custom_api.rstrip("/")
server_host = (settings.MP_SERVER_HOST or "").strip().rstrip("/")
server_host = (get_runtime_setting('MP_SERVER_HOST') or "").strip().rstrip("/")
if not server_host:
return None
return f"{server_host}{cls._RECOGNIZE_SHARE_PATH}"
@@ -1429,7 +1428,7 @@ class MoviePilotServerHelper:
"""
查询共享识别结果。
"""
if not settings.MEDIA_RECOGNIZE_SHARE:
if not get_runtime_setting('MEDIA_RECOGNIZE_SHARE'):
return None
params = cls._build_recognize_query_params(
meta=meta,
@@ -1453,7 +1452,7 @@ class MoviePilotServerHelper:
"""
异步查询共享识别结果。
"""
if not settings.MEDIA_RECOGNIZE_SHARE:
if not get_runtime_setting('MEDIA_RECOGNIZE_SHARE'):
return None
params = cls._build_recognize_query_params(
meta=meta,
@@ -1476,7 +1475,7 @@ class MoviePilotServerHelper:
"""
上报共享识别结果,电影、电视剧、音乐共用。
"""
if not settings.MEDIA_RECOGNIZE_SHARE:
if not get_runtime_setting('MEDIA_RECOGNIZE_SHARE'):
return False
payload = cls._build_recognize_report_payload(
meta=meta,
@@ -1498,7 +1497,7 @@ class MoviePilotServerHelper:
"""
异步上报共享识别结果,电影、电视剧、音乐共用。
"""
if not settings.MEDIA_RECOGNIZE_SHARE:
if not get_runtime_setting('MEDIA_RECOGNIZE_SHARE'):
return False
payload = cls._build_recognize_report_payload(
meta=meta,
@@ -1855,7 +1854,7 @@ class MoviePilotServerHelper:
"""
根据服务端基础地址和路径生成完整 URL。
"""
return f"{settings.MP_SERVER_HOST.rstrip('/')}{path}"
return f"{get_runtime_setting('MP_SERVER_HOST').rstrip('/')}{path}"
@classmethod
def _get(
@@ -1869,7 +1868,7 @@ class MoviePilotServerHelper:
发送服务端 GET 请求,默认携带安装用户 ID。
"""
return RequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=timeout,
headers=cls.build_headers(url) if include_user_uid else {},
).get_res(url, params=params)
@@ -1886,7 +1885,7 @@ class MoviePilotServerHelper:
异步发送服务端 GET 请求,默认携带安装用户 ID。
"""
return await AsyncRequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=timeout,
headers=cls.build_headers(url) if include_user_uid else {},
).get_res(url, params=params)
@@ -1897,7 +1896,7 @@ class MoviePilotServerHelper:
发送携带安装用户 ID 的服务端 JSON POST 请求。
"""
return RequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=timeout,
headers=cls.build_headers(url, content_type="application/json"),
).post(url, json=payload)
@@ -1908,7 +1907,7 @@ class MoviePilotServerHelper:
异步发送携带安装用户 ID 的服务端 JSON POST 请求。
"""
return await AsyncRequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=timeout,
headers=cls.build_headers(url, content_type="application/json"),
).post(url, json=payload)
@@ -1919,7 +1918,7 @@ class MoviePilotServerHelper:
发送携带安装用户 ID 的服务端 DELETE 请求。
"""
return RequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=timeout,
headers=cls.build_headers(url),
).delete_res(url, params=params)
@@ -1930,7 +1929,7 @@ class MoviePilotServerHelper:
异步发送携带安装用户 ID 的服务端 DELETE 请求。
"""
return await AsyncRequestUtils(
proxies=settings.PROXY,
proxies=get_runtime_setting('PROXY'),
timeout=timeout,
headers=cls.build_headers(url),
).delete_res(url, params=params)
+14 -13
View File
@@ -9,7 +9,7 @@ from typing import Any, Callable, Optional, Protocol
from urllib.parse import urlparse
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.runtime.managed_resources import (
acquire_managed_resource,
acquire_managed_resource_async,
@@ -17,10 +17,6 @@ from app.runtime.managed_resources import (
from app.adapters.network.http import RequestUtils, cookie_parse
# 保留旧插件可覆盖的模块级入口,默认通过 runtime 代理动态读取浏览器配置。
settings = RuntimeSettingsCompat()
class BrowserElement(Protocol):
"""
页面元素的最小接口,避免为了类型标注直接导入 Playwright。
@@ -721,8 +717,8 @@ class BrowserSessionHelper:
) -> BrowserContext:
"""按宿主反检测配置创建 CloakBrowser 上下文。"""
context_kwargs = {
"humanize": settings.CLOAKBROWSER_HUMANIZE,
"human_preset": settings.CLOAKBROWSER_HUMAN_PRESET,
"humanize": get_runtime_setting('CLOAKBROWSER_HUMANIZE'),
"human_preset": get_runtime_setting('CLOAKBROWSER_HUMAN_PRESET'),
}
if user_agent:
context_kwargs["user_agent"] = user_agent
@@ -922,14 +918,18 @@ class PlaywrightHelper:
"""
兼容旧的 PlaywrightHelper(browser_type=...) 构造方式。
"""
self.browser_type = browser_type or settings.PLAYWRIGHT_BROWSER_TYPE
self.browser_type = browser_type or get_runtime_setting(
"PLAYWRIGHT_BROWSER_TYPE"
)
@staticmethod
def __browser_emulation() -> str:
"""
当前浏览器仿真类型。
"""
return (settings.BROWSER_EMULATION or "cloakbrowser").lower()
return (
get_runtime_setting('BROWSER_EMULATION') or "cloakbrowser"
).lower()
@staticmethod
def __launch_cloakbrowser_context(headless: bool,
@@ -941,8 +941,8 @@ class PlaywrightHelper:
return launch_browser_context(headless=headless,
proxy=proxies,
user_agent=user_agent,
humanize=settings.CLOAKBROWSER_HUMANIZE,
human_preset=settings.CLOAKBROWSER_HUMAN_PRESET)
humanize=get_runtime_setting('CLOAKBROWSER_HUMANIZE'),
human_preset=get_runtime_setting('CLOAKBROWSER_HUMAN_PRESET'))
@staticmethod
def __fs_cookie_str(cookies: list) -> str:
@@ -960,11 +960,12 @@ class PlaywrightHelper:
调用 FlareSolverr 解决 Cloudflare 并返回 solution 结果
参考: https://github.com/FlareSolverr/FlareSolverr
"""
if not settings.FLARESOLVERR_URL:
flaresolverr_url = get_runtime_setting('FLARESOLVERR_URL')
if not flaresolverr_url:
logger.warn("未配置 FLARESOLVERR_URL,无法使用 FlareSolverr")
return None
fs_api = settings.FLARESOLVERR_URL.rstrip("/") + "/v1"
fs_api = flaresolverr_url.rstrip("/") + "/v1"
session_id = None
try:
+6 -6
View File
@@ -27,10 +27,8 @@ import threading
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Union
from app.runtime.settings import RuntimeSettingsCompat
settings = RuntimeSettingsCompat()
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
# worker 脚本路径。用绝对路径直接执行,而不是 -m 或 import:
# 直接执行文件不会触发 app/__init__.py 的导入链,代理启动才是毫秒级的
@@ -258,7 +256,7 @@ class FileSystemProxy:
"""
if self._timeout_override is not None:
return self._timeout_override
return float(getattr(settings, "FS_PROXY_TIMEOUT", DEFAULT_TIMEOUT))
return float(get_runtime_setting("FS_PROXY_TIMEOUT", DEFAULT_TIMEOUT))
@property
def _stall_timeout(self) -> float:
@@ -267,14 +265,16 @@ class FileSystemProxy:
"""
if self._stall_timeout_override is not None:
return self._stall_timeout_override
return float(getattr(settings, "FS_PROXY_STALL_TIMEOUT", DEFAULT_STALL_TIMEOUT))
return float(
get_runtime_setting("FS_PROXY_STALL_TIMEOUT", DEFAULT_STALL_TIMEOUT)
)
@staticmethod
def _enabled() -> bool:
"""
代理是否启用关闭时退回直接调用行为与引入代理之前完全一致
"""
return bool(getattr(settings, "FS_PROXY_ENABLED", True))
return bool(get_runtime_setting("FS_PROXY_ENABLED", True))
@staticmethod
def _direct(op: str, payload: Dict[str, Any]) -> Any:
+1 -1
View File
@@ -52,7 +52,7 @@ class PluginDependencyInstaller:
self._helper = helper
self._installed_plugins_provider = installed_plugins_provider or (lambda: [])
self._plugin_dir = plugin_dir or (
Path(get_runtime_setting("ROOT_PATH")) / "app" / "plugins"
Path(get_runtime_setting('ROOT_PATH')) / "app" / "plugins"
)
@staticmethod
+11 -10
View File
@@ -16,10 +16,7 @@ from app.runtime.execution import (
run_in_threadpool_to_completion as _await_thread_operation,
)
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
# 保留旧模块级入口,插件本地同步测试和旧扩展仍可能覆盖这些设置。
settings = RuntimeSettingsCompat()
from app.runtime.settings import get_runtime_setting
@dataclass(frozen=True, slots=True)
class PluginPackageCheckpoint:
@@ -58,7 +55,7 @@ class PluginPackageManager:
def __plugin_dir(plugin_id: str) -> Path:
"""解析插件运行目录并拒绝越出宿主插件根目录的标识。"""
plugins_root = (
Path(settings.ROOT_PATH) / "app" / "plugins"
Path(get_runtime_setting('ROOT_PATH')) / "app" / "plugins"
).resolve()
plugin_dir = (plugins_root / plugin_id.lower()).resolve()
if plugin_dir == plugins_root or not plugin_dir.is_relative_to(plugins_root):
@@ -74,7 +71,9 @@ class PluginPackageManager:
plugin_dir = self.__plugin_dir(plugin_id)
durable = transaction_id is not None
persistent_backup_dir = (
Path(settings.CONFIG_PATH) / "plugins_backup" / plugin_id.lower()
Path(get_runtime_setting('CONFIG_PATH'))
/ "plugins_backup"
/ plugin_id.lower()
).resolve()
backup_staging_dir = (
persistent_backup_dir.parent
@@ -89,9 +88,9 @@ class PluginPackageManager:
else None
)
transaction_root = (
Path(settings.CONFIG_PATH)
Path(get_runtime_setting('CONFIG_PATH'))
if durable
else Path(settings.TEMP_PATH)
else Path(get_runtime_setting('TEMP_PATH'))
)
transaction_dir = (
transaction_root
@@ -129,7 +128,9 @@ class PluginPackageManager:
"""按受控根目录和事务 ID 重建崩溃回放所需的文件引用。"""
plugin_dir = self.__plugin_dir(plugin_id)
persistent_backup_dir = (
Path(settings.CONFIG_PATH) / "plugins_backup" / plugin_id.lower()
Path(get_runtime_setting('CONFIG_PATH'))
/ "plugins_backup"
/ plugin_id.lower()
).resolve()
durable_backup = SystemUtils.is_docker()
return PluginPackageCheckpoint(
@@ -149,7 +150,7 @@ class PluginPackageManager:
else None
),
transaction_dir=(
Path(settings.CONFIG_PATH)
Path(get_runtime_setting('CONFIG_PATH'))
/ "plugin_transactions"
/ transaction_id
),
+11 -17
View File
@@ -5,18 +5,12 @@ import sysconfig
from pathlib import Path
from typing import Callable
from app.runtime import config as _runtime_config
from app.runtime.log import logger
from app.adapters.network.http import RequestUtils
from app.foundation.version import compare_version
from app.adapters.system.host import SystemUtils
from app.foundation.version import compare_version
from app.runtime.log import logger
from app.runtime.settings import get_runtime_setting
# 保留模块级旧 Settings 入口,旧插件和测试可能仍会对其做运行时覆盖;实现读取统一走 runtime 端口。
settings = _runtime_config.settings
ResourceVersionProvider = Callable[[], tuple[str, str]]
@@ -39,9 +33,9 @@ class ResourceHelper:
检测和更新资源包
"""
_base_dir: Path = get_runtime_setting("ROOT_PATH")
_base_dir: Path = get_runtime_setting('ROOT_PATH')
_resource_target = Path("app/application/site")
_version_flag = get_runtime_setting("RESOURCE_VERSION_FLAG")
_version_flag = get_runtime_setting('RESOURCE_VERSION_FLAG')
_repo = (
f"{get_runtime_setting('GITHUB_PROXY')}https://raw.githubusercontent.com/"
f"jxxghp/MoviePilot-Resources/main/package.{_version_flag}.json"
@@ -56,8 +50,8 @@ class ResourceHelper:
"""返回访问 GitHub 资源时应使用的代理配置。"""
return (
None
if get_runtime_setting("GITHUB_PROXY")
else get_runtime_setting("PROXY")
if get_runtime_setting('GITHUB_PROXY')
else get_runtime_setting('PROXY')
)
@staticmethod
@@ -97,7 +91,7 @@ class ResourceHelper:
"""读取 V3 资源清单。"""
response = RequestUtils(
proxies=self.proxies,
headers=get_runtime_setting("GITHUB_HEADERS"),
headers=get_runtime_setting('GITHUB_HEADERS'),
timeout=10,
).get_res(self._repo)
return response if response and response.status_code == 200 else None
@@ -115,7 +109,7 @@ class ResourceHelper:
:param indexer_version: 当前已加载的站点索引资源版本省略时使用组合根注入值
:return: 是否成功安装了需要由上层处理重启的新资源
"""
if not get_runtime_setting("AUTO_UPDATE_RESOURCE"):
if not get_runtime_setting('AUTO_UPDATE_RESOURCE'):
return False
if SystemUtils.is_frozen():
return False
@@ -170,8 +164,8 @@ class ResourceHelper:
if need_updates:
# 下载文件信息列表
r = RequestUtils(
proxies=get_runtime_setting("PROXY"),
headers=get_runtime_setting("GITHUB_HEADERS"),
proxies=get_runtime_setting('PROXY'),
headers=get_runtime_setting('GITHUB_HEADERS'),
timeout=30,
).get_res(self._files_api)
if r and not r.ok:
@@ -201,7 +195,7 @@ class ResourceHelper:
)
res = RequestUtils(
proxies=self.proxies,
headers=get_runtime_setting("GITHUB_HEADERS"),
headers=get_runtime_setting('GITHUB_HEADERS'),
timeout=180,
).get_res(download_url)
if not res:
+1 -1
View File
@@ -17,7 +17,7 @@ else:
def _rust_accel_enabled() -> bool:
"""读取 Rust 开关快照,组合根未装配时回退旧 Settings。"""
return bool(get_runtime_setting("RUST_ACCEL"))
return bool(get_runtime_setting('RUST_ACCEL'))
def is_required() -> bool:
+4 -4
View File
@@ -43,7 +43,7 @@ class SystemUpdateManager(metaclass=SingletonClass):
@property
def _root(self) -> Path:
return Path(get_runtime_setting("TEMP_PATH")) / "moviepilot-update"
return Path(get_runtime_setting('TEMP_PATH')) / "moviepilot-update"
@property
def _state_file(self) -> Path:
@@ -268,8 +268,8 @@ class SystemUpdateManager(metaclass=SingletonClass):
def _request(self) -> RequestUtils:
return RequestUtils(
proxies=get_runtime_setting("PROXY"),
headers=get_runtime_setting("GITHUB_HEADERS"),
proxies=get_runtime_setting('PROXY'),
headers=get_runtime_setting('GITHUB_HEADERS'),
timeout=60,
)
@@ -474,7 +474,7 @@ class SystemUpdateManager(metaclass=SingletonClass):
@staticmethod
def _proxied(url: str) -> str:
proxy = str(get_runtime_setting("GITHUB_PROXY") or "").strip()
proxy = str(get_runtime_setting('GITHUB_PROXY') or "").strip()
return f"{proxy}{url}" if proxy else url
+16 -13
View File
@@ -16,7 +16,7 @@ from fastapi.security import (
from app.runtime.cache import cached
from app.runtime.log import logger
from app.runtime.settings import RuntimeSettingsCompat
from app.runtime.settings import get_runtime_setting
from app.schemas.token import TokenPayload
SuperuserTokenPayloadProvider = Callable[[], TokenPayload]
@@ -28,15 +28,12 @@ _token_decoder: Optional[TokenDecoder] = None
JWT_ALGORITHM = "HS256"
# 兼容旧鉴权插件覆盖模块级设置;令牌实际策略仍由已注入 codec 和 runtime 配置提供。
settings = RuntimeSettingsCompat()
oauth2_scheme_manual_error = OAuth2PasswordBearer(
auto_error=False,
tokenUrl=f"{settings.API_V1_STR}/login/access-token",
tokenUrl=f"{get_runtime_setting('API_V1_STR')}/login/access-token",
)
resource_token_cookie = APIKeyCookie(
name=settings.PROJECT_NAME,
name=get_runtime_setting('PROJECT_NAME'),
auto_error=False,
scheme_name="resource_token_cookie",
)
@@ -133,12 +130,13 @@ def set_or_refresh_resource_token_cookie(
payload: TokenPayload,
) -> None:
"""复用匹配的资源令牌,或为当前身份写入新的安全 Cookie。"""
resource_token = request.cookies.get(settings.PROJECT_NAME)
project_name = get_runtime_setting('PROJECT_NAME')
resource_token = request.cookies.get(project_name)
if resource_token:
try:
decoded = jwt.decode(
resource_token,
settings.RESOURCE_SECRET_KEY,
get_runtime_setting('RESOURCE_SECRET_KEY'),
algorithms=[JWT_ALGORITHM],
)
exp = decoded.get("exp")
@@ -148,7 +146,12 @@ def set_or_refresh_resource_token_cookie(
tz=datetime.UTC,
) - datetime.datetime.now(datetime.UTC)
if remaining_time < timedelta(
seconds=settings.RESOURCE_ACCESS_TOKEN_EXPIRE_SECONDS / 3
seconds=(
get_runtime_setting(
"RESOURCE_ACCESS_TOKEN_EXPIRE_SECONDS"
)
/ 3
)
):
raise jwt.ExpiredSignatureError
expected_claims = {
@@ -177,7 +180,7 @@ def set_or_refresh_resource_token_cookie(
username=payload.username or "",
super_user=payload.super_user,
expires_delta=timedelta(
seconds=settings.RESOURCE_ACCESS_TOKEN_EXPIRE_SECONDS
seconds=get_runtime_setting('RESOURCE_ACCESS_TOKEN_EXPIRE_SECONDS')
),
level=payload.level,
purpose="resource",
@@ -187,7 +190,7 @@ def set_or_refresh_resource_token_cookie(
or request.headers.get("x-forwarded-proto", "").lower() == "https"
)
response.set_cookie(
key=settings.PROJECT_NAME,
key=project_name,
value=resource_token,
httponly=True,
secure=is_https,
@@ -261,11 +264,11 @@ def verify_apitoken(
token: Annotated[str | None, Security(_get_api_token)],
) -> str:
"""校验 URL 查询参数中的兼容 API Token。"""
return _verify_key(token, settings.API_TOKEN, "token")
return _verify_key(token, get_runtime_setting('API_TOKEN'), "token")
def verify_apikey(
apikey: Annotated[str | None, Security(_get_api_key)],
) -> str:
"""校验请求头或查询参数中的兼容 API Key。"""
return _verify_key(apikey, settings.API_TOKEN, "apikey")
return _verify_key(apikey, get_runtime_setting('API_TOKEN'), "apikey")