Implement `From<Bound<'py, T>>` for PyErr (#3881)

* Implement `From<Bound<'py, T>>` for PyErr

* Replace PyErr::from_value_bound calls with .into

* Fix From<MyError> expected error message

* Add a trait bound to From<Bound<'py, T>> for PyErr
This commit is contained in:
Lily Foote 2024-02-26 23:14:41 +00:00 committed by GitHub
parent cd1c0dbf39
commit 5c41ea0ade
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 40 additions and 26 deletions

View File

@ -982,6 +982,24 @@ impl PyErrArguments for PyDowncastErrorArguments {
} }
} }
/// Python exceptions that can be converted to [`PyErr`].
///
/// This is used to implement [`From<Bound<'_, T>> for PyErr`].
///
/// Users should not need to implement this trait directly. It is implemented automatically in the
/// [`crate::import_exception!`] and [`crate::create_exception!`] macros.
pub trait ToPyErr {}
impl<'py, T> std::convert::From<Bound<'py, T>> for PyErr
where
T: ToPyErr,
{
#[inline]
fn from(err: Bound<'py, T>) -> PyErr {
PyErr::from_value_bound(err.into_any())
}
}
/// Convert `PyDowncastError` to Python `TypeError`. /// Convert `PyDowncastError` to Python `TypeError`.
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr { impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
fn from(err: PyDowncastError<'_>) -> PyErr { fn from(err: PyDowncastError<'_>) -> PyErr {

View File

@ -54,6 +54,8 @@ macro_rules! impl_exception_boilerplate {
} }
} }
} }
impl $crate::ToPyErr for $name {}
}; };
} }
@ -1074,7 +1076,7 @@ mod tests {
); );
// Restoring should preserve the same error // Restoring should preserve the same error
let e = PyErr::from_value_bound(decode_err.into_any()); let e: PyErr = decode_err.into();
e.restore(py); e.restore(py);
assert_eq!( assert_eq!(

View File

@ -308,7 +308,7 @@ pub use crate::conversion::{AsPyPointer, FromPyObject, FromPyPointer, IntoPy, To
#[allow(deprecated)] #[allow(deprecated)]
pub use crate::conversion::{PyTryFrom, PyTryInto}; pub use crate::conversion::{PyTryFrom, PyTryInto};
pub use crate::err::{ pub use crate::err::{
DowncastError, DowncastIntoError, PyDowncastError, PyErr, PyErrArguments, PyResult, DowncastError, DowncastIntoError, PyDowncastError, PyErr, PyErrArguments, PyResult, ToPyErr,
}; };
pub use crate::gil::GILPool; pub use crate::gil::GILPool;
#[cfg(not(PyPy))] #[cfg(not(PyPy))]

View File

@ -73,9 +73,7 @@ impl<'a> PyStringData<'a> {
match self { match self {
Self::Ucs1(data) => match str::from_utf8(data) { Self::Ucs1(data) => match str::from_utf8(data) {
Ok(s) => Ok(Cow::Borrowed(s)), Ok(s) => Ok(Cow::Borrowed(s)),
Err(e) => Err(crate::PyErr::from_value_bound( Err(e) => Err(PyUnicodeDecodeError::new_utf8_bound(py, data, e)?.into()),
PyUnicodeDecodeError::new_utf8_bound(py, data, e)?.into_any(),
)),
}, },
Self::Ucs2(data) => match String::from_utf16(data) { Self::Ucs2(data) => match String::from_utf16(data) {
Ok(s) => Ok(Cow::Owned(s)), Ok(s) => Ok(Cow::Owned(s)),
@ -83,30 +81,26 @@ impl<'a> PyStringData<'a> {
let mut message = e.to_string().as_bytes().to_vec(); let mut message = e.to_string().as_bytes().to_vec();
message.push(0); message.push(0);
Err(crate::PyErr::from_value_bound( Err(PyUnicodeDecodeError::new_bound(
PyUnicodeDecodeError::new_bound( py,
py, CStr::from_bytes_with_nul(b"utf-16\0").unwrap(),
CStr::from_bytes_with_nul(b"utf-16\0").unwrap(), self.as_bytes(),
self.as_bytes(), 0..self.as_bytes().len(),
0..self.as_bytes().len(), CStr::from_bytes_with_nul(&message).unwrap(),
CStr::from_bytes_with_nul(&message).unwrap(), )?
)? .into())
.into_any(),
))
} }
}, },
Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() { Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() {
Some(s) => Ok(Cow::Owned(s)), Some(s) => Ok(Cow::Owned(s)),
None => Err(crate::PyErr::from_value_bound( None => Err(PyUnicodeDecodeError::new_bound(
PyUnicodeDecodeError::new_bound( py,
py, CStr::from_bytes_with_nul(b"utf-32\0").unwrap(),
CStr::from_bytes_with_nul(b"utf-32\0").unwrap(), self.as_bytes(),
self.as_bytes(), 0..self.as_bytes().len(),
0..self.as_bytes().len(), CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(),
CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(), )?
)? .into()),
.into_any(),
)),
}, },
} }
} }

View File

@ -5,6 +5,7 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
| ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr` | ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`
| |
= help: the following other types implement trait `From<T>`: = help: the following other types implement trait `From<T>`:
<PyErr as From<pyo3::Bound<'py, T>>>
<PyErr as From<std::io::Error>> <PyErr as From<std::io::Error>>
<PyErr as From<PyBorrowError>> <PyErr as From<PyBorrowError>>
<PyErr as From<PyBorrowMutError>> <PyErr as From<PyBorrowMutError>>
@ -12,7 +13,6 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
<PyErr as From<DowncastError<'_, '_>>> <PyErr as From<DowncastError<'_, '_>>>
<PyErr as From<DowncastIntoError<'_>>> <PyErr as From<DowncastIntoError<'_>>>
<PyErr as From<NulError>> <PyErr as From<NulError>>
<PyErr as From<IntoStringError>>
and $N others and $N others
= note: required for `MyError` to implement `Into<PyErr>` = note: required for `MyError` to implement `Into<PyErr>`
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)