diff --git a/newsfragments/5887.added.md b/newsfragments/5887.added.md new file mode 100644 index 00000000000..a10d4ed0130 --- /dev/null +++ b/newsfragments/5887.added.md @@ -0,0 +1 @@ +Add `PyErr::set_context`, `PyErr::context`, `ffi::PyErr_GetHandledException` and `ffi::PyErr_SetHandledException`. \ No newline at end of file diff --git a/pyo3-ffi/src/pyerrors.rs b/pyo3-ffi/src/pyerrors.rs index bd89c5aab57..2d4835033c7 100644 --- a/pyo3-ffi/src/pyerrors.rs +++ b/pyo3-ffi/src/pyerrors.rs @@ -53,6 +53,12 @@ extern_libpython! { pub fn PyErr_GetRaisedException() -> *mut PyObject; #[cfg(Py_3_12)] pub fn PyErr_SetRaisedException(exc: *mut PyObject); + #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyErr_GetHandledException")] + pub fn PyErr_GetHandledException() -> *mut PyObject; + #[cfg(Py_3_11)] + #[cfg_attr(PyPy, link_name = "PyPyErr_SetHandledException")] + pub fn PyErr_SetHandledException(exc: *mut PyObject); #[cfg_attr(PyPy, link_name = "PyPyException_SetTraceback")] pub fn PyException_SetTraceback(arg1: *mut PyObject, arg2: *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyException_GetTraceback")] diff --git a/src/err/mod.rs b/src/err/mod.rs index af306a8c79a..53f97fa1c45 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -604,6 +604,29 @@ impl PyErr { } } + /// Return the context (either an exception instance, or None, set by an implicit exception + /// during handling of another exception) associated with the exception, as accessible from + /// Python through `__context__`. + pub fn context(&self, py: Python<'_>) -> Option { + unsafe { + ffi::PyException_GetContext(self.value(py).as_ptr()) + .assume_owned_or_opt(py) + .map(Self::from_value) + } + } + + /// Set the context associated with the exception, pass `None` to clear it. + pub fn set_context(&self, py: Python<'_>, context: Option) { + let value = self.value(py); + let context = context.map(|err| err.into_value(py)); + unsafe { + ffi::PyException_SetContext( + value.as_ptr(), + context.map_or(std::ptr::null_mut(), Py::into_ptr), + ); + } + } + /// Equivalent to calling `add_note` on the exception in Python. #[cfg(Py_3_11)] pub fn add_note IntoPyObject<'py, Target = PyString>>( @@ -1027,4 +1050,19 @@ mod tests { ); }); } + + #[test] + fn test_set_context() { + Python::attach(|py| { + let err = PyErr::new::("original error"); + assert!(err.context(py).is_none()); + + let context = PyErr::new::("context error"); + err.set_context(py, Some(context)); + assert!(err.context(py).unwrap().is_instance_of::(py)); + + err.set_context(py, None); + assert!(err.context(py).is_none()); + }) + } }