Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ smallvec = { version = "1.0", optional = true }
uuid = { version = "1.11.0", optional = true }
lock_api = { version = "0.4", optional = true }
parking_lot = { version = "0.12", optional = true }
iana-time-zone = { version = "0.1", optional = true, features = ["fallback"]}
iana-time-zone = { version = "0.1", optional = true, features = ["fallback"] }

[target.'cfg(not(target_has_atomic = "64"))'.dependencies]
portable-atomic = "1.0"
Expand Down
1 change: 1 addition & 0 deletions newsfragments/5666.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Introspection: adds support for exception created using `create_exception!`
40 changes: 39 additions & 1 deletion pyo3-macros-backend/src/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,48 @@ use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::mem::take;
use std::sync::atomic::{AtomicUsize, Ordering};
use syn::{Attribute, Ident, ReturnType, Type, TypePath};
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::{Attribute, Ident, Path, ReturnType, Token, Type, TypePath};

static GLOBAL_COUNTER_FOR_UNIQUE_NAMES: AtomicUsize = AtomicUsize::new(0);

/// Entry point to implement introspection on exceptions
pub fn implement_class_introspection(options: ClassIntrospectionOptions) -> TokenStream {
class_introspection_code(
&PyO3CratePath::Given(options.pyo3_class_path),
&options.name,
&options.name.unraw().to_string(),
options.base.map(|t| PythonTypeHint::from_type(t, None)),
false,
)
}

pub struct ClassIntrospectionOptions {
pub pyo3_class_path: Path,
pub name: Ident,
pub base: Option<Type>,
}

impl Parse for ClassIntrospectionOptions {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let pyo3_class_path = input.parse()?;
let _: Token![,] = input.parse()?;
let name = input.parse()?;
let base = if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
Some(input.parse()?)
} else {
None
};
Ok(Self {
pyo3_class_path,
name,
base,
})
}
}

pub fn module_introspection_code<'a>(
pyo3_crate_path: &PyO3CratePath,
name: &str,
Expand Down
2 changes: 2 additions & 0 deletions pyo3-macros-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ mod type_hint;

pub use frompyobject::build_derive_from_pyobject;
pub use intopyobject::build_derive_into_pyobject;
#[cfg(feature = "experimental-inspect")]
pub use introspection::{implement_class_introspection, ClassIntrospectionOptions};
pub use module::{pymodule_function_impl, pymodule_module_impl, PyModuleOptions};
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
pub use pyfunction::{build_py_function, PyFunctionOptions};
Expand Down
33 changes: 17 additions & 16 deletions pyo3-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,32 +159,25 @@ pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_derive(IntoPyObject, attributes(pyo3))]
pub fn derive_into_py_object(item: TokenStream) -> TokenStream {
let ast = parse_macro_input!(item as syn::DeriveInput);
let expanded = build_derive_into_pyobject::<false>(&ast).unwrap_or_compile_error();
quote!(
#expanded
)
.into()
build_derive_into_pyobject::<false>(&ast)
.unwrap_or_compile_error()
.into()
}

#[proc_macro_derive(IntoPyObjectRef, attributes(pyo3))]
pub fn derive_into_py_object_ref(item: TokenStream) -> TokenStream {
let ast = parse_macro_input!(item as syn::DeriveInput);
let expanded =
pyo3_macros_backend::build_derive_into_pyobject::<true>(&ast).unwrap_or_compile_error();
quote!(
#expanded
)
.into()
build_derive_into_pyobject::<true>(&ast)
.unwrap_or_compile_error()
.into()
}

#[proc_macro_derive(FromPyObject, attributes(pyo3))]
pub fn derive_from_py_object(item: TokenStream) -> TokenStream {
let ast = parse_macro_input!(item as syn::DeriveInput);
let expanded = build_derive_from_pyobject(&ast).unwrap_or_compile_error();
quote!(
#expanded
)
.into()
build_derive_from_pyobject(&ast)
.unwrap_or_compile_error()
.into()
}

fn pyclass_impl(
Expand Down Expand Up @@ -254,3 +247,11 @@ impl UnwrapOrCompileError for syn::Result<TokenStream2> {
self.unwrap_or_else(|e| e.into_compile_error())
}
}

#[cfg(feature = "experimental-inspect")]
#[doc(hidden)]
#[proc_macro]
pub fn implement_class_introspection(args: TokenStream) -> TokenStream {
let options = parse_macro_input!(args as pyo3_macros_backend::ClassIntrospectionOptions);
pyo3_macros_backend::implement_class_introspection(options).into()
}
19 changes: 19 additions & 0 deletions pytests/src/exception.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use pyo3::create_exception;
use pyo3::exceptions::{PyException, PyValueError};
use pyo3::prelude::*;

create_exception!(pyo3_pytests.exception, CustomValueError, PyValueError);

create_exception!(pyo3_pytests.exception, CustomException, PyException);

#[pymodule(gil_used = false)]
pub mod exception {
#[pymodule_export]
use super::{CustomException, CustomValueError};
use pyo3::prelude::*;

#[pyfunction]
fn raise_custom_value_error() -> PyResult<()> {
Err(CustomValueError::new_err("custom value error"))
}
}
5 changes: 3 additions & 2 deletions pytests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod consts;
pub mod datetime;
pub mod dict_iter;
pub mod enums;
mod exception;
pub mod misc;
pub mod objstore;
pub mod othermod;
Expand All @@ -27,8 +28,8 @@ mod pyo3_pytests {
use buf_and_str::buf_and_str;
#[pymodule_export]
use {
comparisons::comparisons, consts::consts, enums::enums, pyclasses::pyclasses,
pyfunctions::pyfunctions, subclassing::subclassing,
comparisons::comparisons, consts::consts, enums::enums, exception::exception,
pyclasses::pyclasses, pyfunctions::pyfunctions, subclassing::subclassing,
};

// Inserting to sys.modules allows importing submodules nicely from Python
Expand Down
6 changes: 6 additions & 0 deletions pytests/stubs/exception.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import Any

class CustomException(Exception): ...
class CustomValueError(ValueError): ...

def raise_custom_value_error() -> Any: ...
19 changes: 19 additions & 0 deletions src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,28 @@ macro_rules! create_exception_type_object {
).as_ptr() as *mut $crate::ffi::PyTypeObject
}
}

$crate::create_exception_introspection_data!($name, $base);
};
}

/// Adds some introspection data for the exception if the `experimental-inspect` feature is enabled.
#[cfg(not(feature = "experimental-inspect"))]
#[doc(hidden)]
#[macro_export]
macro_rules! create_exception_introspection_data(
($name: ident, $base: ty) => {};
);

#[cfg(all(feature = "experimental-inspect", feature = "macros"))]
#[doc(hidden)]
#[macro_export]
macro_rules! create_exception_introspection_data(
($name: ident, $base: ty) => {
$crate::implement_class_introspection!($crate, $name, $base);
};
);

macro_rules! impl_native_exception (
($name:ident, $exc_name:ident, $python_name:expr, $doc:expr, $layout:path $(, #checkfunction=$checkfunction:path)?) => (
#[doc = $doc]
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ mod version;
)]
pub use crate::conversions::*;

#[cfg(all(feature = "experimental-inspect", feature = "macros"))]
#[doc(hidden)]
pub use pyo3_macros::implement_class_introspection;
#[cfg(feature = "macros")]
pub use pyo3_macros::{
pyfunction, pymethods, pymodule, FromPyObject, IntoPyObject, IntoPyObjectRef,
Expand Down
Loading