AI Skill Report Card
Resolving Dependency Graphs
Quick Start15 / 15
Pythonfrom graphlib import TopologicalSorter, CycleError graph = { "deploy": {"test", "build"}, "test": {"build"}, "build": {"lint"}, "lint": set(), } ts = TopologicalSorter(graph) print(list(ts.static_order())) # ['lint', 'build', 'test', 'deploy']
graph[node] = {dependencies} — keys depend on their values (values must be resolved first).
Recommendation▾
Add an example showing recovery/handling of CycleError in a real workflow (e.g., reporting which nodes form the cycle to the user) rather than just showing the raised exception
Workflow14 / 15
Progress:
- Build the dependency dict:
{node: set_of_prerequisites} - Choose API:
static_order()for a simple full sort, or the manual protocol for streaming/parallel execution - Handle
CycleErrorif the graph might contain cycles - (If parallel) drive the sorter manually with
prepare()/get_ready()/done()
Simple case — full ordering:
Pythonts = TopologicalSorter(graph) order = list(ts.static_order())
Parallel/streaming case — process ready nodes as they unblock:
Pythonts = TopologicalSorter(graph) ts.prepare() # raises CycleError here if cyclic while ts.is_active(): ready = ts.get_ready() # tuple of nodes with no unresolved deps for node in ready: do_work(node) ts.done(node) # mark complete, unblocks dependents
get_ready() returns all currently unblocked nodes — dispatch them concurrently (thread pool, async tasks, subprocess batch) for real parallelism.
Adding edges incrementally:
Pythonts = TopologicalSorter() ts.add(node, *predecessors) # can call repeatedly to build up graph ts.add("build", "lint") ts.add("test", "build")
Recommendation▾
Consider showing the async/thread-pool dispatch pattern concretely for the parallel case, since it's mentioned but not demonstrated in code
Examples17 / 20
Example 1: Detecting a cycle Input:
Pythongraph = {"a": {"b"}, "b": {"c"}, "c": {"a"}} ts = TopologicalSorter(graph) ts.static_order()
Output:
graphlib.CycleError: ('nodes are in a cycle', ['a', 'b', 'c', 'a'])
Example 2: Parallel build batches Input:
Pythongraph = {"link": {"a.o", "b.o"}, "a.o": {"a.c"}, "b.o": {"b.c"}, "a.c": set(), "b.c": set()} ts = TopologicalSorter(graph) ts.prepare() batches = [] while ts.is_active(): ready = ts.get_ready() batches.append(sorted(ready)) for n in ready: ts.done(n)
Output:
batches == [['a.c', 'b.c'], ['a.o', 'b.o'], ['link']]
Example 3: Independent nodes (no dependencies) Input:
Pythongraph = {"x": set(), "y": set()} list(TopologicalSorter(graph).static_order())
Output: ['x', 'y'] (order among unrelated nodes is arbitrary but valid)
Recommendation▾
A brief note on performance characteristics or node count limits would round out completeness for large-scale build systems
Best Practices
- Call
prepare()explicitly when using the manual protocol — it finalizes the graph and does the upfront cycle check. - Never mutate the graph (via
add()) after callingprepare(); it raisesValueError. - Each node returned by
get_ready()must eventually get a matchingdone()call, oris_active()stays true forever. - Nodes must be hashable; they don't need to be pre-declared as dict keys — referencing them as a dependency is enough.
- Use
static_order()when you just need a single sequential list; it's a thin convenience wrapper around the manual protocol.
Common Pitfalls
- Don't confuse edge direction:
graph[x] = {y}means x depends on y (y runs first), not the reverse. - Don't call
get_ready()again without callingdone()on previously returned nodes — already-returned nodes won't reappear, but forgettingdone()stalls the sort. - Don't assume
get_ready()order is deterministic/meaningful — treat it as an unordered ready-set. - Don't reuse a
TopologicalSorterinstance after exhausting it — create a new one per sort.