Skip to content

Commit

Permalink
Added support for PyErr_WriteUnraisable
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jan 18, 2023
1 parent bed4f9d commit 8dd78f2
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 0 deletions.
1 change: 1 addition & 0 deletions newsfragments/2884.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `PyErr::write_unraisable()` to report an unraisable exception to Python.
9 changes: 9 additions & 0 deletions src/err/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,15 @@ impl PyErr {
unsafe { ffi::PyErr_Restore(ptype, pvalue, ptraceback) }
}

/// Reports the error as unraisable.
///
/// This calls `sys.unraisablehook()` using the current exception and obj argument.
#[inline]
pub fn write_unraisable(self, py: Python<'_>, obj: Option<&PyAny>) {
self.restore(py);
unsafe { ffi::PyErr_WriteUnraisable(obj.map_or(std::ptr::null_mut(), |x| x.as_ptr())) }
}

/// Issues a warning message.
///
/// May return an `Err(PyErr)` if warnings-as-errors is enabled.
Expand Down
49 changes: 49 additions & 0 deletions tests/test_exceptions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg(feature = "macros")]

use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::{exceptions, py_run, PyErr, PyResult};
use std::error::Error;
use std::fmt;
Expand Down Expand Up @@ -96,3 +97,51 @@ fn test_exception_nosegfault() {
assert!(io_err().is_err());
assert!(parse_int().is_err());
}

#[pyfunction]
fn report_unraisable(py: Python) {
use pyo3::exceptions::PyRuntimeError;
let err = PyRuntimeError::new_err("foo");
err.write_unraisable(py, None);

let err = PyRuntimeError::new_err("bar");
err.write_unraisable(py, Some(py.NotImplemented().as_ref(py)));
}

#[test]
fn test_write_unraisable() {
Python::with_gil(|py| {
let report_unraisable = wrap_pyfunction!(report_unraisable, py).unwrap();
let locals = PyDict::new(py);
locals
.set_item("report_unraisable", report_unraisable)
.unwrap();

let source = r#"if True:
import sys
captured = []
def report(data):
captured.append(list(data))
original_hook = sys.unraisablehook
try:
sys.unraisablehook = report
report_unraisable()
assert len(captured) == 2
assert captured[0][0] is RuntimeError
assert str(captured[0][1]) == 'foo'
assert captured[0][4] is None
assert captured[1][0] is RuntimeError
assert str(captured[1][1]) == 'bar'
assert captured[1][4] is NotImplemented
finally:
sys.unraisablehook = original_hook
"#;

py.run(source, Some(locals), None).unwrap();
});
}

0 comments on commit 8dd78f2

Please sign in to comment.