diff --git a/src/exceptions.rs b/src/exceptions.rs
index 66b5b57dc0e..25306c02e6f 100644
--- a/src/exceptions.rs
+++ b/src/exceptions.rs
@@ -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(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)> {
@@ -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(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
@@ -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()
}
}
};
diff --git a/src/impl_.rs b/src/impl_.rs
index ea71b257c0e..71ba397cb94 100644
--- a/src/impl_.rs
+++ b/src/impl_.rs
@@ -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;
diff --git a/src/impl_/exceptions.rs b/src/impl_/exceptions.rs
new file mode 100644
index 00000000000..f6e9315eef3
--- /dev/null
+++ b/src/impl_/exceptions.rs
@@ -0,0 +1,47 @@
+use crate::{
+ sync::GILOnceCell,
+ types::{PyAnyMethods, PyTracebackMethods, PyType},
+ Bound, Py, Python,
+};
+
+pub struct ImportedExceptionTypeObject {
+ imported_value: GILOnceCell>,
+ 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)
+ }
+}