diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index e2f849492a0..2422615b090 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -864,10 +864,19 @@ fn impl_complex_enum( } }; + let enum_pyinitializable_impl = quote! { + impl _pyo3::PyInitializable for #cls { + fn initialize(py: Python<'_>, value: impl Into>) -> PyResult> { + todo!() + } + } + }; + let pyclass_impls: TokenStream = vec![ impl_builder.impl_pyclass(), impl_builder.impl_extractext(), enum_into_py_impl, + enum_pyinitializable_impl, impl_builder.impl_pyclassimpl()?, impl_builder.impl_freelist(), ] @@ -1336,6 +1345,7 @@ impl<'a> PyClassImplsBuilder<'a> { let tokens = vec![ self.impl_pyclass(), self.impl_extractext(), + self.impl_pyinitializable(), self.impl_into_py(), self.impl_pyclassimpl()?, self.impl_freelist(), @@ -1399,6 +1409,13 @@ impl<'a> PyClassImplsBuilder<'a> { } } + fn impl_pyinitializable(&self) -> TokenStream { + let cls = self.cls; + quote! { + impl _pyo3::PyInitializable for #cls {} + } + } + fn impl_into_py(&self) -> TokenStream { let cls = self.cls; let attr = self.attr; diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 23cebb26705..6fe94f74126 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -7,7 +7,8 @@ use crate::{ pycell::PyCellLayout, pyclass_init::PyObjectInit, types::PyBool, - Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, + Py, PyAny, PyCell, PyClass, PyClassInitializer, PyErr, PyMethodDefType, PyNativeType, PyResult, + PyTypeInfo, Python, }; use std::{ borrow::Cow, @@ -136,6 +137,19 @@ pub struct PyClassItems { // Allow PyClassItems in statics unsafe impl Sync for PyClassItems {} +/// Allows customizing how a Py is created from a value, while also providing a default implementation. +pub trait PyInitializable: PyClass { + fn initialize( + py: Python<'_>, + value: impl Into>, + ) -> PyResult> { + let initializer: PyClassInitializer = value.into(); + let obj = initializer.create_cell(py)?; + let ob = unsafe { Py::from_owned_ptr(py, obj as _) }; + Ok(ob) + } +} + /// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros. /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail diff --git a/src/instance.rs b/src/instance.rs index 5779e9ada6f..1d31ea34469 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -6,8 +6,8 @@ use crate::types::any::PyAnyMethods; use crate::types::string::PyStringMethods; use crate::types::{PyDict, PyString, PyTuple}; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyClass, PyClassInitializer, PyRef, PyRefMut, - PyTypeInfo, Python, ToPyObject, + ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyClass, PyClassInitializer, PyInitializable, + PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, }; use crate::{gil, PyTypeCheck}; use std::marker::PhantomData; @@ -548,7 +548,7 @@ unsafe impl Sync for Py {} impl Py where - T: PyClass, + T: PyInitializable, { /// Creates a new instance `Py` of a `#[pyclass]` on the Python heap. /// @@ -569,10 +569,7 @@ where /// # } /// ``` pub fn new(py: Python<'_>, value: impl Into>) -> PyResult> { - let initializer = value.into(); - let obj = initializer.create_cell(py)?; - let ob = unsafe { Py::from_owned_ptr(py, obj as _) }; - Ok(ob) + ::initialize(py, value) } } diff --git a/src/lib.rs b/src/lib.rs index 3f91eb56913..9ccb43dd440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,7 +309,7 @@ pub use crate::gil::{prepare_freethreaded_python, with_embedded_python_interpret pub use crate::instance::{Borrowed, Bound, Py, PyNativeType, PyObject}; pub use crate::marker::Python; pub use crate::pycell::{PyCell, PyRef, PyRefMut}; -pub use crate::pyclass::PyClass; +pub use crate::pyclass::{PyClass, PyInitializable}; pub use crate::pyclass_init::PyClassInitializer; pub use crate::type_object::{PyTypeCheck, PyTypeInfo}; pub use crate::types::PyAny; diff --git a/src/pyclass.rs b/src/pyclass.rs index eb4a5595ca9..e3e263a8121 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -10,6 +10,7 @@ mod gc; pub(crate) use self::create_type_object::{create_type_object, PyClassTypeObject}; pub use self::gc::{PyTraverseError, PyVisit}; +pub use crate::impl_::pyclass::PyInitializable; /// Types that can be used as Python classes. /// diff --git a/tests/test_enum.rs b/tests/test_enum.rs index 33e94c241c7..3b1e42ebc6c 100644 --- a/tests/test_enum.rs +++ b/tests/test_enum.rs @@ -214,3 +214,18 @@ fn test_renaming_all_enum_variants() { ); }); } + +#[test] +fn test_complex_enum_py_new_into_py() { + #[pyclass] + enum MyEnum { + Variant { i: i32 }, + } + + Python::with_gil(|py| { + let x = Py::new(py, MyEnum::Variant { i: 42 }).unwrap(); + let cls = py.get_type::(); + py_assert!(py, x cls, "isinstance(x, cls)"); + py_assert!(py, x cls, "isinstance(x, cls.Variant)"); + }); +}