1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::py_result_ext::PyResultExt;
4use crate::types::any::PyAny;
5use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt};
6
7#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
8use crate::type_object::PyTypeCheck;
9
10use super::PyWeakrefMethods;
11
12/// Represents a Python `weakref.ReferenceType`.
13///
14/// In Python this is created by calling `weakref.ref`.
15#[repr(transparent)]
16pub struct PyWeakrefReference(PyAny);
17
18#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))]
19pyobject_subclassable_native_type!(PyWeakrefReference, crate::ffi::PyWeakReference);
20
21#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API)))]
22pyobject_native_type!(
23 PyWeakrefReference,
24 ffi::PyWeakReference,
25 pyobject_native_static_type_object!(ffi::_PyWeakref_RefType),
26 #module=Some("weakref"),
27 #checkfunction=ffi::PyWeakref_CheckRefExact
28);
29
30// When targetting alternative or multiple interpreters, it is better to not use the internal API.
31#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
32pyobject_native_type_named!(PyWeakrefReference);
33
34#[cfg(any(PyPy, GraalPy, Py_LIMITED_API))]
35impl PyTypeCheck for PyWeakrefReference {
36 const NAME: &'static str = "weakref.ReferenceType";
37
38 fn type_check(object: &Bound<'_, PyAny>) -> bool {
39 unsafe { ffi::PyWeakref_CheckRef(op:object.as_ptr()) > 0 }
40 }
41}
42
43impl PyWeakrefReference {
44 /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object.
45 ///
46 /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag).
47 ///
48 /// # Examples
49 #[cfg_attr(
50 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
51 doc = "```rust,ignore"
52 )]
53 #[cfg_attr(
54 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
55 doc = "```rust"
56 )]
57 /// use pyo3::prelude::*;
58 /// use pyo3::types::PyWeakrefReference;
59 ///
60 /// #[pyclass(weakref)]
61 /// struct Foo { /* fields omitted */ }
62 ///
63 /// # fn main() -> PyResult<()> {
64 /// Python::with_gil(|py| {
65 /// let foo = Bound::new(py, Foo {})?;
66 /// let weakref = PyWeakrefReference::new(&foo)?;
67 /// assert!(
68 /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
69 /// weakref.upgrade()
70 /// .map_or(false, |obj| obj.is(&foo))
71 /// );
72 ///
73 /// let weakref2 = PyWeakrefReference::new(&foo)?;
74 /// assert!(weakref.is(&weakref2));
75 ///
76 /// drop(foo);
77 ///
78 /// assert!(weakref.upgrade().is_none());
79 /// Ok(())
80 /// })
81 /// # }
82 /// ```
83 pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
84 unsafe {
85 Bound::from_owned_ptr_or_err(
86 object.py(),
87 ffi::PyWeakref_NewRef(object.as_ptr(), ffi::Py_None()),
88 )
89 .downcast_into_unchecked()
90 }
91 }
92
93 /// Deprecated name for [`PyWeakrefReference::new`].
94 #[deprecated(since = "0.23.0", note = "renamed to `PyWeakrefReference::new`")]
95 #[inline]
96 pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
97 Self::new(object)
98 }
99
100 /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object with a callback.
101 ///
102 /// 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.
103 ///
104 /// # Examples
105 #[cfg_attr(
106 not(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9))))),
107 doc = "```rust,ignore"
108 )]
109 #[cfg_attr(
110 all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))),
111 doc = "```rust"
112 )]
113 /// use pyo3::prelude::*;
114 /// use pyo3::types::PyWeakrefReference;
115 /// use pyo3::ffi::c_str;
116 ///
117 /// #[pyclass(weakref)]
118 /// struct Foo { /* fields omitted */ }
119 ///
120 /// #[pyfunction]
121 /// fn callback(wref: Bound<'_, PyWeakrefReference>) -> PyResult<()> {
122 /// let py = wref.py();
123 /// assert!(wref.upgrade_as::<Foo>()?.is_none());
124 /// py.run(c_str!("counter = 1"), None, None)
125 /// }
126 ///
127 /// # fn main() -> PyResult<()> {
128 /// Python::with_gil(|py| {
129 /// py.run(c_str!("counter = 0"), None, None)?;
130 /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
131 /// let foo = Bound::new(py, Foo{})?;
132 ///
133 /// // This is fine.
134 /// let weakref = PyWeakrefReference::new_with(&foo, py.None())?;
135 /// assert!(weakref.upgrade_as::<Foo>()?.is_some());
136 /// assert!(
137 /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>`
138 /// weakref.upgrade()
139 /// .map_or(false, |obj| obj.is(&foo))
140 /// );
141 /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 0);
142 ///
143 /// let weakref2 = PyWeakrefReference::new_with(&foo, wrap_pyfunction!(callback, py)?)?;
144 /// assert!(!weakref.is(&weakref2)); // Not the same weakref
145 /// assert!(weakref.eq(&weakref2)?); // But Equal, since they point to the same object
146 ///
147 /// drop(foo);
148 ///
149 /// assert!(weakref.upgrade_as::<Foo>()?.is_none());
150 /// assert_eq!(py.eval(c_str!("counter"), None, None)?.extract::<u32>()?, 1);
151 /// Ok(())
152 /// })
153 /// # }
154 /// ```
155 pub fn new_with<'py, C>(
156 object: &Bound<'py, PyAny>,
157 callback: C,
158 ) -> PyResult<Bound<'py, PyWeakrefReference>>
159 where
160 C: IntoPyObject<'py>,
161 {
162 fn inner<'py>(
163 object: &Bound<'py, PyAny>,
164 callback: Borrowed<'_, 'py, PyAny>,
165 ) -> PyResult<Bound<'py, PyWeakrefReference>> {
166 unsafe {
167 Bound::from_owned_ptr_or_err(
168 object.py(),
169 ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()),
170 )
171 .downcast_into_unchecked()
172 }
173 }
174
175 let py = object.py();
176 inner(
177 object,
178 callback
179 .into_pyobject_or_pyerr(py)?
180 .into_any()
181 .as_borrowed(),
182 )
183 }
184
185 /// Deprecated name for [`PyWeakrefReference::new_with`].
186 #[deprecated(since = "0.23.0", note = "renamed to `PyWeakrefReference::new_with`")]
187 #[allow(deprecated)]
188 #[inline]
189 pub fn new_bound_with<'py, C>(
190 object: &Bound<'py, PyAny>,
191 callback: C,
192 ) -> PyResult<Bound<'py, PyWeakrefReference>>
193 where
194 C: crate::ToPyObject,
195 {
196 Self::new_with(object, callback.to_object(object.py()))
197 }
198}
199
200impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> {
201 fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
202 let mut obj: *mut ffi::PyObject = std::ptr::null_mut();
203 match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
204 std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)"),
205 0 => None,
206 1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use crate::types::any::{PyAny, PyAnyMethods};
214 use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference};
215 use crate::{Bound, PyResult, Python};
216
217 #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
218 const CLASS_NAME: &str = "<class 'weakref.ReferenceType'>";
219 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
220 const CLASS_NAME: &str = "<class 'weakref'>";
221
222 fn check_repr(
223 reference: &Bound<'_, PyWeakrefReference>,
224 object: Option<(&Bound<'_, PyAny>, &str)>,
225 ) -> PyResult<()> {
226 let repr = reference.repr()?.to_string();
227 let (first_part, second_part) = repr.split_once("; ").unwrap();
228
229 {
230 let (msg, addr) = first_part.split_once("0x").unwrap();
231
232 assert_eq!(msg, "<weakref at ");
233 assert!(addr
234 .to_lowercase()
235 .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
236 }
237
238 match object {
239 Some((object, class)) => {
240 let (msg, addr) = second_part.split_once("0x").unwrap();
241
242 // Avoid testing on reprs directly since they the quoting and full path vs class name tends to be changedi undocumented.
243 assert!(msg.starts_with("to '"));
244 assert!(msg.contains(class));
245 assert!(msg.ends_with("' at "));
246
247 assert!(addr
248 .to_lowercase()
249 .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
250 }
251 None => {
252 assert_eq!(second_part, "dead>")
253 }
254 }
255
256 Ok(())
257 }
258
259 mod python_class {
260 use super::*;
261 use crate::ffi;
262 use crate::{py_result_ext::PyResultExt, types::PyType};
263 use std::ptr;
264
265 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
266 py.run(ffi::c_str!("class A:\n pass\n"), None, None)?;
267 py.eval(ffi::c_str!("A"), None, None)
268 .downcast_into::<PyType>()
269 }
270
271 #[test]
272 fn test_weakref_reference_behavior() -> PyResult<()> {
273 Python::with_gil(|py| {
274 let class = get_type(py)?;
275 let object = class.call0()?;
276 let reference = PyWeakrefReference::new(&object)?;
277
278 assert!(!reference.is(&object));
279 assert!(reference.upgrade().unwrap().is(&object));
280
281 #[cfg(not(Py_LIMITED_API))]
282 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
283
284 #[cfg(not(Py_LIMITED_API))]
285 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
286
287 #[cfg(not(Py_LIMITED_API))]
288 check_repr(&reference, Some((object.as_any(), "A")))?;
289
290 assert!(reference
291 .getattr("__callback__")
292 .map_or(false, |result| result.is_none()));
293
294 assert!(reference.call0()?.is(&object));
295
296 drop(object);
297
298 assert!(reference.upgrade().is_none());
299 #[cfg(not(Py_LIMITED_API))]
300 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
301 check_repr(&reference, None)?;
302
303 assert!(reference
304 .getattr("__callback__")
305 .map_or(false, |result| result.is_none()));
306
307 assert!(reference.call0()?.is_none());
308
309 Ok(())
310 })
311 }
312
313 #[test]
314 fn test_weakref_upgrade_as() -> PyResult<()> {
315 Python::with_gil(|py| {
316 let class = get_type(py)?;
317 let object = class.call0()?;
318 let reference = PyWeakrefReference::new(&object)?;
319
320 {
321 // This test is a bit weird but ok.
322 let obj = reference.upgrade_as::<PyAny>();
323
324 assert!(obj.is_ok());
325 let obj = obj.unwrap();
326
327 assert!(obj.is_some());
328 assert!(
329 obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
330 && obj.is_exact_instance(&class))
331 );
332 }
333
334 drop(object);
335
336 {
337 // This test is a bit weird but ok.
338 let obj = reference.upgrade_as::<PyAny>();
339
340 assert!(obj.is_ok());
341 let obj = obj.unwrap();
342
343 assert!(obj.is_none());
344 }
345
346 Ok(())
347 })
348 }
349
350 #[test]
351 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
352 Python::with_gil(|py| {
353 let class = get_type(py)?;
354 let object = class.call0()?;
355 let reference = PyWeakrefReference::new(&object)?;
356
357 {
358 // This test is a bit weird but ok.
359 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
360
361 assert!(obj.is_some());
362 assert!(
363 obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())
364 && obj.is_exact_instance(&class))
365 );
366 }
367
368 drop(object);
369
370 {
371 // This test is a bit weird but ok.
372 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
373
374 assert!(obj.is_none());
375 }
376
377 Ok(())
378 })
379 }
380
381 #[test]
382 fn test_weakref_upgrade() -> PyResult<()> {
383 Python::with_gil(|py| {
384 let class = get_type(py)?;
385 let object = class.call0()?;
386 let reference = PyWeakrefReference::new(&object)?;
387
388 assert!(reference.call0()?.is(&object));
389 assert!(reference.upgrade().is_some());
390 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
391
392 drop(object);
393
394 assert!(reference.call0()?.is_none());
395 assert!(reference.upgrade().is_none());
396
397 Ok(())
398 })
399 }
400
401 #[test]
402 #[allow(deprecated)]
403 fn test_weakref_get_object() -> PyResult<()> {
404 Python::with_gil(|py| {
405 let class = get_type(py)?;
406 let object = class.call0()?;
407 let reference = PyWeakrefReference::new(&object)?;
408
409 assert!(reference.call0()?.is(&object));
410 assert!(reference.get_object().is(&object));
411
412 drop(object);
413
414 assert!(reference.call0()?.is(&reference.get_object()));
415 assert!(reference.call0()?.is_none());
416 assert!(reference.get_object().is_none());
417
418 Ok(())
419 })
420 }
421 }
422
423 // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable.
424 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
425 mod pyo3_pyclass {
426 use super::*;
427 use crate::{pyclass, Py};
428 use std::ptr;
429
430 #[pyclass(weakref, crate = "crate")]
431 struct WeakrefablePyClass {}
432
433 #[test]
434 fn test_weakref_reference_behavior() -> PyResult<()> {
435 Python::with_gil(|py| {
436 let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
437 let reference = PyWeakrefReference::new(&object)?;
438
439 assert!(!reference.is(&object));
440 assert!(reference.upgrade().unwrap().is(&object));
441 #[cfg(not(Py_LIMITED_API))]
442 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
443
444 #[cfg(not(Py_LIMITED_API))]
445 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
446 #[cfg(not(Py_LIMITED_API))]
447 check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
448
449 assert!(reference
450 .getattr("__callback__")
451 .map_or(false, |result| result.is_none()));
452
453 assert!(reference.call0()?.is(&object));
454
455 drop(object);
456
457 assert!(reference.upgrade().is_none());
458 #[cfg(not(Py_LIMITED_API))]
459 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
460 check_repr(&reference, None)?;
461
462 assert!(reference
463 .getattr("__callback__")
464 .map_or(false, |result| result.is_none()));
465
466 assert!(reference.call0()?.is_none());
467
468 Ok(())
469 })
470 }
471
472 #[test]
473 fn test_weakref_upgrade_as() -> PyResult<()> {
474 Python::with_gil(|py| {
475 let object = Py::new(py, WeakrefablePyClass {})?;
476 let reference = PyWeakrefReference::new(object.bind(py))?;
477
478 {
479 let obj = reference.upgrade_as::<WeakrefablePyClass>();
480
481 assert!(obj.is_ok());
482 let obj = obj.unwrap();
483
484 assert!(obj.is_some());
485 assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
486 }
487
488 drop(object);
489
490 {
491 let obj = reference.upgrade_as::<WeakrefablePyClass>();
492
493 assert!(obj.is_ok());
494 let obj = obj.unwrap();
495
496 assert!(obj.is_none());
497 }
498
499 Ok(())
500 })
501 }
502
503 #[test]
504 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
505 Python::with_gil(|py| {
506 let object = Py::new(py, WeakrefablePyClass {})?;
507 let reference = PyWeakrefReference::new(object.bind(py))?;
508
509 {
510 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
511
512 assert!(obj.is_some());
513 assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
514 }
515
516 drop(object);
517
518 {
519 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
520
521 assert!(obj.is_none());
522 }
523
524 Ok(())
525 })
526 }
527
528 #[test]
529 fn test_weakref_upgrade() -> PyResult<()> {
530 Python::with_gil(|py| {
531 let object = Py::new(py, WeakrefablePyClass {})?;
532 let reference = PyWeakrefReference::new(object.bind(py))?;
533
534 assert!(reference.call0()?.is(&object));
535 assert!(reference.upgrade().is_some());
536 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
537
538 drop(object);
539
540 assert!(reference.call0()?.is_none());
541 assert!(reference.upgrade().is_none());
542
543 Ok(())
544 })
545 }
546
547 #[test]
548 #[allow(deprecated)]
549 fn test_weakref_get_object() -> PyResult<()> {
550 Python::with_gil(|py| {
551 let object = Py::new(py, WeakrefablePyClass {})?;
552 let reference = PyWeakrefReference::new(object.bind(py))?;
553
554 assert!(reference.call0()?.is(&object));
555 assert!(reference.get_object().is(&object));
556
557 drop(object);
558
559 assert!(reference.call0()?.is(&reference.get_object()));
560 assert!(reference.call0()?.is_none());
561 assert!(reference.get_object().is_none());
562
563 Ok(())
564 })
565 }
566 }
567}
568