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#[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#[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(object.as_ptr()) > 0 }
40 }
41}
42
43impl PyWeakrefReference {
44 #[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 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(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 #[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 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(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 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
264 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
265 py.run(ffi::c_str!("class A:\n pass\n"), None, None)?;
266 py.eval(ffi::c_str!("A"), None, None)
267 .downcast_into::<PyType>()
268 }
269
270 #[test]
271 fn test_weakref_reference_behavior() -> PyResult<()> {
272 Python::with_gil(|py| {
273 let class = get_type(py)?;
274 let object = class.call0()?;
275 let reference = PyWeakrefReference::new(&object)?;
276
277 assert!(!reference.is(&object));
278 assert!(reference.upgrade().unwrap().is(&object));
279
280 #[cfg(not(Py_LIMITED_API))]
281 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
282
283 #[cfg(not(Py_LIMITED_API))]
284 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
285
286 #[cfg(not(Py_LIMITED_API))]
287 check_repr(&reference, Some((object.as_any(), "A")))?;
288
289 assert!(reference
290 .getattr("__callback__")
291 .map_or(false, |result| result.is_none()));
292
293 assert!(reference.call0()?.is(&object));
294
295 drop(object);
296
297 assert!(reference.upgrade().is_none());
298 #[cfg(not(Py_LIMITED_API))]
299 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
300 check_repr(&reference, None)?;
301
302 assert!(reference
303 .getattr("__callback__")
304 .map_or(false, |result| result.is_none()));
305
306 assert!(reference.call0()?.is_none());
307
308 Ok(())
309 })
310 }
311
312 #[test]
313 fn test_weakref_upgrade_as() -> PyResult<()> {
314 Python::with_gil(|py| {
315 let class = get_type(py)?;
316 let object = class.call0()?;
317 let reference = PyWeakrefReference::new(&object)?;
318
319 {
320 let obj = reference.upgrade_as::<PyAny>();
322
323 assert!(obj.is_ok());
324 let obj = obj.unwrap();
325
326 assert!(obj.is_some());
327 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
328 && obj.is_exact_instance(&class)));
329 }
330
331 drop(object);
332
333 {
334 let obj = reference.upgrade_as::<PyAny>();
336
337 assert!(obj.is_ok());
338 let obj = obj.unwrap();
339
340 assert!(obj.is_none());
341 }
342
343 Ok(())
344 })
345 }
346
347 #[test]
348 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
349 Python::with_gil(|py| {
350 let class = get_type(py)?;
351 let object = class.call0()?;
352 let reference = PyWeakrefReference::new(&object)?;
353
354 {
355 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
357
358 assert!(obj.is_some());
359 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
360 && obj.is_exact_instance(&class)));
361 }
362
363 drop(object);
364
365 {
366 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
368
369 assert!(obj.is_none());
370 }
371
372 Ok(())
373 })
374 }
375
376 #[test]
377 fn test_weakref_upgrade() -> PyResult<()> {
378 Python::with_gil(|py| {
379 let class = get_type(py)?;
380 let object = class.call0()?;
381 let reference = PyWeakrefReference::new(&object)?;
382
383 assert!(reference.call0()?.is(&object));
384 assert!(reference.upgrade().is_some());
385 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
386
387 drop(object);
388
389 assert!(reference.call0()?.is_none());
390 assert!(reference.upgrade().is_none());
391
392 Ok(())
393 })
394 }
395
396 #[test]
397 #[allow(deprecated)]
398 fn test_weakref_get_object() -> PyResult<()> {
399 Python::with_gil(|py| {
400 let class = get_type(py)?;
401 let object = class.call0()?;
402 let reference = PyWeakrefReference::new(&object)?;
403
404 assert!(reference.call0()?.is(&object));
405 assert!(reference.get_object().is(&object));
406
407 drop(object);
408
409 assert!(reference.call0()?.is(&reference.get_object()));
410 assert!(reference.call0()?.is_none());
411 assert!(reference.get_object().is_none());
412
413 Ok(())
414 })
415 }
416 }
417
418 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
420 mod pyo3_pyclass {
421 use super::*;
422 use crate::{pyclass, Py};
423
424 #[pyclass(weakref, crate = "crate")]
425 struct WeakrefablePyClass {}
426
427 #[test]
428 fn test_weakref_reference_behavior() -> PyResult<()> {
429 Python::with_gil(|py| {
430 let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
431 let reference = PyWeakrefReference::new(&object)?;
432
433 assert!(!reference.is(&object));
434 assert!(reference.upgrade().unwrap().is(&object));
435 #[cfg(not(Py_LIMITED_API))]
436 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
437
438 #[cfg(not(Py_LIMITED_API))]
439 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
440 #[cfg(not(Py_LIMITED_API))]
441 check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
442
443 assert!(reference
444 .getattr("__callback__")
445 .map_or(false, |result| result.is_none()));
446
447 assert!(reference.call0()?.is(&object));
448
449 drop(object);
450
451 assert!(reference.upgrade().is_none());
452 #[cfg(not(Py_LIMITED_API))]
453 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
454 check_repr(&reference, None)?;
455
456 assert!(reference
457 .getattr("__callback__")
458 .map_or(false, |result| result.is_none()));
459
460 assert!(reference.call0()?.is_none());
461
462 Ok(())
463 })
464 }
465
466 #[test]
467 fn test_weakref_upgrade_as() -> PyResult<()> {
468 Python::with_gil(|py| {
469 let object = Py::new(py, WeakrefablePyClass {})?;
470 let reference = PyWeakrefReference::new(object.bind(py))?;
471
472 {
473 let obj = reference.upgrade_as::<WeakrefablePyClass>();
474
475 assert!(obj.is_ok());
476 let obj = obj.unwrap();
477
478 assert!(obj.is_some());
479 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
480 }
481
482 drop(object);
483
484 {
485 let obj = reference.upgrade_as::<WeakrefablePyClass>();
486
487 assert!(obj.is_ok());
488 let obj = obj.unwrap();
489
490 assert!(obj.is_none());
491 }
492
493 Ok(())
494 })
495 }
496
497 #[test]
498 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
499 Python::with_gil(|py| {
500 let object = Py::new(py, WeakrefablePyClass {})?;
501 let reference = PyWeakrefReference::new(object.bind(py))?;
502
503 {
504 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
505
506 assert!(obj.is_some());
507 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
508 }
509
510 drop(object);
511
512 {
513 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
514
515 assert!(obj.is_none());
516 }
517
518 Ok(())
519 })
520 }
521
522 #[test]
523 fn test_weakref_upgrade() -> PyResult<()> {
524 Python::with_gil(|py| {
525 let object = Py::new(py, WeakrefablePyClass {})?;
526 let reference = PyWeakrefReference::new(object.bind(py))?;
527
528 assert!(reference.call0()?.is(&object));
529 assert!(reference.upgrade().is_some());
530 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
531
532 drop(object);
533
534 assert!(reference.call0()?.is_none());
535 assert!(reference.upgrade().is_none());
536
537 Ok(())
538 })
539 }
540
541 #[test]
542 #[allow(deprecated)]
543 fn test_weakref_get_object() -> PyResult<()> {
544 Python::with_gil(|py| {
545 let object = Py::new(py, WeakrefablePyClass {})?;
546 let reference = PyWeakrefReference::new(object.bind(py))?;
547
548 assert!(reference.call0()?.is(&object));
549 assert!(reference.get_object().is(&object));
550
551 drop(object);
552
553 assert!(reference.call0()?.is(&reference.get_object()));
554 assert!(reference.call0()?.is_none());
555 assert!(reference.get_object().is_none());
556
557 Ok(())
558 })
559 }
560 }
561}