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
243 fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> {
244 py.run(ffi::c_str!("class A:\n pass\n"), None, None)?;
245 py.eval(ffi::c_str!("A"), None, None)
246 .downcast_into::<PyType>()
247 }
248
249 #[test]
250 fn test_weakref_reference_behavior() -> PyResult<()> {
251 Python::with_gil(|py| {
252 let class = get_type(py)?;
253 let object = class.call0()?;
254 let reference = PyWeakrefReference::new(&object)?;
255
256 assert!(!reference.is(&object));
257 assert!(reference.upgrade().unwrap().is(&object));
258
259 #[cfg(not(Py_LIMITED_API))]
260 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
261
262 #[cfg(not(Py_LIMITED_API))]
263 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
264
265 #[cfg(not(Py_LIMITED_API))]
266 check_repr(&reference, Some((object.as_any(), "A")))?;
267
268 assert!(reference
269 .getattr("__callback__")
270 .map_or(false, |result| result.is_none()));
271
272 assert!(reference.call0()?.is(&object));
273
274 drop(object);
275
276 assert!(reference.upgrade().is_none());
277 #[cfg(not(Py_LIMITED_API))]
278 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
279 check_repr(&reference, None)?;
280
281 assert!(reference
282 .getattr("__callback__")
283 .map_or(false, |result| result.is_none()));
284
285 assert!(reference.call0()?.is_none());
286
287 Ok(())
288 })
289 }
290
291 #[test]
292 fn test_weakref_upgrade_as() -> PyResult<()> {
293 Python::with_gil(|py| {
294 let class = get_type(py)?;
295 let object = class.call0()?;
296 let reference = PyWeakrefReference::new(&object)?;
297
298 {
299 let obj = reference.upgrade_as::<PyAny>();
301
302 assert!(obj.is_ok());
303 let obj = obj.unwrap();
304
305 assert!(obj.is_some());
306 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
307 && obj.is_exact_instance(&class)));
308 }
309
310 drop(object);
311
312 {
313 let obj = reference.upgrade_as::<PyAny>();
315
316 assert!(obj.is_ok());
317 let obj = obj.unwrap();
318
319 assert!(obj.is_none());
320 }
321
322 Ok(())
323 })
324 }
325
326 #[test]
327 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
328 Python::with_gil(|py| {
329 let class = get_type(py)?;
330 let object = class.call0()?;
331 let reference = PyWeakrefReference::new(&object)?;
332
333 {
334 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
336
337 assert!(obj.is_some());
338 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()
339 && obj.is_exact_instance(&class)));
340 }
341
342 drop(object);
343
344 {
345 let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() };
347
348 assert!(obj.is_none());
349 }
350
351 Ok(())
352 })
353 }
354
355 #[test]
356 fn test_weakref_upgrade() -> PyResult<()> {
357 Python::with_gil(|py| {
358 let class = get_type(py)?;
359 let object = class.call0()?;
360 let reference = PyWeakrefReference::new(&object)?;
361
362 assert!(reference.call0()?.is(&object));
363 assert!(reference.upgrade().is_some());
364 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
365
366 drop(object);
367
368 assert!(reference.call0()?.is_none());
369 assert!(reference.upgrade().is_none());
370
371 Ok(())
372 })
373 }
374 }
375
376 #[cfg(all(feature = "macros", not(all(Py_LIMITED_API, not(Py_3_9)))))]
378 mod pyo3_pyclass {
379 use super::*;
380 use crate::{pyclass, Py};
381
382 #[pyclass(weakref, crate = "crate")]
383 struct WeakrefablePyClass {}
384
385 #[test]
386 fn test_weakref_reference_behavior() -> PyResult<()> {
387 Python::with_gil(|py| {
388 let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?;
389 let reference = PyWeakrefReference::new(&object)?;
390
391 assert!(!reference.is(&object));
392 assert!(reference.upgrade().unwrap().is(&object));
393 #[cfg(not(Py_LIMITED_API))]
394 assert_eq!(reference.get_type().to_string(), CLASS_NAME);
395
396 #[cfg(not(Py_LIMITED_API))]
397 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
398 #[cfg(not(Py_LIMITED_API))]
399 check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass")))?;
400
401 assert!(reference
402 .getattr("__callback__")
403 .map_or(false, |result| result.is_none()));
404
405 assert!(reference.call0()?.is(&object));
406
407 drop(object);
408
409 assert!(reference.upgrade().is_none());
410 #[cfg(not(Py_LIMITED_API))]
411 assert_eq!(reference.getattr("__class__")?.to_string(), CLASS_NAME);
412 check_repr(&reference, None)?;
413
414 assert!(reference
415 .getattr("__callback__")
416 .map_or(false, |result| result.is_none()));
417
418 assert!(reference.call0()?.is_none());
419
420 Ok(())
421 })
422 }
423
424 #[test]
425 fn test_weakref_upgrade_as() -> PyResult<()> {
426 Python::with_gil(|py| {
427 let object = Py::new(py, WeakrefablePyClass {})?;
428 let reference = PyWeakrefReference::new(object.bind(py))?;
429
430 {
431 let obj = reference.upgrade_as::<WeakrefablePyClass>();
432
433 assert!(obj.is_ok());
434 let obj = obj.unwrap();
435
436 assert!(obj.is_some());
437 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
438 }
439
440 drop(object);
441
442 {
443 let obj = reference.upgrade_as::<WeakrefablePyClass>();
444
445 assert!(obj.is_ok());
446 let obj = obj.unwrap();
447
448 assert!(obj.is_none());
449 }
450
451 Ok(())
452 })
453 }
454
455 #[test]
456 fn test_weakref_upgrade_as_unchecked() -> PyResult<()> {
457 Python::with_gil(|py| {
458 let object = Py::new(py, WeakrefablePyClass {})?;
459 let reference = PyWeakrefReference::new(object.bind(py))?;
460
461 {
462 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
463
464 assert!(obj.is_some());
465 assert!(obj.map_or(false, |obj| obj.as_ptr() == object.as_ptr()));
466 }
467
468 drop(object);
469
470 {
471 let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() };
472
473 assert!(obj.is_none());
474 }
475
476 Ok(())
477 })
478 }
479
480 #[test]
481 fn test_weakref_upgrade() -> PyResult<()> {
482 Python::with_gil(|py| {
483 let object = Py::new(py, WeakrefablePyClass {})?;
484 let reference = PyWeakrefReference::new(object.bind(py))?;
485
486 assert!(reference.call0()?.is(&object));
487 assert!(reference.upgrade().is_some());
488 assert!(reference.upgrade().map_or(false, |obj| obj.is(&object)));
489
490 drop(object);
491
492 assert!(reference.call0()?.is_none());
493 assert!(reference.upgrade().is_none());
494
495 Ok(())
496 })
497 }
498 }
499}