pyo3/types/weakref/
proxy.rs

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