Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 26 additions & 24 deletions Lib/graphlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@ def __init__(self, node):


class CycleError(ValueError):
"""Subclass of ValueError raised by TopologicalSorter.prepare if cycles
exist in the working graph.

If multiple cycles exist, only one undefined choice among them will be reported
and included in the exception. The detected cycle can be accessed via the second
element in the *args* attribute of the exception instance and consists in a list
of nodes, such that each node is, in the graph, an immediate predecessor of the
next node in the list. In the reported list, the first and the last node will be
the same, to make it clear that it is cyclic.
"""Subclass of ValueError raised by TopologicalSorter when cycles exist in the graph.

When a cycle is detected, only one arbitrary cycle will be reported and included in the
exception. The cycle can be accessed via the second element in the *args*
attribute of the exception instance. The cycle is represented as a list of nodes
where each node is a direct predecessor of the next node in the list. The first
and last nodes in the list are identical to indicate the cycle's completion.
"""

pass
Expand Down Expand Up @@ -85,11 +83,16 @@ def add(self, node, *predecessors):

def prepare(self):
"""Mark the graph as finished and check for cycles in the graph.

If any cycle is detected, "CycleError" will be raised, but "get_ready" can
still be used to obtain as many nodes as possible until cycles block more
progress. After a call to this function, the graph cannot be modified and
therefore no more nodes can be added using "add".
This method must be called exactly once, after all nodes are added and
before calling get_ready(). It checks for cycles and initializes the
internal data structures for processing.

Even if a cycle is detected and CycleError is raised, the graph can still
be processed up to the nodes involved in the cycle.

Raises:
CycleError: If any cycles are detected in the graph.
ValueError: If prepare() has already been called.
"""
if self._ready_nodes is not None:
raise ValueError("cannot prepare() more than once")
Expand All @@ -101,14 +104,13 @@ def prepare(self):
# if the user wants to catch the CycleError, that's fine,
# they can continue using the instance to grab as many
# nodes as possible before cycles block more progress
cycle = self._find_cycle()
if cycle:
raise CycleError(f"nodes are in a cycle", cycle)
if (cycle := self._find_cycle()):
raise CycleError("nodes are in a cycle", cycle)

def get_ready(self):
"""Return a tuple of all the nodes that are ready.
"""Return a tuple of all the ready nodes.

Initially it returns all nodes with no predecessors; once those are marked
Initially, it returns all nodes with no predecessors; once those are marked
as processed by calling "done", further calls will return all new nodes that
have all their predecessors already processed. Once no more progress can be made,
empty tuples are returned.
Expand All @@ -134,7 +136,7 @@ def get_ready(self):
def is_active(self):
"""Return ``True`` if more progress can be made and ``False`` otherwise.

Progress can be made if cycles do not block the resolution and either there
Progress can be made if cycles do not block the resolution and if there
are still nodes ready that haven't yet been returned by "get_ready" or the
number of nodes marked "done" is less than the number that have been returned
by "get_ready".
Expand All @@ -154,7 +156,7 @@ def done(self, *nodes):
This method unblocks any successor of each node in *nodes* for being returned
in the future by a call to "get_ready".

Raises :exec:`ValueError` if any node in *nodes* has already been marked as
Raises :exc:`ValueError` if any node in *nodes* has already been marked as
processed by a previous call to this method, if a node was not added to the
graph by using "add" or if called without calling "prepare" previously or if
node has not yet been returned by "get_ready".
Expand Down Expand Up @@ -186,8 +188,8 @@ def done(self, *nodes):
# Mark the node as processed
nodeinfo.npredecessors = _NODE_DONE

# Go to all the successors and reduce the number of predecessors, collecting all the ones
# that are ready to be returned in the next get_ready() call.
# Go to all the successors and reduce the number of predecessors,
# collecting all the ones that are ready to be returned in the next get_ready() call.
for successor in nodeinfo.successors:
successor_info = n2i[successor]
successor_info.npredecessors -= 1
Expand Down Expand Up @@ -233,7 +235,7 @@ def _find_cycle(self):
return None

def static_order(self):
"""Returns an iterable of nodes in a topological order.
"""Returns an iterable of nodes in topological order.

The particular order that is returned may depend on the specific
order in which the items were inserted in the graph.
Expand Down
Loading