Make new_type return Py<PyType> instead

This commit is contained in:
mejrs 2021-12-04 12:52:19 +01:00
parent 64a2456d5e
commit 0ee13c1c1b
2 changed files with 83 additions and 71 deletions

View File

@ -12,7 +12,6 @@ use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ffi::CString;
use std::os::raw::c_int;
use std::ptr::NonNull;
mod err_state;
mod impls;
@ -305,65 +304,27 @@ impl PyErr {
}
}
/// Creates a new exception type with the given name.
///
/// - `base` can be an existing exception type to subclass, or a tuple of classes.
/// - `dict` specifies an optional dictionary of class variables and methods.
///
/// For a version of this function that also takes a docstring, see [`PyErr::new_type_with_doc`].
///
/// # Panics
///
/// This function will panic if:
/// - `name` is not of the form `<module>.<ExceptionName>`.
/// - `name` cannot be converted to [`CString`]s.
pub fn new_type<'p>(
py: Python<'p>,
name: &str,
base: Option<&PyType>,
dict: Option<PyObject>,
) -> NonNull<ffi::PyTypeObject> {
let base: *mut ffi::PyObject = match base {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let dict: *mut ffi::PyObject = match dict {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let null_terminated_name =
CString::new(name).expect("Failed to initialize nul terminated exception name");
let ptr = unsafe {
ffi::PyErr_NewException(null_terminated_name.as_ptr(), base, dict)
as *mut ffi::PyTypeObject
};
match NonNull::new(ptr) {
Some(not_null) => not_null,
None => panic!("{}", PyErr::fetch(py)),
}
}
/// Creates a new exception type with the given name and docstring.
///
/// - `base` can be an existing exception type to subclass, or a tuple of classes.
/// - `dict` specifies an optional dictionary of class variables and methods.
/// - `doc` will be the docstring seen by python users.
///
///
/// # Errors
///
/// This function returns an error if `name` is not of the form `<module>.<ExceptionName>`.
///
/// # Panics
///
/// This function will panic if:
/// - `name` is not of the form `<module>.<ExceptionName>`.
/// - `name` or `doc` cannot be converted to [`CString`]s.
pub fn new_type_with_doc<'p>(
py: Python<'p>,
/// This function will panic if `name` or `doc` cannot be converted to [`CString`]s.
pub fn new_type(
py: Python,
name: &str,
doc: Option<&str>,
base: Option<&PyType>,
dict: Option<PyObject>,
) -> NonNull<ffi::PyTypeObject> {
) -> PyResult<Py<PyType>> {
let base: *mut ffi::PyObject = match base {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
@ -391,13 +352,10 @@ impl PyErr {
null_terminated_doc_ptr,
base,
dict,
) as *mut ffi::PyTypeObject
)
};
match NonNull::new(ptr) {
Some(not_null) => not_null,
None => panic!("{}", PyErr::fetch(py)),
}
unsafe { Py::from_owned_ptr_or_err(py, ptr) }
}
/// Prints a standard traceback to `sys.stderr`.

View File

@ -131,7 +131,7 @@ macro_rules! import_exception {
///
/// * `module` is the name of the containing module.
/// * `name` is the name of the new exception type.
/// * `base` is the superclass of `MyError`, usually [`PyException`].
/// * `base` is the base class of `MyError`, usually [`PyException`].
/// * `doc` (optional) is the docstring visible to users (with `.__doc__` and `help()`) and
/// accompanies your error type in your crate's documentation.
///
@ -159,9 +159,9 @@ macro_rules! import_exception {
/// # fn main() -> PyResult<()> {
/// # Python::with_gil(|py| -> PyResult<()> {
/// # let fun = wrap_pyfunction!(raise_myerror, py)?;
/// # let globals = pyo3::types::PyDict::new(py);
/// # globals.set_item("MyError", py.get_type::<MyError>())?;
/// # globals.set_item("raise_myerror", fun)?;
/// # let locals = pyo3::types::PyDict::new(py);
/// # locals.set_item("MyError", py.get_type::<MyError>())?;
/// # locals.set_item("raise_myerror", fun)?;
/// #
/// # py.run(
/// # "try:
@ -169,8 +169,8 @@ macro_rules! import_exception {
/// # except MyError as e:
/// # assert e.__doc__ == 'Some description.'
/// # assert str(e) == 'Some error happened.'",
/// # Some(globals),
/// # None,
/// # Some(locals),
/// # )?;
/// #
/// # Ok(())
@ -238,20 +238,15 @@ macro_rules! create_exception_type_object {
GILOnceCell::new();
TYPE_OBJECT
.get_or_init(py, || unsafe {
$crate::Py::from_owned_ptr(
.get_or_init(py, ||
$crate::PyErr::new_type(
py,
$crate::PyErr::new_type_with_doc(
py,
concat!(stringify!($module), ".", stringify!($name)),
$doc,
::std::option::Option::Some(py.get_type::<$base>()),
::std::option::Option::None,
)
.as_ptr() as *mut $crate::ffi::PyObject,
)
})
.as_ptr() as *mut _
concat!(stringify!($module), ".", stringify!($name)),
$doc,
::std::option::Option::Some(py.get_type::<$base>()),
::std::option::Option::None,
).expect("Failed to initialize new exception type.")
).as_ptr() as *mut $crate::ffi::PyTypeObject
}
}
};
@ -796,6 +791,65 @@ mod tests {
Some(ctx),
)
.unwrap();
py.run("assert CustomError.__doc__ is None", None, Some(ctx))
.unwrap();
});
}
#[test]
fn custom_exception_doc() {
create_exception!(mymodule, CustomError, PyException, "Some docs");
Python::with_gil(|py| {
let error_type = py.get_type::<CustomError>();
let ctx = [("CustomError", error_type)].into_py_dict(py);
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();
py.run("assert CustomError.__doc__ == 'Some docs'", None, Some(ctx))
.unwrap();
});
}
#[test]
fn custom_exception_doc_expr() {
create_exception!(
mymodule,
CustomError,
PyException,
concat!("Some", " more ", stringify!(docs))
);
Python::with_gil(|py| {
let error_type = py.get_type::<CustomError>();
let ctx = [("CustomError", error_type)].into_py_dict(py);
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();
py.run(
"assert CustomError.__doc__ == 'Some more docs'",
None,
Some(ctx),
)
.unwrap();
});
}