pyo3/types/weakref/
reference.rs

1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::py_result_ext::PyResultExt;
4use crate::types::any::PyAny;
5use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt};
6
7#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
8use crate::type_object::PyTypeCheck;
9
10use super::PyWeakrefMethods;
11
12/// Represents a Python `weakref.ReferenceType`.
13///
14/// In Python this is created by calling `weakref.ref`.
15#[repr(transparent)]
16pub struct PyWeakrefReference(PyAny);
17
18#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))]
19pyobject_subclassable_native_type!(PyWeakrefReference, crate::ffi::PyWeakReference);
20
21#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))]
22pyobject_native_type!(
23    PyWeakrefReference,
24    ffi::PyWeakReference,
25    pyobject_native_static_type_object!(ffi::_PyWeakref_RefType),
26    #module=Some("weakref"),
27    #checkfunction=ffi::PyWeakref_CheckRefExact
28);
29
30// When targetting alternative or multiple interpreters, it is better to not use the internal API.
31#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
32pyobject_native_type_named!(PyWeakrefReference);
33
34#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
35impl PyTypeCheck for PyWeakrefReference {
36    const NAME: &'static str = "weakref.ReferenceType";
37
38    fn type_check(object: &Bound<'_, PyAny>) -> bool {
39        unsafe { ffi::PyWeakref_CheckRef(object.as_ptr()) > 0 }
40    }
41}
42
43impl PyWeakrefReference {
44    /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object.
45    ///
46    /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag).
47    ///
48    /// # Examples
49    #[cfg_attr(
50        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
51        doc = "```rust,ignore"
52    )]
53    #[cfg_attr(
54        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
55        doc = "```rust"
56    )]
57    /// use pyo3::prelude::*;
58    /// use pyo3::types::PyWeakrefReference;
59    ///
60    /// #[pyclass(weakref)]
61    /// struct Foo { /* fields omitted */ }
62    ///
63    /// # fn main() -> PyResult<()> {
64    /// Python::with_gil(|py| {
65    ///     let foo = Bound::new(py, Foo {})?;
66    ///     let weakref = PyWeakrefReference::new(&foo)?;
67    ///     assert!(
68    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
69    ///         weakref.upgrade()
70    ///             .map_or(false, |obj| obj.is(&foo))
71    ///     );
72    ///
73    ///     let weakref2 = PyWeakrefReference::new(&foo)?;
74    ///     assert!(weakref.is(&weakref2));
75    ///
76    ///     drop(foo);
77    ///
78    ///     assert!(weakref.upgrade().is_none());
79    ///     Ok(())
80    /// })
81    /// # }
82    /// ```
83    pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
84        unsafe {
85            Bound::from_owned_ptr_or_err(
86                object.py(),
87                ffi::PyWeakref_NewRef(object.as_ptr(), ffi::Py_None()),
88            )
89            .downcast_into_unchecked()
90        }
91    }
92
93    /// Deprecated name for [`PyWeakrefReference::new`].
94    #[deprecated(since = "0.23.0", note = "renamed to `PyWeakrefReference::new`")]
95    #[inline]
96    pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
97        Self::new(object)
98    }
99
100    /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object with a callback.
101    ///
102    /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag) or if the `callback` is not callable or None.
103    ///
104    /// # Examples
105    #[cfg_attr(
106        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
107        doc = "```rust,ignore"
108    )]
109    #[cfg_attr(
110        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
111        doc = "```rust"
112    )]
113    /// use pyo3::prelude::*;
114    /// use pyo3::types::PyWeakrefReference;
115    /// use pyo3::ffi::c_str;
116    ///
117    /// #[pyclass(weakref)]
118    /// struct Foo { /* fields omitted */ }
119    ///
120    /// #[pyfunction]
121    /// fn callback(wref: Bound<'_, PyWeakrefReference>) -> PyResult<()> {
122    ///         let py = wref.py();
123    ///         assert!(wref.upgrade_as::<Foo>()?.is_none());
124    ///         py.run(c_str!("counter = 1"), None, None)
125    /// }
126    ///
127    /// # fn main() -> PyResult<()> {
128    /// Python::with_gil(|py| {
129    ///     py.run(c_str!("counter = 0"), None, None)?;
130    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
131    ///     let foo = Bound::new(py, Foo{})?;
132    ///
133    ///     // This is fine.
134    ///     let weakref = PyWeakrefReference::new_with(&foo, py.None())?;
135    ///     assert!(weakref.upgrade_as::<Foo>()?.is_some());
136    ///     assert!(
137    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
138    ///         weakref.upgrade()
139    ///             .map_or(false, |obj| obj.is(&foo))
140    ///     );
141    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
142    ///
143    ///     let weakref2 = PyWeakrefReference::new_with(&foo, wrap_pyfunction!(callback, py)?)?;
144    ///     assert!(!weakref.is(&weakref2)); // Not the same weakref
145    ///     assert!(weakref.eq(&weakref2)?);  // But Equal, since they point to the same object
146    ///
147    ///     drop(foo);
148    ///
149    ///     assert!(weakref.upgrade_as::<Foo>()?.is_none());
150    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 1);
151    ///     Ok(())
152    /// })
153    /// # }
154    /// ```
155    pub fn new_with<'py, C>(
156        object: &Bound<'py, PyAny>,
157        callback: C,
158    ) -> PyResult<Bound<'py, PyWeakrefReference>>
159    where
160        C: IntoPyObject<'py>,
161    {
162        fn inner<'py>(
163            object: &Bound<'py, PyAny>,
164            callback: Borrowed<'_, 'py, PyAny>,
165        ) -> PyResult<Bound<'py, PyWeakrefReference>> {
166            unsafe {
167                Bound::from_owned_ptr_or_err(
168                    object.py(),
169                    ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()),
170                )
171                .downcast_into_unchecked()
172            }
173        }
174
175        let py = object.py();
176        inner(
177            object,
178            callback
179                .into_pyobject_or_pyerr(py)?
180                .into_any()
181                .as_borrowed(),
182        )
183    }
184
185    /// Deprecated name for [`PyWeakrefReference::new_with`].
186    #[deprecated(since = "0.23.0", note = "renamed to `PyWeakrefReference::new_with`")]
187    #[allow(deprecated)]
188    #[inline]
189    pub fn new_bound_with<'py, C>(
190        object: &Bound<'py, PyAny>,
191        callback: C,
192    ) -> PyResult<Bound<'py, PyWeakrefReference>>
193    where
194        C: crate::ToPyObject,
195    {
196        Self::new_with(object, callback.to_object(object.py()))
197    }
198}
199
200impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> {
201    fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
202        let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
203        match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
204            std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)"),
205            0 => None,
206            1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
207        }
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use crate::types::any::{PyAny, PyAnyMethods};
214    use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference};
215    use crate::{Bound, PyResult, Python};
216
217    #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
218    const CLASS_NAME: &str = "<class 'weakref.ReferenceType'>";
219    #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
220    const CLASS_NAME: &str = "<class 'weakref'>";
221
222    fn check_repr(
223        reference: &Bound<'_, PyWeakrefReference>,
224        object: Option<(&Bound<'_, PyAny>, &str)>,
225    ) -> PyResult<()> {
226        let repr = reference.repr()?.to_string();
227        let (first_part, second_part) = repr.split_once("; ").unwrap();
228
229        {
230            let (msg, addr) = first_part.split_once("0x").unwrap();
231
232            assert_eq!(msg, "<weakref at ");
233            assert!(addr
234                .to_lowercase()
235                .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
236        }
237
238        match object {
239            Some((object, class)) => {
240                let (msg, addr) = second_part.split_once("0x").unwrap();
241
242                // Avoid testing on reprs directly since they the quoting and full path vs class name tends to be changedi undocumented.
243                assert!(msg.starts_with("to '"));
244                assert!(msg.contains(class));
245                assert!(msg.ends_with("' at "));
246
247                assert!(addr
248                    .to_lowercase()
249                    .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
250            }
251            None => {
252                assert_eq!(second_part, "dead>")
253            }
254        }
255
256        Ok(())
257    }
258
259    mod python_class {
260        use super::*;
261        use crate::ffi;
262        use crate::{py_result_ext::PyResultExt, types::PyType};
263
264        fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
265            py.run(ffi::c_str!("class A:\n    pass\n"), None, None)?;
266            py.eval(ffi::c_str!("A"), None, None)
267                .downcast_into::<PyType>()
268        }
269
270        #[test]
271        fn test_weakref_reference_behavior() -> PyResult<()> {
272            Python::with_gil(|py| {
273                let class = get_type(py)?;
274                let object = class.call0()?;
275                let reference = PyWeakrefReference::new(&object)?;
276
277                assert!(!reference.is(&object));
278                assert!(reference.upgrade().unwrap().is(&object));
279
280                #[cfg(not(Py_LIMITED_API))]
281                assert_eq!(reference.get_type().to_string(), CLASS_NAME);
282
283                #[cfg(not(Py_LIMITED_API))]
284                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
285
286                #[cfg(not(Py_LIMITED_API))]
287                check_repr(&reference, Some((object.as_any(), "A")))?;
288
289                assert!(reference
290                    .getattr("__callback__")
291                    .map_or(false, |result| result.is_none()));
292
293                assert!(reference.call0()?.is(&object));
294
295                drop(object);
296
297                assert!(reference.upgrade().is_none());
298                #[cfg(not(Py_LIMITED_API))]
299                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
300                check_repr(&reference, None)?;
301
302                assert!(reference
303                    .getattr("__callback__")
304                    .map_or(false, |result| result.is_none()));
305
306                assert!(reference.call0()?.is_none());
307
308                Ok(())
309            })
310        }
311
312        #[test]
313        fn test_weakref_upgrade_as() -> PyResult<()> {
314            Python::with_gil(|py| {
315                let class = get_type(py)?;
316                let object = class.call0()?;
317                let reference = PyWeakrefReference::new(&object)?;
318
319                {
320                    // This test is a bit weird but ok.
321                    let obj = reference.upgrade_as::<PyAny>();
322
323                    assert!(obj.is_ok());
324                    let obj = obj.unwrap();
325
326                    assert!(obj.is_some());
327                    assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
328                        && obj.is_exact_instance(&class)));
329                }
330
331                drop(object);
332
333                {
334                    // This test is a bit weird but ok.
335                    let obj = reference.upgrade_as::<PyAny>();
336
337                    assert!(obj.is_ok());
338                    let obj = obj.unwrap();
339
340                    assert!(obj.is_none());
341                }
342
343                Ok(())
344            })
345        }
346
347        #[test]
348        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
349            Python::with_gil(|py| {
350                let class = get_type(py)?;
351                let object = class.call0()?;
352                let reference = PyWeakrefReference::new(&object)?;
353
354                {
355                    // This test is a bit weird but ok.
356                    let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
357
358                    assert!(obj.is_some());
359                    assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
360                        && obj.is_exact_instance(&class)));
361                }
362
363                drop(object);
364
365                {
366                    // This test is a bit weird but ok.
367                    let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
368
369                    assert!(obj.is_none());
370                }
371
372                Ok(())
373            })
374        }
375
376        #[test]
377        fn test_weakref_upgrade() -> PyResult<()> {
378            Python::with_gil(|py| {
379                let class = get_type(py)?;
380                let object = class.call0()?;
381                let reference = PyWeakrefReference::new(&object)?;
382
383                assert!(reference.call0()?.is(&object));
384                assert!(reference.upgrade().is_some());
385                assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
386
387                drop(object);
388
389                assert!(reference.call0()?.is_none());
390                assert!(reference.upgrade().is_none());
391
392                Ok(())
393            })
394        }
395
396        #[test]
397        #[allow(deprecated)]
398        fn test_weakref_get_object() -> PyResult<()> {
399            Python::with_gil(|py| {
400                let class = get_type(py)?;
401                let object = class.call0()?;
402                let reference = PyWeakrefReference::new(&object)?;
403
404                assert!(reference.call0()?.is(&object));
405                assert!(reference.get_object().is(&object));
406
407                drop(object);
408
409                assert!(reference.call0()?.is(&reference.get_object()));
410                assert!(reference.call0()?.is_none());
411                assert!(reference.get_object().is_none());
412
413                Ok(())
414            })
415        }
416    }
417
418    // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
419    #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
420    mod pyo3_pyclass {
421        use super::*;
422        use crate::{pyclass, Py};
423
424        #[pyclass(weakref, crate = "crate")]
425        struct WeakrefablePyClass {}
426
427        #[test]
428        fn test_weakref_reference_behavior() -> PyResult<()> {
429            Python::with_gil(|py| {
430                let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
431                let reference = PyWeakrefReference::new(&object)?;
432
433                assert!(!reference.is(&object));
434                assert!(reference.upgrade().unwrap().is(&object));
435                #[cfg(not(Py_LIMITED_API))]
436                assert_eq!(reference.get_type().to_string(), CLASS_NAME);
437
438                #[cfg(not(Py_LIMITED_API))]
439                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
440                #[cfg(not(Py_LIMITED_API))]
441                check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
442
443                assert!(reference
444                    .getattr("__callback__")
445                    .map_or(false, |result| result.is_none()));
446
447                assert!(reference.call0()?.is(&object));
448
449                drop(object);
450
451                assert!(reference.upgrade().is_none());
452                #[cfg(not(Py_LIMITED_API))]
453                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
454                check_repr(&reference, None)?;
455
456                assert!(reference
457                    .getattr("__callback__")
458                    .map_or(false, |result| result.is_none()));
459
460                assert!(reference.call0()?.is_none());
461
462                Ok(())
463            })
464        }
465
466        #[test]
467        fn test_weakref_upgrade_as() -> PyResult<()> {
468            Python::with_gil(|py| {
469                let object = Py::new(py, WeakrefablePyClass {})?;
470                let reference = PyWeakrefReference::new(object.bind(py))?;
471
472                {
473                    let obj = reference.upgrade_as::<WeakrefablePyClass>();
474
475                    assert!(obj.is_ok());
476                    let obj = obj.unwrap();
477
478                    assert!(obj.is_some());
479                    assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
480                }
481
482                drop(object);
483
484                {
485                    let obj = reference.upgrade_as::<WeakrefablePyClass>();
486
487                    assert!(obj.is_ok());
488                    let obj = obj.unwrap();
489
490                    assert!(obj.is_none());
491                }
492
493                Ok(())
494            })
495        }
496
497        #[test]
498        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
499            Python::with_gil(|py| {
500                let object = Py::new(py, WeakrefablePyClass {})?;
501                let reference = PyWeakrefReference::new(object.bind(py))?;
502
503                {
504                    let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
505
506                    assert!(obj.is_some());
507                    assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
508                }
509
510                drop(object);
511
512                {
513                    let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
514
515                    assert!(obj.is_none());
516                }
517
518                Ok(())
519            })
520        }
521
522        #[test]
523        fn test_weakref_upgrade() -> PyResult<()> {
524            Python::with_gil(|py| {
525                let object = Py::new(py, WeakrefablePyClass {})?;
526                let reference = PyWeakrefReference::new(object.bind(py))?;
527
528                assert!(reference.call0()?.is(&object));
529                assert!(reference.upgrade().is_some());
530                assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
531
532                drop(object);
533
534                assert!(reference.call0()?.is_none());
535                assert!(reference.upgrade().is_none());
536
537                Ok(())
538            })
539        }
540
541        #[test]
542        #[allow(deprecated)]
543        fn test_weakref_get_object() -> PyResult<()> {
544            Python::with_gil(|py| {
545                let object = Py::new(py, WeakrefablePyClass {})?;
546                let reference = PyWeakrefReference::new(object.bind(py))?;
547
548                assert!(reference.call0()?.is(&object));
549                assert!(reference.get_object().is(&object));
550
551                drop(object);
552
553                assert!(reference.call0()?.is(&reference.get_object()));
554                assert!(reference.call0()?.is_none());
555                assert!(reference.get_object().is_none());
556
557                Ok(())
558            })
559        }
560    }
561}