Files

93 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from collections.abc import AsyncIterator
from typing import Any, Mapping, Optional
from app.core.task_manager.stores import TaskStore
from app.core.task_manager.strategies import DeliveryStrategy
from app.core.task_manager.types import BaseTask, DeliveryMode, TaskRecord, TaskStatusView
class TaskManager:
"""任务管理器:对上层(路由/业务)提供统一入口。
init params:
- store: TaskStore, 任务存储
特性:
- store 可插拔:内存 or MySQL(通过 SQLAlchemy)
- delivery strategy 可插拔:streaming / async_polling / ...(后续扩展只需新增策略并注册)
- 为高频轮询提供轻量查询:get_status()
"""
def __init__(self, *, store: TaskStore, strategies: Mapping[DeliveryMode, DeliveryStrategy]) -> None:
self.store = store
self._strategies = dict(strategies)
def _strategy_for(self, mode: DeliveryMode) -> DeliveryStrategy:
strategy = self._strategies.get(mode)
if strategy is None:
raise ValueError(f"No delivery strategy registered for mode={mode!r}")
return strategy
async def create(
self,
*,
task: BaseTask,
mode: DeliveryMode,
task_kind: str | None = None,
run_args: dict[str, Any] | None = None,
) -> TaskRecord:
"""创建任务记录。
参数:
- task: 实际要执行的 Task 对象,必须实现 run/status/is_done/get_result
- mode: 结果交付方式(流式 / 任务+轮询)
- run_args: 传给 task.run 的参数(会序列化到 payload,便于后续 worker 使用)
"""
payload: dict[str, Any] = {
"task_class": task.__class__.__name__,
"task_kind": task_kind or task.__class__.__name__,
"run_args": run_args or {},
}
return await self.store.create(
payload=payload,
mode=mode,
task_kind=str(payload["task_kind"]),
)
async def start(self, *, task_id: str) -> Optional[AsyncIterator[Any]]:
task = await self.store.get(task_id)
if task is None:
raise ValueError(f"Task not found: {task_id}")
strategy = self._strategy_for(task.mode)
return await strategy.start(task)
async def get_status(self, *, task_id: str) -> TaskStatusView:
view = await self.store.get_status_view(task_id)
if view is None:
raise ValueError(f"Task not found: {task_id}")
return view
async def request_cancel(self, *, task_id: str, reason: str | None = None) -> TaskRecord:
rec = await self.store.request_cancel(task_id, reason)
if rec is None:
raise ValueError(f"Task not found: {task_id}")
return rec
async def is_cancel_requested(self, *, task_id: str) -> bool:
return await self.store.is_cancel_requested(task_id)
async def mark_cancelled(self, *, task_id: str) -> TaskRecord:
rec = await self.store.mark_cancelled(task_id)
if rec is None:
raise ValueError(f"Task not found: {task_id}")
return rec
async def stream(self, *, task_id: str) -> AsyncIterator[Any]:
task = await self.store.get(task_id)
if task is None:
raise ValueError(f"Task not found: {task_id}")
strategy = self._strategy_for(task.mode)
return await strategy.stream(task)