Training Pipeline Orchestration Mock DAG in Python
Build a mock DAG orchestrator that runs ML pipeline stages in dependency order using topological sorting (Kahn's algorithm).
Python code
58 linesfrom collections import deque
from dataclasses import dataclass, field
@dataclass
class DAGNode:
name: str
task: callable
dependencies: list[str] = field(default_factory=list)
class MockDAG:
def __init__(self, nodes: list[DAGNode]):
self.nodes = {n.name: n for n in nodes}
self.executed: list[str] = []
def run(self):
# Build adjacency and in-degree from dependencies
adjacency = {name: [] for name in self.nodes}
in_degree = {name: 0 for name in self.nodes}
for name, node in self.nodes.items():
for dep in node.dependencies:
adjacency[dep].append(name)
in_degree[name] += 1
# Topological sort via Kahn's algorithm
queue = deque([n for n, d in in_degree.items() if d == 0])
while queue:
current = queue.popleft()
node = self.nodes[current]
node.task()
self.executed.append(node.name)
for neighbor in adjacency[current]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
if len(self.executed) != len(self.nodes):
raise RuntimeError("DAG contains cycles")
def main():
# Example: training pipeline with data loading → preprocessing → training → evaluation
dag = MockDAG(nodes=[
DAGNode(name="load_data", task=lambda: print("Loading data")),
DAGNode(name="preprocess", task=lambda: print("Preprocessing"),
dependencies=["load_data"]),
DAGNode(name="train", task=lambda: print("Training model"),
dependencies=["preprocess"]),
DAGNode(name="evaluate", task=lambda: print("Evaluating"),
dependencies=["train"]),
])
dag.run()
print("Execution order:", dag.executed)
if __name__ == "__main__":
main()
Output
Loading data
Preprocessing
Training model
Evaluating
Execution order: ['load_data', 'preprocess', 'train', 'evaluate']
How it works
The DAGNode dataclass captures each pipeline stage with its name, callable, and declared dependencies. The MockDAG.run() method builds an adjacency list and computes in-degrees, then repeatedly executes nodes with zero in-degree (ready nodes) via a queue. Each executed node decrements the in-degree of its downstream dependents, making them eligible when all prerequisites complete. The while loop guarantees that every node runs exactly once in valid topological order, and the cycle check catches malformed pipelines that would deadlock.
Common mistakes
- Forgetting that dependencies list must reference existing node names, causing KeyError at build time.
- Assuming insertion order equals dependency order — the graph must be traversed, not listed.
- Overlooking cycle handling; a circular dependency will raise RuntimeError but only after consuming all queue items.
Variations
- Use graphlib.TopologicalSorter from the standard library to replace the custom Kahn implementation.
- Add parallel execution of independent nodes using concurrent.futures.ThreadPoolExecutor.
Real-world use cases
- Orchestrating batch ML jobs where feature extraction, model training, and evaluation must respect prerequisite stage order.
- Powering experiment pipelines that fan out data transforms and fan back in for aggregate metric computation.
- Scheduling cascading ETL steps in a regression-test harness to validate new data flows before deployment.
Sponsored
More from ML engineering pipelines
- Bayesian Optimization in Python: A Simplified Mock Implementation medium
- Build a Data Helper Class in Python for ML Pipelines easy
- Build a Mock Random Forest Classifier in Python easy
- Champion Challenger Deployment Mock in Python easy
- Compare Model A vs Model B Metrics in Python easy
- Create a Minimal Great Expectations Suite Mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.