From bdd590f495fd872dcc6f845de369c47b5e6a0528 Mon Sep 17 00:00:00 2001 From: Nikola Savic <76233425+nikolasavic3@users.noreply.github.com> Date: Sat, 18 Jan 2025 16:35:13 +0100 Subject: [PATCH 1/4] Update graphlib.py docs: fix CycleError and prepare() documentation clarity and --- Lib/graphlib.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Lib/graphlib.py b/Lib/graphlib.py index 636545648e12d3..311952e71299bd 100644 --- a/Lib/graphlib.py +++ b/Lib/graphlib.py @@ -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 @@ -86,10 +84,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") @@ -106,7 +110,7 @@ def prepare(self): raise CycleError(f"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 as processed by calling "done", further calls will return all new nodes that @@ -154,7 +158,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". @@ -233,7 +237,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. From 1f22c703d567336b11e381d86a1e55a4bf26f810 Mon Sep 17 00:00:00 2001 From: Nikola Savic <76233425+nikolasavic3@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:15:04 +0100 Subject: [PATCH 2/4] Update graphlib.py --- Lib/graphlib.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Lib/graphlib.py b/Lib/graphlib.py index 311952e71299bd..15adee1af31486 100644 --- a/Lib/graphlib.py +++ b/Lib/graphlib.py @@ -8,7 +8,7 @@ class _NodeInfo: __slots__ = "node", "npredecessors", "successors" - + def __init__(self, node): # The node this class is augmenting. self.node = node @@ -25,12 +25,12 @@ def __init__(self, node): class CycleError(ValueError): """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. + + 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 @@ -83,7 +83,6 @@ def add(self, node, *predecessors): def prepare(self): """Mark the graph as finished and check for cycles in the graph. - 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. @@ -105,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(f"nodes are in a cycle: {cycle}") def get_ready(self): """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. @@ -138,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". From 0df382f5cacc52da553ee40eab845937b71da5fc Mon Sep 17 00:00:00 2001 From: Nikola Savic <76233425+nikolasavic3@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:16:35 +0100 Subject: [PATCH 3/4] Update graphlib.py --- Lib/graphlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/graphlib.py b/Lib/graphlib.py index 15adee1af31486..9bd1e029f9c5a9 100644 --- a/Lib/graphlib.py +++ b/Lib/graphlib.py @@ -105,7 +105,7 @@ def prepare(self): # they can continue using the instance to grab as many # nodes as possible before cycles block more progress if cycle := self._find_cycle() - raise CycleError(f"nodes are in a cycle: {cycle}") + raise CycleError(f"nodes are in a cycle", cycle) def get_ready(self): """Return a tuple of all the ready nodes. From 0831bf39a8eb2b66e9ead565c1cef4ce097c3452 Mon Sep 17 00:00:00 2001 From: Nikola Savic <76233425+nikolasavic3@users.noreply.github.com> Date: Sat, 18 Jan 2025 17:34:32 +0100 Subject: [PATCH 4/4] Update graphlib.py --- Lib/graphlib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/graphlib.py b/Lib/graphlib.py index 9bd1e029f9c5a9..19a95d5edfd4d8 100644 --- a/Lib/graphlib.py +++ b/Lib/graphlib.py @@ -8,7 +8,7 @@ class _NodeInfo: __slots__ = "node", "npredecessors", "successors" - + def __init__(self, node): # The node this class is augmenting. self.node = node @@ -104,8 +104,8 @@ 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 - if cycle := self._find_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 ready nodes. @@ -188,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