Refactor the exception macros

* Renamed `py_exception` to `create_exception`
  * The split up of the macros makes it possible to create exception structs with bodies to mimic python exceptions' members
  * Used `Once` to fix a (theoretical) race condition with the is_null check
This commit is contained in:
konstin 2018-11-27 23:07:15 +01:00
parent de2108d719
commit 9102f2e364
9 changed files with 289 additions and 251 deletions

View file

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* Renamed `add_function` to `add_wrapped` as it now also supports modules. * Renamed `add_function` to `add_wrapped` as it now also supports modules.
* Renamed `#[pymodinit]` to `#[pymodule]`. * Renamed `#[pymodinit]` to `#[pymodule]`.
* Renamed `py_exception` to `create_exception` and refactored the error macros.
### Removed ### Removed

View file

@ -2,12 +2,12 @@
## Define a new exception ## Define a new exception
You can use the `py_exception!` macro to define a new exception type: You can use the `create_exception!` macro to define a new exception type:
```rust ```rust
#[macro_use] extern crate pyo3; #[macro_use] extern crate pyo3;
py_exception!(module, MyError, pyo3::exceptions::Exception); create_exception!(module, MyError, pyo3::exceptions::Exception);
``` ```
* `module` is the name of the containing module. * `module` is the name of the containing module.
@ -20,8 +20,9 @@ For example:
use pyo3::Python; use pyo3::Python;
use pyo3::types::PyDict; use pyo3::types::PyDict;
use pyo3::exceptions::Exception;
py_exception!(mymodule, CustomError, pyo3::exceptions::Exception); create_exception!(mymodule, CustomError, Exception);
fn main() { fn main() {
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
@ -61,7 +62,7 @@ PyErr::from_instance(py, err).restore(py);
If rust type exists for exception, then it is possible to use `new` method. If rust type exists for exception, then it is possible to use `new` method.
For example each standard exception defined in `exc` module For example each standard exception defined in `exc` module
has corresponding rust type, exceptions defined by `py_exception!` and `import_exception!` macro has corresponding rust type, exceptions defined by `create_exception!` and `import_exception!` macro
have rust type as well. have rust type as well.
```rust ```rust

View file

@ -18,7 +18,7 @@ use syn::parse::Parser;
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn mod2init( pub fn pymodule2(
attr: proc_macro::TokenStream, attr: proc_macro::TokenStream,
input: proc_macro::TokenStream, input: proc_macro::TokenStream,
) -> proc_macro::TokenStream { ) -> proc_macro::TokenStream {
@ -46,7 +46,7 @@ pub fn mod2init(
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn mod3init( pub fn pymodule3(
attr: proc_macro::TokenStream, attr: proc_macro::TokenStream,
input: proc_macro::TokenStream, input: proc_macro::TokenStream,
) -> proc_macro::TokenStream { ) -> proc_macro::TokenStream {

View file

@ -1,10 +1,4 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use libc;
use std;
use std::error::Error;
use std::ffi::CString;
use std::io;
use std::os::raw::c_char;
use crate::conversion::{IntoPyObject, ToBorrowedObject, ToPyObject}; use crate::conversion::{IntoPyObject, ToBorrowedObject, ToPyObject};
use crate::ffi; use crate::ffi;
@ -13,101 +7,12 @@ use crate::object::PyObject;
use crate::python::{IntoPyPointer, Python, ToPyPointer}; use crate::python::{IntoPyPointer, Python, ToPyPointer};
use crate::typeob::PyTypeObject; use crate::typeob::PyTypeObject;
use crate::types::{exceptions, PyObjectRef, PyType}; use crate::types::{exceptions, PyObjectRef, PyType};
use libc::c_int;
/// Defines a new exception type. use std;
/// use std::error::Error;
/// # Syntax use std::ffi::CString;
/// `py_exception!(module, MyError, pyo3::exceptions::Exception)` use std::io;
/// use std::os::raw::c_char;
/// * `module` is the name of the containing module.
/// * `MyError` is the name of the new exception type.
/// * `pyo3::exceptions::Exception` is the name of the base type
///
/// # Example
/// ```
/// #[macro_use]
/// extern crate pyo3;
///
/// use pyo3::Python;
/// use pyo3::types::PyDict;
///
/// py_exception!(mymodule, CustomError, pyo3::exceptions::Exception);
///
/// 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 {
fn from(_err: $name) -> $crate::PyErr {
$crate::PyErr::new::<$name, _>(())
}
}
impl<T> std::convert::Into<$crate::PyResult<T>> for $name {
fn into(self) -> $crate::PyResult<T> {
$crate::PyErr::new::<$name, _>(()).into()
}
}
impl $name {
pub fn py_err<T: $crate::ToPyObject + 'static>(args: T) -> $crate::PyErr {
$crate::PyErr::new::<$name, T>(args)
}
pub fn into<R, T: $crate::ToPyObject + 'static>(args: T) -> $crate::PyResult<R> {
$crate::PyErr::new::<$name, T>(args).into()
}
#[inline]
fn type_object() -> *mut $crate::ffi::PyTypeObject {
static mut TYPE_OBJECT: *mut $crate::ffi::PyTypeObject =
0 as *mut $crate::ffi::PyTypeObject;
unsafe {
if TYPE_OBJECT.is_null() {
let gil = $crate::Python::acquire_gil();
let py = gil.python();
TYPE_OBJECT = $crate::PyErr::new_type(
py,
concat!(stringify!($module), ".", stringify!($name)),
Some(py.get_type::<$base>()),
None,
);
}
TYPE_OBJECT
}
}
}
impl $crate::typeob::PyTypeObject for $name {
#[inline]
fn init_type() {
let _ = $name::type_object();
}
#[inline]
fn type_object() -> $crate::Py<$crate::types::PyType> {
unsafe {
$crate::Py::from_borrowed_ptr(
$name::type_object() as *const _ as *mut $crate::ffi::PyObject
)
}
}
}
};
}
/// Represents a `PyErr` value /// Represents a `PyErr` value
pub enum PyErrValue { pub enum PyErrValue {
@ -607,7 +512,7 @@ pub fn panic_after_error() -> ! {
/// Returns Ok if the error code is not -1. /// Returns Ok if the error code is not -1.
#[inline] #[inline]
pub fn error_on_minusone(py: Python, result: libc::c_int) -> PyResult<()> { pub fn error_on_minusone(py: Python, result: c_int) -> PyResult<()> {
if result != -1 { if result != -1 {
Ok(()) Ok(())
} else { } else {

View file

@ -1,4 +1,5 @@
use crate::ffi3::pyport::{Py_hash_t, Py_ssize_t}; use crate::ffi3::pyport::{Py_hash_t, Py_ssize_t};
use std::mem;
use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void}; use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
use std::ptr; use std::ptr;
@ -24,7 +25,7 @@ pub const PyObject_HEAD_INIT: PyObject = PyObject {
#[cfg(not(py_sys_config = "Py_TRACE_REFS"))] #[cfg(not(py_sys_config = "Py_TRACE_REFS"))]
pub const PyObject_HEAD_INIT: PyObject = PyObject { pub const PyObject_HEAD_INIT: PyObject = PyObject {
ob_refcnt: 1, ob_refcnt: 1,
ob_type: ::std::ptr::null_mut(), ob_type: ptr::null_mut(),
}; };
#[repr(C)] #[repr(C)]
@ -87,6 +88,7 @@ pub type objobjargproc =
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
mod bufferinfo { mod bufferinfo {
use crate::ffi3::pyport::Py_ssize_t; use crate::ffi3::pyport::Py_ssize_t;
use std::mem;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
#[repr(C)] #[repr(C)]
@ -108,7 +110,7 @@ mod bufferinfo {
impl Default for Py_buffer { impl Default for Py_buffer {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
@ -198,7 +200,9 @@ mod typeobject {
mod typeobject { mod typeobject {
use crate::ffi3::pyport::Py_ssize_t; use crate::ffi3::pyport::Py_ssize_t;
use crate::ffi3::{self, object}; use crate::ffi3::{self, object};
use std::mem;
use std::os::raw::{c_char, c_uint, c_ulong, c_void}; use std::os::raw::{c_char, c_uint, c_ulong, c_void};
use std::ptr;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -244,7 +248,7 @@ mod typeobject {
impl Default for PyNumberMethods { impl Default for PyNumberMethods {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
macro_rules! as_expr { macro_rules! as_expr {
@ -320,7 +324,7 @@ mod typeobject {
impl Default for PySequenceMethods { impl Default for PySequenceMethods {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
pub const PySequenceMethods_INIT: PySequenceMethods = PySequenceMethods { pub const PySequenceMethods_INIT: PySequenceMethods = PySequenceMethods {
@ -328,9 +332,9 @@ mod typeobject {
sq_concat: None, sq_concat: None,
sq_repeat: None, sq_repeat: None,
sq_item: None, sq_item: None,
was_sq_slice: ::std::ptr::null_mut(), was_sq_slice: ptr::null_mut(),
sq_ass_item: None, sq_ass_item: None,
was_sq_ass_slice: ::std::ptr::null_mut(), was_sq_ass_slice: ptr::null_mut(),
sq_contains: None, sq_contains: None,
sq_inplace_concat: None, sq_inplace_concat: None,
sq_inplace_repeat: None, sq_inplace_repeat: None,
@ -346,7 +350,7 @@ mod typeobject {
impl Default for PyMappingMethods { impl Default for PyMappingMethods {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
pub const PyMappingMethods_INIT: PyMappingMethods = PyMappingMethods { pub const PyMappingMethods_INIT: PyMappingMethods = PyMappingMethods {
@ -365,7 +369,7 @@ mod typeobject {
impl Default for PyAsyncMethods { impl Default for PyAsyncMethods {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
pub const PyAsyncMethods_INIT: PyAsyncMethods = PyAsyncMethods { pub const PyAsyncMethods_INIT: PyAsyncMethods = PyAsyncMethods {
@ -383,7 +387,7 @@ mod typeobject {
impl Default for PyBufferProcs { impl Default for PyBufferProcs {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
pub const PyBufferProcs_INIT: PyBufferProcs = PyBufferProcs { pub const PyBufferProcs_INIT: PyBufferProcs = PyBufferProcs {
@ -462,37 +466,37 @@ mod typeobject {
ob_base: ffi3::object::PyObject_HEAD_INIT, ob_base: ffi3::object::PyObject_HEAD_INIT,
ob_size: 0 ob_size: 0
}, },
tp_name: ::std::ptr::null(), tp_name: ptr::null(),
tp_basicsize: 0, tp_basicsize: 0,
tp_itemsize: 0, tp_itemsize: 0,
tp_dealloc: None, tp_dealloc: None,
tp_print: None, tp_print: None,
tp_getattr: None, tp_getattr: None,
tp_setattr: None, tp_setattr: None,
$tp_as_async: ::std::ptr::null_mut(), $tp_as_async: ptr::null_mut(),
tp_repr: None, tp_repr: None,
tp_as_number: ::std::ptr::null_mut(), tp_as_number: ptr::null_mut(),
tp_as_sequence: ::std::ptr::null_mut(), tp_as_sequence: ptr::null_mut(),
tp_as_mapping: ::std::ptr::null_mut(), tp_as_mapping: ptr::null_mut(),
tp_hash: None, tp_hash: None,
tp_call: None, tp_call: None,
tp_str: None, tp_str: None,
tp_getattro: None, tp_getattro: None,
tp_setattro: None, tp_setattro: None,
tp_as_buffer: ::std::ptr::null_mut(), tp_as_buffer: ptr::null_mut(),
tp_flags: ffi3::object::Py_TPFLAGS_DEFAULT, tp_flags: ffi3::object::Py_TPFLAGS_DEFAULT,
tp_doc: ::std::ptr::null(), tp_doc: ptr::null(),
tp_traverse: None, tp_traverse: None,
tp_clear: None, tp_clear: None,
tp_richcompare: None, tp_richcompare: None,
tp_weaklistoffset: 0, tp_weaklistoffset: 0,
tp_iter: None, tp_iter: None,
tp_iternext: None, tp_iternext: None,
tp_methods: ::std::ptr::null_mut(), tp_methods: ptr::null_mut(),
tp_members: ::std::ptr::null_mut(), tp_members: ptr::null_mut(),
tp_getset: ::std::ptr::null_mut(), tp_getset: ptr::null_mut(),
tp_base: ::std::ptr::null_mut(), tp_base: ptr::null_mut(),
tp_dict: ::std::ptr::null_mut(), tp_dict: ptr::null_mut(),
tp_descr_get: None, tp_descr_get: None,
tp_descr_set: None, tp_descr_set: None,
tp_dictoffset: 0, tp_dictoffset: 0,
@ -501,11 +505,11 @@ mod typeobject {
tp_new: None, tp_new: None,
tp_free: None, tp_free: None,
tp_is_gc: None, tp_is_gc: None,
tp_bases: ::std::ptr::null_mut(), tp_bases: ptr::null_mut(),
tp_mro: ::std::ptr::null_mut(), tp_mro: ptr::null_mut(),
tp_cache: ::std::ptr::null_mut(), tp_cache: ptr::null_mut(),
tp_subclasses: ::std::ptr::null_mut(), tp_subclasses: ptr::null_mut(),
tp_weaklist: ::std::ptr::null_mut(), tp_weaklist: ptr::null_mut(),
tp_del: None, tp_del: None,
tp_version_tag: 0, tp_version_tag: 0,
$($tail)* $($tail)*
@ -522,8 +526,8 @@ mod typeobject {
tp_allocs: 0, tp_allocs: 0,
tp_frees: 0, tp_frees: 0,
tp_maxalloc: 0, tp_maxalloc: 0,
tp_prev: ::std::ptr::null_mut(), tp_prev: ptr::null_mut(),
tp_next: ::std::ptr::null_mut(), tp_next: ptr::null_mut(),
) )
} }
} }
@ -556,7 +560,7 @@ mod typeobject {
impl Default for PyHeapTypeObject { impl Default for PyHeapTypeObject {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
@ -582,7 +586,7 @@ pub struct PyType_Slot {
impl Default for PyType_Slot { impl Default for PyType_Slot {
fn default() -> PyType_Slot { fn default() -> PyType_Slot {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }
@ -598,7 +602,7 @@ pub struct PyType_Spec {
impl Default for PyType_Spec { impl Default for PyType_Spec {
fn default() -> PyType_Spec { fn default() -> PyType_Spec {
unsafe { ::std::mem::zeroed() } unsafe { mem::zeroed() }
} }
} }

View file

@ -192,9 +192,9 @@ pub mod types;
/// The proc macros, which are also part of the prelude /// The proc macros, which are also part of the prelude
pub mod proc_macro { pub mod proc_macro {
#[cfg(not(Py_3))] #[cfg(not(Py_3))]
pub use pyo3cls::mod2init as pymodule; pub use pyo3cls::pymodule2 as pymodule;
#[cfg(Py_3)] #[cfg(Py_3)]
pub use pyo3cls::mod3init as pymodule; pub use pyo3cls::pymodule3 as pymodule;
/// The proc macro attributes /// The proc macro attributes
pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto};
} }

View file

@ -26,7 +26,7 @@ pub use crate::PyRawObject;
pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto};
#[cfg(Py_3)] #[cfg(Py_3)]
pub use pyo3cls::mod3init as pymodule; pub use pyo3cls::pymodule3 as pymodule;
#[cfg(not(Py_3))] #[cfg(not(Py_3))]
pub use pyo3cls::mod2init as pymodule; pub use pyo3cls::pymodule2 as pymodule;

View file

@ -13,18 +13,32 @@ use std::ffi::CStr;
use std::os::raw::c_char; use std::os::raw::c_char;
use std::{self, ops}; use std::{self, ops};
// Copyright (c) 2017-present PyO3 Project and Contributors /// The boilerplate to convert between a rust type and a python exception
/// Stringify a dotted path.
#[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! dot_stringify { macro_rules! impl_exception_boilerplate {
($e:ident) => ( ($name: ident) => {
stringify!($e) impl ::std::convert::From<$name> for $crate::PyErr {
); fn from(_err: $name) -> $crate::PyErr {
($e:ident. $($es:ident).+) => ( $crate::PyErr::new::<$name, _>(())
concat!(stringify!($e), ".", dot_stringify!($($es).*)) }
); }
impl<T> ::std::convert::Into<$crate::PyResult<T>> for $name {
fn into(self) -> $crate::PyResult<T> {
$crate::PyErr::new::<$name, _>(()).into()
}
}
impl $name {
pub fn py_err<T: $crate::ToPyObject + 'static>(args: T) -> $crate::PyErr {
$crate::PyErr::new::<Self, T>(args)
}
pub fn into<R, T: $crate::ToPyObject + 'static>(args: T) -> $crate::PyResult<R> {
$crate::PyErr::new::<Self, T>(args).into()
}
}
};
} }
/// Defines rust type for exception defined in Python code. /// Defines rust type for exception defined in Python code.
@ -55,68 +69,156 @@ macro_rules! dot_stringify {
/// ``` /// ```
#[macro_export] #[macro_export]
macro_rules! import_exception { macro_rules! import_exception {
($($module:ident).+ , $name: ident) => { ($module: expr, $name: ident) => {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)] // E.g. `socket.herror`
pub struct $name; pub struct $name;
impl ::std::convert::From<$name> for $crate::PyErr { impl_exception_boilerplate!($name);
fn from(_err: $name) -> $crate::PyErr {
$crate::PyErr::new::<$name, _>(())
}
}
impl<T> ::std::convert::Into<$crate::PyResult<T>> for $name { import_exception_type_object!($module, $name);
fn into(self) -> $crate::PyResult<T> { };
$crate::PyErr::new::<$name, _>(()).into() }
}
}
impl $name {
pub fn py_err<T: $crate::ToPyObject + 'static>(args: T) -> $crate::PyErr
where Self: $crate::typeob::PyTypeObject + Sized
{
$crate::PyErr::new::<Self, T>(args)
}
pub fn into<R, T: $crate::ToPyObject + 'static>(args: T) -> $crate::PyResult<R>
where Self: $crate::typeob::PyTypeObject + Sized
{
$crate::PyErr::new::<Self, T>(args).into()
}
}
/// `impl $crate::typeob::PyTypeObject for $name` where `$name` is an exception defined in python
/// code.
#[macro_export]
macro_rules! import_exception_type_object {
($module: expr, $name: ident) => {
impl $crate::typeob::PyTypeObject for $name { impl $crate::typeob::PyTypeObject for $name {
#[inline] #[inline]
fn init_type() {} fn init_type() {
let _ = Self::type_object();
}
#[inline] #[inline]
fn type_object() -> $crate::Py<$crate::types::PyType> { fn type_object() -> $crate::Py<$crate::types::PyType> {
use $crate::IntoPyPointer; // We can't use lazy_static here because raw pointers aren't Send
static TYPE_OBJECT_ONCE: ::std::sync::Once = ::std::sync::Once::new();
static mut TYPE_OBJECT: *mut $crate::ffi::PyTypeObject = ::std::ptr::null_mut(); static mut TYPE_OBJECT: *mut $crate::ffi::PyTypeObject = ::std::ptr::null_mut();
unsafe { TYPE_OBJECT_ONCE.call_once(|| {
if TYPE_OBJECT.is_null() {
let gil = $crate::Python::acquire_gil(); let gil = $crate::Python::acquire_gil();
let py = gil.python(); let py = gil.python();
let imp = py.import(dot_stringify!($($module).*)) unsafe {
.expect(concat!( let imp = py
"Can not import module: ", dot_stringify!($($module).*))); .import(stringify!($module))
let cls = imp.get(stringify!($name)) .expect(concat!("Can not import module: ", stringify!($module)));
.expect(concat!( let cls = imp.get(stringify!($name)).expect(concat!(
"Can not load exception class: {}.{}", dot_stringify!($($module).*), "Can not load exception class: {}.{}",
".", stringify!($name))); stringify!($module),
TYPE_OBJECT = cls.into_ptr() as *mut $crate::ffi::PyTypeObject; ".",
stringify!($name)
));
TYPE_OBJECT =
$crate::IntoPyPointer::into_ptr(cls) as *mut $crate::ffi::PyTypeObject;
} }
});
unsafe {
$crate::Py::from_borrowed_ptr( $crate::Py::from_borrowed_ptr(
TYPE_OBJECT as *const _ as *mut $crate::ffi::PyObject) TYPE_OBJECT as *const _ as *mut $crate::ffi::PyObject,
)
} }
} }
} }
}; };
} }
macro_rules! exc_type ( /// Defines a new exception type.
///
/// # Syntax
/// `create_exception!(module, MyError, BaseException)`
///
/// * `module` is the name of the containing module.
/// * `MyError` is the name of the new exception type.
/// * `BaseException` is the superclass of MyError, usually `pyo3::exceptions::Exception`
///
/// # Example
/// ```
/// #[macro_use]
/// extern crate pyo3;
///
/// use pyo3::prelude::*;
/// use pyo3::types::PyDict;
/// use pyo3::exceptions::Exception;
///
/// create_exception!(mymodule, CustomError, Exception);
///
/// fn main() {
/// let gil = Python::acquire_gil();
/// let py = gil.python();
/// let ctx = PyDict::new(py);
/// let error_type = py.get_type::<CustomError>();
/// ctx.set_item("CustomError", error_type).unwrap();
/// let type_description: String = py
/// .eval("str(CustomError)", None, Some(&ctx))
/// .unwrap()
/// .extract()
/// .unwrap();
/// assert_eq!(type_description, "<class 'mymodule.CustomError'>");
/// py.run(
/// "assert CustomError('oops').args == ('oops',)",
/// None,
/// Some(ctx),
/// )
/// .unwrap();
/// }
/// ```
#[macro_export]
macro_rules! create_exception {
($module: ident, $name: ident, $base: ty) => {
#[allow(non_camel_case_types)] // E.g. `socket.herror`
pub struct $name;
impl_exception_boilerplate!($name);
create_exception_type_object!($module, $name, $base);
};
}
/// `impl $crate::typeob::PyTypeObject for $name` where `$name` is an exception newly defined in
/// rust code.
#[macro_export]
macro_rules! create_exception_type_object {
($module: ident, $name: ident, $base: ty) => {
impl $crate::typeob::PyTypeObject for $name {
#[inline]
fn init_type() {
let _ = Self::type_object();
}
#[inline]
fn type_object() -> $crate::Py<$crate::types::PyType> {
// We can't use lazy_static here because raw pointers aren't Send
static TYPE_OBJECT_ONCE: ::std::sync::Once = ::std::sync::Once::new();
static mut TYPE_OBJECT: *mut $crate::ffi::PyTypeObject = ::std::ptr::null_mut();
TYPE_OBJECT_ONCE.call_once(|| {
let gil = $crate::Python::acquire_gil();
let py = gil.python();
unsafe {
TYPE_OBJECT = $crate::PyErr::new_type(
py,
concat!(stringify!($module), ".", stringify!($name)),
Some(py.get_type::<$base>()),
None,
);
}
});
unsafe {
$crate::Py::from_borrowed_ptr(
TYPE_OBJECT as *const _ as *mut $crate::ffi::PyObject,
)
}
}
}
};
}
macro_rules! impl_native_exception (
($name:ident, $exc_name:ident) => ( ($name:ident, $exc_name:ident) => (
pub struct $name; pub struct $name;
@ -152,84 +254,84 @@ macro_rules! exc_type (
); );
); );
exc_type!(BaseException, PyExc_BaseException); impl_native_exception!(BaseException, PyExc_BaseException);
exc_type!(Exception, PyExc_Exception); impl_native_exception!(Exception, PyExc_Exception);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(StopAsyncIteration, PyExc_StopAsyncIteration); impl_native_exception!(StopAsyncIteration, PyExc_StopAsyncIteration);
exc_type!(StopIteration, PyExc_StopIteration); impl_native_exception!(StopIteration, PyExc_StopIteration);
exc_type!(GeneratorExit, PyExc_GeneratorExit); impl_native_exception!(GeneratorExit, PyExc_GeneratorExit);
exc_type!(ArithmeticError, PyExc_ArithmeticError); impl_native_exception!(ArithmeticError, PyExc_ArithmeticError);
exc_type!(LookupError, PyExc_LookupError); impl_native_exception!(LookupError, PyExc_LookupError);
exc_type!(AssertionError, PyExc_AssertionError); impl_native_exception!(AssertionError, PyExc_AssertionError);
exc_type!(AttributeError, PyExc_AttributeError); impl_native_exception!(AttributeError, PyExc_AttributeError);
exc_type!(BufferError, PyExc_BufferError); impl_native_exception!(BufferError, PyExc_BufferError);
exc_type!(EOFError, PyExc_EOFError); impl_native_exception!(EOFError, PyExc_EOFError);
exc_type!(FloatingPointError, PyExc_FloatingPointError); impl_native_exception!(FloatingPointError, PyExc_FloatingPointError);
exc_type!(OSError, PyExc_OSError); impl_native_exception!(OSError, PyExc_OSError);
exc_type!(ImportError, PyExc_ImportError); impl_native_exception!(ImportError, PyExc_ImportError);
#[cfg(Py_3_6)] #[cfg(Py_3_6)]
exc_type!(ModuleNotFoundError, PyExc_ModuleNotFoundError); impl_native_exception!(ModuleNotFoundError, PyExc_ModuleNotFoundError);
exc_type!(IndexError, PyExc_IndexError); impl_native_exception!(IndexError, PyExc_IndexError);
exc_type!(KeyError, PyExc_KeyError); impl_native_exception!(KeyError, PyExc_KeyError);
exc_type!(KeyboardInterrupt, PyExc_KeyboardInterrupt); impl_native_exception!(KeyboardInterrupt, PyExc_KeyboardInterrupt);
exc_type!(MemoryError, PyExc_MemoryError); impl_native_exception!(MemoryError, PyExc_MemoryError);
exc_type!(NameError, PyExc_NameError); impl_native_exception!(NameError, PyExc_NameError);
exc_type!(OverflowError, PyExc_OverflowError); impl_native_exception!(OverflowError, PyExc_OverflowError);
exc_type!(RuntimeError, PyExc_RuntimeError); impl_native_exception!(RuntimeError, PyExc_RuntimeError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(RecursionError, PyExc_RecursionError); impl_native_exception!(RecursionError, PyExc_RecursionError);
exc_type!(NotImplementedError, PyExc_NotImplementedError); impl_native_exception!(NotImplementedError, PyExc_NotImplementedError);
exc_type!(SyntaxError, PyExc_SyntaxError); impl_native_exception!(SyntaxError, PyExc_SyntaxError);
exc_type!(ReferenceError, PyExc_ReferenceError); impl_native_exception!(ReferenceError, PyExc_ReferenceError);
exc_type!(SystemError, PyExc_SystemError); impl_native_exception!(SystemError, PyExc_SystemError);
exc_type!(SystemExit, PyExc_SystemExit); impl_native_exception!(SystemExit, PyExc_SystemExit);
exc_type!(TypeError, PyExc_TypeError); impl_native_exception!(TypeError, PyExc_TypeError);
exc_type!(UnboundLocalError, PyExc_UnboundLocalError); impl_native_exception!(UnboundLocalError, PyExc_UnboundLocalError);
exc_type!(UnicodeError, PyExc_UnicodeError); impl_native_exception!(UnicodeError, PyExc_UnicodeError);
exc_type!(UnicodeDecodeError, PyExc_UnicodeDecodeError); impl_native_exception!(UnicodeDecodeError, PyExc_UnicodeDecodeError);
exc_type!(UnicodeEncodeError, PyExc_UnicodeEncodeError); impl_native_exception!(UnicodeEncodeError, PyExc_UnicodeEncodeError);
exc_type!(UnicodeTranslateError, PyExc_UnicodeTranslateError); impl_native_exception!(UnicodeTranslateError, PyExc_UnicodeTranslateError);
exc_type!(ValueError, PyExc_ValueError); impl_native_exception!(ValueError, PyExc_ValueError);
exc_type!(ZeroDivisionError, PyExc_ZeroDivisionError); impl_native_exception!(ZeroDivisionError, PyExc_ZeroDivisionError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(BlockingIOError, PyExc_BlockingIOError); impl_native_exception!(BlockingIOError, PyExc_BlockingIOError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(BrokenPipeError, PyExc_BrokenPipeError); impl_native_exception!(BrokenPipeError, PyExc_BrokenPipeError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(ChildProcessError, PyExc_ChildProcessError); impl_native_exception!(ChildProcessError, PyExc_ChildProcessError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(ConnectionError, PyExc_ConnectionError); impl_native_exception!(ConnectionError, PyExc_ConnectionError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(ConnectionAbortedError, PyExc_ConnectionAbortedError); impl_native_exception!(ConnectionAbortedError, PyExc_ConnectionAbortedError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(ConnectionRefusedError, PyExc_ConnectionRefusedError); impl_native_exception!(ConnectionRefusedError, PyExc_ConnectionRefusedError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(ConnectionResetError, PyExc_ConnectionResetError); impl_native_exception!(ConnectionResetError, PyExc_ConnectionResetError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(FileExistsError, PyExc_FileExistsError); impl_native_exception!(FileExistsError, PyExc_FileExistsError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(FileNotFoundError, PyExc_FileNotFoundError); impl_native_exception!(FileNotFoundError, PyExc_FileNotFoundError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(InterruptedError, PyExc_InterruptedError); impl_native_exception!(InterruptedError, PyExc_InterruptedError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(IsADirectoryError, PyExc_IsADirectoryError); impl_native_exception!(IsADirectoryError, PyExc_IsADirectoryError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(NotADirectoryError, PyExc_NotADirectoryError); impl_native_exception!(NotADirectoryError, PyExc_NotADirectoryError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(PermissionError, PyExc_PermissionError); impl_native_exception!(PermissionError, PyExc_PermissionError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(ProcessLookupError, PyExc_ProcessLookupError); impl_native_exception!(ProcessLookupError, PyExc_ProcessLookupError);
#[cfg(Py_3)] #[cfg(Py_3)]
exc_type!(TimeoutError, PyExc_TimeoutError); impl_native_exception!(TimeoutError, PyExc_TimeoutError);
exc_type!(EnvironmentError, PyExc_EnvironmentError); impl_native_exception!(EnvironmentError, PyExc_EnvironmentError);
exc_type!(IOError, PyExc_IOError); impl_native_exception!(IOError, PyExc_IOError);
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
exc_type!(WindowsError, PyExc_WindowsError); impl_native_exception!(WindowsError, PyExc_WindowsError);
impl UnicodeDecodeError { impl UnicodeDecodeError {
pub fn new_err<'p>( pub fn new_err<'p>(
@ -299,6 +401,8 @@ pub mod socket {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::objectprotocol::ObjectProtocol;
use crate::types::exceptions::Exception;
use crate::types::PyDict; use crate::types::PyDict;
use crate::{PyErr, Python}; use crate::{PyErr, Python};
@ -356,4 +460,27 @@ mod test {
.map_err(|e| e.print(py)) .map_err(|e| e.print(py))
.expect("assertion failed"); .expect("assertion failed");
} }
#[test]
fn custom_exception() {
create_exception!(mymodule, CustomError, Exception);
let gil = Python::acquire_gil();
let py = gil.python();
let ctx = PyDict::new(py);
let error_type = py.get_type::<CustomError>();
ctx.set_item("CustomError", error_type).unwrap();
let type_description: String = py
.eval("str(CustomError)", None, Some(&ctx))
.unwrap()
.extract()
.unwrap();
assert_eq!(type_description, "<class 'mymodule.CustomError'>");
py.run(
"assert CustomError('oops').args == ('oops',)",
None,
Some(ctx),
)
.unwrap();
}
} }