Skip to content

Commit 336b1c9

Browse files
authored
add import_exception_bound! macro (#4027)
* add `import_exception_bound!` macro * newsfragment and tidy up
1 parent 3af9a1f commit 336b1c9

File tree

5 files changed

+108
-43
lines changed

5 files changed

+108
-43
lines changed

newsfragments/4027.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `import_exception_bound!` macro to import exception types without generating GIL Ref functionality for them.

src/exceptions.rs

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,7 @@ macro_rules! impl_exception_boilerplate {
2929
}
3030
}
3131

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

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

51+
#[doc(hidden)]
52+
#[macro_export]
53+
macro_rules! impl_exception_boilerplate_bound {
54+
($name: ident) => {
55+
impl $name {
56+
/// Creates a new [`PyErr`] of this type.
57+
///
58+
/// [`PyErr`]: https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html "PyErr in pyo3"
59+
#[inline]
60+
pub fn new_err<A>(args: A) -> $crate::PyErr
61+
where
62+
A: $crate::PyErrArguments + ::std::marker::Send + ::std::marker::Sync + 'static,
63+
{
64+
$crate::PyErr::new::<$name, A>(args)
65+
}
66+
}
67+
};
68+
}
69+
6270
/// Defines a Rust type for an exception defined in Python code.
6371
///
6472
/// # Syntax
@@ -105,34 +113,57 @@ macro_rules! import_exception {
105113

106114
impl $name {
107115
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
108-
use $crate::sync::GILOnceCell;
109-
use $crate::prelude::PyTracebackMethods;
110-
use $crate::prelude::PyAnyMethods;
111-
static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> =
112-
GILOnceCell::new();
116+
use $crate::types::PyTypeMethods;
117+
static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
118+
$crate::impl_::exceptions::ImportedExceptionTypeObject::new(stringify!($module), stringify!($name));
119+
TYPE_OBJECT.get(py).as_type_ptr()
120+
}
121+
}
122+
};
123+
}
113124

114-
TYPE_OBJECT
115-
.get_or_init(py, || {
116-
let imp = py
117-
.import_bound(stringify!($module))
118-
.unwrap_or_else(|err| {
119-
let traceback = err
120-
.traceback_bound(py)
121-
.map(|tb| tb.format().expect("raised exception will have a traceback"))
122-
.unwrap_or_default();
123-
::std::panic!("Can not import module {}: {}\n{}", stringify!($module), err, traceback);
124-
});
125-
let cls = imp.getattr(stringify!($name)).expect(concat!(
126-
"Can not load exception class: ",
127-
stringify!($module),
128-
".",
129-
stringify!($name)
130-
));
131-
132-
cls.extract()
133-
.expect("Imported exception should be a type object")
134-
})
135-
.as_ptr() as *mut _
125+
/// Variant of [`import_exception`](crate::import_exception) that does not emit code needed to
126+
/// use the imported exception type as a GIL Ref.
127+
///
128+
/// This is useful only during migration as a way to avoid generating needless code.
129+
#[macro_export]
130+
macro_rules! import_exception_bound {
131+
($module: expr, $name: ident) => {
132+
/// A Rust type representing an exception defined in Python code.
133+
///
134+
/// This type was created by the [`pyo3::import_exception_bound!`] macro - see its documentation
135+
/// for more information.
136+
///
137+
/// [`pyo3::import_exception_bound!`]: https://docs.rs/pyo3/latest/pyo3/macro.import_exception.html "import_exception in pyo3"
138+
#[repr(transparent)]
139+
#[allow(non_camel_case_types)] // E.g. `socket.herror`
140+
pub struct $name($crate::PyAny);
141+
142+
$crate::impl_exception_boilerplate_bound!($name);
143+
144+
// FIXME remove this: was necessary while `PyTypeInfo` requires `HasPyGilRef`,
145+
// should change in 0.22.
146+
unsafe impl $crate::type_object::HasPyGilRef for $name {
147+
type AsRefTarget = $crate::PyAny;
148+
}
149+
150+
$crate::pyobject_native_type_info!(
151+
$name,
152+
$name::type_object_raw,
153+
::std::option::Option::Some(stringify!($module))
154+
);
155+
156+
impl $crate::types::DerefToPyAny for $name {}
157+
158+
impl $name {
159+
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
160+
use $crate::types::PyTypeMethods;
161+
static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
162+
$crate::impl_::exceptions::ImportedExceptionTypeObject::new(
163+
stringify!($module),
164+
stringify!($name),
165+
);
166+
TYPE_OBJECT.get(py).as_type_ptr()
136167
}
137168
}
138169
};
@@ -849,8 +880,8 @@ mod tests {
849880
use crate::types::{IntoPyDict, PyDict};
850881
use crate::{PyErr, PyNativeType};
851882

852-
import_exception!(socket, gaierror);
853-
import_exception!(email.errors, MessageError);
883+
import_exception_bound!(socket, gaierror);
884+
import_exception_bound!(email.errors, MessageError);
854885

855886
#[test]
856887
fn test_check_exception() {

src/impl_.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#[cfg(feature = "experimental-async")]
1010
pub mod coroutine;
1111
pub mod deprecations;
12+
pub mod exceptions;
1213
pub mod extract_argument;
1314
pub mod freelist;
1415
pub mod frompyobject;

src/impl_/exceptions.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use crate::{sync::GILOnceCell, types::PyType, Bound, Py, Python};
2+
3+
pub struct ImportedExceptionTypeObject {
4+
imported_value: GILOnceCell<Py<PyType>>,
5+
module: &'static str,
6+
name: &'static str,
7+
}
8+
9+
impl ImportedExceptionTypeObject {
10+
pub const fn new(module: &'static str, name: &'static str) -> Self {
11+
Self {
12+
imported_value: GILOnceCell::new(),
13+
module,
14+
name,
15+
}
16+
}
17+
18+
pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyType> {
19+
self.imported_value
20+
.get_or_try_init_type_ref(py, self.module, self.name)
21+
.unwrap_or_else(|e| {
22+
panic!(
23+
"failed to import exception {}.{}: {}",
24+
self.module, self.name, e
25+
)
26+
})
27+
}
28+
}

src/sync.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,17 @@ impl GILOnceCell<Py<PyType>> {
201201
///
202202
/// This is a shorthand method for `get_or_init` which imports the type from Python on init.
203203
pub(crate) fn get_or_try_init_type_ref<'py>(
204-
&'py self,
204+
&self,
205205
py: Python<'py>,
206206
module_name: &str,
207207
attr_name: &str,
208208
) -> PyResult<&Bound<'py, PyType>> {
209209
self.get_or_try_init(py, || {
210-
py.import_bound(module_name)?.getattr(attr_name)?.extract()
210+
let type_object = py
211+
.import_bound(module_name)?
212+
.getattr(attr_name)?
213+
.downcast_into()?;
214+
Ok(type_object.unbind())
211215
})
212216
.map(|ty| ty.bind(py))
213217
}

0 commit comments

Comments
 (0)