Step 1: Order the tasks
Order the tasks
A training pipeline is a set of tasks with dependencies: validate needs ingest, train needs
features, and so on. Before running anything you have to put them in an order where every task comes
after the things it depends on, and reject a pipeline that can never run.
pipeline.py gives you the pipeline. A task is a dict {"name", "deps": [names], "run": fn(ctx), "retries"}. pipeline.build_pipeline() returns the task list and a fresh ctx, and the factories
step, flaky and failing build tasks you can use in your own tests.
Write topo_order(tasks): return the task names in an order where each task follows its dependencies.
Raise ValueError if a dependency names a task that is not in the set, or if the dependencies form a
cycle (which could never be ordered).
orchestrator.py, the file you edit47 lines
"""Your pipeline orchestrator. A pipeline is a list of tasks with dependencies. You order them, run them so
one failure does not corrupt the rest, retry the flaky ones, resume a half-finished run without repeating
work, and report what happened."""
import time
# ---------- Step 1: order the tasks ----------
def topo_order(tasks):
"""The task names in an order where every task comes after its dependencies. Raise ValueError if a
dependency is unknown or the graph has a cycle."""
# TODO (Step 1): visit each task depth-first through its deps; a node seen while
# still visiting is a cycle, a dep not in the task set is unknown; append on the way out.
raise NotImplementedError("Step 1: write topo_order()")
# ---------- Step 2: run one task, with retries ----------
def attempt(task, ctx, sleep=time.sleep, base=0.1):
"""Run a task's function, retrying up to task['retries'] extra times on any exception, waiting
base * 2**i before the (i+1)-th retry. Return {"status": "ok"/"failed", "attempts", "error"}."""
raise NotImplementedError("attempt() arrives in Step 2")
# ---------- Step 3: run the pipeline ----------
def run_pipeline(tasks, ctx, sleep=time.sleep, done=None):
"""Run tasks in dependency order. A task whose dependencies are not all ok is skipped, so one
failure isolates its branch instead of corrupting the rest. Tasks named in `done` are treated as
already complete (cached) and not run again. Return {name: result}."""
raise NotImplementedError("run_pipeline() arrives in Step 3")
# ---------- Step 4: resume a half-finished run ----------
def resume(tasks, ctx, prior, sleep=time.sleep):
"""Re-run only what is not already done: tasks that finished ok in `prior` are kept and skipped, the
rest (failed, skipped or never run) run again, along with anything downstream of them."""
raise NotImplementedError("resume() arrives in Step 4")
# ---------- Step 5: report ----------
def summary(results):
"""A run report: counts per status, the names that failed, the total attempts across all tasks, and
whether the whole run succeeded (every task ok)."""
raise NotImplementedError("summary() arrives in Step 5")pipeline.pytry_it.py