diff --git a/releasenotes/notes/prunesearch-finish-vertex-runtimeerror-9c4a1e7b2f83d605.yaml b/releasenotes/notes/prunesearch-finish-vertex-runtimeerror-9c4a1e7b2f83d605.yaml new file mode 100644 index 0000000000..9a67f0f8d6 --- /dev/null +++ b/releasenotes/notes/prunesearch-finish-vertex-runtimeerror-9c4a1e7b2f83d605.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + Fixed :func:`~rustworkx.bfs_search`, :func:`~rustworkx.dfs_search` and + :func:`~rustworkx.dijkstra_search` raising a + ``pyo3_runtime.PanicException`` when a visitor raises + :class:`~rustworkx.visit.PruneSearch` from its ``finish_vertex`` event. + Pruning is not possible once a vertex is finished, so this was always an + error, but ``PanicException`` inherits from ``BaseException`` instead of + ``Exception``, so it was not caught by an ``except Exception`` block, and a + panic message was printed to ``stderr`` alongside the Python traceback. A + ``RuntimeError`` is now raised instead. Refer to + `#1383 `__ for more details. diff --git a/rustworkx/__init__.py b/rustworkx/__init__.py index 24660f76c5..3a737647c3 100644 --- a/rustworkx/__init__.py +++ b/rustworkx/__init__.py @@ -1834,8 +1834,8 @@ def tree_edge(self, edge): .. note:: - An exception is raised if the :class:`~rustworkx.visit.PruneSearch` is - raised in the :class:`~rustworkx.visit.BFSVisitor.finish_vertex` event. + A ``RuntimeError`` is raised if :class:`~rustworkx.visit.PruneSearch` is + raised in the :meth:`~rustworkx.visit.BFSVisitor.finish_vertex` event. :param graph: The graph to be used. This can be a :class:`~rustworkx.PyGraph` @@ -1930,8 +1930,8 @@ def tree_edge(self, edge): .. note:: - An exception is raised if the :class:`~rustworkx.visit.PruneSearch` is - raised in the :class:`~rustworkx.visit.DFSVisitor.finish_vertex` event. + A ``RuntimeError`` is raised if :class:`~rustworkx.visit.PruneSearch` is + raised in the :meth:`~rustworkx.visit.DFSVisitor.finish_vertex` event. :param graph: The graph to be used. This can be a :class:`~rustworkx.PyGraph` or a :class:`~rustworkx.PyDiGraph` @@ -2042,8 +2042,8 @@ def edge_relaxed(self, edge): .. note:: - An exception is raised if the :class:`~rustworkx.visit.PruneSearch` is - raised in the :class:`~rustworkx.visit.DijkstraVisitor.finish_vertex` event. + A ``RuntimeError`` is raised if :class:`~rustworkx.visit.PruneSearch` is + raised in the :meth:`~rustworkx.visit.DijkstraVisitor.finish_vertex` event. :param graph: The graph to be used. This can be a :class:`~rustworkx.PyGraph` or a :class:`~rustworkx.PyDiGraph`. diff --git a/src/traversal/bfs_visit.rs b/src/traversal/bfs_visit.rs index ef7f87ce27..16b7220436 100644 --- a/src/traversal/bfs_visit.rs +++ b/src/traversal/bfs_visit.rs @@ -10,6 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. +use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use petgraph::stable_graph::NodeIndex; @@ -33,6 +34,11 @@ pub fn bfs_handler( vis: &PyBfsVisitor, event: BfsEvent>, ) -> PyResult> { + // There is nothing left to prune once a vertex is finished, and the core + // traversal panics on a `Prune` for that event. Catch it here instead, so + // Python callers get a regular exception. + let is_finish = matches!(&event, BfsEvent::Finish(..)); + let res = match event { BfsEvent::Discover(u) => vis.discover_vertex.call1(py, (u.index(),)), BfsEvent::TreeEdge(u, v, weight) => { @@ -57,7 +63,13 @@ pub fn bfs_handler( match res { Err(e) => { if e.is_instance_of::(py) { - Ok(Control::Prune) + if is_finish { + Err(PyRuntimeError::new_err( + "Pruning on the `finish_vertex` event is not supported", + )) + } else { + Ok(Control::Prune) + } } else if e.is_instance_of::(py) { Ok(Control::Break(())) } else { diff --git a/src/traversal/dfs_visit.rs b/src/traversal/dfs_visit.rs index b70d3263db..aea2b271ff 100644 --- a/src/traversal/dfs_visit.rs +++ b/src/traversal/dfs_visit.rs @@ -10,6 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. +use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use petgraph::stable_graph::NodeIndex; @@ -32,6 +33,11 @@ pub fn dfs_handler( vis: &PyDfsVisitor, event: DfsEvent>, ) -> PyResult> { + // There is nothing left to prune once a vertex is finished, and the core + // traversal panics on a `Prune` for that event. Catch it here instead, so + // Python callers get a regular exception. + let is_finish = matches!(&event, DfsEvent::Finish(..)); + let res = match event { DfsEvent::Discover(u, Time(t)) => vis.discover_vertex.call1(py, (u.index(), t)), DfsEvent::TreeEdge(u, v, weight) => { @@ -52,7 +58,13 @@ pub fn dfs_handler( match res { Err(e) => { if e.is_instance_of::(py) { - Ok(Control::Prune) + if is_finish { + Err(PyRuntimeError::new_err( + "Pruning on the `finish_vertex` event is not supported", + )) + } else { + Ok(Control::Prune) + } } else { Err(e) } diff --git a/src/traversal/dijkstra_visit.rs b/src/traversal/dijkstra_visit.rs index 8b44ade6c4..47005a91c6 100644 --- a/src/traversal/dijkstra_visit.rs +++ b/src/traversal/dijkstra_visit.rs @@ -10,6 +10,7 @@ // License for the specific language governing permissions and limitations // under the License. +use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use petgraph::stable_graph::NodeIndex; @@ -32,6 +33,11 @@ pub fn dijkstra_handler( vis: &PyDijkstraVisitor, event: DijkstraEvent, f64>, ) -> PyResult> { + // There is nothing left to prune once a vertex is finished, and the core + // traversal panics on a `Prune` for that event. Catch it here instead, so + // Python callers get a regular exception. + let is_finish = matches!(&event, DijkstraEvent::Finish(..)); + let res = match event { DijkstraEvent::Discover(u, score) => vis.discover_vertex.call1(py, (u.index(), score)), DijkstraEvent::ExamineEdge(u, v, weight) => { @@ -52,7 +58,13 @@ pub fn dijkstra_handler( match res { Err(e) => { if e.is_instance_of::(py) { - Ok(Control::Prune) + if is_finish { + Err(PyRuntimeError::new_err( + "Pruning on the `finish_vertex` event is not supported", + )) + } else { + Ok(Control::Prune) + } } else if e.is_instance_of::(py) { Ok(Control::Break(())) } else { diff --git a/src/traversal/mod.rs b/src/traversal/mod.rs index 33db7361a7..cb7b7e700d 100644 --- a/src/traversal/mod.rs +++ b/src/traversal/mod.rs @@ -416,8 +416,8 @@ pub fn descendants(graph: &digraph::PyDiGraph, node: usize) -> PyResult