Files
MoviePilot/app/application/chain/data.py
T

177 lines
4.6 KiB
Python

"""Chain 所需持久化端口的组合根注册表。
Chain 只依赖本模块声明的工厂,不再直接导入数据库 Oper 或 ORM 模型。
具体适配器由 ``app.startup`` 在进程启动时装配,测试也可以登记隔离替身。
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Optional
OperFactory = Callable[[], Any]
@dataclass(frozen=True, slots=True)
class ChainDataPorts:
"""跨领域 Chain 使用的最小持久化端口工厂集合。"""
site: OperFactory
subscribe: OperFactory
workflow: OperFactory
download_history: OperFactory
transfer_history: OperFactory
transfer_pending: OperFactory
media_server: OperFactory
download_failure: OperFactory
user: OperFactory
class _PortProxyMeta(type):
"""让迁移期的 Oper 名称支持按方法打桩,同时仍转发到组合根端口。"""
def __getattr__(cls, name: str) -> Any:
"""把类级方法访问转发到一个新的端口实例。"""
return getattr(cls(), name)
class _ChainDataPortProxy(metaclass=_PortProxyMeta):
"""将旧的 Oper 调用形态转发到 Chain 数据端口的内部代理。"""
port_name: str
def __getattr__(self, name: str) -> Any:
"""转发未被测试替换的数据操作。"""
return getattr(getattr(get_chain_data_ports(), self.port_name)(), name)
class SitePortProxy(_ChainDataPortProxy):
"""站点数据端口代理。"""
port_name = "site"
class SubscribePortProxy(_ChainDataPortProxy):
"""订阅数据端口代理。"""
port_name = "subscribe"
class WorkflowPortProxy(_ChainDataPortProxy):
"""工作流数据端口代理。"""
port_name = "workflow"
class DownloadHistoryPortProxy(_ChainDataPortProxy):
"""下载历史数据端口代理。"""
port_name = "download_history"
class TransferHistoryPortProxy(_ChainDataPortProxy):
"""整理历史数据端口代理。"""
port_name = "transfer_history"
class TransferPendingPortProxy(_ChainDataPortProxy):
"""待整理数据端口代理。"""
port_name = "transfer_pending"
class MediaServerPortProxy(_ChainDataPortProxy):
"""媒体服务器数据端口代理。"""
port_name = "media_server"
class DownloadFailurePortProxy(_ChainDataPortProxy):
"""下载失败数据端口代理。"""
port_name = "download_failure"
class UserPortProxy(_ChainDataPortProxy):
"""用户数据端口代理。"""
port_name = "user"
_ports: Optional[ChainDataPorts] = None
def configure_chain_data_ports(**factories: OperFactory) -> None:
"""由启动组合根登记 Chain 的数据端口实现。"""
required = {
"site",
"subscribe",
"workflow",
"download_history",
"transfer_history",
"transfer_pending",
"media_server",
"download_failure",
"user",
}
missing = sorted(required - factories.keys())
if missing:
raise ValueError(f"Chain 数据端口缺少实现: {', '.join(missing)}")
global _ports
_ports = ChainDataPorts(**{name: factories[name] for name in required})
def get_chain_data_ports() -> ChainDataPorts:
"""返回启动阶段登记的 Chain 数据端口。"""
if _ports is None:
raise RuntimeError("Chain 数据端口尚未配置")
return _ports
def get_chain_site_port() -> Any:
"""创建站点数据端口实例。"""
return get_chain_data_ports().site()
def get_chain_subscribe_port() -> Any:
"""创建订阅数据端口实例。"""
return get_chain_data_ports().subscribe()
def get_chain_workflow_port() -> Any:
"""创建工作流数据端口实例。"""
return get_chain_data_ports().workflow()
def get_chain_download_history_port() -> Any:
"""创建下载历史数据端口实例。"""
return get_chain_data_ports().download_history()
def get_chain_transfer_history_port() -> Any:
"""创建整理历史数据端口实例。"""
return get_chain_data_ports().transfer_history()
def get_chain_transfer_pending_port() -> Any:
"""创建待整理数据端口实例。"""
return get_chain_data_ports().transfer_pending()
def get_chain_media_server_port() -> Any:
"""创建媒体服务器数据端口实例。"""
return get_chain_data_ports().media_server()
def get_chain_download_failure_port() -> Any:
"""创建下载失败数据端口实例。"""
return get_chain_data_ports().download_failure()
def get_chain_user_port() -> Any:
"""创建用户数据端口实例。"""
return get_chain_data_ports().user()