Add py_exception! macro for defining custom exception types

This commit is contained in:
Samuel Cormier-Iijima 2016-06-02 01:02:43 -07:00
parent d7446cf38f
commit d77f93056e
2 changed files with 117 additions and 1 deletions

View file

@ -24,16 +24,109 @@ use objects::{PyObject, PyType, exc};
use objects::oldstyle::PyClass;
use ffi;
use libc;
use std::ptr;
use libc::c_char;
use conversion::ToPyObject;
use std::ffi::CString;
/**
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 cpython;
use cpython::{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(py, "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($crate::PyObject);
pyobject_newtype!($name);
impl $name {
pub fn new<'p, T: $crate::ToPyObject>(py: $crate::Python<'p>, args: T) -> $crate::PyErr {
$crate::PyErr::new::<$name, T>(py, args)
}
}
impl $crate::PythonObjectWithCheckedDowncast for $name {
#[inline]
fn downcast_from<'p>(py: $crate::Python<'p>, obj: $crate::PyObject)
-> Result<$name, $crate::PythonObjectDowncastError<'p>>
{
if <$name as $crate::PythonObjectWithTypeObject>::type_object(py).is_instance(py, &obj) {
Ok(unsafe { $crate::PythonObject::unchecked_downcast_from(obj) })
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
#[inline]
fn downcast_borrow_from<'a, 'p>(py: $crate::Python<'p>, obj: &'a $crate::PyObject)
-> Result<&'a $name, $crate::PythonObjectDowncastError<'p>>
{
if <$name as $crate::PythonObjectWithTypeObject>::type_object(py).is_instance(py, obj) {
Ok(unsafe { $crate::PythonObject::unchecked_downcast_borrow_from(obj) })
} else {
Err($crate::PythonObjectDowncastError(py))
}
}
}
impl $crate::PythonObjectWithTypeObject for $name {
#[inline]
fn type_object(py: $crate::Python) -> $crate::PyType {
unsafe {
static mut type_object: *mut $crate::_detail::ffi::PyTypeObject = 0 as *mut $crate::_detail::ffi::PyTypeObject;
if type_object.is_null() {
type_object = $crate::PyErr::new_type(
py,
concat!(stringify!($module), ".", stringify!($name)),
Some($crate::PythonObject::into_object(py.get_type::<$base>())),
None).as_type_ptr();
}
$crate::PyType::from_type_ptr(py, type_object)
}
}
}
};
($module: ident, $name: ident) => {
py_exception!($module, $name, $crate::exc::Exception);
}
}
/// Represents a Python exception that was raised.
#[derive(Debug)]
pub struct PyErr {
/// The type of the exception. This should be either a `PyClass` or a `PyType`.
pub ptype : PyObject,
/// The value of the exception.
///
///
/// This can be either an instance of `ptype`,
/// a tuple of arguments to be passed to `ptype`'s constructor,
/// or a single argument to be passed to `ptype`'s constructor.
@ -54,6 +147,28 @@ impl PyErr {
unsafe { !ffi::PyErr_Occurred().is_null() }
}
/// Creates a new exception type with the given name, which must be of the form
/// `<module>.<ExceptionName>`, as required by `PyErr_NewException`.
///
/// `base` can be an existing exception type to subclass, or a tuple of classes
/// `dict` specifies an optional dictionary of class variables and methods
pub fn new_type(py: Python, name: &str, base: Option<PyObject>, dict: Option<PyObject>) -> PyType {
let base: *mut ffi::PyObject = match base {
None => ptr::null_mut(),
Some(obj) => obj.steal_ptr()
};
let dict: *mut ffi::PyObject = match dict {
None => ptr::null_mut(),
Some(obj) => obj.steal_ptr()
};
unsafe {
let ptr: *mut ffi::PyObject = ffi::PyErr_NewException(name.as_ptr() as *mut c_char, base, dict);
PyObject::from_borrowed_ptr(py, ptr).unchecked_cast_into::<PyType>()
}
}
/// Retrieves the current error from the Python interpreter's global state.
/// The error is cleared from the Python interpreter.
/// If no error is set, returns a `SystemError`.

View file

@ -38,6 +38,7 @@ pub use self::num::PyLong as PyInt;
pub use self::num::{PyLong, PyFloat};
pub use self::sequence::PySequence;
#[macro_export]
macro_rules! pyobject_newtype(
($name: ident) => (
py_impl_to_py_object_for_python_object!($name);