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) + } +}