Skip to content

Commit

Permalink
Implement std::error::Error for PyErr
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Sep 2, 2020
1 parent ffe543f commit 381ae23
Show file tree
Hide file tree
Showing 27 changed files with 451 additions and 400 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `PyObject` is now just a type alias for `Py<PyAny>`. [#1063](https://github.com/PyO3/pyo3/pull/1063)
- Implement `Send + Sync` for `PyErr`. `PyErr::new`, `PyErr::from_type`, `PyException::py_err` and `PyException::into` have had these bounds added to their arguments. [#1067](https://github.com/PyO3/pyo3/pull/1067)
- Change `#[pyproto]` to return NotImplemented for operators for which Python can try a reversed operation. #[1072](https://github.com/PyO3/pyo3/pull/1072)
- Rework PyErr to be compatible with the `std::error::Error` trait: [#1115](https://github.com/PyO3/pyo3/pull/1115)
- Implement `Display` and `Error` for `PyErr`.
- Add `PyErr::instance()` which returns `&PyBaseException`.
- Add `PyErr::from_err_args`.
- `PyErr`'s fields are now an implementation detail. The equivalent values can be accessed with `PyErr::ptype()`, `PyErr::pvalue()` and `PyErr::ptraceback()`.
- Change `PyErr::print()` and `PyErr::print_and_set_sys_last_vars()` to take `&self` instead of `self`.
- Remove `PyErr::into_normalized()` and `PyErr::normalize()`.
- Remove `PyErr::from_value`, `PyErr::into_normalized()` and `PyErr::normalize()`.
- Change `PyErrValue` to be a private type.
- Remove `PyException::into()` and `Into<PyResult<T>>` for `PyErr` and `PyException`.
- Rename `PyException::py_err()` to `PyException::new_err()`.
- Rename `PyUnicodeDecodeErr::new_err()` to `PyUnicodeDecodeErr::new()`.
- `PyModule::add` now uses `IntoPy<PyObject>` instead of `ToPyObject`. #[1124](https://github.com/PyO3/pyo3/pull/1124)


### Removed
- Remove `PyString::as_bytes`. [#1023](https://github.com/PyO3/pyo3/pull/1023)
- Remove `Python::register_any`. [#1023](https://github.com/PyO3/pyo3/pull/1023)
Expand Down
10 changes: 5 additions & 5 deletions guide/src/exception.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ If you already have a Python exception instance, you can simply call [`PyErr::fr
PyErr::from_instance(py, err).restore(py);
```

If a Rust type exists for the exception, then it is possible to use the `py_err` method.
If a Rust type exists for the exception, then it is possible to use the `new_err` method.
For example, each standard exception defined in the `pyo3::exceptions` module
has a corresponding Rust type, exceptions defined by [`create_exception!`] and [`import_exception!`] macro
have Rust types as well.
Expand All @@ -87,7 +87,7 @@ have Rust types as well.
# fn check_for_error() -> bool {false}
fn my_func(arg: PyObject) -> PyResult<()> {
if check_for_error() {
Err(PyValueError::py_err("argument is wrong"))
Err(PyValueError::new_err("argument is wrong"))
} else {
Ok(())
}
Expand Down Expand Up @@ -123,7 +123,7 @@ To check the type of an exception, you can simply do:
# fn main() {
# let gil = Python::acquire_gil();
# let py = gil.python();
# let err = PyTypeError::py_err(());
# let err = PyTypeError::new_err(());
err.is_instance::<PyTypeError>(py);
# }
```
Expand Down Expand Up @@ -170,7 +170,7 @@ until a `Python` object is available.
# }
impl std::convert::From<CustomIOError> for PyErr {
fn from(err: CustomIOError) -> PyErr {
PyOSError::py_err(err.to_string())
PyOSError::new_err(err.to_string())
}
}

Expand Down Expand Up @@ -213,7 +213,7 @@ fn tell(file: &PyAny) -> PyResult<u64> {
use pyo3::exceptions::*;

match file.call_method0("tell") {
Err(_) => Err(io::UnsupportedOperation::py_err("not supported: tell")),
Err(_) => Err(io::UnsupportedOperation::new_err("not supported: tell")),
Ok(x) => x.extract::<u64>(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions pyo3-derive-backend/src/from_pyobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl<'a> Enum<'a> {
.map(|s| format!("{} ({})", s.to_string_lossy(), type_name))
.unwrap_or_else(|_| type_name.to_string());
let err_msg = format!("Can't convert {} to {}", from, #error_names);
Err(::pyo3::exceptions::PyTypeError::py_err(err_msg))
Err(::pyo3::exceptions::PyTypeError::new_err(err_msg))
)
}
}
Expand Down Expand Up @@ -263,7 +263,7 @@ impl<'a> Container<'a> {
quote!(
let s = <::pyo3::types::PyTuple as ::pyo3::conversion::PyTryFrom>::try_from(obj)?;
if s.len() != #len {
return Err(::pyo3::exceptions::PyValueError::py_err(#msg))
return Err(::pyo3::exceptions::PyValueError::new_err(#msg))
}
let slice = s.as_slice();
Ok(#self_ty(#fields))
Expand Down
12 changes: 6 additions & 6 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ pub unsafe trait Element: Copy {
fn validate(b: &ffi::Py_buffer) -> PyResult<()> {
// shape and stride information must be provided when we use PyBUF_FULL_RO
if b.shape.is_null() {
return Err(exceptions::PyBufferError::py_err("Shape is Null"));
return Err(exceptions::PyBufferError::new_err("Shape is Null"));
}
if b.strides.is_null() {
return Err(exceptions::PyBufferError::py_err(
return Err(exceptions::PyBufferError::new_err(
"PyBuffer: Strides is Null",
));
}
Expand Down Expand Up @@ -190,7 +190,7 @@ impl<T: Element> PyBuffer<T> {
{
Ok(buf)
} else {
Err(exceptions::PyBufferError::py_err(
Err(exceptions::PyBufferError::new_err(
"Incompatible type as buffer",
))
}
Expand Down Expand Up @@ -441,7 +441,7 @@ impl<T: Element> PyBuffer<T> {

fn copy_to_slice_impl(&self, py: Python, target: &mut [T], fort: u8) -> PyResult<()> {
if mem::size_of_val(target) != self.len_bytes() {
return Err(exceptions::PyBufferError::py_err(
return Err(exceptions::PyBufferError::new_err(
"Slice length does not match buffer length.",
));
}
Expand Down Expand Up @@ -528,7 +528,7 @@ impl<T: Element> PyBuffer<T> {
return buffer_readonly_error();
}
if mem::size_of_val(source) != self.len_bytes() {
return Err(exceptions::PyBufferError::py_err(
return Err(exceptions::PyBufferError::new_err(
"Slice length does not match buffer length.",
));
}
Expand Down Expand Up @@ -564,7 +564,7 @@ impl<T: Element> PyBuffer<T> {

#[inline(always)]
fn buffer_readonly_error() -> PyResult<()> {
Err(exceptions::PyBufferError::py_err(
Err(exceptions::PyBufferError::new_err(
"Cannot write to read-only buffer.",
))
}
Expand Down
8 changes: 4 additions & 4 deletions src/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl IntoPyCallbackOutput<ffi::Py_ssize_t> for usize {
if self <= (isize::MAX as usize) {
Ok(self as isize)
} else {
Err(PyOverflowError::py_err(()))
Err(PyOverflowError::new_err(()))
}
}
}
Expand Down Expand Up @@ -244,11 +244,11 @@ macro_rules! callback_body_without_convert {
Err(e) => {
// Try to format the error in the same way panic does
if let Some(string) = e.downcast_ref::<String>() {
Err($crate::panic::PanicException::py_err((string.clone(),)))
Err($crate::panic::PanicException::new_err((string.clone(),)))
} else if let Some(s) = e.downcast_ref::<&str>() {
Err($crate::panic::PanicException::py_err((s.to_string(),)))
Err($crate::panic::PanicException::new_err((s.to_string(),)))
} else {
Err($crate::panic::PanicException::py_err((
Err($crate::panic::PanicException::new_err((
"panic from Rust code",
)))
}
Expand Down
2 changes: 1 addition & 1 deletion src/class/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput {
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
match self {
IterNextOutput::Yield(o) => Ok(o.into_ptr()),
IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::py_err((opt,))),
IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::new_err((opt,))),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/class/pyasync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterANextOutput {
match self {
IterANextOutput::Yield(o) => Ok(o.into_ptr()),
IterANextOutput::Return(opt) => {
Err(crate::exceptions::PyStopAsyncIteration::py_err((opt,)))
Err(crate::exceptions::PyStopAsyncIteration::new_err((opt,)))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/derive_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn parse_fn_args<'p>(
let nargs = args.len();
let mut used_args = 0;
macro_rules! raise_error {
($s: expr $(,$arg:expr)*) => (return Err(PyTypeError::py_err(format!(
($s: expr $(,$arg:expr)*) => (return Err(PyTypeError::new_err(format!(
concat!("{} ", $s), fname.unwrap_or("function") $(,$arg)*
))))
}
Expand Down
63 changes: 63 additions & 0 deletions src/err/err_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::{
exceptions::PyBaseException, ffi, types::PyType, IntoPyPointer, Py, PyObject, Python,
ToPyObject,
};

pub(crate) enum PyErrValue {
ToArgs(Box<dyn PyErrArguments + Send + Sync>),
ToObject(Box<dyn ToPyObject + Send + Sync>),
}

#[derive(Clone)]
pub(crate) struct PyErrStateNormalized {
pub ptype: Py<PyType>,
pub pvalue: Py<PyBaseException>,
pub ptraceback: Option<PyObject>,
}

pub(crate) enum PyErrState {
Lazy {
ptype: Py<PyType>,
pvalue: Option<PyErrValue>,
},
FfiTuple {
ptype: Option<PyObject>,
pvalue: Option<PyObject>,
ptraceback: Option<PyObject>,
},
Normalized(PyErrStateNormalized),
}

/// Helper conversion trait that allows to use custom arguments for exception constructor.
pub trait PyErrArguments {
/// Arguments for exception
fn arguments(&self, _: Python) -> PyObject;
}

impl PyErrState {
pub fn into_ffi_tuple(
self,
py: Python,
) -> (*mut ffi::PyObject, *mut ffi::PyObject, *mut ffi::PyObject) {
match self {
PyErrState::Lazy { ptype, pvalue } => {
let pvalue = match pvalue {
Some(PyErrValue::ToArgs(ob)) => ob.arguments(py).into_ptr(),
Some(PyErrValue::ToObject(ob)) => ob.to_object(py).into_ptr(),
None => std::ptr::null_mut(),
};
(ptype.into_ptr(), pvalue, std::ptr::null_mut())
}
PyErrState::FfiTuple {
ptype,
pvalue,
ptraceback,
} => (ptype.into_ptr(), pvalue.into_ptr(), ptraceback.into_ptr()),
PyErrState::Normalized(PyErrStateNormalized {
ptype,
pvalue,
ptraceback,
}) => (ptype.into_ptr(), pvalue.into_ptr(), ptraceback.into_ptr()),
}
}
}
107 changes: 107 additions & 0 deletions src/err/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use crate::{err::PyErrArguments, exceptions, IntoPy, PyErr, PyObject, Python};
use std::io;

/// Convert `PyErr` to `io::Error`
impl std::convert::From<PyErr> for io::Error {
fn from(err: PyErr) -> Self {
io::Error::new(io::ErrorKind::Other, format!("Python exception: {:?}", err))
}
}

/// Create `OSError` from `io::Error`
impl std::convert::From<io::Error> for PyErr {
fn from(err: io::Error) -> PyErr {
match err.kind() {
io::ErrorKind::BrokenPipe => {
PyErr::from_err_args::<exceptions::PyBrokenPipeError, _>(err)
}
io::ErrorKind::ConnectionRefused => {
PyErr::from_err_args::<exceptions::PyConnectionRefusedError, _>(err)
}
io::ErrorKind::ConnectionAborted => {
PyErr::from_err_args::<exceptions::PyConnectionAbortedError, _>(err)
}
io::ErrorKind::ConnectionReset => {
PyErr::from_err_args::<exceptions::PyConnectionResetError, _>(err)
}
io::ErrorKind::Interrupted => {
PyErr::from_err_args::<exceptions::PyInterruptedError, _>(err)
}
io::ErrorKind::NotFound => {
PyErr::from_err_args::<exceptions::PyFileNotFoundError, _>(err)
}
io::ErrorKind::WouldBlock => {
PyErr::from_err_args::<exceptions::PyBlockingIOError, _>(err)
}
io::ErrorKind::TimedOut => PyErr::from_err_args::<exceptions::PyTimeoutError, _>(err),
_ => PyErr::from_err_args::<exceptions::PyOSError, _>(err),
}
}
}

impl PyErrArguments for io::Error {
fn arguments(&self, py: Python) -> PyObject {
self.to_string().into_py(py)
}
}

impl<W: 'static + Send + Sync + std::fmt::Debug> std::convert::From<std::io::IntoInnerError<W>>
for PyErr
{
fn from(err: std::io::IntoInnerError<W>) -> PyErr {
PyErr::from_err_args::<exceptions::PyOSError, _>(err)
}
}

impl<W: Send + Sync + std::fmt::Debug> PyErrArguments for std::io::IntoInnerError<W> {
fn arguments(&self, py: Python) -> PyObject {
self.to_string().into_py(py)
}
}

impl PyErrArguments for std::convert::Infallible {
fn arguments(&self, py: Python) -> PyObject {
"Infalliable!".into_py(py)
}
}

impl std::convert::From<std::convert::Infallible> for PyErr {
fn from(_: std::convert::Infallible) -> PyErr {
PyErr::new::<exceptions::PyValueError, _>("Infalliable!")
}
}

macro_rules! impl_to_pyerr {
($err: ty, $pyexc: ty) => {
impl PyErrArguments for $err {
fn arguments(&self, py: Python) -> PyObject {
self.to_string().into_py(py)
}
}

impl std::convert::From<$err> for PyErr {
fn from(err: $err) -> PyErr {
PyErr::from_err_args::<$pyexc, _>(err)
}
}
};
}

impl_to_pyerr!(std::array::TryFromSliceError, exceptions::PyValueError);
impl_to_pyerr!(std::num::ParseIntError, exceptions::PyValueError);
impl_to_pyerr!(std::num::ParseFloatError, exceptions::PyValueError);
impl_to_pyerr!(std::num::TryFromIntError, exceptions::PyValueError);
impl_to_pyerr!(std::str::ParseBoolError, exceptions::PyValueError);
impl_to_pyerr!(std::ffi::IntoStringError, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(std::ffi::NulError, exceptions::PyValueError);
impl_to_pyerr!(std::str::Utf8Error, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(std::string::FromUtf8Error, exceptions::PyUnicodeDecodeError);
impl_to_pyerr!(
std::string::FromUtf16Error,
exceptions::PyUnicodeDecodeError
);
impl_to_pyerr!(
std::char::DecodeUtf16Error,
exceptions::PyUnicodeDecodeError
);
impl_to_pyerr!(std::net::AddrParseError, exceptions::PyValueError);
Loading

0 comments on commit 381ae23

Please sign in to comment.