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