mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
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:
parent
f819900245
commit
c1b42db9e4
6 changed files with 44 additions and 7 deletions
|
@ -106,6 +106,14 @@
|
||||||
function, the graph cannot be modified, and therefore no more nodes can be
|
function, the graph cannot be modified, and therefore no more nodes can be
|
||||||
added using :meth:`~TopologicalSorter.add`.
|
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()
|
.. method:: is_active()
|
||||||
|
|
||||||
Returns ``True`` if more progress can be made and ``False`` otherwise.
|
Returns ``True`` if more progress can be made and ``False`` otherwise.
|
||||||
|
|
|
@ -607,6 +607,15 @@ getopt
|
||||||
* Add support for returning intermixed options and non-option arguments in order.
|
* Add support for returning intermixed options and non-option arguments in order.
|
||||||
(Contributed by Serhiy Storchaka in :gh:`126390`.)
|
(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
|
http
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
@ -90,13 +90,17 @@ class TopologicalSorter:
|
||||||
still be used to obtain as many nodes as possible until cycles block more
|
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
|
progress. After a call to this function, the graph cannot be modified and
|
||||||
therefore no more nodes can be added using "add".
|
therefore no more nodes can be added using "add".
|
||||||
"""
|
|
||||||
if self._ready_nodes is not None:
|
|
||||||
raise ValueError("cannot prepare() more than once")
|
|
||||||
|
|
||||||
self._ready_nodes = [
|
Raise ValueError if nodes have already been passed out of the sorter.
|
||||||
i.node for i in self._node2info.values() if i.npredecessors == 0
|
|
||||||
]
|
"""
|
||||||
|
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
|
||||||
|
]
|
||||||
# ready_nodes is set before we look for cycles on purpose:
|
# ready_nodes is set before we look for cycles on purpose:
|
||||||
# if the user wants to catch the CycleError, that's fine,
|
# if the user wants to catch the CycleError, that's fine,
|
||||||
# they can continue using the instance to grab as many
|
# they can continue using the instance to grab as many
|
||||||
|
|
|
@ -140,9 +140,21 @@ class TestTopologicalSort(unittest.TestCase):
|
||||||
def test_prepare_multiple_times(self):
|
def test_prepare_multiple_times(self):
|
||||||
ts = graphlib.TopologicalSorter()
|
ts = graphlib.TopologicalSorter()
|
||||||
ts.prepare()
|
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()
|
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):
|
def test_invalid_nodes_in_done(self):
|
||||||
ts = graphlib.TopologicalSorter()
|
ts = graphlib.TopologicalSorter()
|
||||||
ts.add(1, 2, 3, 4)
|
ts.add(1, 2, 3, 4)
|
||||||
|
|
|
@ -1483,6 +1483,7 @@ Michael Pomraning
|
||||||
Martin Pool
|
Martin Pool
|
||||||
Iustin Pop
|
Iustin Pop
|
||||||
Claudiu Popa
|
Claudiu Popa
|
||||||
|
Daniel Pope
|
||||||
Nick Pope
|
Nick Pope
|
||||||
John Popplewell
|
John Popplewell
|
||||||
Matheus Vieira Portela
|
Matheus Vieira Portela
|
||||||
|
|
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue