Skip to main content

pyo3/types/weakref/
reference.rs

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