base_agent/uas/action/action_context.py

155 lines
5.7 KiB
Python
Raw 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.

# -*- coding: utf-8 -*-
"""
Action 执行上下文
─────────────────────────────────────────────────────────────────
ActionContext 在处理函数执行期间提供:
- 共享数据存储(跨 Action 传递状态)
- Event 收集器(处理函数产生的 Event 暂存于此)
- 进度上报接口
- 取消/暂停检查接口
─────────────────────────────────────────────────────────────────
"""
from __future__ import annotations
import logging
import threading
from typing import Any, Callable, Dict, List, Optional
logger = logging.getLogger(__name__)
class ActionContext:
"""
Action 执行上下文(每次执行独立实例)
处理函数签名:
def my_handler(action: Action, ctx: ActionContext) -> Any:
# 产生事件
ctx.emit_event(make_event(EventType.DEVICE_DATA_RECEIVED, ...))
ctx.emit_event(make_event(EventType.DEVICE_STATUS_CHANGED, ...))
# 上报进度
ctx.report_progress(50, "处理中...")
# 检查取消
if ctx.is_cancelled:
return None
return {"result": "ok"}
"""
def __init__(
self,
action, # Action 对象
shared_store: Optional[Dict] = None, # 全局共享数据
progress_cb: Optional[Callable] = None,
):
self._action = action
self._events: List[Any] = [] # 收集产生的 Event 对象
self._store: Dict[str, Any] = shared_store if shared_store is not None else {}
self._lock = threading.Lock()
self._progress_cb = progress_cb
self._progress: int = 0
self._status_msg: str = ""
self.logger = logging.getLogger(
f"ActionContext[{action.type_key}:{action.action_id[:8]}]"
)
# ── Event 收集 ────────────────────────────────────────────────
def emit_event(self, event: Any) -> None:
"""
在处理函数中产生一个 Event 对象
Event 将被收集后统一由 ActionExecutor 提交到 EventBus
"""
with self._lock:
self._events.append(event)
self.logger.debug(f"产生事件: {event}")
def emit_events(self, events: List[Any]) -> None:
"""批量产生多个 Event 对象"""
for event in events:
self.emit_event(event)
@property
def collected_events(self) -> List[Any]:
"""获取所有已收集的 Event 对象(只读副本)"""
with self._lock:
return list(self._events)
# ── 进度上报 ──────────────────────────────────────────────────
def report_progress(self, percent: int, message: str = "") -> None:
"""
上报执行进度0~100
:param percent: 进度百分比
:param message: 进度描述
"""
self._progress = max(0, min(100, percent))
self._status_msg = message
if self._progress_cb:
self._progress_cb(self._action.action_id, self._progress, message)
self.logger.debug(f"进度 {self._progress}% - {message}")
@property
def progress(self) -> int:
return self._progress
@property
def status_message(self) -> str:
return self._status_msg
# ── 取消/暂停检查 ─────────────────────────────────────────────
@property
def is_cancelled(self) -> bool:
"""处理函数内部轮询此属性以响应取消请求"""
return self._action.cancel_requested
@property
def is_paused(self) -> bool:
return self._action.pause_requested
def check_cancel(self) -> bool:
"""
检查取消标志,若已取消则抛出 ActionCancelledError
在处理函数关键节点调用ctx.check_cancel()
"""
if self.is_cancelled:
raise ActionCancelledError(
f"Action {self._action.type_key} 已被取消"
)
return False
# ── 共享数据存储 ──────────────────────────────────────────────
def set(self, key: str, value: Any) -> None:
"""写入共享数据"""
with self._lock:
self._store[key] = value
def get(self, key: str, default: Any = None) -> Any:
"""读取共享数据"""
with self._lock:
return self._store.get(key, default)
def update(self, data: dict) -> None:
with self._lock:
self._store.update(data)
@property
def store(self) -> dict:
with self._lock:
return dict(self._store)
# ─────────────────────────────────────────────────────────────────
# 自定义异常
# ─────────────────────────────────────────────────────────────────
class ActionCancelledError(Exception):
"""Action 被取消时抛出"""
pass
class ActionTimeoutError(Exception):
"""Action 执行超时时抛出"""
pass