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
355 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
356 py.run(ffi::c_str!("class A:\n pass\n"), None, None)?;
357 py.eval(ffi::c_str!("A"), None, None)
358 .downcast_into::<PyType>()
359 }
360
361 #[test]
362 fn test_weakref_upgrade_as() -> PyResult<()> {
363 fn inner(
364 create_reference: impl for<'py> FnOnce(
365 &Bound<'py, PyAny>,
366 )
367 -> PyResult<Bound<'py, PyWeakref>>,
368 ) -> PyResult<()> {
369 Python::with_gil(|py| {
370 let class = get_type(py)?;
371 let object = class.call0()?;
372 let reference = create_reference(&object)?;
373
374 {
375 // This test is a bit weird but ok.
376 let obj = reference.upgrade_as::<PyAny>();
377
378 assert!(obj.is_ok());
379 let obj = obj.unwrap();
380
381 assert!(obj.is_some());
382 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
383 && obj.is_exact_instance(&class)));
384 }
385
386 drop(object);
387
388 {
389 // This test is a bit weird but ok.
390 let obj = reference.upgrade_as::<PyAny>();
391
392 assert!(obj.is_ok());
393 let obj = obj.unwrap();
394
395 assert!(obj.is_none());
396 }
397
398 Ok(())
399 })
400 }
401
402 inner(new_reference)?;
403 inner(new_proxy)
404 }
405
406 #[test]
407 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
408 fn inner(
409 create_reference: impl for<'py> FnOnce(
410 &Bound<'py, PyAny>,
411 )
412 -> PyResult<Bound<'py, PyWeakref>>,
413 ) -> PyResult<()> {
414 Python::with_gil(|py| {
415 let class = get_type(py)?;
416 let object = class.call0()?;
417 let reference = create_reference(&object)?;
418
419 {
420 // This test is a bit weird but ok.
421 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
422
423 assert!(obj.is_some());
424 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
425 && obj.is_exact_instance(&class)));
426 }
427
428 drop(object);
429
430 {
431 // This test is a bit weird but ok.
432 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
433
434 assert!(obj.is_none());
435 }
436
437 Ok(())
438 })
439 }
440
441 inner(new_reference)?;
442 inner(new_proxy)
443 }
444
445 #[test]
446 fn test_weakref_upgrade() -> PyResult<()> {
447 fn inner(
448 create_reference: impl for<'py> FnOnce(
449 &Bound<'py, PyAny>,
450 )
451 -> PyResult<Bound<'py, PyWeakref>>,
452 call_retrievable: bool,
453 ) -> PyResult<()> {
454 let not_call_retrievable = !call_retrievable;
455
456 Python::with_gil(|py| {
457 let class = get_type(py)?;
458 let object = class.call0()?;
459 let reference = create_reference(&object)?;
460
461 assert!(not_call_retrievable || reference.call0()?.is(&object));
462 assert!(reference.upgrade().is_some());
463 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
464
465 drop(object);
466
467 assert!(not_call_retrievable || reference.call0()?.is_none());
468 assert!(reference.upgrade().is_none());
469
470 Ok(())
471 })
472 }
473
474 inner(new_reference, true)?;
475 inner(new_proxy, false)
476 }
477 }
478
479 // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
480 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
481 mod pyo3_pyclass {
482 use super::*;
483 use crate::{pyclass, Py};
484
485 #[pyclass(weakref, crate = "crate")]
486 struct WeakrefablePyClass {}
487
488 #[test]
489 fn test_weakref_upgrade_as() -> PyResult<()> {
490 fn inner(
491 create_reference: impl for<'py> FnOnce(
492 &Bound<'py, PyAny>,
493 )
494 -> PyResult<Bound<'py, PyWeakref>>,
495 ) -> PyResult<()> {
496 Python::with_gil(|py| {
497 let object = Py::new(py, WeakrefablePyClass {})?;
498 let reference = create_reference(object.bind(py))?;
499
500 {
501 let obj = reference.upgrade_as::<WeakrefablePyClass>();
502
503 assert!(obj.is_ok());
504 let obj = obj.unwrap();
505
506 assert!(obj.is_some());
507 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
508 }
509
510 drop(object);
511
512 {
513 let obj = reference.upgrade_as::<WeakrefablePyClass>();
514
515 assert!(obj.is_ok());
516 let obj = obj.unwrap();
517
518 assert!(obj.is_none());
519 }
520
521 Ok(())
522 })
523 }
524
525 inner(new_reference)?;
526 inner(new_proxy)
527 }
528
529 #[test]
530 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
531 fn inner(
532 create_reference: impl for<'py> FnOnce(
533 &Bound<'py, PyAny>,
534 )
535 -> PyResult<Bound<'py, PyWeakref>>,
536 ) -> PyResult<()> {
537 Python::with_gil(|py| {
538 let object = Py::new(py, WeakrefablePyClass {})?;
539 let reference = create_reference(object.bind(py))?;
540
541 {
542 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
543
544 assert!(obj.is_some());
545 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
546 }
547
548 drop(object);
549
550 {
551 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
552
553 assert!(obj.is_none());
554 }
555
556 Ok(())
557 })
558 }
559
560 inner(new_reference)?;
561 inner(new_proxy)
562 }
563
564 #[test]
565 fn test_weakref_upgrade() -> PyResult<()> {
566 fn inner(
567 create_reference: impl for<'py> FnOnce(
568 &Bound<'py, PyAny>,
569 )
570 -> PyResult<Bound<'py, PyWeakref>>,
571 call_retrievable: bool,
572 ) -> PyResult<()> {
573 let not_call_retrievable = !call_retrievable;
574
575 Python::with_gil(|py| {
576 let object = Py::new(py, WeakrefablePyClass {})?;
577 let reference = create_reference(object.bind(py))?;
578
579 assert!(not_call_retrievable || reference.call0()?.is(&object));
580 assert!(reference.upgrade().is_some());
581 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
582
583 drop(object);
584
585 assert!(not_call_retrievable || reference.call0()?.is_none());
586 assert!(reference.upgrade().is_none());
587
588 Ok(())
589 })
590 }
591
592 inner(new_reference, true)?;
593 inner(new_proxy, false)
594 }
595 }
596}