def enqueue_task(self, execution_plan, task_name, circular_check=None): if not self.has_task(task_name): raise NoSuchTaskException(task_name) task = self.get_task(task_name) if task == circular_check: raise CircularTaskDependencyException(task.name) if task in execution_plan: return try: for dependency in self._task_dependencies[task.name]: self.enqueue_task( execution_plan, dependency.name, circular_check=circular_check if circular_check else task) except CircularTaskDependencyException as e: if e.second: raise raise CircularTaskDependencyException(e.first, task.name) execution_plan.append(task)
def build_execution_plan(self, task_names): self.assert_dependencies_resolved() execution_plan = [] dependency_edges = {} for task in self.collect_all_transitive_tasks(as_list(task_names)): dependency_edges[task.name] = task.dependencies try: Graph(dependency_edges).assert_no_cycles_present() except GraphHasCycles as cycles: raise CircularTaskDependencyException(str(cycles)) for task_name in as_list(task_names): self.enqueue_task(execution_plan, task_name) return execution_plan
def build_shortest_execution_plan(self, task_names): """ Finds the shortest execution plan taking into the account tasks already executed This is useful when you want to execute tasks dynamically without repeating pre-requisite tasks you've already executed """ execution_plan = self.build_execution_plan(task_names) shortest_plan = copy.copy(execution_plan) for executed_task in self._tasks_executed: candidate_task = shortest_plan[0] if candidate_task.name not in task_names and candidate_task == executed_task: shortest_plan.pop(0) else: break if self._current_task and self._current_task in shortest_plan: raise CircularTaskDependencyException("Task '%s' attempted to invoke tasks %s, " "resulting in plan %s, creating circular dependency" % (self._current_task, task_names, shortest_plan)) return shortest_plan