mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 07:56:52 +08:00
Merge origin/v3 into codex/feat/plugin-data-query-sdk-v3
This commit is contained in:
@@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
|
||||
AgentDataFactory = Callable[[], Any]
|
||||
|
||||
|
||||
@@ -76,12 +75,6 @@ class DownloadHistoryPort(_PortProxy):
|
||||
port_name = "download_history"
|
||||
|
||||
|
||||
class WorkflowPort(_PortProxy):
|
||||
"""工作流数据端口代理。"""
|
||||
|
||||
port_name = "workflow"
|
||||
|
||||
|
||||
class PluginDataPort(_PortProxy):
|
||||
"""插件数据端口代理。"""
|
||||
|
||||
@@ -110,7 +103,6 @@ def configure_agent_data_ports(**factories: AgentDataFactory) -> None:
|
||||
"subscribe_history",
|
||||
"transfer_history",
|
||||
"download_history",
|
||||
"workflow",
|
||||
"plugin_data",
|
||||
}
|
||||
missing = sorted(required - factories.keys())
|
||||
@@ -167,11 +159,6 @@ def get_agent_download_history_port() -> Any:
|
||||
return get_agent_data_ports().download_history()
|
||||
|
||||
|
||||
def get_agent_workflow_port() -> Any:
|
||||
"""创建 Agent 工作流数据端口实例。"""
|
||||
return get_agent_data_ports().workflow()
|
||||
|
||||
|
||||
def get_agent_plugin_data_port() -> Any:
|
||||
"""创建 Agent 插件数据端口实例。"""
|
||||
return get_agent_data_ports().plugin_data()
|
||||
|
||||
@@ -6,7 +6,6 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.application.chain.data import ChainDataPorts
|
||||
from app.application.chain.events import ChainDurableEventWriter
|
||||
from app.application.configuration import ChainRuntimeConfig
|
||||
from app.runtime.stop import StopState, runtime_stop_state
|
||||
@@ -31,7 +30,6 @@ class ChainRuntimeContext:
|
||||
message_queue_factory: MessageQueueFactory
|
||||
module_dispatcher_factory: ModuleDispatcherFactory
|
||||
legacy_transfer_command: Optional[LegacyTransferCommand] = None
|
||||
data_ports: Optional[ChainDataPorts] = None
|
||||
durable_event_writer: Optional[ChainDurableEventWriter] = None
|
||||
configuration: ChainRuntimeConfig = field(
|
||||
default_factory=lambda: ChainRuntimeConfig(media_extensions=())
|
||||
|
||||
@@ -24,7 +24,6 @@ class ChainDataPorts:
|
||||
|
||||
site: OperFactory
|
||||
subscribe: OperFactory
|
||||
workflow: OperFactory
|
||||
download_history: OperFactory
|
||||
transfer_history: OperFactory
|
||||
transfer_pending: TransferAdmissionRepositoryFactory
|
||||
@@ -34,94 +33,34 @@ class ChainDataPorts:
|
||||
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 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",
|
||||
"transfer_execution",
|
||||
"media_server",
|
||||
"download_failure",
|
||||
"user",
|
||||
}
|
||||
missing = sorted(required - factories.keys())
|
||||
if missing:
|
||||
raise ValueError(f"Chain 数据端口缺少实现: {', '.join(missing)}")
|
||||
def configure_chain_data_ports(
|
||||
*,
|
||||
site: OperFactory,
|
||||
subscribe: OperFactory,
|
||||
download_history: OperFactory,
|
||||
transfer_history: OperFactory,
|
||||
transfer_pending: TransferAdmissionRepositoryFactory,
|
||||
transfer_execution: TransferExecutionRepositoryFactory,
|
||||
media_server: OperFactory,
|
||||
download_failure: OperFactory,
|
||||
user: OperFactory,
|
||||
) -> None:
|
||||
"""由启动组合根登记显式命名的 Chain 数据端口实现。"""
|
||||
global _ports
|
||||
_ports = ChainDataPorts(**{name: factories[name] for name in required})
|
||||
_ports = ChainDataPorts(
|
||||
site=site,
|
||||
subscribe=subscribe,
|
||||
download_history=download_history,
|
||||
transfer_history=transfer_history,
|
||||
transfer_pending=transfer_pending,
|
||||
transfer_execution=transfer_execution,
|
||||
media_server=media_server,
|
||||
download_failure=download_failure,
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
def get_chain_data_ports() -> ChainDataPorts:
|
||||
@@ -141,11 +80,6 @@ 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()
|
||||
|
||||
@@ -4,8 +4,10 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import asdict
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.application.workflow import WorkflowSnapshot
|
||||
from app.schemas.media import resolve_media_identity
|
||||
|
||||
|
||||
@@ -26,8 +28,10 @@ class ServerSharingService:
|
||||
*,
|
||||
subscribe_provider: Callable[[int], Any],
|
||||
async_subscribe_provider: Callable[[int], Awaitable[Any]],
|
||||
workflow_provider: Callable[[int], Any],
|
||||
async_workflow_provider: Callable[[int], Awaitable[Any]],
|
||||
workflow_provider: Callable[[int], Optional[WorkflowSnapshot]],
|
||||
async_workflow_provider: Callable[
|
||||
[int], Awaitable[Optional[WorkflowSnapshot]]
|
||||
],
|
||||
user_uuid_provider: Callable[[], str],
|
||||
subscribe_sender: Callable[[dict], Any],
|
||||
async_subscribe_sender: Callable[[dict], Awaitable[Any]],
|
||||
@@ -68,9 +72,9 @@ class ServerSharingService:
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
def prepare_workflow(workflow: Any) -> dict:
|
||||
def prepare_workflow(workflow: WorkflowSnapshot) -> dict:
|
||||
"""移除本地字段并把动作和流程编码为中心服务兼容格式。"""
|
||||
workflow_dict = workflow.to_dict()
|
||||
workflow_dict = asdict(workflow)
|
||||
workflow_dict.pop("id", None)
|
||||
workflow_dict.pop("context", None)
|
||||
workflow_dict["actions"] = json.dumps(workflow_dict["actions"] or [])
|
||||
@@ -78,7 +82,9 @@ class ServerSharingService:
|
||||
return workflow_dict
|
||||
|
||||
@staticmethod
|
||||
def validate_workflow(workflow: Any) -> tuple[bool, str]:
|
||||
def validate_workflow(
|
||||
workflow: Optional[WorkflowSnapshot],
|
||||
) -> tuple[bool, str]:
|
||||
"""验证工作流存在且同时包含动作与流程。"""
|
||||
if not workflow:
|
||||
return False, "工作流不存在"
|
||||
@@ -160,6 +166,8 @@ class ServerSharingService:
|
||||
valid, message = self.validate_workflow(workflow)
|
||||
if not valid:
|
||||
return False, message
|
||||
if workflow is None:
|
||||
return False, "工作流不存在"
|
||||
payload = {
|
||||
"share_title": share_title,
|
||||
"share_comment": share_comment,
|
||||
@@ -188,6 +196,8 @@ class ServerSharingService:
|
||||
valid, message = self.validate_workflow(workflow)
|
||||
if not valid:
|
||||
return False, message
|
||||
if workflow is None:
|
||||
return False, "工作流不存在"
|
||||
payload = {
|
||||
"share_title": share_title,
|
||||
"share_comment": share_comment,
|
||||
|
||||
+122
-15
@@ -1,11 +1,12 @@
|
||||
"""工作流状态与定义写操作应用用例。"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
from collections.abc import Awaitable
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Mapping, Optional, Protocol, TypeVar
|
||||
from typing import Any, Callable, List, Mapping, Optional, Protocol, TypeVar
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
WORKFLOW_TRIGGER_TIMER = "timer"
|
||||
WORKFLOW_TRIGGER_EVENT = "event"
|
||||
@@ -17,6 +18,30 @@ SUPPORTED_WORKFLOW_TRIGGERS = {
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class WorkflowSnapshot:
|
||||
"""工作流查询返回的脱离数据库会话的冻结快照。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str]
|
||||
timer: Optional[str]
|
||||
trigger_type: Optional[str]
|
||||
event_type: Optional[str]
|
||||
event_conditions: Mapping[str, JsonData]
|
||||
state: str
|
||||
current_action: Optional[str]
|
||||
result: Optional[str]
|
||||
run_count: Optional[int]
|
||||
actions: tuple[Mapping[str, JsonData], ...]
|
||||
flows: tuple[Mapping[str, JsonData], ...]
|
||||
context: Mapping[str, JsonData]
|
||||
execution_config: Mapping[str, JsonData]
|
||||
execution_state: Mapping[str, JsonData]
|
||||
add_time: Optional[str]
|
||||
last_time: Optional[str]
|
||||
|
||||
|
||||
class WorkflowRuntime(Protocol):
|
||||
"""声明宿主入口与 Chain 消费的工作流运行时能力。"""
|
||||
|
||||
@@ -40,7 +65,7 @@ class WorkflowRuntime(Protocol):
|
||||
"""移除全部或指定工作流的事件触发器。"""
|
||||
...
|
||||
|
||||
def update_workflow_event(self, workflow: Any) -> None:
|
||||
def update_workflow_event(self, workflow: WorkflowSnapshot) -> None:
|
||||
"""按最新定义刷新工作流事件触发器。"""
|
||||
...
|
||||
|
||||
@@ -87,15 +112,31 @@ def get_workflow_manager() -> WorkflowRuntime:
|
||||
return _workflow_runtime_provider()
|
||||
|
||||
|
||||
class AsyncWorkflowQueryRepository(Protocol):
|
||||
"""工作流查询用例需要的异步读取端口。"""
|
||||
class WorkflowQueryRepository(Protocol):
|
||||
"""工作流查询用例需要的同步与异步快照端口。"""
|
||||
|
||||
async def async_list(self) -> list[Any]:
|
||||
"""读取全部工作流。"""
|
||||
def get(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
|
||||
"""按 ID 读取工作流快照。"""
|
||||
...
|
||||
|
||||
async def async_get(self, workflow_id: int) -> Optional[Any]:
|
||||
"""按 ID 读取工作流。"""
|
||||
def list_enabled(self) -> List[WorkflowSnapshot]:
|
||||
"""读取全部启用的工作流快照。"""
|
||||
...
|
||||
|
||||
def list_timer_enabled(self) -> List[WorkflowSnapshot]:
|
||||
"""读取启用的定时工作流快照。"""
|
||||
...
|
||||
|
||||
def list_event_enabled(self) -> List[WorkflowSnapshot]:
|
||||
"""读取启用的事件工作流快照。"""
|
||||
...
|
||||
|
||||
async def async_list(self) -> List[WorkflowSnapshot]:
|
||||
"""异步读取全部工作流快照。"""
|
||||
...
|
||||
|
||||
async def async_get(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
|
||||
"""异步按 ID 读取工作流快照。"""
|
||||
...
|
||||
|
||||
|
||||
@@ -114,18 +155,34 @@ class WorkflowCachePort(Protocol):
|
||||
class WorkflowQueryService:
|
||||
"""提供工作流列表和详情查询,隔离 API 与数据库会话。"""
|
||||
|
||||
def __init__(self, repository: AsyncWorkflowQueryRepository) -> None:
|
||||
"""保存请求级异步查询端口。"""
|
||||
def __init__(self, repository: WorkflowQueryRepository) -> None:
|
||||
"""保存可返回脱离会话快照的查询端口。"""
|
||||
self._repository = repository
|
||||
|
||||
async def list(self) -> list[Any]:
|
||||
"""返回全部工作流。"""
|
||||
async def list(self) -> List[WorkflowSnapshot]:
|
||||
"""返回全部工作流快照。"""
|
||||
return await self._repository.async_list()
|
||||
|
||||
async def get(self, workflow_id: int) -> Optional[Any]:
|
||||
"""返回指定工作流。"""
|
||||
async def get(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
|
||||
"""返回指定工作流快照。"""
|
||||
return await self._repository.async_get(workflow_id)
|
||||
|
||||
def get_sync(self, workflow_id: int) -> Optional[WorkflowSnapshot]:
|
||||
"""同步返回指定工作流快照。"""
|
||||
return self._repository.get(workflow_id)
|
||||
|
||||
def list_enabled(self) -> List[WorkflowSnapshot]:
|
||||
"""同步返回全部启用的工作流快照。"""
|
||||
return self._repository.list_enabled()
|
||||
|
||||
def list_timer_enabled(self) -> List[WorkflowSnapshot]:
|
||||
"""同步返回启用的定时工作流快照。"""
|
||||
return self._repository.list_timer_enabled()
|
||||
|
||||
def list_event_enabled(self) -> List[WorkflowSnapshot]:
|
||||
"""同步返回启用的事件工作流快照。"""
|
||||
return self._repository.list_event_enabled()
|
||||
|
||||
|
||||
_configured_workflow_query: WorkflowQueryService | None = None
|
||||
|
||||
@@ -183,6 +240,56 @@ class UnitOfWork(Protocol):
|
||||
...
|
||||
|
||||
|
||||
class WorkflowExecutionPort(Protocol):
|
||||
"""工作流 Chain 提交执行状态所需的类型化事务端口。"""
|
||||
|
||||
def start(self, workflow_id: int) -> bool:
|
||||
"""提交工作流运行中状态。"""
|
||||
...
|
||||
|
||||
def success(
|
||||
self,
|
||||
workflow_id: int,
|
||||
result: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""提交工作流成功状态。"""
|
||||
...
|
||||
|
||||
def fail(self, workflow_id: int, result: str) -> bool:
|
||||
"""提交工作流失败状态。"""
|
||||
...
|
||||
|
||||
def step(
|
||||
self,
|
||||
workflow_id: int,
|
||||
action_id: str,
|
||||
context: dict[str, Any],
|
||||
execution_state: Optional[dict[str, Any]] = None,
|
||||
) -> bool:
|
||||
"""提交工作流动作进度。"""
|
||||
...
|
||||
|
||||
def reset(self, workflow_id: int, reset_count: bool = False) -> bool:
|
||||
"""提交工作流执行状态重置。"""
|
||||
...
|
||||
|
||||
|
||||
_configured_workflow_execution: Optional[WorkflowExecutionPort] = None
|
||||
|
||||
|
||||
def configure_workflow_execution(service: WorkflowExecutionPort) -> None:
|
||||
"""由启动组合根登记唯一工作流执行状态事务服务。"""
|
||||
global _configured_workflow_execution
|
||||
_configured_workflow_execution = service
|
||||
|
||||
|
||||
def get_configured_workflow_execution() -> WorkflowExecutionPort:
|
||||
"""返回启动阶段登记的工作流执行状态事务服务。"""
|
||||
if _configured_workflow_execution is None:
|
||||
raise RuntimeError("工作流执行状态事务服务尚未配置")
|
||||
return _configured_workflow_execution
|
||||
|
||||
|
||||
class WorkflowExecutionRepository(Protocol):
|
||||
"""工作流执行状态写入所需的最小暂存端口。"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user