Files
MoviePilot/app/application/service.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

202 lines
5.7 KiB
Python

"""
下载器、媒体服务器和通知服务的应用层目录端口。
"""
from __future__ import annotations
from collections.abc import Callable, Iterator
from typing import Any, Dict, Generic, List, Optional, Protocol, Type, TypeVar
from app.schemas.system import ServiceInfo
from app.schemas.types import ModuleType, SystemConfigKey
TConf = TypeVar("TConf")
ServiceConfigLoader = Callable[[SystemConfigKey, Type[Any]], list[Any]]
RunningModuleLoader = Callable[[ModuleType], list[Any]]
def _unconfigured_configs(
_config_key: SystemConfigKey,
_conf_type: Type[Any],
) -> list[Any]:
"""
拒绝在启动组合根装配前隐式读取服务配置。
"""
raise RuntimeError("服务配置目录尚未由启动组合根配置")
def _unconfigured_modules(_module_type: ModuleType) -> list[Any]:
"""
拒绝在启动组合根装配前隐式抓取模块管理器。
"""
raise RuntimeError("运行模块目录尚未由启动组合根配置")
_config_loader: ServiceConfigLoader = _unconfigured_configs
_module_loader: RunningModuleLoader = _unconfigured_modules
def configure_service_directory(
*,
configs: ServiceConfigLoader,
modules: RunningModuleLoader,
) -> None:
"""
由启动组合根注入服务配置和运行模块枚举端口。
"""
global _config_loader, _module_loader
_config_loader = configs
_module_loader = modules
def reset_service_directory() -> None:
"""
恢复未装配服务目录,禁止跨 lifespan 复用旧模块对象。
"""
global _config_loader, _module_loader
_config_loader = _unconfigured_configs
_module_loader = _unconfigured_modules
def get_service_configs(
config_key: SystemConfigKey,
conf_type: Type[TConf],
) -> list[TConf]:
"""
通过组合根登记的读取器返回已校验服务配置。
"""
return _config_loader(config_key, conf_type)
class _ServiceConfig(Protocol):
"""
服务目录读取的配置字段;具体配置模型保留各自附加字段。
"""
@property
def name(self) -> Optional[str]:
"""
返回配置的服务名称。
:return: 已配置服务实例的名称,用于区分同类型的多个实例
"""
...
@property
def type(self) -> Optional[str]:
"""
返回配置的服务类型。
:return: 选择对应模块实现的服务类型标识
"""
...
@property
def enabled(self) -> Optional[bool]:
"""
返回服务是否启用,兼容历史空值。
:return: 该服务配置是否允许实例化和参与业务调用
"""
...
TServiceConf = TypeVar("TServiceConf", bound=_ServiceConfig)
class ServiceBaseHelper(Generic[TServiceConf]):
"""
通过应用端口查询服务配置和对应运行实例。
"""
def __init__(
self,
config_key: SystemConfigKey,
conf_type: Type[TServiceConf],
module_type: ModuleType,
) -> None:
"""
绑定配置类型和模块能力类型,不抓取具体 Runtime 管理器。
"""
self.config_key = config_key
self.conf_type = conf_type
self.module_type = module_type
def get_configs(self, include_disabled: bool = False) -> Dict[str, TServiceConf]:
"""
返回按名称索引的有效服务配置。
"""
configs = get_service_configs(self.config_key, self.conf_type)
return {
config.name: config
for config in configs
if config.name
and config.type
and (config.enabled or include_disabled)
}
def get_config(self, name: str) -> Optional[TServiceConf]:
"""
按名称返回单个启用服务配置。
"""
return self.get_configs().get(name) if name else None
def iterate_module_instances(self) -> Iterator[ServiceInfo]:
"""
迭代当前类型所有运行模块实例及其配置投影。
"""
configs = self.get_configs()
for module in _module_loader(self.module_type):
if not module:
continue
instances = module.get_instances()
if not isinstance(instances, dict):
continue
for name, instance in instances.items():
if not instance:
continue
config = configs.get(name)
yield ServiceInfo(
name=name,
instance=instance,
module=module,
type=config.type if config else None,
config=config,
)
def get_services(
self,
type_filter: Optional[str] = None,
name_filters: Optional[List[str]] = None,
) -> Dict[Optional[str], ServiceInfo]:
"""
按服务类型和名称集合过滤运行实例。
"""
names = set(name_filters) if name_filters else None
return {
service.name: service
for service in self.iterate_module_instances()
if service.config
and (type_filter is None or service.type == type_filter)
and (names is None or service.name in names)
}
def get_service(
self,
name: Optional[str],
type_filter: Optional[str] = None,
) -> Optional[ServiceInfo]:
"""
按名称和可选类型返回单个运行服务。
"""
if not name:
return None
for service in self.iterate_module_instances():
if (
service.name == name
and service.config
and (type_filter is None or service.type == type_filter)
):
return service
return None