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