1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::py_result_ext::PyResultExt;
4use crate::type_object::PyTypeCheck;
5use crate::types::any::PyAny;
6use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt};
7
8use super::PyWeakrefMethods;
9
10/// Represents any Python `weakref` Proxy type.
11///
12/// In Python this is created by calling `weakref.proxy`.
13/// This is either a `weakref.ProxyType` or a `weakref.CallableProxyType` (`weakref.ProxyTypes`).
14#[repr(transparent)]
15pub struct PyWeakrefProxy(PyAny);
16
17pyobject_native_type_named!(PyWeakrefProxy);
18
19// TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers. And it is 2 distinct types
20// #[cfg(not(Py_LIMITED_API))]
21// pyobject_native_type_sized!(PyWeakrefProxy, ffi::PyWeakReference);
22
23impl PyTypeCheck for PyWeakrefProxy {
24 const NAME: &'static str = "weakref.ProxyTypes";
25
26 fn type_check(object: &Bound<'_, PyAny>) -> bool {
27 unsafe { ffi::PyWeakref_CheckProxy(op:object.as_ptr()) > 0 }
28 }
29}
30
31/// TODO: UPDATE DOCS
32impl PyWeakrefProxy {
33 /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object.
34 ///
35 /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag).
36 ///
37 /// # Examples
38 #[cfg_attr(
39 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
40 doc = "```rust,ignore"
41 )]
42 #[cfg_attr(
43 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
44 doc = "```rust"
45 )]
46 /// use pyo3::prelude::*;
47 /// use pyo3::types::PyWeakrefProxy;
48 ///
49 /// #[pyclass(weakref)]
50 /// struct Foo { /* fields omitted */ }
51 ///
52 /// # fn main() -> PyResult<()> {
53 /// Python::with_gil(|py| {
54 /// let foo = Bound::new(py, Foo {})?;
55 /// let weakref = PyWeakrefProxy::new(&foo)?;
56 /// assert!(
57 /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
58 /// weakref.upgrade()
59 /// .map_or(false, |obj| obj.is(&foo))
60 /// );
61 ///
62 /// let weakref2 = PyWeakrefProxy::new(&foo)?;
63 /// assert!(weakref.is(&weakref2));
64 ///
65 /// drop(foo);
66 ///
67 /// assert!(weakref.upgrade().is_none());
68 /// Ok(())
69 /// })
70 /// # }
71 /// ```
72 #[inline]
73 pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefProxy>> {
74 unsafe {
75 Bound::from_owned_ptr_or_err(
76 object.py(),
77 ffi::PyWeakref_NewProxy(object.as_ptr(), ffi::Py_None()),
78 )
79 .downcast_into_unchecked()
80 }
81 }
82
83 /// Deprecated name for [`PyWeakrefProxy::new`].
84 #[deprecated(since = "0.23.0", note = "renamed to `PyWeakrefProxy::new`")]
85 #[inline]
86 pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefProxy>> {
87 Self::new(object)
88 }
89
90 /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object with a callback.
91 ///
92 /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag) or if the `callback` is not callable or None.
93 ///
94 /// # Examples
95 #[cfg_attr(
96 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
97 doc = "```rust,ignore"
98 )]
99 #[cfg_attr(
100 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
101 doc = "```rust"
102 )]
103 /// use pyo3::prelude::*;
104 /// use pyo3::types::PyWeakrefProxy;
105 /// use pyo3::ffi::c_str;
106 ///
107 /// #[pyclass(weakref)]
108 /// struct Foo { /* fields omitted */ }
109 ///
110 /// #[pyfunction]
111 /// fn callback(wref: Bound<'_, PyWeakrefProxy>) -> PyResult<()> {
112 /// let py = wref.py();
113 /// assert!(wref.upgrade_as::<Foo>()?.is_none());
114 /// py.run(c_str!("counter = 1"), None, None)
115 /// }
116 ///
117 /// # fn main() -> PyResult<()> {
118 /// Python::with_gil(|py| {
119 /// py.run(c_str!("counter = 0"), None, None)?;
120 /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
121 /// let foo = Bound::new(py, Foo{})?;
122 ///
123 /// // This is fine.
124 /// let weakref = PyWeakrefProxy::new_with(&foo, py.None())?;
125 /// assert!(weakref.upgrade_as::<Foo>()?.is_some());
126 /// assert!(
127 /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
128 /// weakref.upgrade()
129 /// .map_or(false, |obj| obj.is(&foo))
130 /// );
131 /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
132 ///
133 /// let weakref2 = PyWeakrefProxy::new_with(&foo, wrap_pyfunction!(callback, py)?)?;
134 /// assert!(!weakref.is(&weakref2)); // Not the same weakref
135 /// assert!(weakref.eq(&weakref2)?); // But Equal, since they point to the same object
136 ///
137 /// drop(foo);
138 ///
139 /// assert!(weakref.upgrade_as::<Foo>()?.is_none());
140 /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 1);
141 /// Ok(())
142 /// })
143 /// # }
144 /// ```
145 #[inline]
146 pub fn new_with<'py, C>(
147 object: &Bound<'py, PyAny>,
148 callback: C,
149 ) -> PyResult<Bound<'py, PyWeakrefProxy>>
150 where
151 C: IntoPyObject<'py>,
152 {
153 fn inner<'py>(
154 object: &Bound<'py, PyAny>,
155 callback: Borrowed<'_, 'py, PyAny>,
156 ) -> PyResult<Bound<'py, PyWeakrefProxy>> {
157 unsafe {
158 Bound::from_owned_ptr_or_err(
159 object.py(),
160 ffi::PyWeakref_NewProxy(object.as_ptr(), callback.as_ptr()),
161 )
162 .downcast_into_unchecked()
163 }
164 }
165
166 let py = object.py();
167 inner(
168 object,
169 callback
170 .into_pyobject_or_pyerr(py)?
171 .into_any()
172 .as_borrowed(),
173 )
174 }
175
176 /// Deprecated name for [`PyWeakrefProxy::new_with`].
177 #[deprecated(since = "0.23.0", note = "renamed to `PyWeakrefProxy::new_with`")]
178 #[allow(deprecated)]
179 #[inline]
180 pub fn new_bound_with<'py, C>(
181 object: &Bound<'py, PyAny>,
182 callback: C,
183 ) -> PyResult<Bound<'py, PyWeakrefProxy>>
184 where
185 C: crate::ToPyObject,
186 {
187 Self::new_with(object, callback.to_object(object.py()))
188 }
189}
190
191impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefProxy> {
192 fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
193 let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
194 match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
195 std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ProxyType' (or `weakref.CallableProxyType`) instance should be valid (non-null and actually a weakref reference)"),
196 0 => None,
197 1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use crate::exceptions::{PyAttributeError, PyReferenceError, PyTypeError};
205 use crate::types::any::{PyAny, PyAnyMethods};
206 use crate::types::weakref::{PyWeakrefMethods, PyWeakrefProxy};
207 use crate::{Bound, PyResult, Python};
208
209 #[cfg(all(Py_3_13, not(Py_LIMITED_API)))]
210 const DEADREF_FIX: Option<&str> = None;
211 #[cfg(all(not(Py_3_13), not(Py_LIMITED_API)))]
212 const DEADREF_FIX: Option<&str> = Some("NoneType");
213
214 #[cfg(not(Py_LIMITED_API))]
215 fn check_repr(
216 reference: &Bound<'_, PyWeakrefProxy>,
217 object: &Bound<'_, PyAny>,
218 class: Option<&str>,
219 ) -> PyResult<()> {
220 let repr = reference.repr()?.to_string();
221
222 #[cfg(Py_3_13)]
223 let (first_part, second_part) = repr.split_once(';').unwrap();
224 #[cfg(not(Py_3_13))]
225 let (first_part, second_part) = repr.split_once(" to ").unwrap();
226
227 {
228 let (msg, addr) = first_part.split_once("0x").unwrap();
229
230 assert_eq!(msg, "<weakproxy at ");
231 assert!(addr
232 .to_lowercase()
233 .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
234 }
235
236 if let Some(class) = class.or(DEADREF_FIX) {
237 let (msg, addr) = second_part.split_once("0x").unwrap();
238
239 // Avoids not succeeding at unreliable quotation (Python 3.13-dev adds ' around classname without documenting)
240 #[cfg(Py_3_13)]
241 assert!(msg.starts_with(" to '"));
242 assert!(msg.contains(class));
243 assert!(msg.ends_with(" at "));
244
245 assert!(addr
246 .to_lowercase()
247 .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
248 } else {
249 assert!(second_part.contains("dead"));
250 }
251
252 Ok(())
253 }
254
255 mod proxy {
256 use super::*;
257
258 #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
259 const CLASS_NAME: &str = "'weakref.ProxyType'";
260 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
261 const CLASS_NAME: &str = "'weakproxy'";
262
263 mod python_class {
264 use super::*;
265 use crate::ffi;
266 use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType};
267 use std::ptr;
268
269 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
270 let globals = PyDict::new(py);
271 py.run(ffi::c_str!("class A:\n pass\n"), Some(&globals), None)?;
272 py.eval(ffi::c_str!("A"), Some(&globals), None)
273 .downcast_into::<PyType>()
274 }
275
276 #[test]
277 fn test_weakref_proxy_behavior() -> PyResult<()> {
278 Python::with_gil(|py| {
279 let class = get_type(py)?;
280 let object = class.call0()?;
281 let reference = PyWeakrefProxy::new(&object)?;
282
283 assert!(!reference.is(&object));
284 assert!(reference.upgrade().unwrap().is(&object));
285
286 #[cfg(not(Py_LIMITED_API))]
287 assert_eq!(
288 reference.get_type().to_string(),
289 format!("<class {}>", CLASS_NAME)
290 );
291
292 assert_eq!(reference.getattr("__class__")?.to_string(), "<class 'A'>");
293 #[cfg(not(Py_LIMITED_API))]
294 check_repr(&reference, &object, Some("A"))?;
295
296 assert!(reference
297 .getattr("__callback__")
298 .err()
299 .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
300
301 assert!(reference.call0().err().map_or(false, |err| {
302 let result = err.is_instance_of::<PyTypeError>(py);
303 #[cfg(not(Py_LIMITED_API))]
304 let result = result
305 & (err.value(py).to_string()
306 == format!("{} object is not callable", CLASS_NAME));
307 result
308 }));
309
310 drop(object);
311
312 assert!(reference.upgrade().is_none());
313 assert!(reference
314 .getattr("__class__")
315 .err()
316 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
317 #[cfg(not(Py_LIMITED_API))]
318 check_repr(&reference, py.None().bind(py), None)?;
319
320 assert!(reference
321 .getattr("__callback__")
322 .err()
323 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
324
325 assert!(reference.call0().err().map_or(false, |err| {
326 let result = err.is_instance_of::<PyTypeError>(py);
327 #[cfg(not(Py_LIMITED_API))]
328 let result = result
329 & (err.value(py).to_string()
330 == format!("{} object is not callable", CLASS_NAME));
331 result
332 }));
333
334 Ok(())
335 })
336 }
337
338 #[test]
339 fn test_weakref_upgrade_as() -> PyResult<()> {
340 Python::with_gil(|py| {
341 let class = get_type(py)?;
342 let object = class.call0()?;
343 let reference = PyWeakrefProxy::new(&object)?;
344
345 {
346 // This test is a bit weird but ok.
347 let obj = reference.upgrade_as::<PyAny>();
348
349 assert!(obj.is_ok());
350 let obj = obj.unwrap();
351
352 assert!(obj.is_some());
353 assert!(
354 obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
355 && obj.is_exact_instance(&class))
356 );
357 }
358
359 drop(object);
360
361 {
362 // This test is a bit weird but ok.
363 let obj = reference.upgrade_as::<PyAny>();
364
365 assert!(obj.is_ok());
366 let obj = obj.unwrap();
367
368 assert!(obj.is_none());
369 }
370
371 Ok(())
372 })
373 }
374
375 #[test]
376 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
377 Python::with_gil(|py| {
378 let class = get_type(py)?;
379 let object = class.call0()?;
380 let reference = PyWeakrefProxy::new(&object)?;
381
382 {
383 // This test is a bit weird but ok.
384 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
385
386 assert!(obj.is_some());
387 assert!(
388 obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
389 && obj.is_exact_instance(&class))
390 );
391 }
392
393 drop(object);
394
395 {
396 // This test is a bit weird but ok.
397 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
398
399 assert!(obj.is_none());
400 }
401
402 Ok(())
403 })
404 }
405
406 #[test]
407 fn test_weakref_upgrade() -> PyResult<()> {
408 Python::with_gil(|py| {
409 let class = get_type(py)?;
410 let object = class.call0()?;
411 let reference = PyWeakrefProxy::new(&object)?;
412
413 assert!(reference.upgrade().is_some());
414 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
415
416 drop(object);
417
418 assert!(reference.upgrade().is_none());
419
420 Ok(())
421 })
422 }
423
424 #[test]
425 fn test_weakref_get_object() -> PyResult<()> {
426 Python::with_gil(|py| {
427 let class = get_type(py)?;
428 let object = class.call0()?;
429 let reference = PyWeakrefProxy::new(&object)?;
430
431 assert!(reference.upgrade().unwrap().is(&object));
432
433 drop(object);
434
435 assert!(reference.upgrade().is_none());
436
437 Ok(())
438 })
439 }
440 }
441
442 // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
443 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
444 mod pyo3_pyclass {
445 use super::*;
446 use crate::{pyclass, Py};
447 use std::ptr;
448
449 #[pyclass(weakref, crate = "crate")]
450 struct WeakrefablePyClass {}
451
452 #[test]
453 fn test_weakref_proxy_behavior() -> PyResult<()> {
454 Python::with_gil(|py| {
455 let object: Bound<'_, WeakrefablePyClass> =
456 Bound::new(py, WeakrefablePyClass {})?;
457 let reference = PyWeakrefProxy::new(&object)?;
458
459 assert!(!reference.is(&object));
460 assert!(reference.upgrade().unwrap().is(&object));
461 #[cfg(not(Py_LIMITED_API))]
462 assert_eq!(
463 reference.get_type().to_string(),
464 format!("<class {}>", CLASS_NAME)
465 );
466
467 assert_eq!(
468 reference.getattr("__class__")?.to_string(),
469 "<class 'builtins.WeakrefablePyClass'>"
470 );
471 #[cfg(not(Py_LIMITED_API))]
472 check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?;
473
474 assert!(reference
475 .getattr("__callback__")
476 .err()
477 .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
478
479 assert!(reference.call0().err().map_or(false, |err| {
480 let result = err.is_instance_of::<PyTypeError>(py);
481 #[cfg(not(Py_LIMITED_API))]
482 let result = result
483 & (err.value(py).to_string()
484 == format!("{} object is not callable", CLASS_NAME));
485 result
486 }));
487
488 drop(object);
489
490 assert!(reference.upgrade().is_none());
491 assert!(reference
492 .getattr("__class__")
493 .err()
494 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
495 #[cfg(not(Py_LIMITED_API))]
496 check_repr(&reference, py.None().bind(py), None)?;
497
498 assert!(reference
499 .getattr("__callback__")
500 .err()
501 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
502
503 assert!(reference.call0().err().map_or(false, |err| {
504 let result = err.is_instance_of::<PyTypeError>(py);
505 #[cfg(not(Py_LIMITED_API))]
506 let result = result
507 & (err.value(py).to_string()
508 == format!("{} object is not callable", CLASS_NAME));
509 result
510 }));
511
512 Ok(())
513 })
514 }
515
516 #[test]
517 fn test_weakref_upgrade_as() -> PyResult<()> {
518 Python::with_gil(|py| {
519 let object = Py::new(py, WeakrefablePyClass {})?;
520 let reference = PyWeakrefProxy::new(object.bind(py))?;
521
522 {
523 let obj = reference.upgrade_as::<WeakrefablePyClass>();
524
525 assert!(obj.is_ok());
526 let obj = obj.unwrap();
527
528 assert!(obj.is_some());
529 assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
530 }
531
532 drop(object);
533
534 {
535 let obj = reference.upgrade_as::<WeakrefablePyClass>();
536
537 assert!(obj.is_ok());
538 let obj = obj.unwrap();
539
540 assert!(obj.is_none());
541 }
542
543 Ok(())
544 })
545 }
546
547 #[test]
548 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
549 Python::with_gil(|py| {
550 let object = Py::new(py, WeakrefablePyClass {})?;
551 let reference = PyWeakrefProxy::new(object.bind(py))?;
552
553 {
554 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
555
556 assert!(obj.is_some());
557 assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
558 }
559
560 drop(object);
561
562 {
563 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
564
565 assert!(obj.is_none());
566 }
567
568 Ok(())
569 })
570 }
571
572 #[test]
573 fn test_weakref_upgrade() -> PyResult<()> {
574 Python::with_gil(|py| {
575 let object = Py::new(py, WeakrefablePyClass {})?;
576 let reference = PyWeakrefProxy::new(object.bind(py))?;
577
578 assert!(reference.upgrade().is_some());
579 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
580
581 drop(object);
582
583 assert!(reference.upgrade().is_none());
584
585 Ok(())
586 })
587 }
588
589 #[test]
590 #[allow(deprecated)]
591 fn test_weakref_get_object() -> PyResult<()> {
592 Python::with_gil(|py| {
593 let object = Py::new(py, WeakrefablePyClass {})?;
594 let reference = PyWeakrefProxy::new(object.bind(py))?;
595
596 assert!(reference.get_object().is(&object));
597
598 drop(object);
599
600 assert!(reference.get_object().is_none());
601
602 Ok(())
603 })
604 }
605 }
606 }
607
608 mod callable_proxy {
609 use super::*;
610
611 #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
612 const CLASS_NAME: &str = "<class 'weakref.CallableProxyType'>";
613 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
614 const CLASS_NAME: &str = "<class 'weakcallableproxy'>";
615
616 mod python_class {
617 use super::*;
618 use crate::ffi;
619 use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType};
620 use std::ptr;
621
622 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
623 let globals = PyDict::new(py);
624 py.run(
625 ffi::c_str!("class A:\n def __call__(self):\n return 'This class is callable!'\n"),
626 Some(&globals),
627 None,
628 )?;
629 py.eval(ffi::c_str!("A"), Some(&globals), None)
630 .downcast_into::<PyType>()
631 }
632
633 #[test]
634 fn test_weakref_proxy_behavior() -> PyResult<()> {
635 Python::with_gil(|py| {
636 let class = get_type(py)?;
637 let object = class.call0()?;
638 let reference = PyWeakrefProxy::new(&object)?;
639
640 assert!(!reference.is(&object));
641 assert!(reference.upgrade().unwrap().is(&object));
642 #[cfg(not(Py_LIMITED_API))]
643 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
644
645 assert_eq!(reference.getattr("__class__")?.to_string(), "<class 'A'>");
646 #[cfg(not(Py_LIMITED_API))]
647 check_repr(&reference, &object, Some("A"))?;
648
649 assert!(reference
650 .getattr("__callback__")
651 .err()
652 .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
653
654 assert_eq!(reference.call0()?.to_string(), "This class is callable!");
655
656 drop(object);
657
658 assert!(reference.upgrade().is_none());
659 assert!(reference
660 .getattr("__class__")
661 .err()
662 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
663 #[cfg(not(Py_LIMITED_API))]
664 check_repr(&reference, py.None().bind(py), None)?;
665
666 assert!(reference
667 .getattr("__callback__")
668 .err()
669 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
670
671 assert!(reference
672 .call0()
673 .err()
674 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)
675 & (err.value(py).to_string()
676 == "weakly-referenced object no longer exists")));
677
678 Ok(())
679 })
680 }
681
682 #[test]
683 fn test_weakref_upgrade_as() -> PyResult<()> {
684 Python::with_gil(|py| {
685 let class = get_type(py)?;
686 let object = class.call0()?;
687 let reference = PyWeakrefProxy::new(&object)?;
688
689 {
690 // This test is a bit weird but ok.
691 let obj = reference.upgrade_as::<PyAny>();
692
693 assert!(obj.is_ok());
694 let obj = obj.unwrap();
695
696 assert!(obj.is_some());
697 assert!(
698 obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
699 && obj.is_exact_instance(&class))
700 );
701 }
702
703 drop(object);
704
705 {
706 // This test is a bit weird but ok.
707 let obj = reference.upgrade_as::<PyAny>();
708
709 assert!(obj.is_ok());
710 let obj = obj.unwrap();
711
712 assert!(obj.is_none());
713 }
714
715 Ok(())
716 })
717 }
718
719 #[test]
720 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
721 Python::with_gil(|py| {
722 let class = get_type(py)?;
723 let object = class.call0()?;
724 let reference = PyWeakrefProxy::new(&object)?;
725
726 {
727 // This test is a bit weird but ok.
728 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
729
730 assert!(obj.is_some());
731 assert!(
732 obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
733 && obj.is_exact_instance(&class))
734 );
735 }
736
737 drop(object);
738
739 {
740 // This test is a bit weird but ok.
741 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
742
743 assert!(obj.is_none());
744 }
745
746 Ok(())
747 })
748 }
749
750 #[test]
751 fn test_weakref_upgrade() -> PyResult<()> {
752 Python::with_gil(|py| {
753 let class = get_type(py)?;
754 let object = class.call0()?;
755 let reference = PyWeakrefProxy::new(&object)?;
756
757 assert!(reference.upgrade().is_some());
758 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
759
760 drop(object);
761
762 assert!(reference.upgrade().is_none());
763
764 Ok(())
765 })
766 }
767
768 #[test]
769 #[allow(deprecated)]
770 fn test_weakref_get_object() -> PyResult<()> {
771 Python::with_gil(|py| {
772 let class = get_type(py)?;
773 let object = class.call0()?;
774 let reference = PyWeakrefProxy::new(&object)?;
775
776 assert!(reference.get_object().is(&object));
777
778 drop(object);
779
780 assert!(reference.get_object().is_none());
781
782 Ok(())
783 })
784 }
785 }
786
787 // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
788 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
789 mod pyo3_pyclass {
790 use super::*;
791 use crate::{pyclass, pymethods, Py};
792 use std::ptr;
793
794 #[pyclass(weakref, crate = "crate")]
795 struct WeakrefablePyClass {}
796
797 #[pymethods(crate = "crate")]
798 impl WeakrefablePyClass {
799 fn __call__(&self) -> &str {
800 "This class is callable!"
801 }
802 }
803
804 #[test]
805 fn test_weakref_proxy_behavior() -> PyResult<()> {
806 Python::with_gil(|py| {
807 let object: Bound<'_, WeakrefablePyClass> =
808 Bound::new(py, WeakrefablePyClass {})?;
809 let reference = PyWeakrefProxy::new(&object)?;
810
811 assert!(!reference.is(&object));
812 assert!(reference.upgrade().unwrap().is(&object));
813 #[cfg(not(Py_LIMITED_API))]
814 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
815
816 assert_eq!(
817 reference.getattr("__class__")?.to_string(),
818 "<class 'builtins.WeakrefablePyClass'>"
819 );
820 #[cfg(not(Py_LIMITED_API))]
821 check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?;
822
823 assert!(reference
824 .getattr("__callback__")
825 .err()
826 .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
827
828 assert_eq!(reference.call0()?.to_string(), "This class is callable!");
829
830 drop(object);
831
832 assert!(reference.upgrade().is_none());
833 assert!(reference
834 .getattr("__class__")
835 .err()
836 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
837 #[cfg(not(Py_LIMITED_API))]
838 check_repr(&reference, py.None().bind(py), None)?;
839
840 assert!(reference
841 .getattr("__callback__")
842 .err()
843 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
844
845 assert!(reference
846 .call0()
847 .err()
848 .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)
849 & (err.value(py).to_string()
850 == "weakly-referenced object no longer exists")));
851
852 Ok(())
853 })
854 }
855
856 #[test]
857 fn test_weakref_upgrade_as() -> PyResult<()> {
858 Python::with_gil(|py| {
859 let object = Py::new(py, WeakrefablePyClass {})?;
860 let reference = PyWeakrefProxy::new(object.bind(py))?;
861
862 {
863 let obj = reference.upgrade_as::<WeakrefablePyClass>();
864
865 assert!(obj.is_ok());
866 let obj = obj.unwrap();
867
868 assert!(obj.is_some());
869 assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
870 }
871
872 drop(object);
873
874 {
875 let obj = reference.upgrade_as::<WeakrefablePyClass>();
876
877 assert!(obj.is_ok());
878 let obj = obj.unwrap();
879
880 assert!(obj.is_none());
881 }
882
883 Ok(())
884 })
885 }
886
887 #[test]
888 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
889 Python::with_gil(|py| {
890 let object = Py::new(py, WeakrefablePyClass {})?;
891 let reference = PyWeakrefProxy::new(object.bind(py))?;
892
893 {
894 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
895
896 assert!(obj.is_some());
897 assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
898 }
899
900 drop(object);
901
902 {
903 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
904
905 assert!(obj.is_none());
906 }
907
908 Ok(())
909 })
910 }
911
912 #[test]
913 fn test_weakref_upgrade() -> PyResult<()> {
914 Python::with_gil(|py| {
915 let object = Py::new(py, WeakrefablePyClass {})?;
916 let reference = PyWeakrefProxy::new(object.bind(py))?;
917
918 assert!(reference.upgrade().is_some());
919 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
920
921 drop(object);
922
923 assert!(reference.upgrade().is_none());
924
925 Ok(())
926 })
927 }
928
929 #[test]
930 #[allow(deprecated)]
931 fn test_weakref_get_object() -> PyResult<()> {
932 Python::with_gil(|py| {
933 let object = Py::new(py, WeakrefablePyClass {})?;
934 let reference = PyWeakrefProxy::new(object.bind(py))?;
935
936 assert!(reference.get_object().is(&object));
937
938 drop(object);
939
940 assert!(reference.get_object().is_none());
941
942 Ok(())
943 })
944 }
945 }
946 }
947}
948