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