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