mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor: unify service configuration boundary
This commit is contained in:
@@ -12,6 +12,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from app.application.notification import get_notification_configs
|
||||||
from app.runtime.settings import RuntimeSettingsCompat
|
from app.runtime.settings import RuntimeSettingsCompat
|
||||||
|
|
||||||
settings = RuntimeSettingsCompat()
|
settings = RuntimeSettingsCompat()
|
||||||
@@ -801,9 +802,7 @@ class AgentCapabilityManager:
|
|||||||
if not source:
|
if not source:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
for config in get_notification_configs(include_disabled=True):
|
||||||
|
|
||||||
for config in ServiceConfigHelper.get_notification_configs():
|
|
||||||
if config.name != source:
|
if config.name != source:
|
||||||
continue
|
continue
|
||||||
return (config.config or {}).get("WECHAT_MODE", "app") != "bot"
|
return (config.config or {}).get("WECHAT_MODE", "app") != "bot"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from app.runtime.settings import RuntimeSettingsCompat
|
|||||||
|
|
||||||
settings = RuntimeSettingsCompat()
|
settings = RuntimeSettingsCompat()
|
||||||
from app.application.messaging.agent import matches_channel_admin
|
from app.application.messaging.agent import matches_channel_admin
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
from app.application.notification import get_notification_configs
|
||||||
from app.runtime.log import logger
|
from app.runtime.log import logger
|
||||||
from app.schemas.message import Message
|
from app.schemas.message import Message
|
||||||
from app.schemas.types import NotificationChannel, MessageType
|
from app.schemas.types import NotificationChannel, MessageType
|
||||||
@@ -719,7 +719,7 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
configs = ServiceConfigHelper.get_notification_configs()
|
configs = get_notification_configs(include_disabled=True)
|
||||||
for config in configs:
|
for config in configs:
|
||||||
if config.name == self._source and config.config:
|
if config.name == self._source and config.config:
|
||||||
return matches_channel_admin(
|
return matches_channel_admin(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from pydantic import BaseModel, Field
|
|||||||
from app.agent.tools.base import MoviePilotTool
|
from app.agent.tools.base import MoviePilotTool
|
||||||
from app.agent.tools.tags import ToolTag
|
from app.agent.tools.tags import ToolTag
|
||||||
from app.chain.mediaserver import MediaServerChain
|
from app.chain.mediaserver import MediaServerChain
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
from app.application.mediaserver import get_mediaserver_configs
|
||||||
from app.runtime.log import logger
|
from app.runtime.log import logger
|
||||||
|
|
||||||
PAGE_SIZE = 20
|
PAGE_SIZE = 20
|
||||||
@@ -61,8 +61,7 @@ class QueryLibraryLatestTool(MoviePilotTool):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_enabled_servers() -> list[str]:
|
def _get_enabled_servers() -> list[str]:
|
||||||
"""同步读取启用的媒体服务器列表。"""
|
"""同步读取启用的媒体服务器列表。"""
|
||||||
mediaservers = ServiceConfigHelper.get_mediaserver_configs()
|
return [config.name for config in get_mediaserver_configs()]
|
||||||
return [ms.name for ms in mediaservers if ms.enabled]
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_latest_items(
|
def _load_latest_items(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from app.application.configuration import (
|
|||||||
from app.api.dependencies.agent import get_message_query_service
|
from app.api.dependencies.agent import get_message_query_service
|
||||||
from app.api.dependencies.auth import get_current_active_superuser
|
from app.api.dependencies.auth import get_current_active_superuser
|
||||||
from app.application.messaging.message import MessageQueryService
|
from app.application.messaging.message import MessageQueryService
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
from app.application.notification import get_notification_configs
|
||||||
from app.runtime.log import logger
|
from app.runtime.log import logger
|
||||||
from app.adapters.external.wechat_crypt import WXBizMsgCrypt
|
from app.adapters.external.wechat_crypt import WXBizMsgCrypt
|
||||||
from app.schemas.types import NotificationChannel, SystemConfigKey
|
from app.schemas.types import NotificationChannel, SystemConfigKey
|
||||||
@@ -261,7 +261,7 @@ def wechat_verify(
|
|||||||
微信验证响应
|
微信验证响应
|
||||||
"""
|
"""
|
||||||
# 获取服务配置
|
# 获取服务配置
|
||||||
client_configs = ServiceConfigHelper.get_notification_configs()
|
client_configs = get_notification_configs(include_disabled=True)
|
||||||
if not client_configs:
|
if not client_configs:
|
||||||
return "未找到对应的消息配置"
|
return "未找到对应的消息配置"
|
||||||
client_config = next(
|
client_config = next(
|
||||||
|
|||||||
@@ -304,3 +304,12 @@ class MediaServerHelper(ServiceBaseHelper[MediaServerConf]):
|
|||||||
"""判断给定服务或服务名称是否属于指定媒体服务器类型。"""
|
"""判断给定服务或服务名称是否属于指定媒体服务器类型。"""
|
||||||
service = service or self.get_service(name=name)
|
service = service or self.get_service(name=name)
|
||||||
return bool(service and service.type == service_type)
|
return bool(service and service.type == service_type)
|
||||||
|
|
||||||
|
|
||||||
|
def get_mediaserver_configs(
|
||||||
|
include_disabled: bool = False,
|
||||||
|
) -> list[MediaServerConf]:
|
||||||
|
"""返回媒体服务器配置列表,并按调用方需要决定是否包含禁用项。"""
|
||||||
|
return list(
|
||||||
|
MediaServerHelper().get_configs(include_disabled=include_disabled).values()
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from app.application.service import ServiceBaseHelper
|
from app.application.service import ServiceBaseHelper, get_service_configs
|
||||||
from app.schemas.system import NotificationConf
|
from app.schemas.system import NotificationConf, NotificationSwitchConf
|
||||||
from app.schemas.system import ServiceInfo
|
from app.schemas.system import ServiceInfo
|
||||||
from app.schemas.types import ModuleType, SystemConfigKey
|
from app.schemas.types import MessageType, ModuleType, SystemConfigKey
|
||||||
|
|
||||||
|
|
||||||
class NotificationHelper(ServiceBaseHelper[NotificationConf]):
|
class NotificationHelper(ServiceBaseHelper[NotificationConf]):
|
||||||
@@ -33,3 +33,23 @@ class NotificationHelper(ServiceBaseHelper[NotificationConf]):
|
|||||||
"""
|
"""
|
||||||
service = service or self.get_service(name=name)
|
service = service or self.get_service(name=name)
|
||||||
return bool(service and service.type == service_type)
|
return bool(service and service.type == service_type)
|
||||||
|
|
||||||
|
|
||||||
|
def get_notification_configs(
|
||||||
|
include_disabled: bool = False,
|
||||||
|
) -> list[NotificationConf]:
|
||||||
|
"""返回通知配置列表,并按调用方需要决定是否包含禁用项。"""
|
||||||
|
return list(
|
||||||
|
NotificationHelper().get_configs(include_disabled=include_disabled).values()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_notification_switch(mtype: MessageType) -> Optional[str]:
|
||||||
|
"""返回指定通知场景的目标范围。"""
|
||||||
|
for switch in get_service_configs(
|
||||||
|
SystemConfigKey.NotificationSwitchs,
|
||||||
|
NotificationSwitchConf,
|
||||||
|
):
|
||||||
|
if switch.type == mtype.value:
|
||||||
|
return switch.action
|
||||||
|
return None
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ def configure_service_directory(
|
|||||||
_module_loader = modules
|
_module_loader = modules
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_configs(
|
||||||
|
config_key: SystemConfigKey,
|
||||||
|
conf_type: Type[TConf],
|
||||||
|
) -> list[TConf]:
|
||||||
|
"""通过组合根登记的读取器返回已校验服务配置。"""
|
||||||
|
return _config_loader(config_key, conf_type)
|
||||||
|
|
||||||
|
|
||||||
class ServiceBaseHelper(Generic[TConf]):
|
class ServiceBaseHelper(Generic[TConf]):
|
||||||
"""通过应用端口查询服务配置和对应运行实例。"""
|
"""通过应用端口查询服务配置和对应运行实例。"""
|
||||||
|
|
||||||
@@ -57,7 +65,7 @@ class ServiceBaseHelper(Generic[TConf]):
|
|||||||
|
|
||||||
def get_configs(self, include_disabled: bool = False) -> Dict[str, TConf]:
|
def get_configs(self, include_disabled: bool = False) -> Dict[str, TConf]:
|
||||||
"""返回按名称索引的有效服务配置。"""
|
"""返回按名称索引的有效服务配置。"""
|
||||||
configs = _config_loader(self.config_key, self.conf_type)
|
configs = get_service_configs(self.config_key, self.conf_type)
|
||||||
return {
|
return {
|
||||||
config.name: config
|
config.name: config
|
||||||
for config in configs
|
for config in configs
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo
|
|||||||
from app.domain.meta.metabase import MetaBase
|
from app.domain.meta.metabase import MetaBase
|
||||||
from app.foundation.identity import normalize_internal_user_id
|
from app.foundation.identity import normalize_internal_user_id
|
||||||
from app.application.messaging.message import MessageTemplateHelper
|
from app.application.messaging.message import MessageTemplateHelper
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
from app.application.notification import get_notification_switch
|
||||||
from app.runtime.log import logger
|
from app.runtime.log import logger
|
||||||
from app.schemas.message import MessageResponse
|
from app.schemas.message import MessageResponse
|
||||||
from app.schemas.message import Message
|
from app.schemas.message import Message
|
||||||
@@ -159,7 +159,7 @@ class NotificationMixin:
|
|||||||
# 发送消息按设置隔离
|
# 发送消息按设置隔离
|
||||||
if not dispatch_message.userid and dispatch_message.mtype:
|
if not dispatch_message.userid and dispatch_message.mtype:
|
||||||
# 消息隔离设置
|
# 消息隔离设置
|
||||||
notify_action = ServiceConfigHelper.get_notification_switch(
|
notify_action = get_notification_switch(
|
||||||
dispatch_message.mtype
|
dispatch_message.mtype
|
||||||
)
|
)
|
||||||
if notify_action:
|
if notify_action:
|
||||||
@@ -277,7 +277,7 @@ class NotificationMixin:
|
|||||||
# 发送消息按设置隔离
|
# 发送消息按设置隔离
|
||||||
if not dispatch_message.userid and dispatch_message.mtype:
|
if not dispatch_message.userid and dispatch_message.mtype:
|
||||||
# 消息隔离设置
|
# 消息隔离设置
|
||||||
notify_action = ServiceConfigHelper.get_notification_switch(
|
notify_action = get_notification_switch(
|
||||||
dispatch_message.mtype
|
dispatch_message.mtype
|
||||||
)
|
)
|
||||||
if notify_action:
|
if notify_action:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Callable, Dict, List, Union, Optional, Generator, Any, Tuple
|
|||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.runtime.config import global_vars
|
from app.runtime.config import global_vars
|
||||||
from app.application.chain.data import get_chain_media_server_port
|
from app.application.chain.data import get_chain_media_server_port
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
from app.application.mediaserver import get_mediaserver_configs
|
||||||
from app.runtime.log import logger
|
from app.runtime.log import logger
|
||||||
from app.schemas.mediaserver import MediaServerLibrary
|
from app.schemas.mediaserver import MediaServerLibrary
|
||||||
from app.schemas.mediaserver import MediaServerItem
|
from app.schemas.mediaserver import MediaServerItem
|
||||||
@@ -458,7 +458,7 @@ class MediaServerChain(ChainBase):
|
|||||||
:param server: 指定媒体服务器名称,为空时同步全部已启用服务器
|
:param server: 指定媒体服务器名称,为空时同步全部已启用服务器
|
||||||
"""
|
"""
|
||||||
# 设置的媒体服务器
|
# 设置的媒体服务器
|
||||||
mediaservers = ServiceConfigHelper.get_mediaserver_configs()
|
mediaservers = get_mediaserver_configs(include_disabled=True)
|
||||||
if not mediaservers:
|
if not mediaservers:
|
||||||
if progress_callback:
|
if progress_callback:
|
||||||
progress_callback(value=100, text="未配置媒体服务器,跳过同步")
|
progress_callback(value=100, text="未配置媒体服务器,跳过同步")
|
||||||
|
|||||||
+2
-2
@@ -38,10 +38,10 @@ from app.application.configuration import (
|
|||||||
get_scheduler_runtime_config,
|
get_scheduler_runtime_config,
|
||||||
)
|
)
|
||||||
from app.application.image import WallpaperHelper
|
from app.application.image import WallpaperHelper
|
||||||
|
from app.application.mediaserver import get_mediaserver_configs
|
||||||
from app.application.messaging.message import MessageHelper
|
from app.application.messaging.message import MessageHelper
|
||||||
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
|
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
|
||||||
from app.adapters.external.server import MoviePilotServerHelper
|
from app.adapters.external.server import MoviePilotServerHelper
|
||||||
from app.runtime.extensions.service_config import ServiceConfigHelper
|
|
||||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||||
from app.runtime.log import logger
|
from app.runtime.log import logger
|
||||||
from app.schemas.message import Message
|
from app.schemas.message import Message
|
||||||
@@ -603,7 +603,7 @@ class Scheduler(ConfigReloadMixin, metaclass=SingletonClass):
|
|||||||
|
|
||||||
# 按媒体服务器分别注册自动同步任务
|
# 按媒体服务器分别注册自动同步任务
|
||||||
mediaserver_schedules = self._build_mediaserver_sync_schedules(
|
mediaserver_schedules = self._build_mediaserver_sync_schedules(
|
||||||
mediaservers=ServiceConfigHelper.get_mediaserver_configs(),
|
mediaservers=get_mediaserver_configs(include_disabled=True),
|
||||||
default_interval=config.mediaserver_sync_interval,
|
default_interval=config.mediaserver_sync_interval,
|
||||||
)
|
)
|
||||||
for mediaserver_schedule in mediaserver_schedules:
|
for mediaserver_schedule in mediaserver_schedules:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
> 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本
|
> 审计范围:宿主后端;排除 `app/plugins/**` 运行时插件副本
|
||||||
> 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文
|
> 规范优先级:`AGENTS.md` 与 `docs/rules/` 高于本文
|
||||||
> 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md`
|
> 相关文档:`docs/architecture-overview.md`、`docs/refactor/backend-architecture-governance.md`、`docs/refactor/backend-module-refactor-compatibility.md`
|
||||||
> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名;阶段 12 已完成工作流域的显式 Chain 数据端口迁移;阶段 13 已收口用户、交互与消息链的数据端口;阶段 14 已收口音乐订阅数据端口;阶段 15 已收口站点数据端口;阶段 16 已收口媒体服务器数据端口;阶段 17 已收口下载数据端口;阶段 18 已收口主订阅数据端口;阶段 19 已收口整理数据端口;阶段 20 已收口 Agent 数据端口;阶段 21 已收口监控历史端口。
|
> 实施进度:阶段 0~6 的宿主架构能力已完成收口;API/Application 公共复杂度基线已清零,启动组合根的 SystemConfigOper 构造点已由 14 降至 1;API 进程内后台任务已完成首批统一登记,插件仓适配和 Outbox 外围扩展仍按风险切片推进。Model/Base 查询与写装饰器、legacy 隐式会话外壳均已清零,插件 SDK 也不再导出宿主 Model。2026-08-23 的长期整改阶段 0 已恢复宿主、启动性能、官方插件和 SDK 契约门禁的可信基线;阶段 1a 已补齐 TaskRegistry owner 零债务门禁和诚实的关停超时语义;阶段 1b1 已收口整理 worker、pending 回放、失败通知、进程内 AI 重试、插件监控与事件投递的生命周期所有权;2026-08-24 的阶段 2 已将 212 个已观察宿主模块方法的 legacy aggregation 清零,并补齐可执行 fanout 与下载器文件 DTO 边界;阶段 3 已将消息交互和远程命令的订阅删除统一到 Application/UoW/outbox,宿主不再调用裸线程统计入口;阶段 4 已统一七种消息渠道的宿主回环与后台执行边界;阶段 5 已补齐事件窗口聚合任务的生命周期所有权;阶段 6 已统一插件文件操作的取消完成语义;阶段 7 已统一插件协程补偿的终态等待;阶段 8 已统一宿主同步函数的异步线程池入口;阶段 9 已统一工作流运行时的宿主获取路径;阶段 10 已统一模块、插件与调度运行时的显式 getter 调用;阶段 11 已清除系统配置 getter 的 Oper 形别名;阶段 12 已完成工作流域的显式 Chain 数据端口迁移;阶段 13 已收口用户、交互与消息链的数据端口;阶段 14 已收口音乐订阅数据端口;阶段 15 已收口站点数据端口;阶段 16 已收口媒体服务器数据端口;阶段 17 已收口下载数据端口;阶段 18 已收口主订阅数据端口;阶段 19 已收口整理数据端口;阶段 20 已收口 Agent 数据端口;阶段 21 已收口监控历史端口;阶段 22 已统一服务配置应用边界。
|
||||||
|
|
||||||
## 当前复核结论(2026-08-24)
|
## 当前复核结论(2026-08-24)
|
||||||
|
|
||||||
@@ -229,13 +229,24 @@
|
|||||||
- 兼容边界不变:`TransferHistoryPort`、DB Oper、监控候选判定、历史查重、失败重试和整理触发语义均
|
- 兼容边界不变:`TransferHistoryPort`、DB Oper、监控候选判定、历史查重、失败重试和整理触发语义均
|
||||||
未修改。
|
未修改。
|
||||||
|
|
||||||
|
### 长期整改阶段 22:服务配置应用边界统一(2026-08-24)
|
||||||
|
|
||||||
|
- 启动组合根已注入应用层服务目录,但 Chain、消息 API、Scheduler 和 Agent 仍直接读取 runtime
|
||||||
|
`ServiceConfigHelper`,形成两个应用入口;现在统一通过通知与媒体服务器应用模块的命名函数读取。
|
||||||
|
- 命名函数显式区分仅启用配置和包含禁用配置,保持定向同步、定时注册、企业微信模式和渠道管理员判断
|
||||||
|
的原语义;架构门禁覆盖七个生产消费者。
|
||||||
|
- 依赖基线经语义诊断后从 `6544` 条边降为 `6539`:七个消费者移除 runtime service-config 边并改为
|
||||||
|
Application 边,12 组禁止边和唯一隔离 TMDB SCC 均未变化。
|
||||||
|
- `ServiceConfigHelper` 继续保留在 startup、runtime module adapter、模块实例初始化和 `app.sdk.services`
|
||||||
|
插件兼容出口;V2/V3 插件导入、配置 Schema 和热更新读取器均未修改。
|
||||||
|
|
||||||
### 总体判断
|
### 总体判断
|
||||||
|
|
||||||
当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**:
|
当前架构总体合理,已经从跨层混合的遗留单体收敛为**边界清晰的模块化单体**:
|
||||||
|
|
||||||
- 继续采用单进程控制面是正确选择,不建议现在拆成微服务;插件、调度器、工作流、事件和数据库共享进程内状态,拆分会放大部署、事务和兼容成本。
|
- 继续采用单进程控制面是正确选择,不建议现在拆成微服务;插件、调度器、工作流、事件和数据库共享进程内状态,拆分会放大部署、事务和兼容成本。
|
||||||
- `foundation/domain/runtime/adapters/application/chain/api/startup` 的职责方向基本成立;宿主架构基线、复杂度 ratchet、异步阻塞 ratchet 当前均通过。
|
- `foundation/domain/runtime/adapters/application/chain/api/startup` 的职责方向基本成立;宿主架构基线、复杂度 ratchet、异步阻塞 ratchet 当前均通过。
|
||||||
- 依赖图当前为 `806` 个 Python 模块、`6544` 条内部导入边;唯一非平凡 SCC 位于隔离的 TMDB 第三方移植包内部,不应为了指标归零重写。
|
- 依赖图当前为 `806` 个 Python 模块、`6539` 条内部导入边;唯一非平凡 SCC 位于隔离的 TMDB 第三方移植包内部,不应为了指标归零重写。
|
||||||
- 当前主要风险已经从“目录和依赖失控”转移到运行时协议、后台副作用的可靠性和遗留兼容面。换言之,下一阶段重点应是**语义收口和可验证性**,而不是继续搬文件或机械拆大文件。
|
- 当前主要风险已经从“目录和依赖失控”转移到运行时协议、后台副作用的可靠性和遗留兼容面。换言之,下一阶段重点应是**语义收口和可验证性**,而不是继续搬文件或机械拆大文件。
|
||||||
|
|
||||||
综合评价:架构方向可持续,生产可用性较高;可演进性仍处于中等水平。现阶段没有静态审计发现必须立即推倒重来的 P0 架构问题,但存在需要按 P1/P2 计划治理的真实债务。
|
综合评价:架构方向可持续,生产可用性较高;可演进性仍处于中等水平。现阶段没有静态审计发现必须立即推倒重来的 P0 架构问题,但存在需要按 P1/P2 计划治理的真实债务。
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ must not be reintroduced as Oper aliases in canonical Agent modules.
|
|||||||
Monitor history checks use `get_transfer_history_port()` from
|
Monitor history checks use `get_transfer_history_port()` from
|
||||||
`app/application/history.py`; the constructible `TransferHistoryPort` facade is
|
`app/application/history.py`; the constructible `TransferHistoryPort` facade is
|
||||||
retained only for compatibility and is not a canonical Oper substitute.
|
retained only for compatibility and is not a canonical Oper substitute.
|
||||||
|
Canonical Chain, API, Scheduler and Agent consumers read notification and media
|
||||||
|
server configuration through the named helpers in `app/application/notification.py`
|
||||||
|
and `app/application/mediaserver.py`. `ServiceConfigHelper` remains the parser at
|
||||||
|
the startup/runtime module boundary and a plugin SDK compatibility export; it is
|
||||||
|
not a second application-facing service directory.
|
||||||
|
|
||||||
### Adapter boundaries
|
### Adapter boundaries
|
||||||
|
|
||||||
|
|||||||
+11
-16
@@ -13,8 +13,8 @@
|
|||||||
"runtime_to_db": [],
|
"runtime_to_db": [],
|
||||||
"workflow_to_db": []
|
"workflow_to_db": []
|
||||||
},
|
},
|
||||||
"edge_count": 6544,
|
"edge_count": 6539,
|
||||||
"edge_sha256": "562471ed16680ca403b6cdb7661b20b1200c53b75da5e162efe4e7400cde174b",
|
"edge_sha256": "28c7ab8544ede290e6dacf5f126ca1aad95e09627d874a0d23c6476108a88e1b",
|
||||||
"edges": [
|
"edges": [
|
||||||
"app -> app.runtime",
|
"app -> app.runtime",
|
||||||
"app -> app.runtime.compat",
|
"app -> app.runtime.compat",
|
||||||
@@ -202,9 +202,9 @@
|
|||||||
"app.agent.llm.capability -> app.agent",
|
"app.agent.llm.capability -> app.agent",
|
||||||
"app.agent.llm.capability -> app.agent.llm",
|
"app.agent.llm.capability -> app.agent.llm",
|
||||||
"app.agent.llm.capability -> app.agent.llm.helper",
|
"app.agent.llm.capability -> app.agent.llm.helper",
|
||||||
|
"app.agent.llm.capability -> app.application",
|
||||||
|
"app.agent.llm.capability -> app.application.notification",
|
||||||
"app.agent.llm.capability -> app.runtime",
|
"app.agent.llm.capability -> app.runtime",
|
||||||
"app.agent.llm.capability -> app.runtime.extensions",
|
|
||||||
"app.agent.llm.capability -> app.runtime.extensions.service_config",
|
|
||||||
"app.agent.llm.capability -> app.runtime.log",
|
"app.agent.llm.capability -> app.runtime.log",
|
||||||
"app.agent.llm.capability -> app.runtime.settings",
|
"app.agent.llm.capability -> app.runtime.settings",
|
||||||
"app.agent.llm.capability -> app.schemas",
|
"app.agent.llm.capability -> app.schemas",
|
||||||
@@ -432,10 +432,9 @@
|
|||||||
"app.agent.tools.base -> app.application",
|
"app.agent.tools.base -> app.application",
|
||||||
"app.agent.tools.base -> app.application.messaging",
|
"app.agent.tools.base -> app.application.messaging",
|
||||||
"app.agent.tools.base -> app.application.messaging.agent",
|
"app.agent.tools.base -> app.application.messaging.agent",
|
||||||
|
"app.agent.tools.base -> app.application.notification",
|
||||||
"app.agent.tools.base -> app.chain",
|
"app.agent.tools.base -> app.chain",
|
||||||
"app.agent.tools.base -> app.runtime",
|
"app.agent.tools.base -> app.runtime",
|
||||||
"app.agent.tools.base -> app.runtime.extensions",
|
|
||||||
"app.agent.tools.base -> app.runtime.extensions.service_config",
|
|
||||||
"app.agent.tools.base -> app.runtime.log",
|
"app.agent.tools.base -> app.runtime.log",
|
||||||
"app.agent.tools.base -> app.runtime.settings",
|
"app.agent.tools.base -> app.runtime.settings",
|
||||||
"app.agent.tools.base -> app.schemas",
|
"app.agent.tools.base -> app.schemas",
|
||||||
@@ -952,11 +951,11 @@
|
|||||||
"app.agent.tools.impl.query_library_latest -> app.agent.tools",
|
"app.agent.tools.impl.query_library_latest -> app.agent.tools",
|
||||||
"app.agent.tools.impl.query_library_latest -> app.agent.tools.base",
|
"app.agent.tools.impl.query_library_latest -> app.agent.tools.base",
|
||||||
"app.agent.tools.impl.query_library_latest -> app.agent.tools.tags",
|
"app.agent.tools.impl.query_library_latest -> app.agent.tools.tags",
|
||||||
|
"app.agent.tools.impl.query_library_latest -> app.application",
|
||||||
|
"app.agent.tools.impl.query_library_latest -> app.application.mediaserver",
|
||||||
"app.agent.tools.impl.query_library_latest -> app.chain",
|
"app.agent.tools.impl.query_library_latest -> app.chain",
|
||||||
"app.agent.tools.impl.query_library_latest -> app.chain.mediaserver",
|
"app.agent.tools.impl.query_library_latest -> app.chain.mediaserver",
|
||||||
"app.agent.tools.impl.query_library_latest -> app.runtime",
|
"app.agent.tools.impl.query_library_latest -> app.runtime",
|
||||||
"app.agent.tools.impl.query_library_latest -> app.runtime.extensions",
|
|
||||||
"app.agent.tools.impl.query_library_latest -> app.runtime.extensions.service_config",
|
|
||||||
"app.agent.tools.impl.query_library_latest -> app.runtime.log",
|
"app.agent.tools.impl.query_library_latest -> app.runtime.log",
|
||||||
"app.agent.tools.impl.query_market_plugins -> app.agent",
|
"app.agent.tools.impl.query_market_plugins -> app.agent",
|
||||||
"app.agent.tools.impl.query_market_plugins -> app.agent.tools",
|
"app.agent.tools.impl.query_market_plugins -> app.agent.tools",
|
||||||
@@ -1983,12 +1982,11 @@
|
|||||||
"app.api.endpoints.message -> app.application.configuration",
|
"app.api.endpoints.message -> app.application.configuration",
|
||||||
"app.api.endpoints.message -> app.application.messaging",
|
"app.api.endpoints.message -> app.application.messaging",
|
||||||
"app.api.endpoints.message -> app.application.messaging.message",
|
"app.api.endpoints.message -> app.application.messaging.message",
|
||||||
|
"app.api.endpoints.message -> app.application.notification",
|
||||||
"app.api.endpoints.message -> app.chain",
|
"app.api.endpoints.message -> app.chain",
|
||||||
"app.api.endpoints.message -> app.chain.message",
|
"app.api.endpoints.message -> app.chain.message",
|
||||||
"app.api.endpoints.message -> app.runtime",
|
"app.api.endpoints.message -> app.runtime",
|
||||||
"app.api.endpoints.message -> app.runtime.config",
|
"app.api.endpoints.message -> app.runtime.config",
|
||||||
"app.api.endpoints.message -> app.runtime.extensions",
|
|
||||||
"app.api.endpoints.message -> app.runtime.extensions.service_config",
|
|
||||||
"app.api.endpoints.message -> app.runtime.log",
|
"app.api.endpoints.message -> app.runtime.log",
|
||||||
"app.api.endpoints.message -> app.runtime.tasks",
|
"app.api.endpoints.message -> app.runtime.tasks",
|
||||||
"app.api.endpoints.message -> app.schemas",
|
"app.api.endpoints.message -> app.schemas",
|
||||||
@@ -2941,6 +2939,7 @@
|
|||||||
"app.chain._messaging -> app.application.messaging",
|
"app.chain._messaging -> app.application.messaging",
|
||||||
"app.chain._messaging -> app.application.messaging.agent",
|
"app.chain._messaging -> app.application.messaging.agent",
|
||||||
"app.chain._messaging -> app.application.messaging.message",
|
"app.chain._messaging -> app.application.messaging.message",
|
||||||
|
"app.chain._messaging -> app.application.notification",
|
||||||
"app.chain._messaging -> app.domain",
|
"app.chain._messaging -> app.domain",
|
||||||
"app.chain._messaging -> app.domain.context",
|
"app.chain._messaging -> app.domain.context",
|
||||||
"app.chain._messaging -> app.domain.meta",
|
"app.chain._messaging -> app.domain.meta",
|
||||||
@@ -2948,8 +2947,6 @@
|
|||||||
"app.chain._messaging -> app.foundation",
|
"app.chain._messaging -> app.foundation",
|
||||||
"app.chain._messaging -> app.foundation.identity",
|
"app.chain._messaging -> app.foundation.identity",
|
||||||
"app.chain._messaging -> app.runtime",
|
"app.chain._messaging -> app.runtime",
|
||||||
"app.chain._messaging -> app.runtime.extensions",
|
|
||||||
"app.chain._messaging -> app.runtime.extensions.service_config",
|
|
||||||
"app.chain._messaging -> app.runtime.log",
|
"app.chain._messaging -> app.runtime.log",
|
||||||
"app.chain._messaging -> app.schemas",
|
"app.chain._messaging -> app.schemas",
|
||||||
"app.chain._messaging -> app.schemas.message",
|
"app.chain._messaging -> app.schemas.message",
|
||||||
@@ -3166,13 +3163,12 @@
|
|||||||
"app.chain.mediaserver -> app.application",
|
"app.chain.mediaserver -> app.application",
|
||||||
"app.chain.mediaserver -> app.application.chain",
|
"app.chain.mediaserver -> app.application.chain",
|
||||||
"app.chain.mediaserver -> app.application.chain.data",
|
"app.chain.mediaserver -> app.application.chain.data",
|
||||||
|
"app.chain.mediaserver -> app.application.mediaserver",
|
||||||
"app.chain.mediaserver -> app.application.security",
|
"app.chain.mediaserver -> app.application.security",
|
||||||
"app.chain.mediaserver -> app.application.security.url",
|
"app.chain.mediaserver -> app.application.security.url",
|
||||||
"app.chain.mediaserver -> app.chain",
|
"app.chain.mediaserver -> app.chain",
|
||||||
"app.chain.mediaserver -> app.runtime",
|
"app.chain.mediaserver -> app.runtime",
|
||||||
"app.chain.mediaserver -> app.runtime.config",
|
"app.chain.mediaserver -> app.runtime.config",
|
||||||
"app.chain.mediaserver -> app.runtime.extensions",
|
|
||||||
"app.chain.mediaserver -> app.runtime.extensions.service_config",
|
|
||||||
"app.chain.mediaserver -> app.runtime.log",
|
"app.chain.mediaserver -> app.runtime.log",
|
||||||
"app.chain.mediaserver -> app.schemas",
|
"app.chain.mediaserver -> app.schemas",
|
||||||
"app.chain.mediaserver -> app.schemas.mediaserver",
|
"app.chain.mediaserver -> app.schemas.mediaserver",
|
||||||
@@ -5801,6 +5797,7 @@
|
|||||||
"app.scheduler -> app.application.configuration",
|
"app.scheduler -> app.application.configuration",
|
||||||
"app.scheduler -> app.application.database",
|
"app.scheduler -> app.application.database",
|
||||||
"app.scheduler -> app.application.image",
|
"app.scheduler -> app.application.image",
|
||||||
|
"app.scheduler -> app.application.mediaserver",
|
||||||
"app.scheduler -> app.application.messaging",
|
"app.scheduler -> app.application.messaging",
|
||||||
"app.scheduler -> app.application.messaging.message",
|
"app.scheduler -> app.application.messaging.message",
|
||||||
"app.scheduler -> app.application.outbox",
|
"app.scheduler -> app.application.outbox",
|
||||||
@@ -5824,8 +5821,6 @@
|
|||||||
"app.scheduler -> app.runtime.config",
|
"app.scheduler -> app.runtime.config",
|
||||||
"app.scheduler -> app.runtime.correlation",
|
"app.scheduler -> app.runtime.correlation",
|
||||||
"app.scheduler -> app.runtime.events",
|
"app.scheduler -> app.runtime.events",
|
||||||
"app.scheduler -> app.runtime.extensions",
|
|
||||||
"app.scheduler -> app.runtime.extensions.service_config",
|
|
||||||
"app.scheduler -> app.runtime.gc",
|
"app.scheduler -> app.runtime.gc",
|
||||||
"app.scheduler -> app.runtime.log",
|
"app.scheduler -> app.runtime.log",
|
||||||
"app.scheduler -> app.runtime.observability",
|
"app.scheduler -> app.runtime.observability",
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ class AgentCapabilityManagerTest(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"app.runtime.extensions.service_config.ServiceConfigHelper.get_notification_configs",
|
"app.agent.llm.capability.get_notification_configs",
|
||||||
return_value=configs,
|
return_value=configs,
|
||||||
):
|
):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ def test_channel_primary_id_defaults_to_admin_without_admin_list():
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
"app.agent.tools.base.get_notification_configs",
|
||||||
return_value=[
|
return_value=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name="telegram-main",
|
name="telegram-main",
|
||||||
@@ -477,7 +477,7 @@ def test_channel_primary_id_mismatch_remains_non_admin():
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
"app.agent.tools.base.get_notification_configs",
|
||||||
return_value=[
|
return_value=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name="telegram-main",
|
name="telegram-main",
|
||||||
@@ -500,7 +500,7 @@ def test_feishu_primary_open_id_defaults_to_admin():
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
"app.agent.tools.base.get_notification_configs",
|
||||||
return_value=[
|
return_value=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name="feishu-main",
|
name="feishu-main",
|
||||||
@@ -523,7 +523,7 @@ def test_channel_primary_id_still_prefers_admin_list():
|
|||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"app.agent.tools.base.ServiceConfigHelper.get_notification_configs",
|
"app.agent.tools.base.get_notification_configs",
|
||||||
return_value=[
|
return_value=[
|
||||||
SimpleNamespace(
|
SimpleNamespace(
|
||||||
name="telegram-main",
|
name="telegram-main",
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""应用层服务配置目录测试。"""
|
||||||
|
|
||||||
|
from app.application import service
|
||||||
|
from app.application.mediaserver import get_mediaserver_configs
|
||||||
|
from app.application.notification import (
|
||||||
|
get_notification_configs,
|
||||||
|
get_notification_switch,
|
||||||
|
)
|
||||||
|
from app.schemas.system import (
|
||||||
|
MediaServerConf,
|
||||||
|
NotificationConf,
|
||||||
|
NotificationSwitchConf,
|
||||||
|
)
|
||||||
|
from app.schemas.types import MessageType, SystemConfigKey
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_service_config_helpers_preserve_enabled_policy(monkeypatch) -> None:
|
||||||
|
"""命名应用函数应复用同一配置目录,并显式控制禁用项可见性。"""
|
||||||
|
configs = {
|
||||||
|
SystemConfigKey.MediaServers: [
|
||||||
|
MediaServerConf(name="enabled-media", type="plex", enabled=True),
|
||||||
|
MediaServerConf(name="disabled-media", type="emby", enabled=False),
|
||||||
|
],
|
||||||
|
SystemConfigKey.Notifications: [
|
||||||
|
NotificationConf(name="enabled-channel", type="telegram", enabled=True),
|
||||||
|
NotificationConf(name="disabled-channel", type="wechat", enabled=False),
|
||||||
|
],
|
||||||
|
SystemConfigKey.NotificationSwitchs: [
|
||||||
|
NotificationSwitchConf(
|
||||||
|
type=MessageType.Download.value,
|
||||||
|
action="admin",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
monkeypatch.setattr(
|
||||||
|
service,
|
||||||
|
"_config_loader",
|
||||||
|
lambda config_key, _conf_type: configs.get(config_key, []),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert [item.name for item in get_mediaserver_configs()] == ["enabled-media"]
|
||||||
|
assert [
|
||||||
|
item.name for item in get_mediaserver_configs(include_disabled=True)
|
||||||
|
] == ["enabled-media", "disabled-media"]
|
||||||
|
assert [item.name for item in get_notification_configs()] == ["enabled-channel"]
|
||||||
|
assert [
|
||||||
|
item.name for item in get_notification_configs(include_disabled=True)
|
||||||
|
] == ["enabled-channel", "disabled-channel"]
|
||||||
|
assert get_notification_switch(MessageType.Download) == "admin"
|
||||||
@@ -614,6 +614,32 @@ def test_monitor_dispatcher_uses_explicit_history_port_getter():
|
|||||||
assert violations == []
|
assert violations == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_service_config_consumers_use_application_directory():
|
||||||
|
"""Chain、API、Scheduler 与 Agent 不得绕过应用目录读取运行时配置 Helper。"""
|
||||||
|
paths = [
|
||||||
|
APP_ROOT / "chain" / "_messaging.py",
|
||||||
|
APP_ROOT / "chain" / "mediaserver.py",
|
||||||
|
APP_ROOT / "api" / "endpoints" / "message.py",
|
||||||
|
APP_ROOT / "scheduler.py",
|
||||||
|
APP_ROOT / "agent" / "llm" / "capability.py",
|
||||||
|
APP_ROOT / "agent" / "tools" / "base.py",
|
||||||
|
APP_ROOT / "agent" / "tools" / "impl" / "query_library_latest.py",
|
||||||
|
]
|
||||||
|
violations: list[str] = []
|
||||||
|
for path in paths:
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if (
|
||||||
|
isinstance(node, ast.ImportFrom)
|
||||||
|
and node.module == "app.runtime.extensions.service_config"
|
||||||
|
):
|
||||||
|
violations.append(
|
||||||
|
f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert violations == []
|
||||||
|
|
||||||
|
|
||||||
def test_plugin_components_do_not_reexport_legacy_abi_names():
|
def test_plugin_components_do_not_reexport_legacy_abi_names():
|
||||||
"""新插件组件只提供 canonical 能力,不得复制旧 Helper、Manager 或 Oper 导出。"""
|
"""新插件组件只提供 canonical 能力,不得复制旧 Helper、Manager 或 Oper 导出。"""
|
||||||
violations: list[str] = []
|
violations: list[str] = []
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ def test_sync_persists_music_without_querying_tv_episodes(database):
|
|||||||
"get_chain_media_server_port",
|
"get_chain_media_server_port",
|
||||||
lambda: MediaServerOper(session),
|
lambda: MediaServerOper(session),
|
||||||
), patch.object(
|
), patch.object(
|
||||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
MEDIA_SERVER_CHAIN_MODULE,
|
||||||
"get_mediaserver_configs",
|
"get_mediaserver_configs",
|
||||||
return_value=[SimpleNamespace(name="navidrome", enabled=True, sync_libraries=["all"])],
|
return_value=[SimpleNamespace(name="navidrome", enabled=True, sync_libraries=["all"])],
|
||||||
):
|
):
|
||||||
@@ -199,7 +199,7 @@ def test_sync_updates_rows_and_removes_stale_entries(database):
|
|||||||
"get_chain_media_server_port",
|
"get_chain_media_server_port",
|
||||||
lambda: MediaServerOper(session),
|
lambda: MediaServerOper(session),
|
||||||
), patch.object(
|
), patch.object(
|
||||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
MEDIA_SERVER_CHAIN_MODULE,
|
||||||
"get_mediaserver_configs",
|
"get_mediaserver_configs",
|
||||||
return_value=[SimpleNamespace(name="plex", enabled=True, sync_libraries=["movies"])],
|
return_value=[SimpleNamespace(name="plex", enabled=True, sync_libraries=["movies"])],
|
||||||
):
|
):
|
||||||
@@ -283,7 +283,7 @@ def test_sync_queries_counts_before_items_and_reports_media_progress(database):
|
|||||||
"get_chain_media_server_port",
|
"get_chain_media_server_port",
|
||||||
lambda: MediaServerOper(session),
|
lambda: MediaServerOper(session),
|
||||||
), patch.object(
|
), patch.object(
|
||||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
MEDIA_SERVER_CHAIN_MODULE,
|
||||||
"get_mediaserver_configs",
|
"get_mediaserver_configs",
|
||||||
return_value=[
|
return_value=[
|
||||||
SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
|
SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
|
||||||
@@ -340,9 +340,9 @@ def test_sync_targets_one_server_without_excluding_other_enabled_servers(monkeyp
|
|||||||
FakeMediaServerOper,
|
FakeMediaServerOper,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
MEDIA_SERVER_CHAIN_MODULE,
|
||||||
"get_mediaserver_configs",
|
"get_mediaserver_configs",
|
||||||
lambda: [
|
lambda **_kwargs: [
|
||||||
SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
|
SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
|
||||||
SimpleNamespace(name="plex-b", enabled=True, sync_libraries=["all"]),
|
SimpleNamespace(name="plex-b", enabled=True, sync_libraries=["all"]),
|
||||||
],
|
],
|
||||||
@@ -383,9 +383,9 @@ def test_sync_stops_without_emitting_completion_after_stop_signal(monkeypatch):
|
|||||||
)
|
)
|
||||||
monkeypatch.setattr(chain, "_sync_server_libraries", stop_during_sync)
|
monkeypatch.setattr(chain, "_sync_server_libraries", stop_during_sync)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
|
MEDIA_SERVER_CHAIN_MODULE,
|
||||||
"get_mediaserver_configs",
|
"get_mediaserver_configs",
|
||||||
lambda: [server],
|
lambda **_kwargs: [server],
|
||||||
)
|
)
|
||||||
global_vars.STOP_EVENT.clear()
|
global_vars.STOP_EVENT.clear()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -90,9 +90,9 @@ def test_clear_cache_is_manual_only(monkeypatch):
|
|||||||
]:
|
]:
|
||||||
monkeypatch.setattr(scheduler_module, name, lambda: generic_chain)
|
monkeypatch.setattr(scheduler_module, name, lambda: generic_chain)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
scheduler_module.ServiceConfigHelper,
|
scheduler_module,
|
||||||
"get_mediaserver_configs",
|
"get_mediaserver_configs",
|
||||||
lambda: [],
|
lambda **_kwargs: [],
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
scheduler_module,
|
scheduler_module,
|
||||||
|
|||||||
Reference in New Issue
Block a user