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