#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![deny(clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
#![warn(clippy::nursery)]
#![allow(clippy::redundant_pub_crate)]
#![deny(clippy::missing_docs_in_private_items)]
#![deny(
absolute_paths_not_starting_with_crate,
anonymous_parameters,
bad_style,
dead_code,
keyword_idents,
improper_ctypes,
macro_use_extern_crate,
meta_variable_misuse,
missing_abi,
missing_debug_implementations,
missing_docs,
no_mangle_generic_items,
non_shorthand_field_patterns,
noop_method_call,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
semicolon_in_expressions_from_macros,
trivial_casts,
trivial_numeric_casts,
unconditional_recursion,
unreachable_pub,
unsafe_code,
unused,
unused_allocation,
unused_comparisons,
unused_extern_crates,
unused_import_braces,
unused_lifetimes,
unused_parens,
unused_qualifications,
variant_size_differences,
while_true
)]
mod errors;
#[cfg(feature = "stubs")]
pub mod stubs;
#[cfg(feature = "async-tokio")]
pub mod sync;
mod traits;
pub use pyo3;
#[cfg(feature = "async-tokio")]
pub use pyo3_async_runtimes;
#[cfg(feature = "stubs")]
pub use pyo3_stub_gen;
#[cfg(feature = "async-tokio")]
pub use tokio;
use pyo3::{prelude::*, types::PyType};
#[macro_export]
macro_rules! create_init_submodule {
(
$(#[$meta:meta])*
$(classes: [ $($class: ty),+ $(,)? ],)?
$(complex_enums: [ $($complex_enum: ty),+ $(,)? ],)?
$(consts: [ $($const: ident),+ $(,)? ],)?
$(errors: [ $($error: ty),+ $(,)? ],)?
$(funcs: [ $($func: path),+ $(,)? ],)?
$(submodules: [ $($mod_name: literal: $init_submod: path),+ $(,)? ],)?
) => {
$(#[$meta])*
pub(crate) fn init_submodule<'py>(_name: &str, _py: $crate::pyo3::Python<'py>, m: &$crate::pyo3::Bound<'py, $crate::pyo3::types::PyModule>) -> $crate::pyo3::PyResult<()> {
$($(
$crate::pyo3::types::PyModuleMethods::add_class::<$class>(m)?;
)+)?
$($(
$crate::pyo3::types::PyModuleMethods::add_class::<$complex_enum>(m)?;
)+)?
$($(
$crate::pyo3::types::PyModuleMethods::add(m,
::std::stringify!($const),
$crate::pyo3::IntoPyObject::into_pyobject(&$const, _py)?
)?;
)+)?
$($(
$crate::pyo3::types::PyModuleMethods::add(m,
$crate::pyo3::types::PyTypeMethods::name(&_py.get_type::<$error>())?,
_py.get_type::<$error>()
)?;
)+)?
$($(
$crate::pyo3::types::PyModuleMethods::add_function(m, $crate::pyo3::wrap_pyfunction!($func, m)?)?;
)+)?
$(
let sys = $crate::pyo3::types::PyModule::import(_py, "sys")?;
let modules = $crate::pyo3::types::PyAnyMethods::getattr(sys.as_any(), "modules")?;
$(
let qualified_name = format!("{}.{}", _name, $mod_name);
let submod = $crate::pyo3::types::PyModule::new(_py, $mod_name)?;
$init_submod(&qualified_name, _py, &submod)?;
$crate::pyo3::types::PyModuleMethods::add_submodule(m, &submod)?;
$crate::pyo3::types::PyAnyMethods::set_item(modules.as_any(), &qualified_name, &submod)?;
)+
)?
$(
$crate::fix_complex_enums!(_py, $($complex_enum),+);
)?
Ok(())
}
}
}
pub fn fix_enum_qual_names(typ: &Bound<'_, PyType>) -> PyResult<()> {
let py = typ.py();
let (is_class, get_members) = __private::import_inspect(py)?;
__private::fix_enum_qual_names_impl(py, typ, &is_class, &get_members)
}
#[doc(hidden)]
pub mod __private {
use pyo3::{
prelude::*,
types::{PyList, PyTuple, PyType, PyTypeMethods},
};
pub fn import_inspect(py: Python<'_>) -> PyResult<(Bound<'_, PyAny>, Bound<'_, PyAny>)> {
let inspect = PyModule::import(py, pyo3::intern!(py, "inspect"))?;
let is_class = inspect.getattr(pyo3::intern!(py, "isclass"))?;
let get_members = inspect.getattr(pyo3::intern!(py, "getmembers"))?;
Ok((is_class, get_members))
}
pub fn fix_enum_qual_names_impl<'py>(
py: Python<'py>,
typ: &Bound<'py, PyType>,
is_class: &Bound<'py, PyAny>,
get_members: &Bound<'py, PyAny>,
) -> PyResult<()> {
let prefix = typ.qualname()?;
let prefix = prefix.to_str()?;
let inner = get_members.call((typ, is_class), None)?;
for item in inner.cast::<PyList>()? {
let item = item.cast::<PyTuple>()?;
let cls = item.get_borrowed_item(1)?;
if cls.cast()?.is_subclass(typ)? {
let name = item.get_borrowed_item(0)?;
let fixed_name = format!("{prefix}.{}", name.cast()?.to_str()?);
cls.setattr(pyo3::intern!(py, "__qualname__"), fixed_name)?;
}
}
Ok(())
}
}
#[macro_export]
macro_rules! fix_complex_enums {
($py:expr, $($name:path),* $(,)?) => {
{
let py = $py;
let (is_class, get_members) = $crate::__private::import_inspect(py)?;
$($crate::__private::fix_enum_qual_names_impl(py, &py.get_type::<$name>(), &is_class, &get_members)?;)*
}
};
}
#[cfg(test)]
mod test_fix_qualname {
use pyo3::types::{PyDict, PyTuple};
use pyo3::{prelude::*, py_run};
#[pyclass(module = "mymod")]
enum Foo {
Integer { value: i64 },
Real { value: f64 },
}
#[pymethods]
impl Foo {
fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
match self {
Self::Integer { value } => PyTuple::new(py, [value]),
Self::Real { value } => PyTuple::new(py, [value]),
}
}
}
#[pyclass(module = "mymod")]
enum Bar {
Integer(i64),
Real(f64),
}
#[pymethods]
impl Bar {
fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
match self {
Self::Integer(value) => PyTuple::new(py, [value]),
Self::Real(value) => PyTuple::new(py, [value]),
}
}
}
#[pyclass(module = "mymod")]
enum Baz {
Integer(i64),
Real(f64),
}
#[pymethods]
impl Baz {
fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
match self {
Self::Integer(value) => PyTuple::new(py, [value]),
Self::Real(value) => PyTuple::new(py, [value]),
}
}
}
#[pymodule(name = "mymod")]
fn mymod(m: &Bound<'_, PyModule>) -> PyResult<()> {
let py = m.py();
m.add_class::<Foo>()?;
m.add_class::<Bar>()?;
m.add_class::<Baz>()?;
fix_complex_enums!(py, Foo, Bar);
Ok(())
}
#[test]
fn test_fix_enum_qual_names() {
pyo3::append_to_inittab!(mymod);
Python::initialize();
Python::attach(|py| {
let locals = PyDict::new(py);
py_run!(
py,
*locals,
r#"
import pickle
import mymod
from mymod import Foo
objs = [
Foo.Integer(42),
Foo.Real(3.14),
# This still works even if not imported.
mymod.Bar.Integer(42),
mymod.Bar.Real(3.14),
]
for obj in objs:
result = pickle.loads(pickle.dumps(obj))
match obj:
case Foo.Integer(value=x) | Foo.Real(value=x):
assert result.value == x
case mymod.Bar.Integer(x) | mymod.Bar.Real(x):
assert result._0 == x
case _:
raise TypeError(f"Unexpected object: {obj}")
# Baz doesn't have the __qualname__ fix, so pickling fails:
from mymod import Baz
objs = [
Baz.Integer(42),
Baz.Real(3.14),
]
for obj in objs:
try:
pickle.dumps(obj)
except pickle.PicklingError:
continue
raise TypeError(f"{obj} should not be picklable")
"#
);
});
}
}