from abc import ABC, abstractmethod from enum import Enum from typing import List, Any, Optional from api.io_port import InPort, OutPort from api.execution_context import ExecutionContext class Status(Enum): PENDING = "pending" READY = "ready" RUNNING = "running" PAUSED = "paused" STOPPED = "stopped" COMPLETED = "completed" FAILED = "failed" class TaskEvent(Enum): BEFORE_RUN = "before_run" AFTER_RUN = "after_run" ON_ERROR = "on_error" STATUS_CHANGED = "status_changed" ON_START = "on_start" ON_FINISH = "on_finish" class TaskEventListener: def handle(self, event: TaskEvent, task: "Task", payload: Optional[Any] = None): pass class Task(ABC): def __init__(self, name: str): self.name = name self.status = Status.PENDING self.in_ports: List[InPort] = [] self.out_ports: List[OutPort] = [] self._listeners: List[TaskEventListener] = [] self.parent_task: Optional["Task"] = None # Event System def add_listener(self, listener: TaskEventListener): self._listeners.append(listener) def _emit(self, event: TaskEvent, payload: Optional[Any] = None): for listener in self._listeners: listener.handle(event, self, payload) def _set_status(self, new_status: Status): old_status = self.status self.status = new_status if old_status != new_status: self._emit(TaskEvent.STATUS_CHANGED, {"from": old_status, "to": new_status}) # Input Readiness Check def check_inputs_ready(self, context: ExecutionContext) -> bool: for port in self.in_ports: if not port.is_ready(context): return False return True # Execution Wrapper def run(self, context: ExecutionContext): """ Base Task.run() SHOULD NOT be used for Module. Module overrides run() completely. """ raise NotImplementedError( "Task.run() should not be called for Module. Use Module.run() instead." ) @abstractmethod def execute(self, context: ExecutionContext): pass