pyo3/types/weakref/anyref.rs
1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3#[cfg(feature = "experimental-inspect")]
4use crate::inspect::{type_hint_union, PyStaticExpr};
5use crate::sync::PyOnceLock;
6use crate::type_object::{PyTypeCheck, PyTypeInfo};
7use crate::types::any::PyAny;
8use crate::types::{PyTuple, PyWeakrefProxy, PyWeakrefReference};
9use crate::{ffi, Bound, Py, Python};
10
11/// Represents any Python `weakref` reference.
12///
13/// In Python this is created by calling `weakref.ref` or `weakref.proxy`.
14#[repr(transparent)]
15pub struct PyWeakref(PyAny);
16
17pyobject_native_type_named!(PyWeakref);
18
19// TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers
20// #[cfg(not(Py_LIMITED_API))]
21// pyobject_native_type_sized!(PyWeakref, ffi::PyWeakReference);
22
23unsafe impl PyTypeCheck for PyWeakref {
24 const NAME: &'static str = "weakref";
25
26 #[cfg(feature = "experimental-inspect")]
27 const TYPE_HINT: PyStaticExpr = type_hint_union!(
28 PyWeakrefProxy::TYPE_HINT,
29 <PyWeakrefReference as PyTypeCheck>::TYPE_HINT
30 );
31
32 #[inline]
33 fn type_check(object: &Bound<'_, PyAny>) -> bool {
34 unsafe { ffi::PyWeakref_Check(object.as_ptr()) > 0 }
35 }
36
37 fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny> {
38 static TYPE: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
39 TYPE.get_or_try_init(py, || {
40 PyResult::Ok(
41 PyTuple::new(
42 py,
43 [
44 PyWeakrefProxy::classinfo_object(py),
45 PyWeakrefReference::classinfo_object(py),
46 ],
47 )?
48 .into_any()
49 .unbind(),
50 )
51 })
52 .unwrap()
53 .bind(py)
54 .clone()
55 }
56}
57
58/// Implementation of functionality for [`PyWeakref`].
59///
60/// These methods are defined for the `Bound<'py, PyWeakref>` smart pointer, so to use method call
61/// syntax these methods are separated into a trait, because stable Rust does not yet support
62/// `arbitrary_self_types`.
63#[doc(alias = "PyWeakref")]
64pub trait PyWeakrefMethods<'py>: crate::sealed::Sealed {
65 /// Upgrade the weakref to a direct Bound object reference.
66 ///
67 /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
68 /// In Python it would be equivalent to [`PyWeakref_GetRef`].
69 ///
70 /// # Example
71 #[cfg_attr(
72 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
73 doc = "```rust,ignore"
74 )]
75 #[cfg_attr(
76 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
77 doc = "```rust"
78 )]
79 /// use pyo3::prelude::*;
80 /// use pyo3::types::PyWeakrefReference;
81 ///
82 /// #[pyclass(weakref)]
83 /// struct Foo { /* fields omitted */ }
84 ///
85 /// #[pymethods]
86 /// impl Foo {
87 /// fn get_data(&self) -> (&str, u32) {
88 /// ("Dave", 10)
89 /// }
90 /// }
91 ///
92 /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult<String> {
93 /// if let Some(data_src) = reference.upgrade_as::<Foo>()? {
94 /// let data = data_src.borrow();
95 /// let (name, score) = data.get_data();
96 /// Ok(format!("Processing '{}': score = {}", name, score))
97 /// } else {
98 /// Ok("The supplied data reference is no longer relevant.".to_owned())
99 /// }
100 /// }
101 ///
102 /// # fn main() -> PyResult<()> {
103 /// Python::attach(|py| {
104 /// let data = Bound::new(py, Foo{})?;
105 /// let reference = PyWeakrefReference::new(&data)?;
106 ///
107 /// assert_eq!(
108 /// parse_data(reference.as_borrowed())?,
109 /// "Processing 'Dave': score = 10"
110 /// );
111 ///
112 /// drop(data);
113 ///
114 /// assert_eq!(
115 /// parse_data(reference.as_borrowed())?,
116 /// "The supplied data reference is no longer relevant."
117 /// );
118 ///
119 /// Ok(())
120 /// })
121 /// # }
122 /// ```
123 ///
124 /// # Panics
125 /// This function panics is the current object is invalid.
126 /// If used properly this is never the case. (NonNull and actually a weakref type)
127 ///
128 /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
129 /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
130 /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
131 fn upgrade_as<T>(&self) -> PyResult<Option<Bound<'py, T>>>
132 where
133 T: PyTypeCheck,
134 {
135 self.upgrade()
136 .map(Bound::cast_into::<T>)
137 .transpose()
138 .map_err(Into::into)
139 }
140
141 /// Upgrade the weakref to a direct Bound object reference unchecked. The type of the recovered object is not checked before casting, this could lead to unexpected behavior. Use only when absolutely certain the type can be guaranteed. The `weakref` may still return `None`.
142 ///
143 /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
144 /// In Python it would be equivalent to [`PyWeakref_GetRef`].
145 ///
146 /// # Safety
147 /// Callers must ensure that the type is valid or risk type confusion.
148 /// The `weakref` is still allowed to be `None`, if the referenced object has been cleaned up.
149 ///
150 /// # Example
151 #[cfg_attr(
152 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
153 doc = "```rust,ignore"
154 )]
155 #[cfg_attr(
156 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
157 doc = "```rust"
158 )]
159 /// use pyo3::prelude::*;
160 /// use pyo3::types::PyWeakrefReference;
161 ///
162 /// #[pyclass(weakref)]
163 /// struct Foo { /* fields omitted */ }
164 ///
165 /// #[pymethods]
166 /// impl Foo {
167 /// fn get_data(&self) -> (&str, u32) {
168 /// ("Dave", 10)
169 /// }
170 /// }
171 ///
172 /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> String {
173 /// if let Some(data_src) = unsafe { reference.upgrade_as_unchecked::<Foo>() } {
174 /// let data = data_src.borrow();
175 /// let (name, score) = data.get_data();
176 /// format!("Processing '{}': score = {}", name, score)
177 /// } else {
178 /// "The supplied data reference is no longer relevant.".to_owned()
179 /// }
180 /// }
181 ///
182 /// # fn main() -> PyResult<()> {
183 /// Python::attach(|py| {
184 /// let data = Bound::new(py, Foo{})?;
185 /// let reference = PyWeakrefReference::new(&data)?;
186 ///
187 /// assert_eq!(
188 /// parse_data(reference.as_borrowed()),
189 /// "Processing 'Dave': score = 10"
190 /// );
191 ///
192 /// drop(data);
193 ///
194 /// assert_eq!(
195 /// parse_data(reference.as_borrowed()),
196 /// "The supplied data reference is no longer relevant."
197 /// );
198 ///
199 /// Ok(())
200 /// })
201 /// # }
202 /// ```
203 ///
204 /// # Panics
205 /// This function panics is the current object is invalid.
206 /// If used properly this is never the case. (NonNull and actually a weakref type)
207 ///
208 /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
209 /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
210 /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
211 unsafe fn upgrade_as_unchecked<T>(&self) -> Option<Bound<'py, T>> {
212 Some(unsafe { self.upgrade()?.cast_into_unchecked() })
213 }
214
215 /// Upgrade the weakref to a exact direct Bound object reference.
216 ///
217 /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
218 /// In Python it would be equivalent to [`PyWeakref_GetRef`].
219 ///
220 /// # Example
221 #[cfg_attr(
222 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
223 doc = "```rust,ignore"
224 )]
225 #[cfg_attr(
226 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
227 doc = "```rust"
228 )]
229 /// use pyo3::prelude::*;
230 /// use pyo3::types::PyWeakrefReference;
231 ///
232 /// #[pyclass(weakref)]
233 /// struct Foo { /* fields omitted */ }
234 ///
235 /// #[pymethods]
236 /// impl Foo {
237 /// fn get_data(&self) -> (&str, u32) {
238 /// ("Dave", 10)
239 /// }
240 /// }
241 ///
242 /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult<String> {
243 /// if let Some(data_src) = reference.upgrade_as_exact::<Foo>()? {
244 /// let data = data_src.borrow();
245 /// let (name, score) = data.get_data();
246 /// Ok(format!("Processing '{}': score = {}", name, score))
247 /// } else {
248 /// Ok("The supplied data reference is no longer relevant.".to_owned())
249 /// }
250 /// }
251 ///
252 /// # fn main() -> PyResult<()> {
253 /// Python::attach(|py| {
254 /// let data = Bound::new(py, Foo{})?;
255 /// let reference = PyWeakrefReference::new(&data)?;
256 ///
257 /// assert_eq!(
258 /// parse_data(reference.as_borrowed())?,
259 /// "Processing 'Dave': score = 10"
260 /// );
261 ///
262 /// drop(data);
263 ///
264 /// assert_eq!(
265 /// parse_data(reference.as_borrowed())?,
266 /// "The supplied data reference is no longer relevant."
267 /// );
268 ///
269 /// Ok(())
270 /// })
271 /// # }
272 /// ```
273 ///
274 /// # Panics
275 /// This function panics is the current object is invalid.
276 /// If used properly this is never the case. (NonNull and actually a weakref type)
277 ///
278 /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
279 /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
280 /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
281 fn upgrade_as_exact<T>(&self) -> PyResult<Option<Bound<'py, T>>>
282 where
283 T: PyTypeInfo,
284 {
285 self.upgrade()
286 .map(Bound::cast_into_exact)
287 .transpose()
288 .map_err(Into::into)
289 }
290
291 /// Upgrade the weakref to a Bound [`PyAny`] reference to the target object if possible.
292 ///
293 /// It is named `upgrade` to be inline with [rust's `Weak::upgrade`](std::rc::Weak::upgrade).
294 /// This function returns `Some(Bound<'py, PyAny>)` if the reference still exists, otherwise `None` will be returned.
295 ///
296 /// This function gets the optional target of this [`weakref.ReferenceType`] (result of calling [`weakref.ref`]).
297 /// It produces similar results to using [`PyWeakref_GetRef`] in the C api.
298 ///
299 /// # Example
300 #[cfg_attr(
301 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
302 doc = "```rust,ignore"
303 )]
304 #[cfg_attr(
305 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
306 doc = "```rust"
307 )]
308 /// use pyo3::prelude::*;
309 /// use pyo3::types::PyWeakrefReference;
310 ///
311 /// #[pyclass(weakref)]
312 /// struct Foo { /* fields omitted */ }
313 ///
314 /// fn parse_data(reference: Borrowed<'_, '_, PyWeakrefReference>) -> PyResult<String> {
315 /// if let Some(object) = reference.upgrade() {
316 /// Ok(format!("The object '{}' referred by this reference still exists.", object.getattr("__class__")?.getattr("__qualname__")?))
317 /// } else {
318 /// Ok("The object, which this reference referred to, no longer exists".to_owned())
319 /// }
320 /// }
321 ///
322 /// # fn main() -> PyResult<()> {
323 /// Python::attach(|py| {
324 /// let data = Bound::new(py, Foo{})?;
325 /// let reference = PyWeakrefReference::new(&data)?;
326 ///
327 /// assert_eq!(
328 /// parse_data(reference.as_borrowed())?,
329 /// "The object 'Foo' referred by this reference still exists."
330 /// );
331 ///
332 /// drop(data);
333 ///
334 /// assert_eq!(
335 /// parse_data(reference.as_borrowed())?,
336 /// "The object, which this reference referred to, no longer exists"
337 /// );
338 ///
339 /// Ok(())
340 /// })
341 /// # }
342 /// ```
343 ///
344 /// # Panics
345 /// This function panics is the current object is invalid.
346 /// If used properly this is never the case. (NonNull and actually a weakref type)
347 ///
348 /// [`PyWeakref_GetRef`]: https://docs.python.org/3/c-api/weakref.html#c.PyWeakref_GetRef
349 /// [`weakref.ReferenceType`]: https://docs.python.org/3/library/weakref.html#weakref.ReferenceType
350 /// [`weakref.ref`]: https://docs.python.org/3/library/weakref.html#weakref.ref
351 fn upgrade(&self) -> Option<Bound<'py, PyAny>>;
352}
353
354impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakref> {
355 fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
356 let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
357 match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
358 std::ffi::c_int::MIN..=-1 => panic!("The 'weakref' weak reference instance should be valid (non-null and actually a weakref reference)"),
359 0 => None,
360 1..=std::ffi::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
361 }
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use crate::types::any::{PyAny, PyAnyMethods};
368 use crate::types::weakref::{PyWeakref, PyWeakrefMethods, PyWeakrefProxy, PyWeakrefReference};
369 use crate::{Bound, PyResult, Python};
370
371 fn new_reference<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakref>> {
372 let reference = PyWeakrefReference::new(object)?;
373 reference.cast_into().map_err(Into::into)
374 }
375
376 fn new_proxy<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakref>> {
377 let reference = PyWeakrefProxy::new(object)?;
378 reference.cast_into().map_err(Into::into)
379 }
380
381 mod python_class {
382 use super::*;
383 #[cfg(Py_3_10)]
384 use crate::types::PyInt;
385 use crate::PyTypeCheck;
386 use crate::{py_result_ext::PyResultExt, types::PyType};
387 use std::ptr;
388
389 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
390 py.run(c"class A:\n pass\n", None, None)?;
391 py.eval(c"A", None, None).cast_into::<PyType>()
392 }
393
394 #[test]
395 fn test_weakref_upgrade_as() -> PyResult<()> {
396 fn inner(
397 create_reference: impl for<'py> FnOnce(
398 &Bound<'py, PyAny>,
399 )
400 -> PyResult<Bound<'py, PyWeakref>>,
401 ) -> PyResult<()> {
402 Python::attach(|py| {
403 let class = get_type(py)?;
404 let object = class.call0()?;
405 let reference = create_reference(&object)?;
406
407 {
408 // This test is a bit weird but ok.
409 let obj = reference.upgrade_as::<PyAny>();
410
411 assert!(obj.is_ok());
412 let obj = obj.unwrap();
413
414 assert!(obj.is_some());
415 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
416 && obj.is_exact_instance(&class)));
417 }
418
419 drop(object);
420
421 {
422 // This test is a bit weird but ok.
423 let obj = reference.upgrade_as::<PyAny>();
424
425 assert!(obj.is_ok());
426 let obj = obj.unwrap();
427
428 assert!(obj.is_none());
429 }
430
431 Ok(())
432 })
433 }
434
435 inner(new_reference)?;
436 inner(new_proxy)
437 }
438
439 #[test]
440 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
441 fn inner(
442 create_reference: impl for<'py> FnOnce(
443 &Bound<'py, PyAny>,
444 )
445 -> PyResult<Bound<'py, PyWeakref>>,
446 ) -> PyResult<()> {
447 Python::attach(|py| {
448 let class = get_type(py)?;
449 let object = class.call0()?;
450 let reference = create_reference(&object)?;
451
452 {
453 // This test is a bit weird but ok.
454 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
455
456 assert!(obj.is_some());
457 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
458 && obj.is_exact_instance(&class)));
459 }
460
461 drop(object);
462
463 {
464 // This test is a bit weird but ok.
465 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
466
467 assert!(obj.is_none());
468 }
469
470 Ok(())
471 })
472 }
473
474 inner(new_reference)?;
475 inner(new_proxy)
476 }
477
478 #[test]
479 fn test_weakref_upgrade() -> PyResult<()> {
480 fn inner(
481 create_reference: impl for<'py> FnOnce(
482 &Bound<'py, PyAny>,
483 )
484 -> PyResult<Bound<'py, PyWeakref>>,
485 call_retrievable: bool,
486 ) -> PyResult<()> {
487 let not_call_retrievable = !call_retrievable;
488
489 Python::attach(|py| {
490 let class = get_type(py)?;
491 let object = class.call0()?;
492 let reference = create_reference(&object)?;
493
494 assert!(not_call_retrievable || reference.call0()?.is(&object));
495 assert!(reference.upgrade().is_some());
496 assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
497
498 drop(object);
499
500 assert!(not_call_retrievable || reference.call0()?.is_none());
501 assert!(reference.upgrade().is_none());
502
503 Ok(())
504 })
505 }
506
507 inner(new_reference, true)?;
508 inner(new_proxy, false)
509 }
510
511 #[test]
512 fn test_classinfo_object() -> PyResult<()> {
513 fn inner(
514 create_reference: impl for<'py> FnOnce(
515 &Bound<'py, PyAny>,
516 )
517 -> PyResult<Bound<'py, PyWeakref>>,
518 ) -> PyResult<()> {
519 Python::attach(|py| {
520 let class = get_type(py)?;
521 let object = class.call0()?;
522 let reference = create_reference(&object)?;
523 let t = PyWeakref::classinfo_object(py);
524 assert!(reference.is_instance(&t)?);
525 Ok(())
526 })
527 }
528
529 inner(new_reference)?;
530 inner(new_proxy)
531 }
532
533 #[cfg(Py_3_10)] // Name is different in 3.9
534 #[test]
535 fn test_classinfo_downcast_error() -> PyResult<()> {
536 Python::attach(|py| {
537 assert_eq!(
538 PyInt::new(py, 1)
539 .cast_into::<PyWeakref>()
540 .unwrap_err()
541 .to_string(),
542 "'int' object is not an instance of 'ProxyType | CallableProxyType | ReferenceType'"
543 );
544 Ok(())
545 })
546 }
547 }
548
549 // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
550 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
551 mod pyo3_pyclass {
552 use super::*;
553 use crate::{pyclass, Py};
554 use std::ptr;
555
556 #[pyclass(weakref, crate = "crate")]
557 struct WeakrefablePyClass {}
558
559 #[test]
560 fn test_weakref_upgrade_as() -> PyResult<()> {
561 fn inner(
562 create_reference: impl for<'py> FnOnce(
563 &Bound<'py, PyAny>,
564 )
565 -> PyResult<Bound<'py, PyWeakref>>,
566 ) -> PyResult<()> {
567 Python::attach(|py| {
568 let object = Py::new(py, WeakrefablePyClass {})?;
569 let reference = create_reference(object.bind(py))?;
570
571 {
572 let obj = reference.upgrade_as::<WeakrefablePyClass>();
573
574 assert!(obj.is_ok());
575 let obj = obj.unwrap();
576
577 assert!(obj.is_some());
578 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
579 }
580
581 drop(object);
582
583 {
584 let obj = reference.upgrade_as::<WeakrefablePyClass>();
585
586 assert!(obj.is_ok());
587 let obj = obj.unwrap();
588
589 assert!(obj.is_none());
590 }
591
592 Ok(())
593 })
594 }
595
596 inner(new_reference)?;
597 inner(new_proxy)
598 }
599
600 #[test]
601 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
602 fn inner(
603 create_reference: impl for<'py> FnOnce(
604 &Bound<'py, PyAny>,
605 )
606 -> PyResult<Bound<'py, PyWeakref>>,
607 ) -> PyResult<()> {
608 Python::attach(|py| {
609 let object = Py::new(py, WeakrefablePyClass {})?;
610 let reference = create_reference(object.bind(py))?;
611
612 {
613 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
614
615 assert!(obj.is_some());
616 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
617 }
618
619 drop(object);
620
621 {
622 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
623
624 assert!(obj.is_none());
625 }
626
627 Ok(())
628 })
629 }
630
631 inner(new_reference)?;
632 inner(new_proxy)
633 }
634
635 #[test]
636 fn test_weakref_upgrade() -> PyResult<()> {
637 fn inner(
638 create_reference: impl for<'py> FnOnce(
639 &Bound<'py, PyAny>,
640 )
641 -> PyResult<Bound<'py, PyWeakref>>,
642 call_retrievable: bool,
643 ) -> PyResult<()> {
644 let not_call_retrievable = !call_retrievable;
645
646 Python::attach(|py| {
647 let object = Py::new(py, WeakrefablePyClass {})?;
648 let reference = create_reference(object.bind(py))?;
649
650 assert!(not_call_retrievable || reference.call0()?.is(&object));
651 assert!(reference.upgrade().is_some());
652 assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
653
654 drop(object);
655
656 assert!(not_call_retrievable || reference.call0()?.is_none());
657 assert!(reference.upgrade().is_none());
658
659 Ok(())
660 })
661 }
662
663 inner(new_reference, true)?;
664 inner(new_proxy, false)
665 }
666 }
667}