diff --git a/newsfragments/3334.added.md b/newsfragments/3334.added.md new file mode 100644 index 00000000..fd3824ad --- /dev/null +++ b/newsfragments/3334.added.md @@ -0,0 +1 @@ +Add `PyErr::Display` for all Python versions, and FFI symbol `PyErr_DisplayException` for Python 3.12. diff --git a/pyo3-ffi/src/pythonrun.rs b/pyo3-ffi/src/pythonrun.rs index 196cf535..e5f20de0 100644 --- a/pyo3-ffi/src/pythonrun.rs +++ b/pyo3-ffi/src/pythonrun.rs @@ -15,6 +15,9 @@ extern "C" { pub fn PyErr_PrintEx(arg1: c_int); #[cfg_attr(PyPy, link_name = "PyPyErr_Display")] pub fn PyErr_Display(arg1: *mut PyObject, arg2: *mut PyObject, arg3: *mut PyObject); + + #[cfg(Py_3_12)] + pub fn PyErr_DisplayException(exc: *mut PyObject); } // skipped PyOS_InputHook diff --git a/src/err/mod.rs b/src/err/mod.rs index f50b9568..a824a383 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -431,13 +431,32 @@ impl PyErr { } /// Prints a standard traceback to `sys.stderr`. + pub fn display(&self, py: Python<'_>) { + #[cfg(Py_3_12)] + unsafe { + ffi::PyErr_DisplayException(self.value(py).as_ptr()) + } + + #[cfg(not(Py_3_12))] + unsafe { + ffi::PyErr_Display( + self.get_type(py).as_ptr(), + self.value(py).as_ptr(), + self.traceback(py) + .map_or(std::ptr::null_mut(), PyTraceback::as_ptr), + ) + } + } + + /// Calls `sys.excepthook` and then prints a standard traceback to `sys.stderr`. pub fn print(&self, py: Python<'_>) { self.clone_ref(py).restore(py); unsafe { ffi::PyErr_PrintEx(0) } } - /// Prints a standard traceback to `sys.stderr`, and sets - /// `sys.last_{type,value,traceback}` attributes to this exception's data. + /// Calls `sys.excepthook` and then prints a standard traceback to `sys.stderr`. + /// + /// Additionally sets `sys.last_{type,value,traceback,exc}` attributes to this exception. pub fn print_and_set_sys_last_vars(&self, py: Python<'_>) { self.clone_ref(py).restore(py); unsafe { ffi::PyErr_PrintEx(1) } diff --git a/src/exceptions.rs b/src/exceptions.rs index 9e20b1ce..64a3a6f0 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -813,20 +813,20 @@ mod tests { let err: PyErr = gaierror::new_err(()); let socket = py .import("socket") - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("could not import socket"); let d = PyDict::new(py); d.set_item("socket", socket) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("could not setitem"); d.set_item("exc", err) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("could not setitem"); py.run("assert isinstance(exc, socket.gaierror)", None, Some(d)) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("assertion failed"); }); } @@ -837,15 +837,15 @@ mod tests { let err: PyErr = MessageError::new_err(()); let email = py .import("email") - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("could not import email"); let d = PyDict::new(py); d.set_item("email", email) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("could not setitem"); d.set_item("exc", err) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("could not setitem"); py.run( @@ -853,7 +853,7 @@ mod tests { None, Some(d), ) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .expect("assertion failed"); }); } diff --git a/src/instance.rs b/src/instance.rs index c75574ba..8ae94b37 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1228,7 +1228,7 @@ a = A() Python::with_gil(|py| { let v = py .eval("...", None, None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap() .to_object(py); diff --git a/src/marker.rs b/src/marker.rs index fb1d7472..4dd51000 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -1041,7 +1041,7 @@ mod tests { // Make sure builtin names are accessible let v: i32 = py .eval("min(1, 2)", None, None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap() .extract() .unwrap(); @@ -1158,7 +1158,10 @@ mod tests { Python::with_gil(|py| { assert_eq!(py.Ellipsis().to_string(), "Ellipsis"); - let v = py.eval("...", None, None).map_err(|e| e.print(py)).unwrap(); + let v = py + .eval("...", None, None) + .map_err(|e| e.display(py)) + .unwrap(); assert!(v.eq(py.Ellipsis()).unwrap()); }); diff --git a/src/types/any.rs b/src/types/any.rs index 2c0b1789..a9ae30b6 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1402,7 +1402,10 @@ class SimpleClass: #[test] fn test_is_ellipsis() { Python::with_gil(|py| { - let v = py.eval("...", None, None).map_err(|e| e.print(py)).unwrap(); + let v = py + .eval("...", None, None) + .map_err(|e| e.display(py)) + .unwrap(); assert!(v.is_ellipsis()); diff --git a/tests/test_append_to_inittab.rs b/tests/test_append_to_inittab.rs index 220a7391..e0a57da1 100644 --- a/tests/test_append_to_inittab.rs +++ b/tests/test_append_to_inittab.rs @@ -26,7 +26,7 @@ assert module_with_functions.foo() == 123 None, None, ) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); }) } diff --git a/tests/test_class_new.rs b/tests/test_class_new.rs index 77c571ea..8cb42686 100644 --- a/tests/test_class_new.rs +++ b/tests/test_class_new.rs @@ -127,7 +127,7 @@ fn new_with_two_args() { let typeobj = py.get_type::(); let wrp = typeobj .call((10, 20), None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); let obj = wrp.downcast::>().unwrap(); let obj_ref = obj.borrow(); @@ -172,7 +172,7 @@ assert c.from_rust is False let globals = PyModule::import(py, "__main__").unwrap().dict(); globals.set_item("SuperClass", super_cls).unwrap(); py.run(source, Some(globals), None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); }); } diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs index f863afdc..37b2d060 100644 --- a/tests/test_datetime.rs +++ b/tests/test_datetime.rs @@ -86,7 +86,7 @@ fn test_time_check() { fn test_datetime_check() { Python::with_gil(|py| { let (obj, sub_obj, sub_sub_obj) = _get_subclasses(py, "datetime", "2018, 1, 1, 13, 30, 15") - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); unsafe { PyDateTime_IMPORT() } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 8c46a4de..aa4166a7 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -26,7 +26,7 @@ fn subclass() { None, Some(d), ) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); }); } diff --git a/tests/test_proto_methods.rs b/tests/test_proto_methods.rs index 7b14d4e1..ab36d59f 100644 --- a/tests/test_proto_methods.rs +++ b/tests/test_proto_methods.rs @@ -689,7 +689,7 @@ asyncio.run(main()) let globals = PyModule::import(py, "__main__").unwrap().dict(); globals.set_item("Once", once).unwrap(); py.run(source, Some(globals), None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); }); } @@ -746,7 +746,7 @@ asyncio.run(main()) .set_item("AsyncIterator", py.get_type::()) .unwrap(); py.run(source, Some(globals), None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); }); } @@ -815,7 +815,7 @@ assert c.counter.count == 1 let globals = PyModule::import(py, "__main__").unwrap().dict(); globals.set_item("Counter", counter).unwrap(); py.run(source, Some(globals), None) - .map_err(|e| e.print(py)) + .map_err(|e| e.display(py)) .unwrap(); }); }