From 6d8e8b302f6605dbcfe72e82bec28b519d6e14e1 Mon Sep 17 00:00:00 2001 From: Tpt Date: Fri, 18 Aug 2023 15:54:44 +0200 Subject: [PATCH] Unwrap the underlying PyErr when converting an io::Error back to a PyErr Exposes it directly instead of loosing all information outside the message --- newsfragments/3402.changed.md | 1 + src/err/impls.rs | 37 +++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 newsfragments/3402.changed.md diff --git a/newsfragments/3402.changed.md b/newsfragments/3402.changed.md new file mode 100644 index 00000000000..2328efdedb8 --- /dev/null +++ b/newsfragments/3402.changed.md @@ -0,0 +1 @@ +`std::io::Error` cast: Reuses the underlying `PyErr` Python error instead of wrapping it again if the `io::Error` has been built using a Python exception diff --git a/src/err/impls.rs b/src/err/impls.rs index a814c17d964..f49ac3eb1a8 100644 --- a/src/err/impls.rs +++ b/src/err/impls.rs @@ -29,13 +29,19 @@ impl From for io::Error { io::ErrorKind::Other } }); - io::Error::new(kind, format!("Python exception: {}", err)) + io::Error::new(kind, err) } } -/// Create `OSError` from `io::Error` +/// Create `PyErr` from `io::Error` +/// (`OSError` except if the `io::Error` is wrapping a Python exception, +/// in this case the exception is returned) impl From for PyErr { fn from(err: io::Error) -> PyErr { + // If the error wraps a Python error we return it + if err.get_ref().map_or(false, |e| e.is::()) { + return *err.into_inner().unwrap().downcast().unwrap(); + } match err.kind() { io::ErrorKind::BrokenPipe => exceptions::PyBrokenPipeError::new_err(err), io::ErrorKind::ConnectionRefused => exceptions::PyConnectionRefusedError::new_err(err), @@ -113,24 +119,29 @@ impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError); #[cfg(test)] mod tests { - use crate::PyErr; + use crate::{PyErr, Python}; use std::io; #[test] fn io_errors() { let check_err = |kind, expected_ty| { - let rust_err = io::Error::new(kind, "some error msg"); + Python::with_gil(|py| { + let rust_err = io::Error::new(kind, "some error msg"); + + let py_err: PyErr = rust_err.into(); + let py_err_msg = format!("{}: some error msg", expected_ty); + assert_eq!(py_err.to_string(), py_err_msg); + let py_error_clone = py_err.clone_ref(py); - let py_err: PyErr = rust_err.into(); - let py_err_msg = format!("{}: some error msg", expected_ty); - assert_eq!(py_err.to_string(), py_err_msg); + let rust_err_from_py_err: io::Error = py_err.into(); + assert_eq!(rust_err_from_py_err.to_string(), py_err_msg); + assert_eq!(rust_err_from_py_err.kind(), kind); - let rust_err_from_py_err: io::Error = py_err.into(); - assert_eq!( - rust_err_from_py_err.to_string(), - format!("Python exception: {}", py_err_msg) - ); - assert_eq!(rust_err_from_py_err.kind(), kind); + let py_err_recovered_from_rust_err: PyErr = rust_err_from_py_err.into(); + assert!(py_err_recovered_from_rust_err + .value(py) + .is(py_error_clone.value(py))); // It should be the same exception + }) }; check_err(io::ErrorKind::BrokenPipe, "BrokenPipeError");