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    /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object with a callback.
94    ///
95    /// 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.
96    ///
97    /// # Examples
98    #[cfg_attr(
99        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
100        doc = "```rust,ignore"
101    )]
102    #[cfg_attr(
103        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
104        doc = "```rust"
105    )]
106    /// use pyo3::prelude::*;
107    /// use pyo3::types::PyWeakrefReference;
108    /// use pyo3::ffi::c_str;
109    ///
110    /// #[pyclass(weakref)]
111    /// struct Foo { /* fields omitted */ }
112    ///
113    /// #[pyfunction]
114    /// fn callback(wref: Bound<'_, PyWeakrefReference>) -> PyResult<()> {
115    ///         let py = wref.py();
116    ///         assert!(wref.upgrade_as::<Foo>()?.is_none());
117    ///         py.run(c_str!("counter = 1"), None, None)
118    /// }
119    ///
120    /// # fn main() -> PyResult<()> {
121    /// Python::with_gil(|py| {
122    ///     py.run(c_str!("counter = 0"), None, None)?;
123    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
124    ///     let foo = Bound::new(py, Foo{})?;
125    ///
126    ///     // This is fine.
127    ///     let weakref = PyWeakrefReference::new_with(&foo, py.None())?;
128    ///     assert!(weakref.upgrade_as::<Foo>()?.is_some());
129    ///     assert!(
130    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
131    ///         weakref.upgrade()
132    ///             .map_or(false, |obj| obj.is(&foo))
133    ///     );
134    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
135    ///
136    ///     let weakref2 = PyWeakrefReference::new_with(&foo, wrap_pyfunction!(callback, py)?)?;
137    ///     assert!(!weakref.is(&weakref2)); // Not the same weakref
138    ///     assert!(weakref.eq(&weakref2)?);  // But Equal, since they point to the same object
139    ///
140    ///     drop(foo);
141    ///
142    ///     assert!(weakref.upgrade_as::<Foo>()?.is_none());
143    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 1);
144    ///     Ok(())
145    /// })
146    /// # }
147    /// ```
148    pub fn new_with<'py, C>(
149        object: &Bound<'py, PyAny>,
150        callback: C,
151    ) -> PyResult<Bound<'py, PyWeakrefReference>>
152    where
153        C: IntoPyObject<'py>,
154    {
155        fn inner<'py>(
156            object: &Bound<'py, PyAny>,
157            callback: Borrowed<'_, 'py, PyAny>,
158        ) -> PyResult<Bound<'py, PyWeakrefReference>> {
159            unsafe {
160                Bound::from_owned_ptr_or_err(
161                    object.py(),
162                    ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()),
163                )
164                .downcast_into_unchecked()
165            }
166        }
167
168        let py = object.py();
169        inner(
170            object,
171            callback
172                .into_pyobject_or_pyerr(py)?
173                .into_any()
174                .as_borrowed(),
175        )
176    }
177}
178
179impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> {
180    fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
181        let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
182        match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
183            std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)"),
184            0 => None,
185            1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use crate::types::any::{PyAny, PyAnyMethods};
193    use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference};
194    use crate::{Bound, PyResult, Python};
195
196    #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
197    const CLASS_NAME: &str = "<class 'weakref.ReferenceType'>";
198    #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
199    const CLASS_NAME: &str = "<class 'weakref'>";
200
201    fn check_repr(
202        reference: &Bound<'_, PyWeakrefReference>,
203        object: Option<(&Bound<'_, PyAny>, &str)>,
204    ) -> PyResult<()> {
205        let repr = reference.repr()?.to_string();
206        let (first_part, second_part) = repr.split_once("; ").unwrap();
207
208        {
209            let (msg, addr) = first_part.split_once("0x").unwrap();
210
211            assert_eq!(msg, "<weakref at ");
212            assert!(addr
213                .to_lowercase()
214                .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
215        }
216
217        match object {
218            Some((object, class)) => {
219                let (msg, addr) = second_part.split_once("0x").unwrap();
220
221                // Avoid testing on reprs directly since they the quoting and full path vs class name tends to be changedi undocumented.
222                assert!(msg.starts_with("to '"));
223                assert!(msg.contains(class));
224                assert!(msg.ends_with("' at "));
225
226                assert!(addr
227                    .to_lowercase()
228                    .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
229            }
230            None => {
231                assert_eq!(second_part, "dead>")
232            }
233        }
234
235        Ok(())
236    }
237
238    mod python_class {
239        use super::*;
240        use crate::ffi;
241        use crate::{py_result_ext::PyResultExt, types::PyType};
242        use std::ptr;
243
244        fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
245            py.run(ffi::c_str!("class A:\n    pass\n"), None, None)?;
246            py.eval(ffi::c_str!("A"), None, None)
247                .downcast_into::<PyType>()
248        }
249
250        #[test]
251        fn test_weakref_reference_behavior() -> PyResult<()> {
252            Python::with_gil(|py| {
253                let class = get_type(py)?;
254                let object = class.call0()?;
255                let reference = PyWeakrefReference::new(&object)?;
256
257                assert!(!reference.is(&object));
258                assert!(reference.upgrade().unwrap().is(&object));
259
260                #[cfg(not(Py_LIMITED_API))]
261                assert_eq!(reference.get_type().to_string(), CLASS_NAME);
262
263                #[cfg(not(Py_LIMITED_API))]
264                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
265
266                #[cfg(not(Py_LIMITED_API))]
267                check_repr(&reference, Some((object.as_any(), "A")))?;
268
269                assert!(reference
270                    .getattr("__callback__")
271                    .map_or(false, |result| result.is_none()));
272
273                assert!(reference.call0()?.is(&object));
274
275                drop(object);
276
277                assert!(reference.upgrade().is_none());
278                #[cfg(not(Py_LIMITED_API))]
279                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
280                check_repr(&reference, None)?;
281
282                assert!(reference
283                    .getattr("__callback__")
284                    .map_or(false, |result| result.is_none()));
285
286                assert!(reference.call0()?.is_none());
287
288                Ok(())
289            })
290        }
291
292        #[test]
293        fn test_weakref_upgrade_as() -> PyResult<()> {
294            Python::with_gil(|py| {
295                let class = get_type(py)?;
296                let object = class.call0()?;
297                let reference = PyWeakrefReference::new(&object)?;
298
299                {
300                    // This test is a bit weird but ok.
301                    let obj = reference.upgrade_as::<PyAny>();
302
303                    assert!(obj.is_ok());
304                    let obj = obj.unwrap();
305
306                    assert!(obj.is_some());
307                    assert!(
308                        obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
309                            && obj.is_exact_instance(&class))
310                    );
311                }
312
313                drop(object);
314
315                {
316                    // This test is a bit weird but ok.
317                    let obj = reference.upgrade_as::<PyAny>();
318
319                    assert!(obj.is_ok());
320                    let obj = obj.unwrap();
321
322                    assert!(obj.is_none());
323                }
324
325                Ok(())
326            })
327        }
328
329        #[test]
330        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
331            Python::with_gil(|py| {
332                let class = get_type(py)?;
333                let object = class.call0()?;
334                let reference = PyWeakrefReference::new(&object)?;
335
336                {
337                    // This test is a bit weird but ok.
338                    let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
339
340                    assert!(obj.is_some());
341                    assert!(
342                        obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
343                            && obj.is_exact_instance(&class))
344                    );
345                }
346
347                drop(object);
348
349                {
350                    // This test is a bit weird but ok.
351                    let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
352
353                    assert!(obj.is_none());
354                }
355
356                Ok(())
357            })
358        }
359
360        #[test]
361        fn test_weakref_upgrade() -> PyResult<()> {
362            Python::with_gil(|py| {
363                let class = get_type(py)?;
364                let object = class.call0()?;
365                let reference = PyWeakrefReference::new(&object)?;
366
367                assert!(reference.call0()?.is(&object));
368                assert!(reference.upgrade().is_some());
369                assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
370
371                drop(object);
372
373                assert!(reference.call0()?.is_none());
374                assert!(reference.upgrade().is_none());
375
376                Ok(())
377            })
378        }
379    }
380
381    // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
382    #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
383    mod pyo3_pyclass {
384        use super::*;
385        use crate::{pyclass, Py};
386        use std::ptr;
387
388        #[pyclass(weakref, crate = "crate")]
389        struct WeakrefablePyClass {}
390
391        #[test]
392        fn test_weakref_reference_behavior() -> PyResult<()> {
393            Python::with_gil(|py| {
394                let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
395                let reference = PyWeakrefReference::new(&object)?;
396
397                assert!(!reference.is(&object));
398                assert!(reference.upgrade().unwrap().is(&object));
399                #[cfg(not(Py_LIMITED_API))]
400                assert_eq!(reference.get_type().to_string(), CLASS_NAME);
401
402                #[cfg(not(Py_LIMITED_API))]
403                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
404                #[cfg(not(Py_LIMITED_API))]
405                check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
406
407                assert!(reference
408                    .getattr("__callback__")
409                    .map_or(false, |result| result.is_none()));
410
411                assert!(reference.call0()?.is(&object));
412
413                drop(object);
414
415                assert!(reference.upgrade().is_none());
416                #[cfg(not(Py_LIMITED_API))]
417                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
418                check_repr(&reference, None)?;
419
420                assert!(reference
421                    .getattr("__callback__")
422                    .map_or(false, |result| result.is_none()));
423
424                assert!(reference.call0()?.is_none());
425
426                Ok(())
427            })
428        }
429
430        #[test]
431        fn test_weakref_upgrade_as() -> PyResult<()> {
432            Python::with_gil(|py| {
433                let object = Py::new(py, WeakrefablePyClass {})?;
434                let reference = PyWeakrefReference::new(object.bind(py))?;
435
436                {
437                    let obj = reference.upgrade_as::<WeakrefablePyClass>();
438
439                    assert!(obj.is_ok());
440                    let obj = obj.unwrap();
441
442                    assert!(obj.is_some());
443                    assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
444                }
445
446                drop(object);
447
448                {
449                    let obj = reference.upgrade_as::<WeakrefablePyClass>();
450
451                    assert!(obj.is_ok());
452                    let obj = obj.unwrap();
453
454                    assert!(obj.is_none());
455                }
456
457                Ok(())
458            })
459        }
460
461        #[test]
462        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
463            Python::with_gil(|py| {
464                let object = Py::new(py, WeakrefablePyClass {})?;
465                let reference = PyWeakrefReference::new(object.bind(py))?;
466
467                {
468                    let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
469
470                    assert!(obj.is_some());
471                    assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
472                }
473
474                drop(object);
475
476                {
477                    let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
478
479                    assert!(obj.is_none());
480                }
481
482                Ok(())
483            })
484        }
485
486        #[test]
487        fn test_weakref_upgrade() -> PyResult<()> {
488            Python::with_gil(|py| {
489                let object = Py::new(py, WeakrefablePyClass {})?;
490                let reference = PyWeakrefReference::new(object.bind(py))?;
491
492                assert!(reference.call0()?.is(&object));
493                assert!(reference.upgrade().is_some());
494                assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
495
496                drop(object);
497
498                assert!(reference.call0()?.is_none());
499                assert!(reference.upgrade().is_none());
500
501                Ok(())
502            })
503        }
504    }
505}