pyo3/types/weakref/
anyref.rs

1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::type_object::{PyTypeCheck, PyTypeInfo};
4use crate::types::any::PyAny;
5use crate::{ffi, Bound};
6
7/// Represents any Python `weakref` reference.
8///
9/// In Python this is created by calling `weakref.ref` or `weakref.proxy`.
10#[repr(transparent)]
11pub struct PyWeakref(PyAny);
12
13pyobject_native_type_named!(PyWeakref);
14
15// TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers
16// #[cfg(not(Py_LIMITED_API))]
17// pyobject_native_type_sized!(PyWeakref, ffi::PyWeakReference);
18
19impl PyTypeCheck for PyWeakref {
20    const NAME: &'static str = "weakref";
21    #[cfg(feature = "experimental-inspect")]
22    const PYTHON_TYPE: &'static str = "weakref.ProxyTypes";
23
24    fn type_check(object: &Bound<'_, PyAny>) -> bool {
25        unsafe { ffi::PyWeakref_Check(object.as_ptr()) > 0 }
26    }
27}
28
29/// Implementation of functionality for [`PyWeakref`].
30///
31/// These methods are defined for the `Bound<'py, PyWeakref>` smart pointer, so to use method call
32/// syntax these methods are separated into a trait, because stable Rust does not yet support
33/// `arbitrary_self_types`.
34#[doc(alias = "PyWeakref")]
35pub trait PyWeakrefMethods<'py>: crate::sealed::Sealed {
36    /// Upgrade the weakref to a direct Bound object reference.
37    ///
38    /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
39    /// In Python it would be equivalent to [`PyWeakref_GetRef`].
40    ///
41    /// # Example
42    #[cfg_attr(
43        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
44        doc = "```rust,ignore"
45    )]
46    #[cfg_attr(
47        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
48        doc = "```rust"
49    )]
50    /// use pyo3::prelude::*;
51    /// use pyo3::types::PyWeakrefReference;
52    ///
53    /// #[pyclass(weakref)]
54    /// struct Foo { /* fields omitted */ }
55    ///
56    /// #[pymethods]
57    /// impl Foo {
58    ///     fn get_data(&self) -> (&str, u32) {
59    ///         ("Dave", 10)
60    ///     }
61    /// }
62    ///
63    /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult<String> {
64    ///     if let Some(data_src) = reference.upgrade_as::<Foo>()? {
65    ///         let data = data_src.borrow();
66    ///         let (name, score) = data.get_data();
67    ///         Ok(format!("Processing '{}': score = {}", name, score))
68    ///     } else {
69    ///         Ok("The supplied data reference is nolonger relavent.".to_owned())
70    ///     }
71    /// }
72    ///
73    /// # fn main() -> PyResult<()> {
74    /// Python::attach(|py| {
75    ///     let data = Bound::new(py, Foo{})?;
76    ///     let reference = PyWeakrefReference::new(&data)?;
77    ///
78    ///     assert_eq!(
79    ///         parse_data(reference.as_borrowed())?,
80    ///         "Processing 'Dave': score = 10"
81    ///     );
82    ///
83    ///     drop(data);
84    ///
85    ///     assert_eq!(
86    ///         parse_data(reference.as_borrowed())?,
87    ///         "The supplied data reference is nolonger relavent."
88    ///     );
89    ///
90    ///     Ok(())
91    /// })
92    /// # }
93    /// ```
94    ///
95    /// # Panics
96    /// This function panics is the current object is invalid.
97    /// If used propperly this is never the case. (NonNull and actually a weakref type)
98    ///
99    /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
100    /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
101    /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
102    fn upgrade_as<T>(&self) -> PyResult<Option<Bound<'py, T>>>
103    where
104        T: PyTypeCheck,
105    {
106        self.upgrade()
107            .map(Bound::cast_into::<T>)
108            .transpose()
109            .map_err(Into::into)
110    }
111
112    /// Upgrade the weakref to a direct Bound object reference unchecked. The type of the recovered object is not checked before casting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`.
113    ///
114    /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
115    /// In Python it would be equivalent to [`PyWeakref_GetRef`].
116    ///
117    /// # Safety
118    /// Callers must ensure that the type is valid or risk type confusion.
119    /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up.
120    ///
121    /// # Example
122    #[cfg_attr(
123        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
124        doc = "```rust,ignore"
125    )]
126    #[cfg_attr(
127        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
128        doc = "```rust"
129    )]
130    /// use pyo3::prelude::*;
131    /// use pyo3::types::PyWeakrefReference;
132    ///
133    /// #[pyclass(weakref)]
134    /// struct Foo { /* fields omitted */ }
135    ///
136    /// #[pymethods]
137    /// impl Foo {
138    ///     fn get_data(&self) -> (&str, u32) {
139    ///         ("Dave", 10)
140    ///     }
141    /// }
142    ///
143    /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> String {
144    ///     if let Some(data_src) = unsafe { reference.upgrade_as_unchecked::<Foo>() } {
145    ///         let data = data_src.borrow();
146    ///         let (name, score) = data.get_data();
147    ///         format!("Processing '{}': score = {}", name, score)
148    ///     } else {
149    ///         "The supplied data reference is nolonger relavent.".to_owned()
150    ///     }
151    /// }
152    ///
153    /// # fn main() -> PyResult<()> {
154    /// Python::attach(|py| {
155    ///     let data = Bound::new(py, Foo{})?;
156    ///     let reference = PyWeakrefReference::new(&data)?;
157    ///
158    ///     assert_eq!(
159    ///         parse_data(reference.as_borrowed()),
160    ///         "Processing 'Dave': score = 10"
161    ///     );
162    ///
163    ///     drop(data);
164    ///
165    ///     assert_eq!(
166    ///         parse_data(reference.as_borrowed()),
167    ///         "The supplied data reference is nolonger relavent."
168    ///     );
169    ///
170    ///     Ok(())
171    /// })
172    /// # }
173    /// ```
174    ///
175    /// # Panics
176    /// This function panics is the current object is invalid.
177    /// If used propperly this is never the case. (NonNull and actually a weakref type)
178    ///
179    /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
180    /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
181    /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
182    unsafe fn upgrade_as_unchecked<T>(&self) -> Option<Bound<'py, T>> {
183        Some(unsafe { self.upgrade()?.cast_into_unchecked() })
184    }
185
186    /// Upgrade the weakref to a exact direct Bound object reference.
187    ///
188    /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
189    /// In Python it would be equivalent to [`PyWeakref_GetRef`].
190    ///
191    /// # Example
192    #[cfg_attr(
193        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
194        doc = "```rust,ignore"
195    )]
196    #[cfg_attr(
197        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
198        doc = "```rust"
199    )]
200    /// use pyo3::prelude::*;
201    /// use pyo3::types::PyWeakrefReference;
202    ///
203    /// #[pyclass(weakref)]
204    /// struct Foo { /* fields omitted */ }
205    ///
206    /// #[pymethods]
207    /// impl Foo {
208    ///     fn get_data(&self) -> (&str, u32) {
209    ///         ("Dave", 10)
210    ///     }
211    /// }
212    ///
213    /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult<String> {
214    ///     if let Some(data_src) = reference.upgrade_as_exact::<Foo>()? {
215    ///         let data = data_src.borrow();
216    ///         let (name, score) = data.get_data();
217    ///         Ok(format!("Processing '{}': score = {}", name, score))
218    ///     } else {
219    ///         Ok("The supplied data reference is nolonger relavent.".to_owned())
220    ///     }
221    /// }
222    ///
223    /// # fn main() -> PyResult<()> {
224    /// Python::attach(|py| {
225    ///     let data = Bound::new(py, Foo{})?;
226    ///     let reference = PyWeakrefReference::new(&data)?;
227    ///
228    ///     assert_eq!(
229    ///         parse_data(reference.as_borrowed())?,
230    ///         "Processing 'Dave': score = 10"
231    ///     );
232    ///
233    ///     drop(data);
234    ///
235    ///     assert_eq!(
236    ///         parse_data(reference.as_borrowed())?,
237    ///         "The supplied data reference is nolonger relavent."
238    ///     );
239    ///
240    ///     Ok(())
241    /// })
242    /// # }
243    /// ```
244    ///
245    /// # Panics
246    /// This function panics is the current object is invalid.
247    /// If used propperly this is never the case. (NonNull and actually a weakref type)
248    ///
249    /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
250    /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
251    /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
252    fn upgrade_as_exact<T>(&self) -> PyResult<Option<Bound<'py, T>>>
253    where
254        T: PyTypeInfo,
255    {
256        self.upgrade()
257            .map(Bound::cast_into_exact)
258            .transpose()
259            .map_err(Into::into)
260    }
261
262    /// Upgrade the weakref to a Bound [`PyAny`] reference to the target object if possible.
263    ///
264    /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
265    /// This function returns `Some(Bound<'py, PyAny>)` if the reference still exists, otherwise `None` will be returned.
266    ///
267    /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]).
268    /// It produces similar results to using [`PyWeakref_GetRef`] in the C api.
269    ///
270    /// # Example
271    #[cfg_attr(
272        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
273        doc = "```rust,ignore"
274    )]
275    #[cfg_attr(
276        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
277        doc = "```rust"
278    )]
279    /// use pyo3::prelude::*;
280    /// use pyo3::types::PyWeakrefReference;
281    ///
282    /// #[pyclass(weakref)]
283    /// struct Foo { /* fields omitted */ }
284    ///
285    /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult<String> {
286    ///     if let Some(object) = reference.upgrade() {
287    ///         Ok(format!("The object '{}' refered by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?))
288    ///     } else {
289    ///         Ok("The object, which this reference refered to, no longer exists".to_owned())
290    ///     }
291    /// }
292    ///
293    /// # fn main() -> PyResult<()> {
294    /// Python::attach(|py| {
295    ///     let data = Bound::new(py, Foo{})?;
296    ///     let reference = PyWeakrefReference::new(&data)?;
297    ///
298    ///     assert_eq!(
299    ///         parse_data(reference.as_borrowed())?,
300    ///         "The object 'Foo' refered by this reference still exists."
301    ///     );
302    ///
303    ///     drop(data);
304    ///
305    ///     assert_eq!(
306    ///         parse_data(reference.as_borrowed())?,
307    ///         "The object, which this reference refered to, no longer exists"
308    ///     );
309    ///
310    ///     Ok(())
311    /// })
312    /// # }
313    /// ```
314    ///
315    /// # Panics
316    /// This function panics is the current object is invalid.
317    /// If used properly this is never the case. (NonNull and actually a weakref type)
318    ///
319    /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
320    /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
321    /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
322    fn upgrade(&self) -> Option<Bound<'py, PyAny>>;
323}
324
325impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakref> {
326    fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
327        let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
328        match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
329            std::ffi::c_int::MIN..=-1 => panic!("The 'weakref' weak reference instance should be valid (non-null and actually a weakref reference)"),
330            0 => None,
331            1..=std::ffi::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
332        }
333    }
334}
335
336#[cfg(test)]
337mod tests {
338    use crate::types::any::{PyAny, PyAnyMethods};
339    use crate::types::weakref::{PyWeakref, PyWeakrefMethods, PyWeakrefProxy, PyWeakrefReference};
340    use crate::{Bound, PyResult, Python};
341
342    fn new_reference<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakref>> {
343        let reference = PyWeakrefReference::new(object)?;
344        reference.cast_into().map_err(Into::into)
345    }
346
347    fn new_proxy<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakref>> {
348        let reference = PyWeakrefProxy::new(object)?;
349        reference.cast_into().map_err(Into::into)
350    }
351
352    mod python_class {
353        use super::*;
354        use crate::ffi;
355        use crate::{py_result_ext::PyResultExt, types::PyType};
356        use std::ptr;
357
358        fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
359            py.run(ffi::c_str!("class A:\n    pass\n"), None, None)?;
360            py.eval(ffi::c_str!("A"), None, None).cast_into::<PyType>()
361        }
362
363        #[test]
364        fn test_weakref_upgrade_as() -> PyResult<()> {
365            fn inner(
366                create_reference: impl for<'py> FnOnce(
367                    &Bound<'py, PyAny>,
368                )
369                    -> PyResult<Bound<'py, PyWeakref>>,
370            ) -> PyResult<()> {
371                Python::attach(|py| {
372                    let class = get_type(py)?;
373                    let object = class.call0()?;
374                    let reference = create_reference(&object)?;
375
376                    {
377                        // This test is a bit weird but ok.
378                        let obj = reference.upgrade_as::<PyAny>();
379
380                        assert!(obj.is_ok());
381                        let obj = obj.unwrap();
382
383                        assert!(obj.is_some());
384                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
385                            && obj.is_exact_instance(&class)));
386                    }
387
388                    drop(object);
389
390                    {
391                        // This test is a bit weird but ok.
392                        let obj = reference.upgrade_as::<PyAny>();
393
394                        assert!(obj.is_ok());
395                        let obj = obj.unwrap();
396
397                        assert!(obj.is_none());
398                    }
399
400                    Ok(())
401                })
402            }
403
404            inner(new_reference)?;
405            inner(new_proxy)
406        }
407
408        #[test]
409        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
410            fn inner(
411                create_reference: impl for<'py> FnOnce(
412                    &Bound<'py, PyAny>,
413                )
414                    -> PyResult<Bound<'py, PyWeakref>>,
415            ) -> PyResult<()> {
416                Python::attach(|py| {
417                    let class = get_type(py)?;
418                    let object = class.call0()?;
419                    let reference = create_reference(&object)?;
420
421                    {
422                        // This test is a bit weird but ok.
423                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
424
425                        assert!(obj.is_some());
426                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
427                            && obj.is_exact_instance(&class)));
428                    }
429
430                    drop(object);
431
432                    {
433                        // This test is a bit weird but ok.
434                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
435
436                        assert!(obj.is_none());
437                    }
438
439                    Ok(())
440                })
441            }
442
443            inner(new_reference)?;
444            inner(new_proxy)
445        }
446
447        #[test]
448        fn test_weakref_upgrade() -> PyResult<()> {
449            fn inner(
450                create_reference: impl for<'py> FnOnce(
451                    &Bound<'py, PyAny>,
452                )
453                    -> PyResult<Bound<'py, PyWeakref>>,
454                call_retrievable: bool,
455            ) -> PyResult<()> {
456                let not_call_retrievable = !call_retrievable;
457
458                Python::attach(|py| {
459                    let class = get_type(py)?;
460                    let object = class.call0()?;
461                    let reference = create_reference(&object)?;
462
463                    assert!(not_call_retrievable || reference.call0()?.is(&object));
464                    assert!(reference.upgrade().is_some());
465                    assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
466
467                    drop(object);
468
469                    assert!(not_call_retrievable || reference.call0()?.is_none());
470                    assert!(reference.upgrade().is_none());
471
472                    Ok(())
473                })
474            }
475
476            inner(new_reference, true)?;
477            inner(new_proxy, false)
478        }
479    }
480
481    // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
482    #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
483    mod pyo3_pyclass {
484        use super::*;
485        use crate::{pyclass, Py};
486        use std::ptr;
487
488        #[pyclass(weakref, crate = "crate")]
489        struct WeakrefablePyClass {}
490
491        #[test]
492        fn test_weakref_upgrade_as() -> PyResult<()> {
493            fn inner(
494                create_reference: impl for<'py> FnOnce(
495                    &Bound<'py, PyAny>,
496                )
497                    -> PyResult<Bound<'py, PyWeakref>>,
498            ) -> PyResult<()> {
499                Python::attach(|py| {
500                    let object = Py::new(py, WeakrefablePyClass {})?;
501                    let reference = create_reference(object.bind(py))?;
502
503                    {
504                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
505
506                        assert!(obj.is_ok());
507                        let obj = obj.unwrap();
508
509                        assert!(obj.is_some());
510                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
511                    }
512
513                    drop(object);
514
515                    {
516                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
517
518                        assert!(obj.is_ok());
519                        let obj = obj.unwrap();
520
521                        assert!(obj.is_none());
522                    }
523
524                    Ok(())
525                })
526            }
527
528            inner(new_reference)?;
529            inner(new_proxy)
530        }
531
532        #[test]
533        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
534            fn inner(
535                create_reference: impl for<'py> FnOnce(
536                    &Bound<'py, PyAny>,
537                )
538                    -> PyResult<Bound<'py, PyWeakref>>,
539            ) -> PyResult<()> {
540                Python::attach(|py| {
541                    let object = Py::new(py, WeakrefablePyClass {})?;
542                    let reference = create_reference(object.bind(py))?;
543
544                    {
545                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
546
547                        assert!(obj.is_some());
548                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
549                    }
550
551                    drop(object);
552
553                    {
554                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
555
556                        assert!(obj.is_none());
557                    }
558
559                    Ok(())
560                })
561            }
562
563            inner(new_reference)?;
564            inner(new_proxy)
565        }
566
567        #[test]
568        fn test_weakref_upgrade() -> PyResult<()> {
569            fn inner(
570                create_reference: impl for<'py> FnOnce(
571                    &Bound<'py, PyAny>,
572                )
573                    -> PyResult<Bound<'py, PyWeakref>>,
574                call_retrievable: bool,
575            ) -> PyResult<()> {
576                let not_call_retrievable = !call_retrievable;
577
578                Python::attach(|py| {
579                    let object = Py::new(py, WeakrefablePyClass {})?;
580                    let reference = create_reference(object.bind(py))?;
581
582                    assert!(not_call_retrievable || reference.call0()?.is(&object));
583                    assert!(reference.upgrade().is_some());
584                    assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
585
586                    drop(object);
587
588                    assert!(not_call_retrievable || reference.call0()?.is_none());
589                    assert!(reference.upgrade().is_none());
590
591                    Ok(())
592                })
593            }
594
595            inner(new_reference, true)?;
596            inner(new_proxy, false)
597        }
598    }
599}