gh-130914: Make graphlib.TopologicalSorter.prepare() idempotent (#131317)

Closes #130914: Make graphlib.TopologicalSorter.prepare() idempotent

Relax the rules so that `.prepare()` can be called multiple times, provided that no work has been passed out by `.get_ready()` yet.
This commit is contained in:
Daniel Pope 2025-03-18 21:28:00 +00:00 committed by GitHub
parent f819900245
commit c1b42db9e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 44 additions and 7 deletions

View file

@ -106,6 +106,14 @@
function, the graph cannot be modified, and therefore no more nodes can be
added using :meth:`~TopologicalSorter.add`.
A :exc:`ValueError` will be raised if the sort has been started by
:meth:`~.static_order` or :meth:`~.get_ready`.
.. versionchanged:: next
``prepare()`` can now be called more than once as long as the sort has
not started. Previously this raised :exc:`ValueError`.
.. method:: is_active()
Returns ``True`` if more progress can be made and ``False`` otherwise.

View file

@ -607,6 +607,15 @@ getopt
* Add support for returning intermixed options and non-option arguments in order.
(Contributed by Serhiy Storchaka in :gh:`126390`.)
graphlib
--------
* Allow :meth:`graphlib.TopologicalSorter.prepare` to be called more than once
as long as sorting has not started.
(Contributed by Daniel Pope in :gh:`130914`)
http
----

View file

@ -90,10 +90,14 @@ class TopologicalSorter:
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".
"""
if self._ready_nodes is not None:
raise ValueError("cannot prepare() more than once")
Raise ValueError if nodes have already been passed out of the sorter.
"""
if self._npassedout > 0:
raise ValueError("cannot prepare() after starting sort")
if self._ready_nodes is None:
self._ready_nodes = [
i.node for i in self._node2info.values() if i.npredecessors == 0
]

View file

@ -140,7 +140,19 @@ class TestTopologicalSort(unittest.TestCase):
def test_prepare_multiple_times(self):
ts = graphlib.TopologicalSorter()
ts.prepare()
with self.assertRaisesRegex(ValueError, r"cannot prepare\(\) more than once"):
ts.prepare()
def test_prepare_after_pass_out(self):
ts = graphlib.TopologicalSorter({'a': 'bc'})
ts.prepare()
self.assertEqual(set(ts.get_ready()), {'b', 'c'})
with self.assertRaisesRegex(ValueError, r"cannot prepare\(\) after starting sort"):
ts.prepare()
def test_prepare_cycleerror_each_time(self):
ts = graphlib.TopologicalSorter({'a': 'b', 'b': 'a'})
for attempt in range(1, 4):
with self.assertRaises(graphlib.CycleError, msg=f"{attempt=}"):
ts.prepare()
def test_invalid_nodes_in_done(self):

View file

@ -1483,6 +1483,7 @@ Michael Pomraning
Martin Pool
Iustin Pop
Claudiu Popa
Daniel Pope
Nick Pope
John Popplewell
Matheus Vieira Portela

View file

@ -0,0 +1,3 @@
Allow :meth:`graphlib.TopologicalSorter.prepare` to be called more than once
as long as sorting has not started.
Patch by Daniel Pope.