diff --git a/newsfragments/3835.added.md b/newsfragments/3835.added.md new file mode 100644 index 00000000000..2970a4c8db4 --- /dev/null +++ b/newsfragments/3835.added.md @@ -0,0 +1 @@ +Add `PyWeakref`, `PyWeakrefReference` and `PyWeakrefProxy`. diff --git a/src/prelude.rs b/src/prelude.rs index 4052f7c2d0b..6a0657c8a98 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -49,3 +49,4 @@ pub use crate::types::string::PyStringMethods; pub use crate::types::traceback::PyTracebackMethods; pub use crate::types::tuple::PyTupleMethods; pub use crate::types::typeobject::PyTypeMethods; +pub use crate::types::weakref::PyWeakrefMethods; diff --git a/src/types/mod.rs b/src/types/mod.rs index 12dabda7463..d74c7bc234c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -47,6 +47,7 @@ pub use self::string::{PyString, PyString as PyUnicode, PyStringMethods}; pub use self::traceback::{PyTraceback, PyTracebackMethods}; pub use self::tuple::{PyTuple, PyTupleMethods}; pub use self::typeobject::{PyType, PyTypeMethods}; +pub use self::weakref::{PyWeakref, PyWeakrefMethods, PyWeakrefProxy, PyWeakrefReference}; /// Iteration over Python collections. /// @@ -365,3 +366,4 @@ pub(crate) mod string; pub(crate) mod traceback; pub(crate) mod tuple; pub(crate) mod typeobject; +pub(crate) mod weakref; diff --git a/src/types/weakref/anyref.rs b/src/types/weakref/anyref.rs new file mode 100644 index 00000000000..82e16293e62 --- /dev/null +++ b/src/types/weakref/anyref.rs @@ -0,0 +1,1752 @@ +use crate::err::{DowncastError, PyResult}; +use crate::ffi_ptr_ext::FfiPtrExt; +use crate::type_object::{PyTypeCheck, PyTypeInfo}; +use crate::types::any::{PyAny, PyAnyMethods}; +use crate::{ffi, Borrowed, Bound}; + +#[cfg(feature = "gil-refs")] +use crate::PyNativeType; + +/// Represents any Python `weakref` reference. +/// +/// In Python this is created by calling `weakref.ref` or `weakref.proxy`. +#[repr(transparent)] +pub struct PyWeakref(PyAny); + +pyobject_native_type_named!(PyWeakref); +pyobject_native_type_extract!(PyWeakref); + +// TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers +// #[cfg(not(Py_LIMITED_API))] +// pyobject_native_type_sized!(PyWeakref, ffi::PyWeakReference); + +impl PyTypeCheck for PyWeakref { + const NAME: &'static str = "weakref"; + + fn type_check(object: &Bound<'_, PyAny>) -> bool { + unsafe { ffi::PyWeakref_Check(object.as_ptr()) > 0 } + } +} + +#[cfg(feature = "gil-refs")] +impl PyWeakref { + // TODO: MAYBE ADD CREATION METHODS OR EASY CASTING?; + + /// Upgrade the weakref to a direct object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`] or retrieving the Object from Python. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::{PyWeakref, PyWeakrefProxy}; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakref>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let proxy = PyWeakrefProxy::new_bound(&data)?; // Retrieve this as an PyMethods argument. + /// let reference = proxy.downcast::()?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + pub fn upgrade_as(&self) -> PyResult> + where + T: PyTypeCheck, + { + Ok(self + .as_borrowed() + .upgrade_as::()? + .map(Bound::into_gil_ref)) + } + + /// Upgrade the weakref to a direct object reference unchecked. The type of the recovered object is not checked before downcasting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`] or retrieving the Object from Python. + /// + /// # Safety + /// Callers must ensure that the type is valid or risk type confusion. + /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::{PyWeakref, PyWeakrefProxy}; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakref>) -> String { + /// if let Some(data_src) = unsafe { reference.upgrade_as_unchecked::() } { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// format!("Processing '{}': score = {}", name, score) + /// } else { + /// "The supplied data reference is nolonger relavent.".to_owned() + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let proxy = PyWeakrefProxy::new_bound(&data)?; // Retrieve this as an PyMethods argument. + /// let reference = proxy.downcast::()?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + pub unsafe fn upgrade_as_unchecked(&self) -> Option<&T::AsRefTarget> + where + T: PyTypeCheck, + { + self.as_borrowed() + .upgrade_as_unchecked::() + .map(Bound::into_gil_ref) + } + + /// Upgrade the weakref to an exact direct object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`] or retrieving the Object from Python. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::{PyWeakref, PyWeakrefProxy}; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakref>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as_exact::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let proxy = PyWeakrefProxy::new_bound(&data)?; // Retrieve this as an PyMethods argument. + /// let reference = proxy.downcast::()?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + pub fn upgrade_as_exact(&self) -> PyResult> + where + T: PyTypeInfo, + { + Ok(self + .as_borrowed() + .upgrade_as_exact::()? + .map(Bound::into_gil_ref)) + } + + /// Upgrade the weakref to a [`PyAny`] reference to the target if possible. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// This function returns `Some(&'py PyAny)` if the reference still exists, otherwise `None` will be returned. + /// + /// This function gets the optional target of this [`PyWeakref`] (Any Python `weakref` weakreference). + /// It produces similair results as using [`PyWeakref_GetObject`] in the C api or retrieving the Object from Python. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::{PyWeakref, PyWeakrefProxy}; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakref>) -> PyResult { + /// if let Some(object) = reference.upgrade() { + /// Ok(format!("The object '{}' refered by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?)) + /// } else { + /// Ok("The object, which this reference refered to, no longer exists".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let proxy = PyWeakrefProxy::new_bound(&data)?; // Retrieve this as an PyMethods argument. + /// let reference = proxy.downcast::()?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object 'Foo' refered by this reference still exists." + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object, which this reference refered to, no longer exists" + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + pub fn upgrade(&self) -> Option<&'_ PyAny> { + self.as_borrowed().upgrade().map(Bound::into_gil_ref) + } + + /// Retrieve to a object pointed to by the weakref. + /// + /// This function returns `&'py PyAny`, which is either the object if it still exists, otherwise it will refer to [`PyNone`](crate::types::PyNone). + /// + /// This function gets the optional target of this [`PyWeakref`] (Any Python `weakref` weakreference). + /// It produces similair results as using [`PyWeakref_GetObject`] in the C api or retrieving the Object from Python. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::{PyWeakref, PyWeakrefProxy}; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn get_class(reference: Borrowed<'_, '_, PyWeakref>) -> PyResult { + /// reference + /// .get_object() + /// .getattr("__class__")? + /// .repr() + /// .map(|repr| repr.to_string()) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let object = Bound::new(py, Foo{})?; + /// let proxy = PyWeakrefProxy::new_bound(&object)?; // Retrieve this as an PyMethods argument. + /// let reference = proxy.downcast::()?; + /// + /// assert_eq!( + /// get_class(reference.as_borrowed())?, + /// "" + /// ); + /// + /// drop(object); + /// + /// assert_eq!(get_class(reference.as_borrowed())?, ""); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + pub fn get_object(&self) -> &'_ PyAny { + self.as_borrowed().get_object().into_gil_ref() + } +} + +/// Implementation of functionality for [`PyWeakref`]. +/// +/// These methods are defined for the `Bound<'py, PyWeakref>` smart pointer, so to use method call +/// syntax these methods are separated into a trait, because stable Rust does not yet support +/// `arbitrary_self_types`. +#[doc(alias = "PyWeakref")] +pub trait PyWeakrefMethods<'py> { + /// Upgrade the weakref to a direct Bound object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + fn upgrade_as(&self) -> PyResult>> + where + T: PyTypeCheck, + { + self.upgrade() + .map(Bound::downcast_into::) + .transpose() + .map_err(Into::into) + } + + /// Upgrade the weakref to a Borrowed object reference. + /// + /// It is named `upgrade_borrowed` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_borrowed_as::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref? + fn upgrade_borrowed_as<'a, T>(&'a self) -> PyResult>> + where + T: PyTypeCheck, + 'py: 'a, + { + // TODO: Replace when Borrowed::downcast exists + match self.upgrade_borrowed() { + None => Ok(None), + Some(object) if T::type_check(&object) => { + Ok(Some(unsafe { object.downcast_unchecked() })) + } + Some(object) => Err(DowncastError::new(&object, T::NAME).into()), + } + } + + /// Upgrade the weakref to a direct Bound object reference unchecked. The type of the recovered object is not checked before downcasting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Safety + /// Callers must ensure that the type is valid or risk type confusion. + /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> String { + /// if let Some(data_src) = unsafe { reference.upgrade_as_unchecked::() } { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// format!("Processing '{}': score = {}", name, score) + /// } else { + /// "The supplied data reference is nolonger relavent.".to_owned() + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + unsafe fn upgrade_as_unchecked(&self) -> Option> { + Some(self.upgrade()?.downcast_into_unchecked()) + } + + /// Upgrade the weakref to a Borrowed object reference unchecked. The type of the recovered object is not checked before downcasting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`. + /// + /// It is named `upgrade_borrowed` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Safety + /// Callers must ensure that the type is valid or risk type confusion. + /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> String { + /// if let Some(data_src) = unsafe { reference.upgrade_borrowed_as_unchecked::() } { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// format!("Processing '{}': score = {}", name, score) + /// } else { + /// "The supplied data reference is nolonger relavent.".to_owned() + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref? + unsafe fn upgrade_borrowed_as_unchecked<'a, T>(&'a self) -> Option> + where + 'py: 'a, + { + Some(self.upgrade_borrowed()?.downcast_unchecked()) + } + + /// Upgrade the weakref to a exact direct Bound object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as_exact::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + fn upgrade_as_exact(&self) -> PyResult>> + where + T: PyTypeInfo, + { + self.upgrade() + .map(Bound::downcast_into_exact) + .transpose() + .map_err(Into::into) + } + + /// Upgrade the weakref to a exact Borrowed object reference. + /// + /// It is named `upgrade_borrowed` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_borrowed_as_exact::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref? + fn upgrade_borrowed_as_exact<'a, T>(&'a self) -> PyResult>> + where + T: PyTypeInfo, + 'py: 'a, + { + // TODO: Replace when Borrowed::downcast_exact exists + match self.upgrade_borrowed() { + None => Ok(None), + Some(object) if object.is_exact_instance_of::() => { + Ok(Some(unsafe { object.downcast_unchecked() })) + } + Some(object) => Err(DowncastError::new(&object, T::NAME).into()), + } + } + + /// Upgrade the weakref to a Bound [`PyAny`] reference to the target object if possible. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// This function returns `Some(Bound<'py, PyAny>)` if the reference still exists, otherwise `None` will be returned. + /// + /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// It produces similair results to using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(object) = reference.upgrade() { + /// Ok(format!("The object '{}' refered by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?)) + /// } else { + /// Ok("The object, which this reference refered to, no longer exists".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object 'Foo' refered by this reference still exists." + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object, which this reference refered to, no longer exists" + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + fn upgrade(&self) -> Option> { + let object = self.get_object(); + + if object.is_none() { + None + } else { + Some(object) + } + } + + /// Upgrade the weakref to a Borrowed [`PyAny`] reference to the target object if possible. + /// + /// It is named `upgrade_borrowed` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// This function returns `Some(Borrowed<'_, 'py, PyAny>)` if the reference still exists, otherwise `None` will be returned. + /// + /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// It produces similair results to using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(object) = reference.upgrade_borrowed() { + /// Ok(format!("The object '{}' refered by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?)) + /// } else { + /// Ok("The object, which this reference refered to, no longer exists".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object 'Foo' refered by this reference still exists." + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object, which this reference refered to, no longer exists" + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + fn upgrade_borrowed<'a>(&'a self) -> Option> + where + 'py: 'a, + { + let object = self.get_object_borrowed(); + + if object.is_none() { + None + } else { + Some(object) + } + } + + /// Retrieve to a Bound object pointed to by the weakref. + /// + /// This function returns `Bound<'py, PyAny>`, which is either the object if it still exists, otherwise it will refer to [`PyNone`](crate::types::PyNone). + /// + /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// It produces similair results to using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn get_class(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// reference + /// .get_object() + /// .getattr("__class__")? + /// .repr() + /// .map(|repr| repr.to_string()) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let object = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&object)?; + /// + /// assert_eq!( + /// get_class(reference.as_borrowed())?, + /// "" + /// ); + /// + /// drop(object); + /// + /// assert_eq!(get_class(reference.as_borrowed())?, ""); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + fn get_object(&self) -> Bound<'py, PyAny> { + // PyWeakref_GetObject does some error checking, however we ensure the passed object is Non-Null and a Weakref type. + self.get_object_borrowed().to_owned() + } + + /// Retrieve to a Borrowed object pointed to by the weakref. + /// + /// This function returns `Borrowed<'py, PyAny>`, which is either the object if it still exists, otherwise it will refer to [`PyNone`](crate::types::PyNone). + /// + /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// It produces similair results to using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn get_class(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// reference + /// .get_object_borrowed() + /// .getattr("__class__")? + /// .repr() + /// .map(|repr| repr.to_string()) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let object = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&object)?; + /// + /// assert_eq!( + /// get_class(reference.as_borrowed())?, + /// "" + /// ); + /// + /// drop(object); + /// + /// assert_eq!(get_class(reference.as_borrowed())?, ""); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + #[track_caller] + // TODO: This function is the reason every function tracks caller, however it only panics when the weakref object is not actually a weakreference type. So is it this neccessary? + fn get_object_borrowed(&self) -> Borrowed<'_, 'py, PyAny>; +} + +impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakref> { + fn get_object_borrowed(&self) -> Borrowed<'_, 'py, PyAny> { + // PyWeakref_GetObject does some error checking, however we ensure the passed object is Non-Null and a Weakref type. + unsafe { ffi::PyWeakref_GetObject(self.as_ptr()).assume_borrowed_or_err(self.py()) } + .expect("The 'weakref' weak reference instance should be valid (non-null and actually a weakref reference)") + } +} + +#[cfg(test)] +mod tests { + use crate::types::any::{PyAny, PyAnyMethods}; + use crate::types::weakref::{PyWeakref, PyWeakrefMethods, PyWeakrefProxy, PyWeakrefReference}; + use crate::{Bound, PyResult, Python}; + + fn new_reference<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + let reference = PyWeakrefReference::new_bound(object)?; + reference.into_any().downcast_into().map_err(Into::into) + } + + fn new_proxy<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + let reference = PyWeakrefProxy::new_bound(object)?; + reference.into_any().downcast_into().map_err(Into::into) + } + + mod python_class { + use super::*; + use crate::{py_result_ext::PyResultExt, types::PyType}; + + fn get_type(py: Python<'_>) -> PyResult> { + py.run_bound("class A:\n pass\n", None, None)?; + py.eval_bound("A", None, None).downcast_into::() + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is(&reference.get_object())); + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = create_reference(&object)?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + } + + // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. + #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))] + mod pyo3_pyclass { + use super::*; + use crate::{pyclass, Py}; + + #[pyclass(weakref, crate = "crate")] + struct WeakrefablePyClass {} + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + ) -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + { + let obj = unsafe { + reference.upgrade_borrowed_as_unchecked::() + }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { + reference.upgrade_borrowed_as_unchecked::() + }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + inner(new_reference)?; + inner(new_proxy) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is(&reference.get_object())); + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + fn inner( + create_reference: impl for<'py> FnOnce( + &Bound<'py, PyAny>, + ) + -> PyResult>, + call_retrievable: bool, + ) -> PyResult<()> { + let not_call_retrievable = !call_retrievable; + + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = create_reference(object.bind(py))?; + + assert!(not_call_retrievable || reference.call0()?.is(&object)); + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(not_call_retrievable || reference.call0()?.is_none()); + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + + inner(new_reference, true)?; + inner(new_proxy, false) + } + } +} diff --git a/src/types/weakref/mod.rs b/src/types/weakref/mod.rs new file mode 100644 index 00000000000..49d4e515b12 --- /dev/null +++ b/src/types/weakref/mod.rs @@ -0,0 +1,7 @@ +pub use anyref::{PyWeakref, PyWeakrefMethods}; +pub use proxy::PyWeakrefProxy; +pub use reference::PyWeakrefReference; + +pub(crate) mod anyref; +pub(crate) mod proxy; +pub(crate) mod reference; diff --git a/src/types/weakref/proxy.rs b/src/types/weakref/proxy.rs new file mode 100644 index 00000000000..71334488b54 --- /dev/null +++ b/src/types/weakref/proxy.rs @@ -0,0 +1,1688 @@ +use crate::err::PyResult; +use crate::ffi_ptr_ext::FfiPtrExt; +use crate::py_result_ext::PyResultExt; +use crate::type_object::PyTypeCheck; +use crate::types::any::PyAny; +use crate::{ffi, Borrowed, Bound, ToPyObject}; + +#[cfg(feature = "gil-refs")] +use crate::{type_object::PyTypeInfo, PyNativeType}; + +use super::PyWeakrefMethods; + +/// Represents any Python `weakref` Proxy type. +/// +/// In Python this is created by calling `weakref.proxy`. +/// This is either a `weakref.ProxyType` or a `weakref.CallableProxyType` (`weakref.ProxyTypes`). +#[repr(transparent)] +pub struct PyWeakrefProxy(PyAny); + +pyobject_native_type_named!(PyWeakrefProxy); +pyobject_native_type_extract!(PyWeakrefProxy); + +// TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers. And it is 2 distinct types +// #[cfg(not(Py_LIMITED_API))] +// pyobject_native_type_sized!(PyWeakrefProxy, ffi::PyWeakReference); + +impl PyTypeCheck for PyWeakrefProxy { + const NAME: &'static str = "weakref.ProxyTypes"; + + fn type_check(object: &Bound<'_, PyAny>) -> bool { + unsafe { ffi::PyWeakref_CheckProxy(object.as_ptr()) > 0 } + } +} + +/// TODO: UPDATE DOCS +impl PyWeakrefProxy { + /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object. + /// + /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag). + /// + /// # Examples + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let foo = Bound::new(py, Foo {})?; + /// let weakref = PyWeakrefProxy::new_bound(&foo)?; + /// assert!( + /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` + /// weakref.upgrade() + /// .map_or(false, |obj| obj.is(&foo)) + /// ); + /// + /// let weakref2 = PyWeakrefProxy::new_bound(&foo)?; + /// assert!(weakref.is(&weakref2)); + /// + /// drop(foo); + /// + /// assert!(weakref.upgrade().is_none()); + /// Ok(()) + /// }) + /// # } + /// ``` + #[inline] + pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + // TODO: Is this inner pattern still necessary Here? + fn inner<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + unsafe { + Bound::from_owned_ptr_or_err( + object.py(), + ffi::PyWeakref_NewProxy(object.as_ptr(), ffi::Py_None()), + ) + .downcast_into_unchecked() + } + } + + inner(object) + } + + /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object with a callback. + /// + /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag) or if the `callback` is not callable or None. + /// + /// # Examples + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pyfunction] + /// fn callback(wref: Bound<'_, PyWeakrefProxy>) -> PyResult<()> { + /// let py = wref.py(); + /// assert!(wref.upgrade_as::()?.is_none()); + /// py.run_bound("counter = 1", None, None) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// py.run_bound("counter = 0", None, None)?; + /// assert_eq!(py.eval_bound("counter", None, None)?.extract::()?, 0); + /// let foo = Bound::new(py, Foo{})?; + /// + /// // This is fine. + /// let weakref = PyWeakrefProxy::new_bound_with(&foo, py.None())?; + /// assert!(weakref.upgrade_as::()?.is_some()); + /// assert!( + /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` + /// weakref.upgrade() + /// .map_or(false, |obj| obj.is(&foo)) + /// ); + /// assert_eq!(py.eval_bound("counter", None, None)?.extract::()?, 0); + /// + /// let weakref2 = PyWeakrefProxy::new_bound_with(&foo, wrap_pyfunction_bound!(callback, py)?)?; + /// assert!(!weakref.is(&weakref2)); // Not the same weakref + /// assert!(weakref.eq(&weakref2)?); // But Equal, since they point to the same object + /// + /// drop(foo); + /// + /// assert!(weakref.upgrade_as::()?.is_none()); + /// assert_eq!(py.eval_bound("counter", None, None)?.extract::()?, 1); + /// Ok(()) + /// }) + /// # } + /// ``` + #[inline] + pub fn new_bound_with<'py, C>( + object: &Bound<'py, PyAny>, + callback: C, + ) -> PyResult> + where + C: ToPyObject, + { + fn inner<'py>( + object: &Bound<'py, PyAny>, + callback: Bound<'py, PyAny>, + ) -> PyResult> { + unsafe { + Bound::from_owned_ptr_or_err( + object.py(), + ffi::PyWeakref_NewProxy(object.as_ptr(), callback.as_ptr()), + ) + .downcast_into_unchecked() + } + } + + let py = object.py(); + inner(object, callback.to_object(py).into_bound(py)) + } +} + +/// TODO: UPDATE DOCS +#[cfg(feature = "gil-refs")] +impl PyWeakrefProxy { + /// Deprecated form of [`PyWeakrefProxy::new_bound`]. + #[inline] + #[deprecated( + since = "0.21.0", + note = "`PyWeakrefProxy::new` will be replaced by `PyWeakrefProxy::new_bound` in a future PyO3 version" + )] + pub fn new(object: &T) -> PyResult<&PyWeakrefProxy> + where + T: PyNativeType, + { + Self::new_bound(object.as_borrowed().as_any()).map(Bound::into_gil_ref) + } + + /// Deprecated form of [`PyWeakrefProxy::new_bound_with`]. + #[inline] + #[deprecated( + since = "0.21.0", + note = "`PyWeakrefProxy::new_with` will be replaced by `PyWeakrefProxy::new_bound_with` in a future PyO3 version" + )] + pub fn new_with(object: &T, callback: C) -> PyResult<&PyWeakrefProxy> + where + T: PyNativeType, + C: ToPyObject, + { + Self::new_bound_with(object.as_borrowed().as_any(), callback).map(Bound::into_gil_ref) + } + + /// Upgrade the weakref to a direct object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefProxy>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefProxy::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.ProxyType + /// [`weakref.proxy`]: https://docs.python.org/3/library/weakref.html#weakref.proxy + pub fn upgrade_as(&self) -> PyResult> + where + T: PyTypeCheck, + { + Ok(self + .as_borrowed() + .upgrade_as::()? + .map(Bound::into_gil_ref)) + } + + /// Upgrade the weakref to a direct object reference unchecked. The type of the recovered object is not checked before downcasting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Safety + /// Callers must ensure that the type is valid or risk type confusion. + /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefProxy>) -> String { + /// if let Some(data_src) = unsafe { reference.upgrade_as_unchecked::() } { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// format!("Processing '{}': score = {}", name, score) + /// } else { + /// "The supplied data reference is nolonger relavent.".to_owned() + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefProxy::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.ProxyType + /// [`weakref.proxy`]: https://docs.python.org/3/library/weakref.html#weakref.proxy + pub unsafe fn upgrade_as_unchecked(&self) -> Option<&T::AsRefTarget> + where + T: PyTypeCheck, + { + self.as_borrowed() + .upgrade_as_unchecked::() + .map(Bound::into_gil_ref) + } + + /// Upgrade the weakref to an exact direct object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`]. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefProxy>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as_exact::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefProxy::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.ProxyType + /// [`weakref.proxy`]: https://docs.python.org/3/library/weakref.html#weakref.proxy + pub fn upgrade_as_exact(&self) -> PyResult> + where + T: PyTypeInfo, + { + Ok(self + .as_borrowed() + .upgrade_as_exact::()? + .map(Bound::into_gil_ref)) + } + + /// Upgrade the weakref to a [`PyAny`] reference to the target if possible. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// This function returns `Some(&'py PyAny)` if the reference still exists, otherwise `None` will be returned. + /// + /// This function gets the optional target of this [`weakref.ProxyType`] (or [`weakref.CallableProxyType`], result of calling [`weakref.proxy`]). + /// It produces similair results using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefProxy>) -> PyResult { + /// if let Some(object) = reference.upgrade() { + /// Ok(format!("The object '{}' refered by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?)) + /// } else { + /// Ok("The object, which this reference refered to, no longer exists".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefProxy::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object 'Foo' refered by this reference still exists." + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object, which this reference refered to, no longer exists" + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.ProxyType + /// [`weakref.CallableProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.CallableProxyType + /// [`weakref.proxy`]: https://docs.python.org/3/library/weakref.html#weakref.proxy + pub fn upgrade(&self) -> Option<&'_ PyAny> { + self.as_borrowed().upgrade().map(Bound::into_gil_ref) + } + + /// Retrieve to a object pointed to by the weakref. + /// + /// This function returns `&'py PyAny`, which is either the object if it still exists, otherwise it will refer to [`PyNone`](crate::types::PyNone). + /// + /// This function gets the optional target of this [`weakref.ProxyType`] (or [`weakref.CallableProxyType`], result of calling [`weakref.proxy`]). + /// It produces similair results using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefProxy; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn get_class(reference: Borrowed<'_, '_, PyWeakrefProxy>) -> PyResult { + /// reference + /// .get_object() + /// .getattr("__class__")? + /// .repr() + /// .map(|repr| repr.to_string()) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let object = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefProxy::new_bound(&object)?; + /// + /// assert_eq!( + /// get_class(reference.as_borrowed())?, + /// "" + /// ); + /// + /// drop(object); + /// + /// assert_eq!(get_class(reference.as_borrowed())?, ""); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.ProxyType + /// [`weakref.CallableProxyType`]: https://docs.python.org/3/library/weakref.html#weakref.CallableProxyType + /// [`weakref.proxy`]: https://docs.python.org/3/library/weakref.html#weakref.proxy + pub fn get_object(&self) -> &'_ PyAny { + self.as_borrowed().get_object().into_gil_ref() + } +} + +impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefProxy> { + fn get_object_borrowed(&self) -> Borrowed<'_, 'py, PyAny> { + // PyWeakref_GetObject does some error checking, however we ensure the passed object is Non-Null and a Weakref type. + unsafe { ffi::PyWeakref_GetObject(self.as_ptr()).assume_borrowed_or_err(self.py()) } + .expect("The 'weakref.ProxyType' (or `weakref.CallableProxyType`) instance should be valid (non-null and actually a weakref reference)") + } +} + +#[cfg(test)] +mod tests { + use crate::exceptions::{PyAttributeError, PyReferenceError, PyTypeError}; + use crate::types::any::{PyAny, PyAnyMethods}; + use crate::types::weakref::{PyWeakrefMethods, PyWeakrefProxy}; + use crate::{Bound, PyResult, Python}; + + #[cfg(all(Py_3_13, not(Py_LIMITED_API)))] + const DEADREF_FIX: Option<&str> = None; + #[cfg(all(not(Py_3_13), not(Py_LIMITED_API)))] + const DEADREF_FIX: Option<&str> = Some("NoneType"); + + #[cfg(not(Py_LIMITED_API))] + fn check_repr( + reference: &Bound<'_, PyWeakrefProxy>, + object: &Bound<'_, PyAny>, + class: Option<&str>, + ) -> PyResult<()> { + let repr = reference.repr()?.to_string(); + + #[cfg(Py_3_13)] + let (first_part, second_part) = repr.split_once(';').unwrap(); + #[cfg(not(Py_3_13))] + let (first_part, second_part) = repr.split_once(" to ").unwrap(); + + { + let (msg, addr) = first_part.split_once("0x").unwrap(); + + assert_eq!(msg, ") -> PyResult> { + py.run_bound("class A:\n pass\n", None, None)?; + py.eval_bound("A", None, None).downcast_into::() + } + + #[test] + fn test_weakref_proxy_behavior() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(!reference.is(&object)); + assert!(reference.get_object().is(&object)); + + #[cfg(not(Py_LIMITED_API))] + assert_eq!( + reference.get_type().to_string(), + format!("", CLASS_NAME) + ); + + assert_eq!( + reference.getattr("__class__")?.to_string(), + "" + ); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, &object, Some("A"))?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert!(reference.call0().err().map_or(false, |err| { + let result = err.is_instance_of::(py); + #[cfg(not(Py_LIMITED_API))] + let result = result + & (err.value_bound(py).to_string() + == format!("{} object is not callable", CLASS_NAME)); + result + })); + + drop(object); + + assert!(reference.get_object().is_none()); + assert!(reference + .getattr("__class__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, py.None().bind(py), None)?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert!(reference.call0().err().map_or(false, |err| { + let result = err.is_instance_of::(py); + #[cfg(not(Py_LIMITED_API))] + let result = result + & (err.value_bound(py).to_string() + == format!("{} object is not callable", CLASS_NAME)); + result + })); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + } + + // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. + #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))] + mod pyo3_pyclass { + use super::*; + use crate::{pyclass, Py}; + + #[pyclass(weakref, crate = "crate")] + struct WeakrefablePyClass {} + + #[test] + fn test_weakref_proxy_behavior() -> PyResult<()> { + Python::with_gil(|py| { + let object: Bound<'_, WeakrefablePyClass> = + Bound::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(!reference.is(&object)); + assert!(reference.get_object().is(&object)); + #[cfg(not(Py_LIMITED_API))] + assert_eq!( + reference.get_type().to_string(), + format!("", CLASS_NAME) + ); + + assert_eq!( + reference.getattr("__class__")?.to_string(), + "" + ); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert!(reference.call0().err().map_or(false, |err| { + let result = err.is_instance_of::(py); + #[cfg(not(Py_LIMITED_API))] + let result = result + & (err.value_bound(py).to_string() + == format!("{} object is not callable", CLASS_NAME)); + result + })); + + drop(object); + + assert!(reference.get_object().is_none()); + assert!(reference + .getattr("__class__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, py.None().bind(py), None)?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert!(reference.call0().err().map_or(false, |err| { + let result = err.is_instance_of::(py); + #[cfg(not(Py_LIMITED_API))] + let result = result + & (err.value_bound(py).to_string() + == format!("{} object is not callable", CLASS_NAME)); + result + })); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = unsafe { + reference.upgrade_borrowed_as_unchecked::() + }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { + reference.upgrade_borrowed_as_unchecked::() + }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + } + } + + mod callable_proxy { + use super::*; + + #[cfg(all(not(Py_LIMITED_API), Py_3_10))] + const CLASS_NAME: &str = ""; + #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] + const CLASS_NAME: &str = ""; + + mod python_class { + use super::*; + use crate::{py_result_ext::PyResultExt, types::PyType}; + + fn get_type(py: Python<'_>) -> PyResult> { + py.run_bound( + "class A:\n def __call__(self):\n return 'This class is callable!'\n", + None, + None, + )?; + py.eval_bound("A", None, None).downcast_into::() + } + + #[test] + fn test_weakref_proxy_behavior() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(!reference.is(&object)); + assert!(reference.get_object().is(&object)); + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.get_type().to_string(), CLASS_NAME); + + assert_eq!( + reference.getattr("__class__")?.to_string(), + "" + ); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, &object, Some("A"))?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert_eq!(reference.call0()?.to_string(), "This class is callable!"); + + drop(object); + + assert!(reference.get_object().is_none()); + assert!(reference + .getattr("__class__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, py.None().bind(py), None)?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert!(reference + .call0() + .err() + .map_or(false, |err| err.is_instance_of::(py) + & (err.value_bound(py).to_string() + == "weakly-referenced object no longer exists"))); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + } + + // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. + #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))] + mod pyo3_pyclass { + use super::*; + use crate::{pyclass, pymethods, Py}; + + #[pyclass(weakref, crate = "crate")] + struct WeakrefablePyClass {} + + #[pymethods(crate = "crate")] + impl WeakrefablePyClass { + fn __call__(&self) -> &str { + "This class is callable!" + } + } + + #[test] + fn test_weakref_proxy_behavior() -> PyResult<()> { + Python::with_gil(|py| { + let object: Bound<'_, WeakrefablePyClass> = + Bound::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(&object)?; + + assert!(!reference.is(&object)); + assert!(reference.get_object().is(&object)); + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.get_type().to_string(), CLASS_NAME); + + assert_eq!( + reference.getattr("__class__")?.to_string(), + "" + ); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert_eq!(reference.call0()?.to_string(), "This class is callable!"); + + drop(object); + + assert!(reference.get_object().is_none()); + assert!(reference + .getattr("__class__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, py.None().bind(py), None)?; + + assert!(reference + .getattr("__callback__") + .err() + .map_or(false, |err| err.is_instance_of::(py))); + + assert!(reference + .call0() + .err() + .map_or(false, |err| err.is_instance_of::(py) + & (err.value_bound(py).to_string() + == "weakly-referenced object no longer exists"))); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + { + let obj = unsafe { + reference.upgrade_borrowed_as_unchecked::() + }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { + reference.upgrade_borrowed_as_unchecked::() + }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefProxy::new_bound(object.bind(py))?; + + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + } + } +} diff --git a/src/types/weakref/reference.rs b/src/types/weakref/reference.rs new file mode 100644 index 00000000000..6cdcde3a7f7 --- /dev/null +++ b/src/types/weakref/reference.rs @@ -0,0 +1,1119 @@ +use crate::err::PyResult; +use crate::ffi_ptr_ext::FfiPtrExt; +use crate::py_result_ext::PyResultExt; +use crate::types::any::PyAny; +use crate::{ffi, Borrowed, Bound, ToPyObject}; + +#[cfg(any(any(PyPy, GraalPy, Py_LIMITED_API), feature = "gil-refs"))] +use crate::type_object::PyTypeCheck; +#[cfg(feature = "gil-refs")] +use crate::{type_object::PyTypeInfo, PyNativeType}; + +use super::PyWeakrefMethods; + +/// Represents a Python `weakref.ReferenceType`. +/// +/// In Python this is created by calling `weakref.ref`. +#[repr(transparent)] +pub struct PyWeakrefReference(PyAny); + +#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))] +pyobject_native_type!( + PyWeakrefReference, + ffi::PyWeakReference, + pyobject_native_static_type_object!(ffi::_PyWeakref_RefType), + #module=Some("weakref"), + #checkfunction=ffi::PyWeakref_CheckRefExact +); + +// When targetting alternative or multiple interpreters, it is better to not use the internal API. +#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +pyobject_native_type_named!(PyWeakrefReference); +#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +pyobject_native_type_extract!(PyWeakrefReference); + +#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))] +impl PyTypeCheck for PyWeakrefReference { + const NAME: &'static str = "weakref.ReferenceType"; + + fn type_check(object: &Bound<'_, PyAny>) -> bool { + unsafe { ffi::PyWeakref_CheckRef(object.as_ptr()) > 0 } + } +} + +impl PyWeakrefReference { + /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object. + /// + /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag). + /// + /// # Examples + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let foo = Bound::new(py, Foo {})?; + /// let weakref = PyWeakrefReference::new_bound(&foo)?; + /// assert!( + /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` + /// weakref.upgrade() + /// .map_or(false, |obj| obj.is(&foo)) + /// ); + /// + /// let weakref2 = PyWeakrefReference::new_bound(&foo)?; + /// assert!(weakref.is(&weakref2)); + /// + /// drop(foo); + /// + /// assert!(weakref.upgrade().is_none()); + /// Ok(()) + /// }) + /// # } + /// ``` + pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + // TODO: Is this inner pattern still necessary Here? + fn inner<'py>(object: &Bound<'py, PyAny>) -> PyResult> { + unsafe { + Bound::from_owned_ptr_or_err( + object.py(), + ffi::PyWeakref_NewRef(object.as_ptr(), ffi::Py_None()), + ) + .downcast_into_unchecked() + } + } + + inner(object) + } + + /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object with a callback. + /// + /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag) or if the `callback` is not callable or None. + /// + /// # Examples + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pyfunction] + /// fn callback(wref: Bound<'_, PyWeakrefReference>) -> PyResult<()> { + /// let py = wref.py(); + /// assert!(wref.upgrade_as::()?.is_none()); + /// py.run_bound("counter = 1", None, None) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// py.run_bound("counter = 0", None, None)?; + /// assert_eq!(py.eval_bound("counter", None, None)?.extract::()?, 0); + /// let foo = Bound::new(py, Foo{})?; + /// + /// // This is fine. + /// let weakref = PyWeakrefReference::new_bound_with(&foo, py.None())?; + /// assert!(weakref.upgrade_as::()?.is_some()); + /// assert!( + /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::` + /// weakref.upgrade() + /// .map_or(false, |obj| obj.is(&foo)) + /// ); + /// assert_eq!(py.eval_bound("counter", None, None)?.extract::()?, 0); + /// + /// let weakref2 = PyWeakrefReference::new_bound_with(&foo, wrap_pyfunction_bound!(callback, py)?)?; + /// assert!(!weakref.is(&weakref2)); // Not the same weakref + /// assert!(weakref.eq(&weakref2)?); // But Equal, since they point to the same object + /// + /// drop(foo); + /// + /// assert!(weakref.upgrade_as::()?.is_none()); + /// assert_eq!(py.eval_bound("counter", None, None)?.extract::()?, 1); + /// Ok(()) + /// }) + /// # } + /// ``` + pub fn new_bound_with<'py, C>( + object: &Bound<'py, PyAny>, + callback: C, + ) -> PyResult> + where + C: ToPyObject, + { + fn inner<'py>( + object: &Bound<'py, PyAny>, + callback: Bound<'py, PyAny>, + ) -> PyResult> { + unsafe { + Bound::from_owned_ptr_or_err( + object.py(), + ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()), + ) + .downcast_into_unchecked() + } + } + + let py = object.py(); + inner(object, callback.to_object(py).into_bound(py)) + } +} + +#[cfg(feature = "gil-refs")] +impl PyWeakrefReference { + /// Deprecated form of [`PyWeakrefReference::new_bound`]. + #[inline] + #[deprecated( + since = "0.21.0", + note = "`PyWeakrefReference::new` will be replaced by `PyWeakrefReference::new_bound` in a future PyO3 version" + )] + pub fn new(object: &T) -> PyResult<&PyWeakrefReference> + where + T: PyNativeType, + { + Self::new_bound(object.as_borrowed().as_any()).map(Bound::into_gil_ref) + } + + /// Deprecated form of [`PyWeakrefReference::new_bound_with`]. + #[inline] + #[deprecated( + since = "0.21.0", + note = "`PyWeakrefReference::new_with` will be replaced by `PyWeakrefReference::new_bound_with` in a future PyO3 version" + )] + pub fn new_with(object: &T, callback: C) -> PyResult<&PyWeakrefReference> + where + T: PyNativeType, + C: ToPyObject, + { + Self::new_bound_with(object.as_borrowed().as_any(), callback).map(Bound::into_gil_ref) + } + + /// Upgrade the weakref to a direct object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`] or calling the [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + pub fn upgrade_as(&self) -> PyResult> + where + T: PyTypeCheck, + { + Ok(self + .as_borrowed() + .upgrade_as::()? + .map(Bound::into_gil_ref)) + } + + /// Upgrade the weakref to a direct object reference unchecked. The type of the recovered object is not checked before downcasting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`] or calling the [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// + /// # Safety + /// Callers must ensure that the type is valid or risk type confusion. + /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> String { + /// if let Some(data_src) = unsafe { reference.upgrade_as_unchecked::() } { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// format!("Processing '{}': score = {}", name, score) + /// } else { + /// "The supplied data reference is nolonger relavent.".to_owned() + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed()), + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + pub unsafe fn upgrade_as_unchecked(&self) -> Option<&T::AsRefTarget> + where + T: PyTypeCheck, + { + self.as_borrowed() + .upgrade_as_unchecked::() + .map(Bound::into_gil_ref) + } + + /// Upgrade the weakref to an exact direct object reference. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// In Python it would be equivalent to [`PyWeakref_GetObject`] or calling the [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// #[pymethods] + /// impl Foo { + /// fn get_data(&self) -> (&str, u32) { + /// ("Dave", 10) + /// } + /// } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(data_src) = reference.upgrade_as_exact::()? { + /// let data = data_src.borrow(); + /// let (name, score) = data.get_data(); + /// Ok(format!("Processing '{}': score = {}", name, score)) + /// } else { + /// Ok("The supplied data reference is nolonger relavent.".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "Processing 'Dave': score = 10" + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The supplied data reference is nolonger relavent." + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + pub fn upgrade_as_exact(&self) -> PyResult> + where + T: PyTypeInfo, + { + Ok(self + .as_borrowed() + .upgrade_as_exact::()? + .map(Bound::into_gil_ref)) + } + + /// Upgrade the weakref to a [`PyAny`] reference to the target if possible. + /// + /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade). + /// This function returns `Some(&'py PyAny)` if the reference still exists, otherwise `None` will be returned. + /// + /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// It produces similair results to calling the `weakref.ReferenceType` or using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// if let Some(object) = reference.upgrade() { + /// Ok(format!("The object '{}' refered by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?)) + /// } else { + /// Ok("The object, which this reference refered to, no longer exists".to_owned()) + /// } + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let data = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&data)?; + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object 'Foo' refered by this reference still exists." + /// ); + /// + /// drop(data); + /// + /// assert_eq!( + /// parse_data(reference.as_borrowed())?, + /// "The object, which this reference refered to, no longer exists" + /// ); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + pub fn upgrade(&self) -> Option<&'_ PyAny> { + self.as_borrowed().upgrade().map(Bound::into_gil_ref) + } + + /// Retrieve to a object pointed to by the weakref. + /// + /// This function returns `&'py PyAny`, which is either the object if it still exists, otherwise it will refer to [`PyNone`](crate::types::PyNone). + /// + /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]). + /// It produces similair results to calling the `weakref.ReferenceType` or using [`PyWeakref_GetObject`] in the C api. + /// + /// # Example + #[cfg_attr( + not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))), + doc = "```rust,ignore" + )] + #[cfg_attr( + all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))), + doc = "```rust" + )] + /// use pyo3::prelude::*; + /// use pyo3::types::PyWeakrefReference; + /// + /// #[pyclass(weakref)] + /// struct Foo { /* fields omitted */ } + /// + /// fn get_class(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult { + /// reference + /// .get_object() + /// .getattr("__class__")? + /// .repr() + /// .map(|repr| repr.to_string()) + /// } + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| { + /// let object = Bound::new(py, Foo{})?; + /// let reference = PyWeakrefReference::new_bound(&object)?; + /// + /// assert_eq!( + /// get_class(reference.as_borrowed())?, + /// "" + /// ); + /// + /// drop(object); + /// + /// assert_eq!(get_class(reference.as_borrowed())?, ""); + /// + /// Ok(()) + /// }) + /// # } + /// ``` + /// + /// # Panics + /// This function panics is the current object is invalid. + /// If used propperly this is never the case. (NonNull and actually a weakref type) + /// + /// [`PyWeakref_GetObject`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetObject + /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType + /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref + pub fn get_object(&self) -> &'_ PyAny { + self.as_borrowed().get_object().into_gil_ref() + } +} + +impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> { + fn get_object_borrowed(&self) -> Borrowed<'_, 'py, PyAny> { + // PyWeakref_GetObject does some error checking, however we ensure the passed object is Non-Null and a Weakref type. + unsafe { ffi::PyWeakref_GetObject(self.as_ptr()).assume_borrowed_or_err(self.py()) } + .expect("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)") + } +} + +#[cfg(test)] +mod tests { + use crate::types::any::{PyAny, PyAnyMethods}; + use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference}; + use crate::{Bound, PyResult, Python}; + + #[cfg(all(not(Py_LIMITED_API), Py_3_10))] + const CLASS_NAME: &str = ""; + #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))] + const CLASS_NAME: &str = ""; + + fn check_repr( + reference: &Bound<'_, PyWeakrefReference>, + object: Option<(&Bound<'_, PyAny>, &str)>, + ) -> PyResult<()> { + let repr = reference.repr()?.to_string(); + let (first_part, second_part) = repr.split_once("; ").unwrap(); + + { + let (msg, addr) = first_part.split_once("0x").unwrap(); + + assert_eq!(msg, " { + let (msg, addr) = second_part.split_once("0x").unwrap(); + + // Avoid testing on reprs directly since they the quoting and full path vs class name tends to be changedi undocumented. + assert!(msg.starts_with("to '")); + assert!(msg.contains(class)); + assert!(msg.ends_with("' at ")); + + assert!(addr + .to_lowercase() + .contains(format!("{:x?}", object.as_ptr()).split_at(2).1)); + } + None => { + assert_eq!(second_part, "dead>") + } + } + + Ok(()) + } + + mod python_class { + use super::*; + use crate::{py_result_ext::PyResultExt, types::PyType}; + + fn get_type(py: Python<'_>) -> PyResult> { + py.run_bound("class A:\n pass\n", None, None)?; + py.eval_bound("A", None, None).downcast_into::() + } + + #[test] + fn test_weakref_reference_behavior() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + assert!(!reference.is(&object)); + assert!(reference.get_object().is(&object)); + + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.get_type().to_string(), CLASS_NAME); + + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME); + + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, Some((object.as_any(), "A")))?; + + assert!(reference + .getattr("__callback__") + .map_or(false, |result| result.is_none())); + + assert!(reference.call0()?.is(&object)); + + drop(object); + + assert!(reference.get_object().is_none()); + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME); + check_repr(&reference, None)?; + + assert!(reference + .getattr("__callback__") + .map_or(false, |result| result.is_none())); + + assert!(reference.call0()?.is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr() + && obj.is_exact_instance(&class))); + } + + drop(object); + + { + // This test is a bit weird but ok. + let obj = unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.call0()?.is_none()); + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.call0()?.is_none()); + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(reference.call0()?.is(&reference.get_object())); + assert!(reference.call0()?.is_none()); + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let class = get_type(py)?; + let object = class.call0()?; + let reference = PyWeakrefReference::new_bound(&object)?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(reference.call0()?.is_none()); + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + } + + // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. + #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))] + mod pyo3_pyclass { + use super::*; + use crate::{pyclass, Py}; + + #[pyclass(weakref, crate = "crate")] + struct WeakrefablePyClass {} + + #[test] + fn test_weakref_reference_behavior() -> PyResult<()> { + Python::with_gil(|py| { + let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(&object)?; + + assert!(!reference.is(&object)); + assert!(reference.get_object().is(&object)); + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.get_type().to_string(), CLASS_NAME); + + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME); + #[cfg(not(Py_LIMITED_API))] + check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?; + + assert!(reference + .getattr("__callback__") + .map_or(false, |result| result.is_none())); + + assert!(reference.call0()?.is(&object)); + + drop(object); + + assert!(reference.get_object().is_none()); + #[cfg(not(Py_LIMITED_API))] + assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME); + check_repr(&reference, None)?; + + assert!(reference + .getattr("__callback__") + .map_or(false, |result| result.is_none())); + + assert!(reference.call0()?.is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = reference.upgrade_borrowed_as::(); + + assert!(obj.is_ok()); + let obj = obj.unwrap(); + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = unsafe { reference.upgrade_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed_as_unchecked() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + { + let obj = + unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_some()); + assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr())); + } + + drop(object); + + { + let obj = + unsafe { reference.upgrade_borrowed_as_unchecked::() }; + + assert!(obj.is_none()); + } + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.upgrade().is_some()); + assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.call0()?.is_none()); + assert!(reference.upgrade().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_upgrade_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.upgrade_borrowed().is_some()); + assert!(reference + .upgrade_borrowed() + .map_or(false, |obj| obj.is(&object))); + + drop(object); + + assert!(reference.call0()?.is_none()); + assert!(reference.upgrade_borrowed().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.get_object().is(&object)); + + drop(object); + + assert!(reference.call0()?.is(&reference.get_object())); + assert!(reference.call0()?.is_none()); + assert!(reference.get_object().is_none()); + + Ok(()) + }) + } + + #[test] + fn test_weakref_get_object_borrowed() -> PyResult<()> { + Python::with_gil(|py| { + let object = Py::new(py, WeakrefablePyClass {})?; + let reference = PyWeakrefReference::new_bound(object.bind(py))?; + + assert!(reference.call0()?.is(&object)); + assert!(reference.get_object_borrowed().is(&object)); + + drop(object); + + assert!(reference.call0()?.is_none()); + assert!(reference.get_object_borrowed().is_none()); + + Ok(()) + }) + } + } +}