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