add `import_exception_bound!` macro (#4027)

* add `import_exception_bound!` macro

* newsfragment and tidy up
This commit is contained in:
David Hewitt 2024-03-31 20:55:31 +01:00 committed by GitHub
parent 3af9a1f4e0
commit 336b1c982b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 42 deletions

View File

@ -0,0 +1 @@
Add `import_exception_bound!` macro to import exception types without generating GIL Ref functionality for them.

View File

@ -29,18 +29,7 @@ macro_rules! impl_exception_boilerplate {
} }
} }
impl $name { $crate::impl_exception_boilerplate_bound!($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)
}
}
impl ::std::error::Error for $name { impl ::std::error::Error for $name {
fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> { 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<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. /// Defines a Rust type for an exception defined in Python code.
/// ///
/// # Syntax /// # Syntax
@ -105,34 +113,57 @@ macro_rules! import_exception {
impl $name { impl $name {
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject { fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
use $crate::sync::GILOnceCell; use $crate::types::PyTypeMethods;
use $crate::prelude::PyTracebackMethods; static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
use $crate::prelude::PyAnyMethods; $crate::impl_::exceptions::ImportedExceptionTypeObject::new(stringify!($module), stringify!($name));
static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> = TYPE_OBJECT.get(py).as_type_ptr()
GILOnceCell::new(); }
}
};
}
TYPE_OBJECT /// Variant of [`import_exception`](crate::import_exception) that does not emit code needed to
.get_or_init(py, || { /// use the imported exception type as a GIL Ref.
let imp = py ///
.import_bound(stringify!($module)) /// This is useful only during migration as a way to avoid generating needless code.
.unwrap_or_else(|err| { #[macro_export]
let traceback = err macro_rules! import_exception_bound {
.traceback_bound(py) ($module: expr, $name: ident) => {
.map(|tb| tb.format().expect("raised exception will have a traceback")) /// A Rust type representing an exception defined in Python code.
.unwrap_or_default(); ///
::std::panic!("Can not import module {}: {}\n{}", stringify!($module), err, traceback); /// This type was created by the [`pyo3::import_exception_bound!`] macro - see its documentation
}); /// for more information.
let cls = imp.getattr(stringify!($name)).expect(concat!( ///
"Can not load exception class: ", /// [`pyo3::import_exception_bound!`]: https://docs.rs/pyo3/latest/pyo3/macro.import_exception.html "import_exception in pyo3"
stringify!($module), #[repr(transparent)]
".", #[allow(non_camel_case_types)] // E.g. `socket.herror`
stringify!($name) pub struct $name($crate::PyAny);
));
cls.extract() $crate::impl_exception_boilerplate_bound!($name);
.expect("Imported exception should be a type object")
}) // FIXME remove this: was necessary while `PyTypeInfo` requires `HasPyGilRef`,
.as_ptr() as *mut _ // should change in 0.22.
unsafe impl $crate::type_object::HasPyGilRef for $name {
type AsRefTarget = $crate::PyAny;
}
$crate::pyobject_native_type_info!(
$name,
$name::type_object_raw,
::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()
} }
} }
}; };
@ -849,8 +880,8 @@ mod tests {
use crate::types::{IntoPyDict, PyDict}; use crate::types::{IntoPyDict, PyDict};
use crate::{PyErr, PyNativeType}; use crate::{PyErr, PyNativeType};
import_exception!(socket, gaierror); import_exception_bound!(socket, gaierror);
import_exception!(email.errors, MessageError); import_exception_bound!(email.errors, MessageError);
#[test] #[test]
fn test_check_exception() { fn test_check_exception() {

View File

@ -9,6 +9,7 @@
#[cfg(feature = "experimental-async")] #[cfg(feature = "experimental-async")]
pub mod coroutine; pub mod coroutine;
pub mod deprecations; pub mod deprecations;
pub mod exceptions;
pub mod extract_argument; pub mod extract_argument;
pub mod freelist; pub mod freelist;
pub mod frompyobject; pub mod frompyobject;

28
src/impl_/exceptions.rs Normal file
View File

@ -0,0 +1,28 @@
use crate::{sync::GILOnceCell, types::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_try_init_type_ref(py, self.module, self.name)
.unwrap_or_else(|e| {
panic!(
"failed to import exception {}.{}: {}",
self.module, self.name, e
)
})
}
}

View File

@ -201,13 +201,17 @@ impl GILOnceCell<Py<PyType>> {
/// ///
/// This is a shorthand method for `get_or_init` which imports the type from Python on init. /// This is a shorthand method for `get_or_init` which imports the type from Python on init.
pub(crate) fn get_or_try_init_type_ref<'py>( pub(crate) fn get_or_try_init_type_ref<'py>(
&'py self, &self,
py: Python<'py>, py: Python<'py>,
module_name: &str, module_name: &str,
attr_name: &str, attr_name: &str,
) -> PyResult<&Bound<'py, PyType>> { ) -> PyResult<&Bound<'py, PyType>> {
self.get_or_try_init(py, || { self.get_or_try_init(py, || {
py.import_bound(module_name)?.getattr(attr_name)?.extract() let type_object = py
.import_bound(module_name)?
.getattr(attr_name)?
.downcast_into()?;
Ok(type_object.unbind())
}) })
.map(|ty| ty.bind(py)) .map(|ty| ty.bind(py))
} }