Skip to content
Open
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/Qiskit/rustworkx/issues/1383>`__ for more details.
12 changes: 6 additions & 6 deletions rustworkx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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`.
Expand Down
14 changes: 13 additions & 1 deletion src/traversal/bfs_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,6 +34,11 @@ pub fn bfs_handler(
vis: &PyBfsVisitor,
event: BfsEvent<NodeIndex, &Py<PyAny>>,
) -> PyResult<Control<()>> {
// 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) => {
Expand All @@ -57,7 +63,13 @@ pub fn bfs_handler(
match res {
Err(e) => {
if e.is_instance_of::<PruneSearch>(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::<StopSearch>(py) {
Ok(Control::Break(()))
} else {
Expand Down
14 changes: 13 additions & 1 deletion src/traversal/dfs_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +33,11 @@ pub fn dfs_handler(
vis: &PyDfsVisitor,
event: DfsEvent<NodeIndex, &Py<PyAny>>,
) -> PyResult<Control<()>> {
// 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) => {
Expand All @@ -52,7 +58,13 @@ pub fn dfs_handler(
match res {
Err(e) => {
if e.is_instance_of::<PruneSearch>(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)
}
Expand Down
14 changes: 13 additions & 1 deletion src/traversal/dijkstra_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +33,11 @@ pub fn dijkstra_handler(
vis: &PyDijkstraVisitor,
event: DijkstraEvent<NodeIndex, &Py<PyAny>, f64>,
) -> PyResult<Control<()>> {
// 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) => {
Expand All @@ -52,7 +58,13 @@ pub fn dijkstra_handler(
match res {
Err(e) => {
if e.is_instance_of::<PruneSearch>(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::<StopSearch>(py) {
Ok(Control::Break(()))
} else {
Expand Down
24 changes: 12 additions & 12 deletions src/traversal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,8 @@ pub fn descendants(graph: &digraph::PyDiGraph, node: usize) -> PyResult<HashSet<
///
/// .. 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 PyDiGraph graph: The graph to be used.
Expand Down Expand Up @@ -562,8 +562,8 @@ pub fn digraph_bfs_search(
///
///
/// .. 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 PyGraph graph: The graph to be used.
Expand Down Expand Up @@ -679,8 +679,8 @@ pub fn graph_bfs_search(
///
///
/// .. 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 PyDiGraph graph: The graph to be used.
/// :param source: An optional list of node indices to use as the starting nodes
Expand Down Expand Up @@ -795,8 +795,8 @@ pub fn digraph_dfs_search(
///
///
/// .. 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 PyGraph graph: The graph to be used.
/// :param source: An optional list of node indices to use as the starting nodes
Expand Down Expand Up @@ -929,8 +929,8 @@ pub fn graph_dfs_search(
///
/// .. 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 PyDiGraph graph: The graph to be used.
/// :param source: An optional list of node indices to use as the starting nodes
Expand Down Expand Up @@ -1072,8 +1072,8 @@ pub fn digraph_dijkstra_search(
///
/// .. 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 PyGraph graph: The graph to be used.
/// :param source: An optional list of node indices to use as the starting nodes
Expand Down
45 changes: 45 additions & 0 deletions tests/digraph/test_bfs_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,51 @@ def gray_target_edge(self, _):
vis = PruneGrayTargetEdge()
rustworkx.digraph_bfs_search(self.graph, [0], vis)

def test_digraph_prune_finish_vertex(self):
class PruneFinishVertex(rustworkx.visit.BFSVisitor):
def finish_vertex(self, v):
raise rustworkx.visit.PruneSearch

vis = PruneFinishVertex()
with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_bfs_search(self.graph, [0], vis)

def test_digraph_prune_finish_vertex_mid_traversal(self):
class PruneSecondFinish(rustworkx.visit.BFSVisitor):
def __init__(self):
self.finished = []

def finish_vertex(self, v):
self.finished.append(v)
if len(self.finished) == 2:
raise rustworkx.visit.PruneSearch

vis = PruneSecondFinish()
with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_bfs_search(self.graph, [0], vis)
self.assertEqual(vis.finished, [0, 2])

def test_digraph_prune_finish_vertex_no_starting_point(self):
class PruneFinishVertex(rustworkx.visit.BFSVisitor):
def finish_vertex(self, v):
raise rustworkx.visit.PruneSearch

with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_bfs_search(self.graph, None, PruneFinishVertex())

def test_digraph_stop_search_finish_vertex(self):
class StopFinishVertex(rustworkx.visit.BFSVisitor):
def __init__(self):
self.finished = []

def finish_vertex(self, v):
self.finished.append(v)
raise rustworkx.visit.StopSearch

vis = StopFinishVertex()
rustworkx.digraph_bfs_search(self.graph, [0], vis)
self.assertEqual(vis.finished, [0])

def test_invalid_source(self):
graph = rustworkx.PyDiGraph()
with self.assertRaises(IndexError):
Expand Down
46 changes: 46 additions & 0 deletions tests/digraph/test_dfs_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,52 @@ def reconstruct_path(self):
pass
self.assertEqual(vis.reconstruct_path(), [0, 2, 5, 3])

def test_digraph_prune_finish_vertex(self):
class PruneFinishVertex(rustworkx.visit.DFSVisitor):
def finish_vertex(self, v, t):
raise rustworkx.visit.PruneSearch

vis = PruneFinishVertex()
with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_dfs_search(self.graph, [0], vis)

def test_digraph_prune_finish_vertex_mid_traversal(self):
class PruneSecondFinish(rustworkx.visit.DFSVisitor):
def __init__(self):
self.finished = []

def finish_vertex(self, v, t):
self.finished.append(v)
if len(self.finished) == 2:
raise rustworkx.visit.PruneSearch

vis = PruneSecondFinish()
with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_dfs_search(self.graph, [0], vis)
self.assertEqual(vis.finished, [6, 3])

def test_digraph_prune_finish_vertex_no_starting_point(self):
class PruneFinishVertex(rustworkx.visit.DFSVisitor):
def finish_vertex(self, v, t):
raise rustworkx.visit.PruneSearch

with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_dfs_search(self.graph, None, PruneFinishVertex())

def test_digraph_stop_search_finish_vertex(self):
class StopFinishVertex(rustworkx.visit.DFSVisitor):
def __init__(self):
self.finished = []

def finish_vertex(self, v, t):
self.finished.append(v)
raise rustworkx.visit.StopSearch

vis = StopFinishVertex()
with self.assertRaises(rustworkx.visit.StopSearch):
rustworkx.digraph_dfs_search(self.graph, [0], vis)
self.assertEqual(vis.finished, [6])

def test_invalid_source(self):
graph = rustworkx.PyDiGraph()
with self.assertRaises(IndexError):
Expand Down
45 changes: 45 additions & 0 deletions tests/digraph/test_dijkstra_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,51 @@ def edge_not_relaxed(self, _):
vis = PruneEdgeNotRelaxed()
rustworkx.digraph_dijkstra_search(self.graph, [0], float, vis)

def test_digraph_prune_finish_vertex(self):
class PruneFinishVertex(rustworkx.visit.DijkstraVisitor):
def finish_vertex(self, v):
raise rustworkx.visit.PruneSearch

vis = PruneFinishVertex()
with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_dijkstra_search(self.graph, [0], float, vis)

def test_digraph_prune_finish_vertex_mid_traversal(self):
class PruneSecondFinish(rustworkx.visit.DijkstraVisitor):
def __init__(self):
self.finished = []

def finish_vertex(self, v):
self.finished.append(v)
if len(self.finished) == 2:
raise rustworkx.visit.PruneSearch

vis = PruneSecondFinish()
with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_dijkstra_search(self.graph, [0], float, vis)
self.assertEqual(vis.finished, [0, 1])

def test_digraph_prune_finish_vertex_no_starting_point(self):
class PruneFinishVertex(rustworkx.visit.DijkstraVisitor):
def finish_vertex(self, v):
raise rustworkx.visit.PruneSearch

with self.assertRaisesRegex(RuntimeError, "finish_vertex"):
rustworkx.digraph_dijkstra_search(self.graph, None, float, PruneFinishVertex())

def test_digraph_stop_search_finish_vertex(self):
class StopFinishVertex(rustworkx.visit.DijkstraVisitor):
def __init__(self):
self.finished = []

def finish_vertex(self, v):
self.finished.append(v)
raise rustworkx.visit.StopSearch

vis = StopFinishVertex()
rustworkx.digraph_dijkstra_search(self.graph, [0], float, vis)
self.assertEqual(vis.finished, [0])

def test_invalid_source(self):
graph = rustworkx.PyDiGraph()
with self.assertRaises(IndexError):
Expand Down
Loading
Loading