Unwrap the underlying PyErr when converting an io::Error back to a PyErr

Exposes it directly instead of loosing all information outside the message
This commit is contained in:
Tpt 2023-08-18 15:54:44 +02:00
parent 7f32ed96db
commit 6d8e8b302f
2 changed files with 25 additions and 13 deletions

View File

@ -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

View File

@ -29,13 +29,19 @@ impl From<PyErr> 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<io::Error> 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::<PyErr>()) {
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_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 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 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 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");