Create rust type for exceptions defined in python #45

This commit is contained in:
Nikolay Kim 2017-07-26 12:56:07 -07:00
parent e45eb6e878
commit a20230e73f
6 changed files with 207 additions and 33 deletions

View File

@ -114,7 +114,7 @@ impl std::convert::From<std::io::Error> for PyErr {
}
}
fn connect(py: Python, s: String) -> PyResult<bool> {
fn connect(s: String) -> PyResult<bool> {
TcpListener::bind("127.0.0.1:80")?;
Ok(true)
@ -135,3 +135,28 @@ fn parse_int(py: Python, s: String) -> PyResult<usize> {
```
The code snippet above will raise `ValueError` in Python if `String::parse()` return an error.
## Using exceptions defined in python code
It is possible to use exception defined in python code as native rust types.
`import_exception!` macro allows to import specific exception class and defined zst type
for that exception.
```rust
use pyo3::{PyErr, PyResult, ToPyErr, exc};
import_exception!(asyncio, CancelledError)
fn cancel(fut: PyFuture) -> PyResult<()> {
if fut.cancelled() {
Err(CancelledError.into())
}
Ok(())
}
```
[`exc`](https://pyo3.github.io/PyO3/pyo3/exc/index.html) defines exceptions for
several standard library modules.

View File

@ -14,42 +14,41 @@ use instance::Py;
use typeob::PyTypeObject;
use conversion::{ToPyObject, ToBorrowedObject, IntoPyTuple};
/**
Defines a new exception type.
# Syntax
`py_exception!(module, MyError)`
* `module` is the name of the containing module.
* `MyError` is the name of the new exception type.
# Example
```
#[macro_use]
extern crate pyo3;
use pyo3::{Python, PyDict};
py_exception!(mymodule, CustomError);
fn main() {
let gil = Python::acquire_gil();
let py = gil.python();
let ctx = PyDict::new(py);
ctx.set_item("CustomError", py.get_type::<CustomError>()).unwrap();
py.run("assert str(CustomError) == \"<class 'mymodule.CustomError'>\"", None, Some(&ctx)).unwrap();
py.run("assert CustomError('oops').args == ('oops',)", None, Some(ctx)).unwrap();
}
```
*/
/// Defines a new exception type.
///
/// # Syntax
/// `py_exception!(module, MyError)`
///
/// * `module` is the name of the containing module.
/// * `MyError` is the name of the new exception type.
///
/// # Example
/// ```
/// #[macro_use]
/// extern crate pyo3;
///
/// use pyo3::{Python, PyDict};
///
/// py_exception!(mymodule, CustomError);
///
/// fn main() {
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let ctx = PyDict::new(py);
///
/// ctx.set_item("CustomError", py.get_type::<CustomError>()).unwrap();
///
/// py.run("assert str(CustomError) == \"<class 'mymodule.CustomError'>\"",
/// None, Some(&ctx)).unwrap();
/// py.run("assert CustomError('oops').args == ('oops',)", None, Some(ctx)).unwrap();
/// }
/// ```
#[macro_export]
macro_rules! py_exception {
($module: ident, $name: ident, $base: ty) => {
pub struct $name;
impl std::convert::From<$name> for $crate::PyErr {
impl ::std::convert::From<$name> for $crate::PyErr {
fn from(_err: $name) -> $crate::PyErr {
$crate::PyErr::new::<$name, _>(())
}

View File

@ -160,3 +160,21 @@ impl StopIteration {
}
}
}
/// Exceptions defined in `asyncio` module
pub mod asyncio {
import_exception!(asyncio, CancelledError);
import_exception!(asyncio, InvalidStateError);
import_exception!(asyncio, TimeoutError);
import_exception!(asyncio, IncompleteReadError);
import_exception!(asyncio, LimitOverrunError);
import_exception!(asyncio, QueueEmpty);
import_exception!(asyncio, QueueFull);
}
/// Exceptions defined in `socket` module
pub mod socket {
import_exception!(socket, herror);
import_exception!(socket, gaierror);
import_exception!(socket, timeout);
}

129
src/objects/exc_impl.rs Normal file
View File

@ -0,0 +1,129 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use std;
use ffi;
use err::PyErr;
use typeob::PyTypeObject;
use conversion::ToPyObject;
use python::{Python, IntoPyPointer};
/// Defines rust type for exception defined in Python code.
///
/// # Syntax
/// `import_exception!(module, MyError)`
///
/// * `module` is the name of the containing module.
/// * `MyError` is the name of the new exception type.
///
/// # Example
/// ```
/// #[macro_use]
/// extern crate pyo3;
///
/// use pyo3::{Python, PyDict};
///
/// import_exception!(socket, gaierror);
///
/// fn main() {
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let ctx = PyDict::new(py);
///
/// ctx.set_item("gaierror", py.get_type::<gaierror>()).unwrap();
/// py.run("import socket; assert gaierror is socket.gaierror", None, Some(ctx)).unwrap();
/// }
/// ```
#[macro_export]
macro_rules! import_exception {
($module: ident, $name: ident) => {
#[allow(non_camel_case_types)]
pub struct $name;
impl ::std::convert::From<$name> for $crate::PyErr {
fn from(_err: $name) -> $crate::PyErr {
$crate::PyErr::new::<$name, _>(())
}
}
impl $crate::PyNativeException for $name {
const MOD: &'static str = stringify!($module);
const NAME: &'static str = stringify!($name);
}
impl $crate::typeob::PyTypeObject for $name {
#[inline(always)]
fn init_type() {
use $crate::PyNativeException;
let _ = <$name as PyNativeException>::type_object_ptr();
}
#[inline]
fn type_object() -> $crate::Py<$crate::PyType> {
use $crate::PyNativeException;
unsafe {
$crate::Py::from_borrowed_ptr(
<$name as PyNativeException>::type_object_ptr()
as *const _ as *mut $crate::ffi::PyObject)
}
}
}
};
}
#[doc(hidden)]
/// Description of exception defined in python code.
/// `import_exception!` defines this trait for new exception type.
pub trait PyNativeException {
/// Module name, where exception is defined
const MOD: &'static str;
/// Name of exception
const NAME: &'static str;
fn new<S: PyTypeObject, T: ToPyObject + 'static>(args: T) -> PyErr {
PyErr::new::<S, T>(args)
}
fn type_object_ptr() -> *mut ffi::PyTypeObject {
static mut TYPE_OBJECT: *mut ffi::PyTypeObject = std::ptr::null_mut();
unsafe {
if TYPE_OBJECT.is_null() {
let gil = Python::acquire_gil();
let py = gil.python();
let imp = py.import(Self::MOD)
.expect(format!(
"Can not import module: {}", Self::MOD).as_ref());
let cls = imp.get(Self::NAME)
.expect(format!(
"Can not load exception class: {}.{}", Self::MOD, Self::NAME).as_ref());
TYPE_OBJECT = cls.into_ptr() as *mut ffi::PyTypeObject;
}
TYPE_OBJECT
}
}
}
#[cfg(test)]
mod test {
use {PyErr, Python};
use objects::PyDict;
import_exception!(socket, gaierror);
#[test]
fn test_check_exception() {
let gil = Python::acquire_gil();
let py = gil.python();
let err: PyErr = gaierror.into();
let d = PyDict::new(py);
d.set_item("socket", py.import("socket").unwrap()).unwrap();
d.set_item("exc", err.instance(py)).unwrap();
py.run("assert isinstance(exc, socket.gaierror)", None, Some(d)).unwrap();
}
}

View File

@ -1,5 +1,7 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
#[macro_use] mod exc_impl;
pub use self::typeobject::PyType;
pub use self::module::PyModule;
pub use self::iterator::PyIterator;
@ -13,6 +15,7 @@ pub use self::sequence::PySequence;
pub use self::slice::{PySlice, PySliceIndices};
pub use self::set::{PySet, PyFrozenSet};
pub use self::stringdata::PyStringData;
pub use self::exc_impl::PyNativeException;
#[cfg(Py_3)]
pub use self::string::{PyBytes, PyString};

View File

@ -233,7 +233,7 @@ impl<'p> Python<'p> {
}
/// Import the Python module with the specified name.
pub fn import(self, name : &str) -> PyResult<&'p PyModule> {
pub fn import(self, name: &str) -> PyResult<&'p PyModule> {
PyModule::import(self, name)
}