From 3008528fa67c56d9b1f83cc096605e7d9883117e Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Wed, 22 Apr 2020 22:08:06 +0100 Subject: [PATCH] Allow use of `#[pyo3(get, set)]` with `Py` --- CHANGELOG.md | 3 ++ src/derive_utils.rs | 8 ++- src/instance.rs | 7 +-- tests/test_class_conversion.rs | 96 ++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10de8e2f..2184bf24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * `PyObject` and `Py` reference counts are now decremented sooner after `drop()`. [#851](https://github.com/PyO3/pyo3/pull/851) * When the GIL is held, the refcount is now decreased immediately on drop. (Previously would wait until just before releasing the GIL.) * When the GIL is not held, the refcount is now decreased when the GIL is next acquired. (Previously would wait until next time the GIL was released.) +* `FromPyObject` for `Py` now works for a wider range of `T`, in particular for `T: PyClass`. [#880](https://github.com/PyO3/pyo3/pull/880) ### Added * `_PyDict_NewPresized`. [#849](https://github.com/PyO3/pyo3/pull/849) @@ -21,6 +22,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * `__radd__` and other `__r*__` methods now correctly work with operators. [#839](https://github.com/PyO3/pyo3/pull/839) * Garbage Collector causing random panics when traversing objects that were mutably borrowed. [#855](https://github.com/PyO3/pyo3/pull/855) * `&'static Py~` being allowed as arguments. [#869](https://github.com/PyO3/pyo3/pull/869) +* `#[pyo3(get)]` for `Py`. [#880](https://github.com/PyO3/pyo3/pull/880) + ## [0.9.2] diff --git a/src/derive_utils.rs b/src/derive_utils.rs index e70d5842..76a05182 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -10,7 +10,7 @@ use crate::instance::PyNativeType; use crate::pyclass::PyClass; use crate::pyclass_init::PyClassInitializer; use crate::types::{PyAny, PyDict, PyModule, PyTuple}; -use crate::{ffi, GILPool, IntoPy, PyCell, PyObject, Python}; +use crate::{ffi, GILPool, IntoPy, Py, PyCell, PyObject, Python}; use std::cell::UnsafeCell; /// Description of a python parameter; used for `parse_args()`. @@ -215,6 +215,12 @@ impl GetPropertyValue for PyObject { } } +impl GetPropertyValue for Py { + fn get_property_value(&self, py: Python) -> PyObject { + self.clone_ref(py).into() + } +} + /// Utilities for basetype #[doc(hidden)] pub trait PyBaseTypeUtils { diff --git a/src/instance.rs b/src/instance.rs index f2e0749f..8afaa6f2 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -295,13 +295,14 @@ where impl<'a, T> FromPyObject<'a> for Py where - T: AsPyPointer, - &'a T: 'a + FromPyObject<'a>, + T: PyTypeInfo, + &'a T::AsRefTarget: FromPyObject<'a>, + T::AsRefTarget: 'a + AsPyPointer, { /// Extracts `Self` from the source `PyObject`. fn extract(ob: &'a PyAny) -> PyResult { unsafe { - ob.extract::<&T>() + ob.extract::<&T::AsRefTarget>() .map(|val| Py::from_borrowed_ptr(val.as_ptr())) } } diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index e04e7375..6da710c0 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -1,4 +1,8 @@ use pyo3::prelude::*; +use pyo3::{ObjectProtocol, ToPyObject}; + +#[macro_use] +mod common; #[pyclass] #[derive(Clone, Debug, PartialEq)] @@ -25,3 +29,95 @@ fn test_cloneable_pyclass() { let mrc: PyRefMut = py_c.extract(py).unwrap(); assert_eq!(&c, &*mrc); } + +#[pyclass] +struct BaseClass {} + +#[pymethods] +impl BaseClass { + fn foo(&self) -> &'static str { + "BaseClass" + } +} + +#[pyclass(extends=BaseClass)] +struct SubClass {} + +#[pymethods] +impl SubClass { + fn foo(&self) -> &'static str { + "SubClass" + } +} + +#[pyclass] +struct PolymorphicContainer { + #[pyo3(get, set)] + inner: Py, +} + +#[test] +fn test_polymorphic_container_stores_base_class() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let p = PyCell::new( + py, + PolymorphicContainer { + inner: Py::new(py, BaseClass {}).unwrap(), + }, + ) + .unwrap() + .to_object(py); + + py_assert!(py, p, "p.inner.foo() == 'BaseClass'"); +} + +#[test] +fn test_polymorphic_container_stores_sub_class() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let p = PyCell::new( + py, + PolymorphicContainer { + inner: Py::new(py, BaseClass {}).unwrap(), + }, + ) + .unwrap() + .to_object(py); + + p.as_ref(py) + .setattr( + "inner", + PyCell::new( + py, + PyClassInitializer::from(BaseClass {}).add_subclass(SubClass {}), + ) + .unwrap(), + ) + .unwrap(); + + py_assert!(py, p, "p.inner.foo() == 'SubClass'"); +} + +#[test] +fn test_polymorphic_container_does_not_accept_other_types() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let p = PyCell::new( + py, + PolymorphicContainer { + inner: Py::new(py, BaseClass {}).unwrap(), + }, + ) + .unwrap() + .to_object(py); + + let setattr = |value: PyObject| p.as_ref(py).setattr("inner", value); + + assert!(setattr(1i32.into_py(py)).is_err()); + assert!(setattr(py.None()).is_err()); + assert!(setattr((1i32, 2i32).into_py(py)).is_err()); +}