From cde9141013a34a802aa65d0aefda589889d8c9b0 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Fri, 21 Jul 2023 14:30:02 +0100 Subject: [PATCH] add PyErr::display --- newsfragments/3334.added.md | 1 + pyo3-ffi/src/pythonrun.rs | 3 +++ src/err/mod.rs | 25 ++++++++++++++++++++++--- src/exceptions.rs | 16 ++++++++-------- src/instance.rs | 2 +- src/marker.rs | 7 +++++-- src/types/any.rs | 5 ++++- tests/test_append_to_inittab.rs | 2 +- tests/test_class_new.rs | 4 ++-- tests/test_datetime.rs | 2 +- tests/test_inheritance.rs | 2 +- tests/test_proto_methods.rs | 6 +++--- 12 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 newsfragments/3334.added.md diff --git a/newsfragments/3334.added.md b/newsfragments/3334.added.md new file mode 100644 index 00000000000..6f6ecc21e32 --- /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 196cf5354e5..e5f20de0058 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 f50b9568763..803aaa706c8 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -431,18 +431,37 @@ impl PyErr { } /// Prints a standard traceback to `sys.stderr`. + #[cfg(Py_3_12)] + pub fn display(&self, py: Python<'_>) { + unsafe { ffi::PyErr_DisplayException(self.value(py).as_ptr()) } + } + + /// Prints a standard traceback to `sys.stderr`. + #[cfg(not(Py_3_12))] + pub fn display(&self, py: Python<'_>) { + 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) } } - /// Returns true if the current exception matches the exception in `exc`. /// /// If `exc` is a class object, this also returns `true` when `self` is an instance of a subclass. diff --git a/src/exceptions.rs b/src/exceptions.rs index 9e20b1cefc9..64a3a6f0afb 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 c75574ba8fe..8ae94b371b7 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 fb1d747284a..4dd51000e54 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 2c0b17892ce..a9ae30b6ff0 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 220a73912f1..e0a57da1b5c 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 77c571eac3f..8cb426861db 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 f863afdce74..37b2d060303 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 8c46a4de4ae..aa4166a754d 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 7b14d4e1278..ab36d59f286 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(); }); }