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
26    fn type_check(object: &Bound<'_, PyAny>) -> bool {
27        unsafe { ffi::PyWeakref_CheckProxy(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
268            fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
269                let globals = PyDict::new(py);
270                py.run(ffi::c_str!("class A:\n    pass\n"), Some(&globals), None)?;
271                py.eval(ffi::c_str!("A"), Some(&globals), None)
272                    .downcast_into::<PyType>()
273            }
274
275            #[test]
276            fn test_weakref_proxy_behavior() -> PyResult<()> {
277                Python::with_gil(|py| {
278                    let class = get_type(py)?;
279                    let object = class.call0()?;
280                    let reference = PyWeakrefProxy::new(&object)?;
281
282                    assert!(!reference.is(&object));
283                    assert!(reference.upgrade().unwrap().is(&object));
284
285                    #[cfg(not(Py_LIMITED_API))]
286                    assert_eq!(
287                        reference.get_type().to_string(),
288                        format!("<class {}>", CLASS_NAME)
289                    );
290
291                    assert_eq!(reference.getattr("__class__")?.to_string(), "<class 'A'>");
292                    #[cfg(not(Py_LIMITED_API))]
293                    check_repr(&reference, &object, Some("A"))?;
294
295                    assert!(reference
296                        .getattr("__callback__")
297                        .err()
298                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
299
300                    assert!(reference.call0().err().map_or(false, |err| {
301                        let result = err.is_instance_of::<PyTypeError>(py);
302                        #[cfg(not(Py_LIMITED_API))]
303                        let result = result
304                            & (err.value(py).to_string()
305                                == format!("{} object is not callable", CLASS_NAME));
306                        result
307                    }));
308
309                    drop(object);
310
311                    assert!(reference.upgrade().is_none());
312                    assert!(reference
313                        .getattr("__class__")
314                        .err()
315                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
316                    #[cfg(not(Py_LIMITED_API))]
317                    check_repr(&reference, py.None().bind(py), None)?;
318
319                    assert!(reference
320                        .getattr("__callback__")
321                        .err()
322                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
323
324                    assert!(reference.call0().err().map_or(false, |err| {
325                        let result = err.is_instance_of::<PyTypeError>(py);
326                        #[cfg(not(Py_LIMITED_API))]
327                        let result = result
328                            & (err.value(py).to_string()
329                                == format!("{} object is not callable", CLASS_NAME));
330                        result
331                    }));
332
333                    Ok(())
334                })
335            }
336
337            #[test]
338            fn test_weakref_upgrade_as() -> PyResult<()> {
339                Python::with_gil(|py| {
340                    let class = get_type(py)?;
341                    let object = class.call0()?;
342                    let reference = PyWeakrefProxy::new(&object)?;
343
344                    {
345                        // This test is a bit weird but ok.
346                        let obj = reference.upgrade_as::<PyAny>();
347
348                        assert!(obj.is_ok());
349                        let obj = obj.unwrap();
350
351                        assert!(obj.is_some());
352                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
353                            && obj.is_exact_instance(&class)));
354                    }
355
356                    drop(object);
357
358                    {
359                        // This test is a bit weird but ok.
360                        let obj = reference.upgrade_as::<PyAny>();
361
362                        assert!(obj.is_ok());
363                        let obj = obj.unwrap();
364
365                        assert!(obj.is_none());
366                    }
367
368                    Ok(())
369                })
370            }
371
372            #[test]
373            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
374                Python::with_gil(|py| {
375                    let class = get_type(py)?;
376                    let object = class.call0()?;
377                    let reference = PyWeakrefProxy::new(&object)?;
378
379                    {
380                        // This test is a bit weird but ok.
381                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
382
383                        assert!(obj.is_some());
384                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
385                            && obj.is_exact_instance(&class)));
386                    }
387
388                    drop(object);
389
390                    {
391                        // This test is a bit weird but ok.
392                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
393
394                        assert!(obj.is_none());
395                    }
396
397                    Ok(())
398                })
399            }
400
401            #[test]
402            fn test_weakref_upgrade() -> PyResult<()> {
403                Python::with_gil(|py| {
404                    let class = get_type(py)?;
405                    let object = class.call0()?;
406                    let reference = PyWeakrefProxy::new(&object)?;
407
408                    assert!(reference.upgrade().is_some());
409                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
410
411                    drop(object);
412
413                    assert!(reference.upgrade().is_none());
414
415                    Ok(())
416                })
417            }
418
419            #[test]
420            fn test_weakref_get_object() -> PyResult<()> {
421                Python::with_gil(|py| {
422                    let class = get_type(py)?;
423                    let object = class.call0()?;
424                    let reference = PyWeakrefProxy::new(&object)?;
425
426                    assert!(reference.upgrade().unwrap().is(&object));
427
428                    drop(object);
429
430                    assert!(reference.upgrade().is_none());
431
432                    Ok(())
433                })
434            }
435        }
436
437        // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
438        #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
439        mod pyo3_pyclass {
440            use super::*;
441            use crate::{pyclass, Py};
442
443            #[pyclass(weakref, crate = "crate")]
444            struct WeakrefablePyClass {}
445
446            #[test]
447            fn test_weakref_proxy_behavior() -> PyResult<()> {
448                Python::with_gil(|py| {
449                    let object: Bound<'_, WeakrefablePyClass> =
450                        Bound::new(py, WeakrefablePyClass {})?;
451                    let reference = PyWeakrefProxy::new(&object)?;
452
453                    assert!(!reference.is(&object));
454                    assert!(reference.upgrade().unwrap().is(&object));
455                    #[cfg(not(Py_LIMITED_API))]
456                    assert_eq!(
457                        reference.get_type().to_string(),
458                        format!("<class {}>", CLASS_NAME)
459                    );
460
461                    assert_eq!(
462                        reference.getattr("__class__")?.to_string(),
463                        "<class 'builtins.WeakrefablePyClass'>"
464                    );
465                    #[cfg(not(Py_LIMITED_API))]
466                    check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?;
467
468                    assert!(reference
469                        .getattr("__callback__")
470                        .err()
471                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
472
473                    assert!(reference.call0().err().map_or(false, |err| {
474                        let result = err.is_instance_of::<PyTypeError>(py);
475                        #[cfg(not(Py_LIMITED_API))]
476                        let result = result
477                            & (err.value(py).to_string()
478                                == format!("{} object is not callable", CLASS_NAME));
479                        result
480                    }));
481
482                    drop(object);
483
484                    assert!(reference.upgrade().is_none());
485                    assert!(reference
486                        .getattr("__class__")
487                        .err()
488                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
489                    #[cfg(not(Py_LIMITED_API))]
490                    check_repr(&reference, py.None().bind(py), None)?;
491
492                    assert!(reference
493                        .getattr("__callback__")
494                        .err()
495                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
496
497                    assert!(reference.call0().err().map_or(false, |err| {
498                        let result = err.is_instance_of::<PyTypeError>(py);
499                        #[cfg(not(Py_LIMITED_API))]
500                        let result = result
501                            & (err.value(py).to_string()
502                                == format!("{} object is not callable", CLASS_NAME));
503                        result
504                    }));
505
506                    Ok(())
507                })
508            }
509
510            #[test]
511            fn test_weakref_upgrade_as() -> PyResult<()> {
512                Python::with_gil(|py| {
513                    let object = Py::new(py, WeakrefablePyClass {})?;
514                    let reference = PyWeakrefProxy::new(object.bind(py))?;
515
516                    {
517                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
518
519                        assert!(obj.is_ok());
520                        let obj = obj.unwrap();
521
522                        assert!(obj.is_some());
523                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
524                    }
525
526                    drop(object);
527
528                    {
529                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
530
531                        assert!(obj.is_ok());
532                        let obj = obj.unwrap();
533
534                        assert!(obj.is_none());
535                    }
536
537                    Ok(())
538                })
539            }
540
541            #[test]
542            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
543                Python::with_gil(|py| {
544                    let object = Py::new(py, WeakrefablePyClass {})?;
545                    let reference = PyWeakrefProxy::new(object.bind(py))?;
546
547                    {
548                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
549
550                        assert!(obj.is_some());
551                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
552                    }
553
554                    drop(object);
555
556                    {
557                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
558
559                        assert!(obj.is_none());
560                    }
561
562                    Ok(())
563                })
564            }
565
566            #[test]
567            fn test_weakref_upgrade() -> PyResult<()> {
568                Python::with_gil(|py| {
569                    let object = Py::new(py, WeakrefablePyClass {})?;
570                    let reference = PyWeakrefProxy::new(object.bind(py))?;
571
572                    assert!(reference.upgrade().is_some());
573                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
574
575                    drop(object);
576
577                    assert!(reference.upgrade().is_none());
578
579                    Ok(())
580                })
581            }
582
583            #[test]
584            #[allow(deprecated)]
585            fn test_weakref_get_object() -> PyResult<()> {
586                Python::with_gil(|py| {
587                    let object = Py::new(py, WeakrefablePyClass {})?;
588                    let reference = PyWeakrefProxy::new(object.bind(py))?;
589
590                    assert!(reference.get_object().is(&object));
591
592                    drop(object);
593
594                    assert!(reference.get_object().is_none());
595
596                    Ok(())
597                })
598            }
599        }
600    }
601
602    mod callable_proxy {
603        use super::*;
604
605        #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
606        const CLASS_NAME: &str = "<class 'weakref.CallableProxyType'>";
607        #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
608        const CLASS_NAME: &str = "<class 'weakcallableproxy'>";
609
610        mod python_class {
611            use super::*;
612            use crate::ffi;
613            use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType};
614
615            fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
616                let globals = PyDict::new(py);
617                py.run(
618                    ffi::c_str!("class A:\n    def __call__(self):\n        return 'This class is callable!'\n"),
619                    Some(&globals),
620                    None,
621                )?;
622                py.eval(ffi::c_str!("A"), Some(&globals), None)
623                    .downcast_into::<PyType>()
624            }
625
626            #[test]
627            fn test_weakref_proxy_behavior() -> PyResult<()> {
628                Python::with_gil(|py| {
629                    let class = get_type(py)?;
630                    let object = class.call0()?;
631                    let reference = PyWeakrefProxy::new(&object)?;
632
633                    assert!(!reference.is(&object));
634                    assert!(reference.upgrade().unwrap().is(&object));
635                    #[cfg(not(Py_LIMITED_API))]
636                    assert_eq!(reference.get_type().to_string(), CLASS_NAME);
637
638                    assert_eq!(reference.getattr("__class__")?.to_string(), "<class 'A'>");
639                    #[cfg(not(Py_LIMITED_API))]
640                    check_repr(&reference, &object, Some("A"))?;
641
642                    assert!(reference
643                        .getattr("__callback__")
644                        .err()
645                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
646
647                    assert_eq!(reference.call0()?.to_string(), "This class is callable!");
648
649                    drop(object);
650
651                    assert!(reference.upgrade().is_none());
652                    assert!(reference
653                        .getattr("__class__")
654                        .err()
655                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
656                    #[cfg(not(Py_LIMITED_API))]
657                    check_repr(&reference, py.None().bind(py), None)?;
658
659                    assert!(reference
660                        .getattr("__callback__")
661                        .err()
662                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
663
664                    assert!(reference
665                        .call0()
666                        .err()
667                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)
668                            & (err.value(py).to_string()
669                                == "weakly-referenced object no longer exists")));
670
671                    Ok(())
672                })
673            }
674
675            #[test]
676            fn test_weakref_upgrade_as() -> PyResult<()> {
677                Python::with_gil(|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 = reference.upgrade_as::<PyAny>();
685
686                        assert!(obj.is_ok());
687                        let obj = obj.unwrap();
688
689                        assert!(obj.is_some());
690                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
691                            && obj.is_exact_instance(&class)));
692                    }
693
694                    drop(object);
695
696                    {
697                        // This test is a bit weird but ok.
698                        let obj = reference.upgrade_as::<PyAny>();
699
700                        assert!(obj.is_ok());
701                        let obj = obj.unwrap();
702
703                        assert!(obj.is_none());
704                    }
705
706                    Ok(())
707                })
708            }
709
710            #[test]
711            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
712                Python::with_gil(|py| {
713                    let class = get_type(py)?;
714                    let object = class.call0()?;
715                    let reference = PyWeakrefProxy::new(&object)?;
716
717                    {
718                        // This test is a bit weird but ok.
719                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
720
721                        assert!(obj.is_some());
722                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
723                            && obj.is_exact_instance(&class)));
724                    }
725
726                    drop(object);
727
728                    {
729                        // This test is a bit weird but ok.
730                        let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
731
732                        assert!(obj.is_none());
733                    }
734
735                    Ok(())
736                })
737            }
738
739            #[test]
740            fn test_weakref_upgrade() -> PyResult<()> {
741                Python::with_gil(|py| {
742                    let class = get_type(py)?;
743                    let object = class.call0()?;
744                    let reference = PyWeakrefProxy::new(&object)?;
745
746                    assert!(reference.upgrade().is_some());
747                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
748
749                    drop(object);
750
751                    assert!(reference.upgrade().is_none());
752
753                    Ok(())
754                })
755            }
756
757            #[test]
758            #[allow(deprecated)]
759            fn test_weakref_get_object() -> PyResult<()> {
760                Python::with_gil(|py| {
761                    let class = get_type(py)?;
762                    let object = class.call0()?;
763                    let reference = PyWeakrefProxy::new(&object)?;
764
765                    assert!(reference.get_object().is(&object));
766
767                    drop(object);
768
769                    assert!(reference.get_object().is_none());
770
771                    Ok(())
772                })
773            }
774        }
775
776        // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
777        #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
778        mod pyo3_pyclass {
779            use super::*;
780            use crate::{pyclass, pymethods, Py};
781
782            #[pyclass(weakref, crate = "crate")]
783            struct WeakrefablePyClass {}
784
785            #[pymethods(crate = "crate")]
786            impl WeakrefablePyClass {
787                fn __call__(&self) -> &str {
788                    "This class is callable!"
789                }
790            }
791
792            #[test]
793            fn test_weakref_proxy_behavior() -> PyResult<()> {
794                Python::with_gil(|py| {
795                    let object: Bound<'_, WeakrefablePyClass> =
796                        Bound::new(py, WeakrefablePyClass {})?;
797                    let reference = PyWeakrefProxy::new(&object)?;
798
799                    assert!(!reference.is(&object));
800                    assert!(reference.upgrade().unwrap().is(&object));
801                    #[cfg(not(Py_LIMITED_API))]
802                    assert_eq!(reference.get_type().to_string(), CLASS_NAME);
803
804                    assert_eq!(
805                        reference.getattr("__class__")?.to_string(),
806                        "<class 'builtins.WeakrefablePyClass'>"
807                    );
808                    #[cfg(not(Py_LIMITED_API))]
809                    check_repr(&reference, object.as_any(), Some("WeakrefablePyClass"))?;
810
811                    assert!(reference
812                        .getattr("__callback__")
813                        .err()
814                        .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py)));
815
816                    assert_eq!(reference.call0()?.to_string(), "This class is callable!");
817
818                    drop(object);
819
820                    assert!(reference.upgrade().is_none());
821                    assert!(reference
822                        .getattr("__class__")
823                        .err()
824                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
825                    #[cfg(not(Py_LIMITED_API))]
826                    check_repr(&reference, py.None().bind(py), None)?;
827
828                    assert!(reference
829                        .getattr("__callback__")
830                        .err()
831                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)));
832
833                    assert!(reference
834                        .call0()
835                        .err()
836                        .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py)
837                            & (err.value(py).to_string()
838                                == "weakly-referenced object no longer exists")));
839
840                    Ok(())
841                })
842            }
843
844            #[test]
845            fn test_weakref_upgrade_as() -> PyResult<()> {
846                Python::with_gil(|py| {
847                    let object = Py::new(py, WeakrefablePyClass {})?;
848                    let reference = PyWeakrefProxy::new(object.bind(py))?;
849
850                    {
851                        let obj = reference.upgrade_as::<WeakrefablePyClass>();
852
853                        assert!(obj.is_ok());
854                        let obj = obj.unwrap();
855
856                        assert!(obj.is_some());
857                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
858                    }
859
860                    drop(object);
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_none());
869                    }
870
871                    Ok(())
872                })
873            }
874
875            #[test]
876            fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
877                Python::with_gil(|py| {
878                    let object = Py::new(py, WeakrefablePyClass {})?;
879                    let reference = PyWeakrefProxy::new(object.bind(py))?;
880
881                    {
882                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
883
884                        assert!(obj.is_some());
885                        assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
886                    }
887
888                    drop(object);
889
890                    {
891                        let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
892
893                        assert!(obj.is_none());
894                    }
895
896                    Ok(())
897                })
898            }
899
900            #[test]
901            fn test_weakref_upgrade() -> PyResult<()> {
902                Python::with_gil(|py| {
903                    let object = Py::new(py, WeakrefablePyClass {})?;
904                    let reference = PyWeakrefProxy::new(object.bind(py))?;
905
906                    assert!(reference.upgrade().is_some());
907                    assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
908
909                    drop(object);
910
911                    assert!(reference.upgrade().is_none());
912
913                    Ok(())
914                })
915            }
916
917            #[test]
918            #[allow(deprecated)]
919            fn test_weakref_get_object() -> PyResult<()> {
920                Python::with_gil(|py| {
921                    let object = Py::new(py, WeakrefablePyClass {})?;
922                    let reference = PyWeakrefProxy::new(object.bind(py))?;
923
924                    assert!(reference.get_object().is(&object));
925
926                    drop(object);
927
928                    assert!(reference.get_object().is_none());
929
930                    Ok(())
931                })
932            }
933        }
934    }
935}