Skip to main content

pyo3/types/weakref/
anyref.rs

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