2019-04-17 09:35:29 +00:00
|
|
|
# Python Exceptions
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2020-03-13 13:53:49 +00:00
|
|
|
## Defining a new exception
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2020-03-18 15:26:34 +00:00
|
|
|
You can use the [`create_exception!`] macro to define a new exception type:
|
2017-06-18 02:02:02 +00:00
|
|
|
|
|
|
|
```rust
|
2019-02-14 09:41:52 +00:00
|
|
|
use pyo3::create_exception;
|
2018-04-21 23:45:57 +00:00
|
|
|
|
2020-07-04 15:55:26 +00:00
|
|
|
create_exception!(module, MyError, pyo3::exceptions::PyException);
|
2017-06-18 02:02:02 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
* `module` is the name of the containing module.
|
|
|
|
* `MyError` is the name of the new exception type.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
```rust
|
2019-02-01 13:01:18 +00:00
|
|
|
use pyo3::prelude::*;
|
|
|
|
use pyo3::create_exception;
|
2019-03-20 18:37:27 +00:00
|
|
|
use pyo3::types::IntoPyDict;
|
2020-07-04 15:55:26 +00:00
|
|
|
use pyo3::exceptions::PyException;
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2020-07-04 15:55:26 +00:00
|
|
|
create_exception!(mymodule, CustomError, PyException);
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2021-03-20 07:44:28 +00:00
|
|
|
Python::with_gil(|py| {
|
2019-03-20 18:37:27 +00:00
|
|
|
let ctx = [("CustomError", py.get_type::<CustomError>())].into_py_dict(py);
|
2021-03-20 07:44:28 +00:00
|
|
|
pyo3::py_run!(py, *ctx, "assert str(CustomError) == \"<class 'mymodule.CustomError'>\"");
|
|
|
|
pyo3::py_run!(py, *ctx, "assert CustomError('oops').args == ('oops',)");
|
|
|
|
});
|
2017-06-18 02:02:02 +00:00
|
|
|
```
|
|
|
|
|
2020-08-10 15:08:52 +00:00
|
|
|
When using PyO3 to create an extension module, you can add the new exception to
|
|
|
|
the module like this, so that it is importable from Python:
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
|
|
|
|
create_exception!(mymodule, CustomError, PyException);
|
|
|
|
|
|
|
|
#[pymodule]
|
|
|
|
fn mymodule(py: Python, m: &PyModule) -> PyResult<()> {
|
|
|
|
// ... other elements added to module ...
|
|
|
|
m.add("CustomError", py.get_type::<CustomError>())?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2020-03-13 13:53:49 +00:00
|
|
|
## Raising an exception
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2021-10-14 21:15:25 +00:00
|
|
|
To raise an exception from `pyfunction`s and `pymethods`, you should return an `Err(PyErr)`.
|
|
|
|
If returned to Python code, this [`PyErr`] will then be raised as a Python exception. Many PyO3 APIs also return [`PyResult`].
|
|
|
|
|
|
|
|
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 and exceptions defined by [`create_exception!`] and [`import_exception!`] macro have Rust types as well.
|
|
|
|
|
|
|
|
```rust
|
|
|
|
use pyo3::exceptions::PyZeroDivisionError;
|
|
|
|
use pyo3::prelude::*;
|
|
|
|
|
|
|
|
#[pyfunction]
|
|
|
|
fn divide(a: i32, b: i32) -> PyResult<i32> {
|
|
|
|
match a.checked_div(b) {
|
|
|
|
Some(q) => Ok(q),
|
|
|
|
None => Err(PyZeroDivisionError::new_err("division by zero")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#
|
|
|
|
# fn main(){
|
|
|
|
# Python::with_gil(|py|{
|
|
|
|
# let fun = pyo3::wrap_pyfunction!(divide, py).unwrap();
|
|
|
|
# fun.call1((1,0)).unwrap_err();
|
|
|
|
# fun.call1((1,1)).unwrap();
|
|
|
|
# });
|
|
|
|
# }
|
|
|
|
```
|
|
|
|
|
|
|
|
You can also manually write and fetch errors in the Python interpreter's global state:
|
2017-06-19 05:30:15 +00:00
|
|
|
|
|
|
|
```rust
|
2019-02-14 09:41:52 +00:00
|
|
|
use pyo3::{Python, PyErr};
|
2020-07-04 15:55:26 +00:00
|
|
|
use pyo3::exceptions::PyTypeError;
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2021-03-20 07:44:28 +00:00
|
|
|
Python::with_gil(|py| {
|
2020-08-25 19:33:36 +00:00
|
|
|
PyTypeError::new_err("Error").restore(py);
|
2017-06-19 05:30:15 +00:00
|
|
|
assert!(PyErr::occurred(py));
|
|
|
|
drop(PyErr::fetch(py));
|
2021-03-20 07:44:28 +00:00
|
|
|
});
|
2017-06-19 05:30:15 +00:00
|
|
|
```
|
|
|
|
|
2021-11-25 19:30:03 +00:00
|
|
|
If you already have a Python exception object, you can simply call [`PyErr::from_value`].
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2018-04-21 19:14:15 +00:00
|
|
|
```rust,ignore
|
2021-11-25 19:30:03 +00:00
|
|
|
PyErr::from_value(py, err).restore(py);
|
2017-06-19 05:30:15 +00:00
|
|
|
```
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2017-07-26 21:28:04 +00:00
|
|
|
|
2020-03-13 13:53:49 +00:00
|
|
|
## Checking exception types
|
2017-06-18 02:02:02 +00:00
|
|
|
|
2020-11-15 14:30:21 +00:00
|
|
|
Python has an [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance) method to check an object's type.
|
2021-11-12 18:19:54 +00:00
|
|
|
In PyO3 every object has the [`PyAny::is_instance`] and [`PyAny::is_instance_of`] methods which do the same thing.
|
2017-06-19 05:30:15 +00:00
|
|
|
|
|
|
|
```rust
|
2019-02-14 09:41:52 +00:00
|
|
|
use pyo3::Python;
|
|
|
|
use pyo3::types::{PyBool, PyList};
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2021-03-20 07:44:28 +00:00
|
|
|
Python::with_gil(|py| {
|
2021-11-12 18:19:54 +00:00
|
|
|
assert!(PyBool::new(py, true).is_instance_of::<PyBool>().unwrap());
|
2017-06-19 05:30:15 +00:00
|
|
|
let list = PyList::new(py, &[1, 2, 3, 4]);
|
2021-11-12 18:19:54 +00:00
|
|
|
assert!(!list.is_instance_of::<PyBool>().unwrap());
|
|
|
|
assert!(list.is_instance_of::<PyList>().unwrap());
|
2021-03-20 07:44:28 +00:00
|
|
|
});
|
2017-06-19 05:30:15 +00:00
|
|
|
```
|
|
|
|
|
2020-08-25 19:33:36 +00:00
|
|
|
To check the type of an exception, you can similarly do:
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2018-04-21 21:30:22 +00:00
|
|
|
```rust
|
2020-07-04 15:55:26 +00:00
|
|
|
# use pyo3::exceptions::PyTypeError;
|
2018-04-21 21:30:22 +00:00
|
|
|
# use pyo3::prelude::*;
|
2021-03-20 07:44:28 +00:00
|
|
|
# Python::with_gil(|py| {
|
2020-08-25 19:33:36 +00:00
|
|
|
# let err = PyTypeError::new_err(());
|
2021-11-12 18:19:54 +00:00
|
|
|
err.is_instance_of::<PyTypeError>(py);
|
2021-03-20 07:44:28 +00:00
|
|
|
# });
|
2017-06-19 05:30:15 +00:00
|
|
|
```
|
|
|
|
|
2020-03-13 13:53:49 +00:00
|
|
|
## Handling Rust errors
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2020-08-25 19:33:36 +00:00
|
|
|
The vast majority of operations in this library will return
|
2021-04-02 14:38:49 +00:00
|
|
|
[`PyResult<T>`]({{#PYO3_DOCS_URL}}/pyo3/prelude/type.PyResult.html),
|
2020-03-18 15:26:34 +00:00
|
|
|
which is an alias for the type `Result<T, PyErr>`.
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2020-08-25 19:33:36 +00:00
|
|
|
A [`PyErr`] represents a Python exception. Errors within the PyO3 library are also exposed as
|
|
|
|
Python exceptions.
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2021-11-25 19:30:03 +00:00
|
|
|
If your code has a custom error type, adding an implementation of `std::convert::From<MyError> for PyErr`
|
2021-10-14 21:15:25 +00:00
|
|
|
is usually enough. PyO3 will then automatically convert your error to a Python exception when needed.
|
|
|
|
|
|
|
|
The following code snippet defines a Rust error named `CustomIOError`. In its `From<CustomIOError> for PyErr`
|
|
|
|
implementation it returns a `PyErr` representing Python's `OSError`.
|
2017-06-19 05:30:15 +00:00
|
|
|
|
2019-08-03 00:06:40 +00:00
|
|
|
```rust
|
2021-10-14 21:15:25 +00:00
|
|
|
use pyo3::exceptions::PyOSError;
|
|
|
|
use pyo3::prelude::*;
|
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct CustomIOError;
|
|
|
|
|
|
|
|
impl std::error::Error for CustomIOError {}
|
|
|
|
|
|
|
|
impl fmt::Display for CustomIOError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "Oh no!")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-03 00:06:40 +00:00
|
|
|
impl std::convert::From<CustomIOError> for PyErr {
|
|
|
|
fn from(err: CustomIOError) -> PyErr {
|
2020-08-25 19:33:36 +00:00
|
|
|
PyOSError::new_err(err.to_string())
|
2017-07-26 16:16:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 21:15:25 +00:00
|
|
|
pub struct Connection { /* ... */}
|
|
|
|
|
|
|
|
fn bind(addr: String) -> Result<Connection, CustomIOError> {
|
|
|
|
if &addr == "0.0.0.0"{
|
|
|
|
Err(CustomIOError)
|
|
|
|
} else {
|
|
|
|
Ok(Connection{ /* ... */})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-25 19:33:36 +00:00
|
|
|
#[pyfunction]
|
2021-10-14 21:15:25 +00:00
|
|
|
fn connect(s: String) -> Result<(), CustomIOError> {
|
|
|
|
bind(s)?;
|
|
|
|
Ok(())
|
2017-06-19 05:30:15 +00:00
|
|
|
}
|
|
|
|
|
2021-10-14 21:15:25 +00:00
|
|
|
fn main() {
|
|
|
|
Python::with_gil(|py| {
|
|
|
|
let fun = pyo3::wrap_pyfunction!(connect, py).unwrap();
|
|
|
|
let err = fun.call1(("0.0.0.0",)).unwrap_err();
|
2021-11-12 18:19:54 +00:00
|
|
|
assert!(err.is_instance_of::<PyOSError>(py));
|
2021-10-14 21:15:25 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
```
|
2017-07-26 17:47:17 +00:00
|
|
|
|
2021-10-14 21:15:25 +00:00
|
|
|
This has been implemented for most of Rust's standard library errors, so that you can use the `?`
|
2021-11-25 19:30:03 +00:00
|
|
|
("try") operator with them. The following code snippet will raise a `ValueError` in Python if
|
2021-10-14 21:15:25 +00:00
|
|
|
`String::parse()` returns an error.
|
2017-07-26 17:47:17 +00:00
|
|
|
|
|
|
|
```rust
|
2018-04-21 19:14:15 +00:00
|
|
|
use pyo3::prelude::*;
|
2017-07-26 17:47:17 +00:00
|
|
|
|
2017-07-26 21:56:08 +00:00
|
|
|
fn parse_int(s: String) -> PyResult<usize> {
|
2017-07-26 17:47:17 +00:00
|
|
|
Ok(s.parse::<usize>()?)
|
|
|
|
}
|
2021-10-14 21:15:25 +00:00
|
|
|
#
|
|
|
|
# use pyo3::exceptions::PyValueError;
|
2021-11-12 18:19:54 +00:00
|
|
|
#
|
2021-10-14 21:15:25 +00:00
|
|
|
# fn main() {
|
|
|
|
# Python::with_gil(|py| {
|
|
|
|
# assert_eq!(parse_int(String::from("1")).unwrap(), 1);
|
|
|
|
# assert_eq!(parse_int(String::from("1337")).unwrap(), 1337);
|
2021-11-12 18:19:54 +00:00
|
|
|
#
|
2021-10-14 21:15:25 +00:00
|
|
|
# assert!(parse_int(String::from("-1"))
|
|
|
|
# .unwrap_err()
|
2021-11-12 18:19:54 +00:00
|
|
|
# .is_instance_of::<PyValueError>(py));
|
2021-10-14 21:15:25 +00:00
|
|
|
# assert!(parse_int(String::from("foo"))
|
|
|
|
# .unwrap_err()
|
2021-11-12 18:19:54 +00:00
|
|
|
# .is_instance_of::<PyValueError>(py));
|
2021-10-14 21:15:25 +00:00
|
|
|
# assert!(parse_int(String::from("13.37"))
|
|
|
|
# .unwrap_err()
|
2021-11-12 18:19:54 +00:00
|
|
|
# .is_instance_of::<PyValueError>(py));
|
2021-10-14 21:15:25 +00:00
|
|
|
# })
|
|
|
|
# }
|
2017-07-26 17:47:17 +00:00
|
|
|
```
|
|
|
|
|
2020-08-25 19:33:36 +00:00
|
|
|
If lazy construction of the Python exception instance is desired, the
|
2021-04-02 14:38:49 +00:00
|
|
|
[`PyErrArguments`]({{#PYO3_DOCS_URL}}/pyo3/trait.PyErrArguments.html)
|
2020-08-25 19:33:36 +00:00
|
|
|
trait can be implemented. In that case, actual exception argument creation is delayed
|
|
|
|
until the `PyErr` is needed.
|
2017-07-26 19:56:07 +00:00
|
|
|
|
2020-03-13 13:53:49 +00:00
|
|
|
## Using exceptions defined in Python code
|
2017-07-26 19:56:07 +00:00
|
|
|
|
2019-04-17 09:35:29 +00:00
|
|
|
It is possible to use an exception defined in Python code as a native Rust type.
|
2020-07-04 15:55:26 +00:00
|
|
|
The `import_exception!` macro allows importing a specific exception class and defines a Rust type
|
2017-07-26 19:56:07 +00:00
|
|
|
for that exception.
|
|
|
|
|
|
|
|
```rust
|
2021-10-14 21:15:25 +00:00
|
|
|
#![allow(dead_code)]
|
2018-04-21 19:14:15 +00:00
|
|
|
use pyo3::prelude::*;
|
2017-07-26 19:56:07 +00:00
|
|
|
|
2020-07-04 15:55:26 +00:00
|
|
|
mod io {
|
|
|
|
pyo3::import_exception!(io, UnsupportedOperation);
|
|
|
|
}
|
2017-07-26 19:56:07 +00:00
|
|
|
|
2020-08-06 21:29:05 +00:00
|
|
|
fn tell(file: &PyAny) -> PyResult<u64> {
|
|
|
|
match file.call_method0("tell") {
|
2020-08-25 19:33:36 +00:00
|
|
|
Err(_) => Err(io::UnsupportedOperation::new_err("not supported: tell")),
|
2020-08-06 21:29:05 +00:00
|
|
|
Ok(x) => x.extract::<u64>(),
|
2019-04-17 09:35:29 +00:00
|
|
|
}
|
2017-07-26 19:56:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
2021-04-02 14:38:49 +00:00
|
|
|
[`pyo3::exceptions`]({{#PYO3_DOCS_URL}}/pyo3/exceptions/index.html)
|
2019-04-17 09:35:29 +00:00
|
|
|
defines exceptions for several standard library modules.
|
2020-03-18 15:26:34 +00:00
|
|
|
|
2021-04-02 14:38:49 +00:00
|
|
|
[`create_exception!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.create_exception.html
|
|
|
|
[`import_exception!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.import_exception.html
|
2020-03-18 15:26:34 +00:00
|
|
|
|
2021-04-02 14:38:49 +00:00
|
|
|
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
|
2021-10-14 21:15:25 +00:00
|
|
|
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
|
2021-11-25 19:30:03 +00:00
|
|
|
[`PyErr::from_value`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_value
|
2021-04-02 14:38:49 +00:00
|
|
|
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance
|
2021-11-12 18:19:54 +00:00
|
|
|
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance_of
|