1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::py_result_ext::PyResultExt;
4#[cfg(any(PyPy, GraalPy, Py_LIMITED_API, RustPython))]
5use crate::sync::PyOnceLock;
6use crate::types::any::PyAny;
7#[cfg(any(PyPy, GraalPy, Py_LIMITED_API, RustPython))]
8use crate::types::typeobject::PyTypeMethods;
9#[cfg(any(PyPy, GraalPy, Py_LIMITED_API, RustPython))]
10use crate::types::PyType;
11#[cfg(any(PyPy, GraalPy, Py_LIMITED_API, RustPython))]
12use crate::Py;
13use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt};
14
15use super::PyWeakrefMethods;
16
17#[repr(transparent)]
21pub struct PyWeakrefReference(PyAny);
22
23#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API, RustPython)))]
24pyobject_subclassable_native_type!(PyWeakrefReference, ffi::PyWeakReference);
25
26#[cfg(not(any(PyPy, GraalPy, Py_LIMITED_API, RustPython)))]
27pyobject_native_type!(
28 PyWeakrefReference,
29 ffi::PyWeakReference,
30 pyobject_native_static_type_object!(ffi::_PyWeakref_RefType),
32 "weakref",
33 "ReferenceType",
34 #module=Some("weakref"),
35 #checkfunction=ffi::PyWeakref_CheckRef
36);
37
38#[cfg(any(PyPy, GraalPy, Py_LIMITED_API, RustPython))]
40pyobject_native_type_core!(
41 PyWeakrefReference,
42 |py| {
43 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
44 TYPE.import(py, "weakref", "ref")
45 .unwrap()
46 .as_type_ptr()
47 },
48 "weakref",
49 "ReferenceType",
50 #module=Some("weakref"),
51 #checkfunction=ffi::PyWeakref_CheckRef
52);
53
54impl PyWeakrefReference {
55 #[cfg_attr(not(feature = "macros"), doc = "```rust,ignore")]
61 #[cfg_attr(feature = "macros", doc = "```rust")]
62 pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> {
88 unsafe {
89 Bound::from_owned_ptr_or_err(
90 object.py(),
91 ffi::PyWeakref_NewRef(object.as_ptr(), ffi::Py_None()),
92 )
93 .cast_into_unchecked()
94 }
95 }
96
97 #[cfg_attr(not(feature = "macros"), doc = "```rust,ignore")]
103 #[cfg_attr(feature = "macros", doc = "```rust")]
104 pub fn new_with<'py, C>(
145 object: &Bound<'py, PyAny>,
146 callback: C,
147 ) -> PyResult<Bound<'py, PyWeakrefReference>>
148 where
149 C: IntoPyObject<'py>,
150 {
151 fn inner<'py>(
152 object: &Bound<'py, PyAny>,
153 callback: Borrowed<'_, 'py, PyAny>,
154 ) -> PyResult<Bound<'py, PyWeakrefReference>> {
155 unsafe {
156 Bound::from_owned_ptr_or_err(
157 object.py(),
158 ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()),
159 )
160 .cast_into_unchecked()
161 }
162 }
163
164 let py = object.py();
165 inner(
166 object,
167 callback
168 .into_pyobject_or_pyerr(py)?
169 .into_any()
170 .as_borrowed(),
171 )
172 }
173}
174
175impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> {
176 fn upgrade(&self) -> Option<Bound<'py, PyAny>> {
177 let mut obj: *mut ffi::PyObject = core::ptr::null_mut();
178 match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } {
179 core::ffi::c_int::MIN..=-1 => panic!("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)"),
180 0 => None,
181 1..=core::ffi::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }),
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use crate::platform::prelude::*;
189 use crate::types::any::{PyAny, PyAnyMethods};
190 use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference};
191 use crate::{Bound, PyResult, Python};
192
193 #[cfg(all(not(Py_LIMITED_API), Py_3_10))]
194 const CLASS_NAME: &str = "<class 'weakref.ReferenceType'>";
195 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
196 const CLASS_NAME: &str = "<class 'weakref'>";
197
198 fn check_repr(
199 reference: &Bound<'_, PyWeakrefReference>,
200 object: Option<(&Bound<'_, PyAny>, &str)>,
201 ) -> PyResult<()> {
202 let repr = reference.repr()?.to_string();
203 let (first_part, second_part) = repr.split_once("; ").unwrap();
204
205 {
206 let (msg, addr) = first_part.split_once("0x").unwrap();
207
208 assert_eq!(msg, "<weakref at ");
209 assert!(addr
210 .to_lowercase()
211 .contains(format!("{:x?}", reference.as_ptr()).split_at(2).1));
212 }
213
214 match object {
215 Some((object, class)) => {
216 let (msg, addr) = second_part.split_once("0x").unwrap();
217
218 assert!(msg.starts_with("to '"));
220 assert!(msg.contains(class));
221 assert!(msg.ends_with("' at "));
222
223 assert!(addr
224 .to_lowercase()
225 .contains(format!("{:x?}", object.as_ptr()).split_at(2).1));
226 }
227 None => {
228 assert_eq!(second_part, "dead>")
229 }
230 }
231
232 Ok(())
233 }
234
235 mod python_class {
236 use super::*;
237 use crate::PyTypeInfo;
238 use crate::{py_result_ext::PyResultExt, types::PyType};
239 use core::ptr;
240
241 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
242 py.run(c"class A:\n pass\n", None, None)?;
243 py.eval(c"A", None, None).cast_into::<PyType>()
244 }
245
246 #[test]
247 fn test_weakref_reference_behavior() -> PyResult<()> {
248 Python::attach(|py| {
249 let class = get_type(py)?;
250 let object = class.call0()?;
251 let reference = PyWeakrefReference::new(&object)?;
252
253 assert!(!reference.is(&object));
254 assert!(reference.upgrade().unwrap().is(&object));
255
256 #[cfg(not(Py_LIMITED_API))]
257 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
258
259 #[cfg(not(Py_LIMITED_API))]
260 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
261
262 #[cfg(not(Py_LIMITED_API))]
263 check_repr(&reference, Some((object.as_any(), "A")))?;
264
265 assert!(reference
266 .getattr("__callback__")
267 .is_ok_and(|result| result.is_none()));
268
269 assert!(reference.call0()?.is(&object));
270
271 drop(object);
272
273 assert!(reference.upgrade().is_none());
274 #[cfg(not(Py_LIMITED_API))]
275 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
276 check_repr(&reference, None)?;
277
278 assert!(reference
279 .getattr("__callback__")
280 .is_ok_and(|result| result.is_none()));
281
282 assert!(reference.call0()?.is_none());
283
284 Ok(())
285 })
286 }
287
288 #[test]
289 fn test_weakref_upgrade_as() -> PyResult<()> {
290 Python::attach(|py| {
291 let class = get_type(py)?;
292 let object = class.call0()?;
293 let reference = PyWeakrefReference::new(&object)?;
294
295 {
296 let obj = reference.upgrade_as::<PyAny>();
298
299 assert!(obj.is_ok());
300 let obj = obj.unwrap();
301
302 assert!(obj.is_some());
303 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
304 && obj.is_exact_instance(&class)));
305 }
306
307 drop(object);
308
309 {
310 let obj = reference.upgrade_as::<PyAny>();
312
313 assert!(obj.is_ok());
314 let obj = obj.unwrap();
315
316 assert!(obj.is_none());
317 }
318
319 Ok(())
320 })
321 }
322
323 #[test]
324 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
325 Python::attach(|py| {
326 let class = get_type(py)?;
327 let object = class.call0()?;
328 let reference = PyWeakrefReference::new(&object)?;
329
330 {
331 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
333
334 assert!(obj.is_some());
335 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())
336 && obj.is_exact_instance(&class)));
337 }
338
339 drop(object);
340
341 {
342 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
344
345 assert!(obj.is_none());
346 }
347
348 Ok(())
349 })
350 }
351
352 #[test]
353 fn test_weakref_upgrade() -> PyResult<()> {
354 Python::attach(|py| {
355 let class = get_type(py)?;
356 let object = class.call0()?;
357 let reference = PyWeakrefReference::new(&object)?;
358
359 assert!(reference.call0()?.is(&object));
360 assert!(reference.upgrade().is_some());
361 assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
362
363 drop(object);
364
365 assert!(reference.call0()?.is_none());
366 assert!(reference.upgrade().is_none());
367
368 Ok(())
369 })
370 }
371
372 #[test]
373 fn test_type_object() -> PyResult<()> {
374 Python::attach(|py| {
375 let class = get_type(py)?;
376 let object = class.call0()?;
377 let reference = PyWeakrefReference::new(&object)?;
378
379 assert!(reference.is_instance(&PyWeakrefReference::type_object(py))?);
380 Ok(())
381 })
382 }
383 }
384
385 #[cfg(feature = "macros")]
386 mod pyo3_pyclass {
387 use super::*;
388 use crate::{pyclass, Py};
389 use core::ptr;
390
391 #[pyclass(weakref, crate = "crate")]
392 struct WeakrefablePyClass {}
393
394 #[test]
395 fn test_weakref_reference_behavior() -> PyResult<()> {
396 Python::attach(|py| {
397 let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
398 let reference = PyWeakrefReference::new(&object)?;
399
400 assert!(!reference.is(&object));
401 assert!(reference.upgrade().unwrap().is(&object));
402 #[cfg(not(Py_LIMITED_API))]
403 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
404
405 #[cfg(not(Py_LIMITED_API))]
406 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
407 #[cfg(not(Py_LIMITED_API))]
408 check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
409
410 assert!(reference
411 .getattr("__callback__")
412 .is_ok_and(|result| result.is_none()));
413
414 assert!(reference.call0()?.is(&object));
415
416 drop(object);
417
418 assert!(reference.upgrade().is_none());
419 #[cfg(not(Py_LIMITED_API))]
420 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
421 check_repr(&reference, None)?;
422
423 assert!(reference
424 .getattr("__callback__")
425 .is_ok_and(|result| result.is_none()));
426
427 assert!(reference.call0()?.is_none());
428
429 Ok(())
430 })
431 }
432
433 #[test]
434 fn test_weakref_upgrade_as() -> PyResult<()> {
435 Python::attach(|py| {
436 let object = Py::new(py, WeakrefablePyClass {})?;
437 let reference = PyWeakrefReference::new(object.bind(py))?;
438
439 {
440 let obj = reference.upgrade_as::<WeakrefablePyClass>();
441
442 assert!(obj.is_ok());
443 let obj = obj.unwrap();
444
445 assert!(obj.is_some());
446 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
447 }
448
449 drop(object);
450
451 {
452 let obj = reference.upgrade_as::<WeakrefablePyClass>();
453
454 assert!(obj.is_ok());
455 let obj = obj.unwrap();
456
457 assert!(obj.is_none());
458 }
459
460 Ok(())
461 })
462 }
463
464 #[test]
465 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
466 Python::attach(|py| {
467 let object = Py::new(py, WeakrefablePyClass {})?;
468 let reference = PyWeakrefReference::new(object.bind(py))?;
469
470 {
471 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
472
473 assert!(obj.is_some());
474 assert!(obj.is_some_and(|obj| ptr::eq(obj.as_ptr(), object.as_ptr())));
475 }
476
477 drop(object);
478
479 {
480 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
481
482 assert!(obj.is_none());
483 }
484
485 Ok(())
486 })
487 }
488
489 #[test]
490 fn test_weakref_upgrade() -> PyResult<()> {
491 Python::attach(|py| {
492 let object = Py::new(py, WeakrefablePyClass {})?;
493 let reference = PyWeakrefReference::new(object.bind(py))?;
494
495 assert!(reference.call0()?.is(&object));
496 assert!(reference.upgrade().is_some());
497 assert!(reference.upgrade().is_some_and(|obj| obj.is(&object)));
498
499 drop(object);
500
501 assert!(reference.call0()?.is_none());
502 assert!(reference.upgrade().is_none());
503
504 Ok(())
505 })
506 }
507 }
508}