from typing import List from api.task import Task, Status, TaskEvent from api.execution_context import ExecutionContext from api.io_port import InPort, CompatibilityException class Workflow(Task): """ Composite node: contains children Tasks. Executes tasks in dependency order (topological execution). """ def __init__(self, name: str): super().__init__(name) self.children: List[Task] = [] def execute(self, context: ExecutionContext): pass def add_child(self, task: Task): task.parent_task = self self.children.append(task) def remove_child(self, task: Task): if task in self.children: self.children.remove(task) task.parent_task = None def get_children(self): return list(self.children) def get_all_tasks(self): tasks = [] for child in self.children: tasks.append(child) if isinstance(child, Workflow): tasks.extend(child.get_all_tasks()) return tasks def validate(self): super().validate() for child in self.children: child.validate() def _get_dependencies(self, task: Task) -> List[Task]: deps = [] for in_port in task.in_ports: if isinstance(in_port, InPort) and in_port.connected_output: upstream_task = in_port.connected_output.parent_task if upstream_task and upstream_task != task: deps.append(upstream_task) return deps def run(self, context: ExecutionContext): self._set_status(Status.RUNNING) self._emit(TaskEvent.ON_START, {"workflow": self.name}) remaining = set(self.children) completed = set() while remaining: progress_made = False for task in list(remaining): deps = self._get_dependencies(task) if all(dep in completed for dep in deps): if task.check_inputs_ready(context): try: task.run(context) completed.add(task) remaining.remove(task) progress_made = True except CompatibilityException as e: task._set_status(Status.FAILED) self._emit(TaskEvent.ON_ERROR, {"error": e.reason, "task": task.name}) self._set_status(Status.FAILED) return except Exception as e: task._set_status(Status.FAILED) self._emit(TaskEvent.ON_ERROR, {"error": str(e), "task": task.name}) self._set_status(Status.FAILED) return if not progress_made: self._set_status(Status.FAILED) self._emit(TaskEvent.ON_ERROR, {"error": "Dependency deadlock detected"}) return if all(t.status == Status.COMPLETED for t in self.children): self._set_status(Status.COMPLETED) self._emit(TaskEvent.ON_FINISH, {"workflow": self.name}) else: self._set_status(Status.FAILED)