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