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, PyAnyMethods};
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::downcast_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 downcasting, 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()?.downcast_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::downcast_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::os::raw::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::os::raw::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.into_any().downcast_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.into_any().downcast_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)
361                .downcast_into::<PyType>()
362        }
363
364        #[test]
365        fn test_weakref_upgrade_as() -> PyResult<()> {
366            fn inner(
367                create_reference: impl for<'py> FnOnce(
368                    &Bound<'py, PyAny>,
369                )
370                    -> PyResult<Bound<'py, PyWeakref>>,
371            ) -> PyResult<()> {
372                Python::attach(|py| {
373                    let class = get_type(py)?;
374                    let object = class.call0()?;
375                    let reference = create_reference(&object)?;
376
377                    {
378                        // This test is a bit weird but ok.
379                        let obj = reference.upgrade_as::<PyAny>();
380
381                        assert!(obj.is_ok());
382                        let obj = obj.unwrap();
383
384                        assert!(obj.is_some());
385                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
386                            && obj.is_exact_instance(&class)));
387                    }
388
389                    drop(object);
390
391                    {
392                        // This test is a bit weird but ok.
393                        let obj = reference.upgrade_as::<PyAny>();
394
395                        assert!(obj.is_ok());
396                        let obj = obj.unwrap();
397
398                        assert!(obj.is_none());
399                    }
400
401                    Ok(())
402                })
403            }
404
405            inner(new_reference)?;
406            inner(new_proxy)
407        }
408
409        #[test]
410        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
411            fn inner(
412                create_reference: impl for<'py> FnOnce(
413                    &Bound<'py, PyAny>,
414                )
415                    -> PyResult<Bound<'py, PyWeakref>>,
416            ) -> PyResult<()> {
417                Python::attach(|py| {
418                    let class = get_type(py)?;
419                    let object = class.call0()?;
420                    let reference = create_reference(&object)?;
421
422                    {
423                        // This test is a bit weird but ok.
424                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
425
426                        assert!(obj.is_some());
427                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
428                            && obj.is_exact_instance(&class)));
429                    }
430
431                    drop(object);
432
433                    {
434                        // This test is a bit weird but ok.
435                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
436
437                        assert!(obj.is_none());
438                    }
439
440                    Ok(())
441                })
442            }
443
444            inner(new_reference)?;
445            inner(new_proxy)
446        }
447
448        #[test]
449        fn test_weakref_upgrade() -> PyResult<()> {
450            fn inner(
451                create_reference: impl for<'py> FnOnce(
452                    &Bound<'py, PyAny>,
453                )
454                    -> PyResult<Bound<'py, PyWeakref>>,
455                call_retrievable: bool,
456            ) -> PyResult<()> {
457                let not_call_retrievable = !call_retrievable;
458
459                Python::attach(|py| {
460                    let class = get_type(py)?;
461                    let object = class.call0()?;
462                    let reference = create_reference(&object)?;
463
464                    assert!(not_call_retrievable || reference.call0()?.is(&object));
465                    assert!(reference.upgrade().is_some());
466                    assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
467
468                    drop(object);
469
470                    assert!(not_call_retrievable || reference.call0()?.is_none());
471                    assert!(reference.upgrade().is_none());
472
473                    Ok(())
474                })
475            }
476
477            inner(new_reference, true)?;
478            inner(new_proxy, false)
479        }
480    }
481
482    // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
483    #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
484    mod pyo3_pyclass {
485        use super::*;
486        use crate::{pyclass, Py};
487        use std::ptr;
488
489        #[pyclass(weakref, crate = "crate")]
490        struct WeakrefablePyClass {}
491
492        #[test]
493        fn test_weakref_upgrade_as() -> PyResult<()> {
494            fn inner(
495                create_reference: impl for<'py> FnOnce(
496                    &Bound<'py, PyAny>,
497                )
498                    -> PyResult<Bound<'py, PyWeakref>>,
499            ) -> PyResult<()> {
500                Python::attach(|py| {
501                    let object = Py::new(py, WeakrefablePyClass {})?;
502                    let reference = create_reference(object.bind(py))?;
503
504                    {
505                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
506
507                        assert!(obj.is_ok());
508                        let obj = obj.unwrap();
509
510                        assert!(obj.is_some());
511                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
512                    }
513
514                    drop(object);
515
516                    {
517                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
518
519                        assert!(obj.is_ok());
520                        let obj = obj.unwrap();
521
522                        assert!(obj.is_none());
523                    }
524
525                    Ok(())
526                })
527            }
528
529            inner(new_reference)?;
530            inner(new_proxy)
531        }
532
533        #[test]
534        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
535            fn inner(
536                create_reference: impl for<'py> FnOnce(
537                    &Bound<'py, PyAny>,
538                )
539                    -> PyResult<Bound<'py, PyWeakref>>,
540            ) -> PyResult<()> {
541                Python::attach(|py| {
542                    let object = Py::new(py, WeakrefablePyClass {})?;
543                    let reference = create_reference(object.bind(py))?;
544
545                    {
546                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
547
548                        assert!(obj.is_some());
549                        assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
550                    }
551
552                    drop(object);
553
554                    {
555                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
556
557                        assert!(obj.is_none());
558                    }
559
560                    Ok(())
561                })
562            }
563
564            inner(new_reference)?;
565            inner(new_proxy)
566        }
567
568        #[test]
569        fn test_weakref_upgrade() -> PyResult<()> {
570            fn inner(
571                create_reference: impl for<'py> FnOnce(
572                    &Bound<'py, PyAny>,
573                )
574                    -> PyResult<Bound<'py, PyWeakref>>,
575                call_retrievable: bool,
576            ) -> PyResult<()> {
577                let not_call_retrievable = !call_retrievable;
578
579                Python::attach(|py| {
580                    let object = Py::new(py, WeakrefablePyClass {})?;
581                    let reference = create_reference(object.bind(py))?;
582
583                    assert!(not_call_retrievable || reference.call0()?.is(&object));
584                    assert!(reference.upgrade().is_some());
585                    assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
586
587                    drop(object);
588
589                    assert!(not_call_retrievable || reference.call0()?.is_none());
590                    assert!(reference.upgrade().is_none());
591
592                    Ok(())
593                })
594            }
595
596            inner(new_reference, true)?;
597            inner(new_proxy, false)
598        }
599    }
600}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here