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