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