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    #[cfg(feature = "experimental-inspect")]
39    const PYTHON_TYPE: &'static str = "weakref.ReferenceType";
40
41    fn type_check(object: &Bound<'_, PyAny>) -> bool {
42        unsafe { ffi::PyWeakref_CheckRef(object.as_ptr()) > 0 }
43    }
44}
45
46impl PyWeakrefReference {
47    /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object.
48    ///
49    /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag).
50    ///
51    /// # Examples
52    #[cfg_attr(
53        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
54        doc = "```rust,ignore"
55    )]
56    #[cfg_attr(
57        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
58        doc = "```rust"
59    )]
60    /// use pyo3::prelude::*;
61    /// use pyo3::types::PyWeakrefReference;
62    ///
63    /// #[pyclass(weakref)]
64    /// struct Foo { /* fields omitted */ }
65    ///
66    /// # fn main() -> PyResult<()> {
67    /// Python::attach(|py| {
68    ///     let foo = Bound::new(py, Foo {})?;
69    ///     let weakref = PyWeakrefReference::new(&foo)?;
70    ///     assert!(
71    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
72    ///         weakref.upgrade().is_some_and(|obj| obj.is(&foo))
73    ///     );
74    ///
75    ///     let weakref2 = PyWeakrefReference::new(&foo)?;
76    ///     assert!(weakref.is(&weakref2));
77    ///
78    ///     drop(foo);
79    ///
80    ///     assert!(weakref.upgrade().is_none());
81    ///     Ok(())
82    /// })
83    /// # }
84    /// ```
85    pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
86        unsafe {
87            Bound::from_owned_ptr_or_err(
88                object.py(),
89                ffi::PyWeakref_NewRef(object.as_ptr(), ffi::Py_None()),
90            )
91            .cast_into_unchecked()
92        }
93    }
94
95    /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object with a callback.
96    ///
97    /// 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.
98    ///
99    /// # Examples
100    #[cfg_attr(
101        not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
102        doc = "```rust,ignore"
103    )]
104    #[cfg_attr(
105        all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
106        doc = "```rust"
107    )]
108    /// use pyo3::prelude::*;
109    /// use pyo3::types::PyWeakrefReference;
110    /// use pyo3::ffi::c_str;
111    ///
112    /// #[pyclass(weakref)]
113    /// struct Foo { /* fields omitted */ }
114    ///
115    /// #[pyfunction]
116    /// fn callback(wref: Bound<'_, PyWeakrefReference>) -> PyResult<()> {
117    ///         let py = wref.py();
118    ///         assert!(wref.upgrade_as::<Foo>()?.is_none());
119    ///         py.run(c_str!("counter = 1"), None, None)
120    /// }
121    ///
122    /// # fn main() -> PyResult<()> {
123    /// Python::attach(|py| {
124    ///     py.run(c_str!("counter = 0"), None, None)?;
125    ///     assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
126    ///     let foo = Bound::new(py, Foo{})?;
127    ///
128    ///     // This is fine.
129    ///     let weakref = PyWeakrefReference::new_with(&foo, py.None())?;
130    ///     assert!(weakref.upgrade_as::<Foo>()?.is_some());
131    ///     assert!(
132    ///         // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
133    ///         weakref.upgrade().is_some_and(|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                .cast_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::ffi::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::ffi::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).cast_into::<PyType>()
248        }
249
250        #[test]
251        fn test_weakref_reference_behavior() -> PyResult<()> {
252            Python::attach(|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                    .is_ok_and(|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                    .is_ok_and(|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::attach(|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!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
308                        && obj.is_exact_instance(&class)));
309                }
310
311                drop(object);
312
313                {
314                    // This test is a bit weird but ok.
315                    let obj = reference.upgrade_as::<PyAny>();
316
317                    assert!(obj.is_ok());
318                    let obj = obj.unwrap();
319
320                    assert!(obj.is_none());
321                }
322
323                Ok(())
324            })
325        }
326
327        #[test]
328        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
329            Python::attach(|py| {
330                let class = get_type(py)?;
331                let object = class.call0()?;
332                let reference = PyWeakrefReference::new(&object)?;
333
334                {
335                    // This test is a bit weird but ok.
336                    let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
337
338                    assert!(obj.is_some());
339                    assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
340                        && obj.is_exact_instance(&class)));
341                }
342
343                drop(object);
344
345                {
346                    // This test is a bit weird but ok.
347                    let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
348
349                    assert!(obj.is_none());
350                }
351
352                Ok(())
353            })
354        }
355
356        #[test]
357        fn test_weakref_upgrade() -> PyResult<()> {
358            Python::attach(|py| {
359                let class = get_type(py)?;
360                let object = class.call0()?;
361                let reference = PyWeakrefReference::new(&object)?;
362
363                assert!(reference.call0()?.is(&object));
364                assert!(reference.upgrade().is_some());
365                assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
366
367                drop(object);
368
369                assert!(reference.call0()?.is_none());
370                assert!(reference.upgrade().is_none());
371
372                Ok(())
373            })
374        }
375    }
376
377    // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
378    #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
379    mod pyo3_pyclass {
380        use super::*;
381        use crate::{pyclass, Py};
382        use std::ptr;
383
384        #[pyclass(weakref, crate = "crate")]
385        struct WeakrefablePyClass {}
386
387        #[test]
388        fn test_weakref_reference_behavior() -> PyResult<()> {
389            Python::attach(|py| {
390                let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
391                let reference = PyWeakrefReference::new(&object)?;
392
393                assert!(!reference.is(&object));
394                assert!(reference.upgrade().unwrap().is(&object));
395                #[cfg(not(Py_LIMITED_API))]
396                assert_eq!(reference.get_type().to_string(), CLASS_NAME);
397
398                #[cfg(not(Py_LIMITED_API))]
399                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
400                #[cfg(not(Py_LIMITED_API))]
401                check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
402
403                assert!(reference
404                    .getattr("__callback__")
405                    .is_ok_and(|result| result.is_none()));
406
407                assert!(reference.call0()?.is(&object));
408
409                drop(object);
410
411                assert!(reference.upgrade().is_none());
412                #[cfg(not(Py_LIMITED_API))]
413                assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
414                check_repr(&reference, None)?;
415
416                assert!(reference
417                    .getattr("__callback__")
418                    .is_ok_and(|result| result.is_none()));
419
420                assert!(reference.call0()?.is_none());
421
422                Ok(())
423            })
424        }
425
426        #[test]
427        fn test_weakref_upgrade_as() -> PyResult<()> {
428            Python::attach(|py| {
429                let object = Py::new(py, WeakrefablePyClass {})?;
430                let reference = PyWeakrefReference::new(object.bind(py))?;
431
432                {
433                    let obj = reference.upgrade_as::<WeakrefablePyClass>();
434
435                    assert!(obj.is_ok());
436                    let obj = obj.unwrap();
437
438                    assert!(obj.is_some());
439                    assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
440                }
441
442                drop(object);
443
444                {
445                    let obj = reference.upgrade_as::<WeakrefablePyClass>();
446
447                    assert!(obj.is_ok());
448                    let obj = obj.unwrap();
449
450                    assert!(obj.is_none());
451                }
452
453                Ok(())
454            })
455        }
456
457        #[test]
458        fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
459            Python::attach(|py| {
460                let object = Py::new(py, WeakrefablePyClass {})?;
461                let reference = PyWeakrefReference::new(object.bind(py))?;
462
463                {
464                    let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
465
466                    assert!(obj.is_some());
467                    assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
468                }
469
470                drop(object);
471
472                {
473                    let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
474
475                    assert!(obj.is_none());
476                }
477
478                Ok(())
479            })
480        }
481
482        #[test]
483        fn test_weakref_upgrade() -> PyResult<()> {
484            Python::attach(|py| {
485                let object = Py::new(py, WeakrefablePyClass {})?;
486                let reference = PyWeakrefReference::new(object.bind(py))?;
487
488                assert!(reference.call0()?.is(&object));
489                assert!(reference.upgrade().is_some());
490                assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
491
492                drop(object);
493
494                assert!(reference.call0()?.is_none());
495                assert!(reference.upgrade().is_none());
496
497                Ok(())
498            })
499        }
500    }
501}