Skip to content

Commit

Permalink
add import_exception_bound! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Mar 30, 2024
1 parent 22e8dd1 commit 33371a4
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 39 deletions.
100 changes: 61 additions & 39 deletions src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,7 @@ macro_rules! impl_exception_boilerplate {
}
}

impl $name {
/// Creates a new [`PyErr`] of this type.
///
/// [`PyErr`]: https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html "PyErr in pyo3"
#[inline]
pub fn new_err<A>(args: A) -> $crate::PyErr
where
A: $crate::PyErrArguments + ::std::marker::Send + ::std::marker::Sync + 'static,
{
$crate::PyErr::new::<$name, A>(args)
}
}
$crate::impl_exception_boilerplate_bound!($name);

impl ::std::error::Error for $name {
fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
Expand All @@ -59,6 +48,25 @@ macro_rules! impl_exception_boilerplate {
};
}

#[doc(hidden)]
#[macro_export]
macro_rules! impl_exception_boilerplate_bound {
($name: ident) => {
impl $name {
/// Creates a new [`PyErr`] of this type.
///
/// [`PyErr`]: https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html "PyErr in pyo3"
#[inline]
pub fn new_err<A>(args: A) -> $crate::PyErr
where
A: $crate::PyErrArguments + ::std::marker::Send + ::std::marker::Sync + 'static,
{
$crate::PyErr::new::<$name, A>(args)
}
}
};
}

/// Defines a Rust type for an exception defined in Python code.
///
/// # Syntax
Expand Down Expand Up @@ -105,34 +113,48 @@ macro_rules! import_exception {

impl $name {
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
use $crate::sync::GILOnceCell;
use $crate::prelude::PyTracebackMethods;
use $crate::prelude::PyAnyMethods;
static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> =
GILOnceCell::new();
use $crate::types::PyTypeMethods;
static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
$crate::impl_::exceptions::ImportedExceptionTypeObject::new(stringify!($module), stringify!($name));
TYPE_OBJECT.get(py).as_type_ptr()
}
}
};
}

TYPE_OBJECT
.get_or_init(py, || {
let imp = py
.import_bound(stringify!($module))
.unwrap_or_else(|err| {
let traceback = err
.traceback_bound(py)
.map(|tb| tb.format().expect("raised exception will have a traceback"))
.unwrap_or_default();
::std::panic!("Can not import module {}: {}\n{}", stringify!($module), err, traceback);
});
let cls = imp.getattr(stringify!($name)).expect(concat!(
"Can not load exception class: ",
stringify!($module),
".",
stringify!($name)
));

cls.extract()
.expect("Imported exception should be a type object")
})
.as_ptr() as *mut _
/// Variant of [`import_exception`](crate::import_exception) that does not emit code needed to
/// use the imported exception type as a GIL Ref.
///
/// This is useful only during migration as a way to avoid generating needless code.
#[macro_export]
macro_rules! import_exception_bound {
($module: expr, $name: ident) => {
/// A Rust type representing an exception defined in Python code.
///
/// This type was created by the [`pyo3::import_exception_bound!`] macro - see its documentation
/// for more information.
///
/// [`pyo3::import_exception_bound!`]: https://docs.rs/pyo3/latest/pyo3/macro.import_exception.html "import_exception in pyo3"
#[repr(transparent)]
#[allow(non_camel_case_types)] // E.g. `socket.herror`
pub struct $name($crate::PyAny);

$crate::impl_exception_boilerplate_bound!($name);

$crate::pyobject_native_type_info!(
$name,
$name::type_object_raw,
#module=::std::option::Option::Some(stringify!($module))
);

impl $crate::types::DerefToPyAny for $name {}

impl $name {
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
use $crate::types::PyTypeMethods;
static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
$crate::impl_::exceptions::ImportedExceptionTypeObject::new(stringify!($module), stringify!($name));
TYPE_OBJECT.get(py).as_type_ptr()
}
}
};
Expand Down
1 change: 1 addition & 0 deletions src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#[cfg(feature = "experimental-async")]
pub mod coroutine;
pub mod deprecations;
pub mod exceptions;
pub mod extract_argument;
pub mod freelist;
pub mod frompyobject;
Expand Down
47 changes: 47 additions & 0 deletions src/impl_/exceptions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::{
sync::GILOnceCell,
types::{PyAnyMethods, PyTracebackMethods, PyType},
Bound, Py, Python,
};

pub struct ImportedExceptionTypeObject {
imported_value: GILOnceCell<Py<PyType>>,
module: &'static str,
name: &'static str,
}

impl ImportedExceptionTypeObject {
pub const fn new(module: &'static str, name: &'static str) -> Self {
Self {
imported_value: GILOnceCell::new(),
module,
name,
}
}

pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyType> {
self.imported_value
.get_or_init(py, || {
let imp = py.import_bound(self.module).unwrap_or_else(|err| {
let traceback = err
.traceback_bound(py)
.map(|tb| tb.format().expect("raised exception will have a traceback"))
.unwrap_or_default();
panic!(
"Can not import module {}: {}\n{}",
self.module, err, traceback
);
});
let cls = imp.getattr(self.name).unwrap_or_else(|_| {
panic!(
"Can not load exception class: {}.{}",
self.module, self.name
)
});

cls.extract()
.expect("Imported exception should be a type object")
})
.bind(py)
}
}

0 comments on commit 33371a4

Please sign in to comment.