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