Skip to main content

pyo3/types/weakref/
proxy.rs

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