1 | use crate::err::PyResult; |
2 | use crate::ffi_ptr_ext::FfiPtrExt; |
3 | use crate::py_result_ext::PyResultExt; |
4 | use crate::types::any::PyAny; |
5 | use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt}; |
6 | |
7 | #[cfg (any(PyPy, GraalPy, Py_LIMITED_API))] |
8 | use crate::type_object::PyTypeCheck; |
9 | |
10 | use super::PyWeakrefMethods; |
11 | |
12 | /// Represents a Python `weakref.ReferenceType`. |
13 | /// |
14 | /// In Python this is created by calling `weakref.ref`. |
15 | #[repr (transparent)] |
16 | pub struct PyWeakrefReference(PyAny); |
17 | |
18 | #[cfg (not(any(PyPy, GraalPy, Py_LIMITED_API)))] |
19 | pyobject_subclassable_native_type!(PyWeakrefReference, crate::ffi::PyWeakReference); |
20 | |
21 | #[cfg (not(any(PyPy, GraalPy, Py_LIMITED_API)))] |
22 | pyobject_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 | // When targetting alternative or multiple interpreters, it is better to not use the internal API. |
31 | #[cfg (any(PyPy, GraalPy, Py_LIMITED_API))] |
32 | pyobject_native_type_named!(PyWeakrefReference); |
33 | |
34 | #[cfg (any(PyPy, GraalPy, Py_LIMITED_API))] |
35 | impl PyTypeCheck for PyWeakrefReference { |
36 | const NAME: &'static str = "weakref.ReferenceType" ; |
37 | |
38 | fn type_check(object: &Bound<'_, PyAny>) -> bool { |
39 | unsafe { ffi::PyWeakref_CheckRef(op:object.as_ptr()) > 0 } |
40 | } |
41 | } |
42 | |
43 | impl PyWeakrefReference { |
44 | /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object. |
45 | /// |
46 | /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag). |
47 | /// |
48 | /// # Examples |
49 | #[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 | /// use pyo3::prelude::*; |
58 | /// use pyo3::types::PyWeakrefReference; |
59 | /// |
60 | /// #[pyclass(weakref)] |
61 | /// struct Foo { /* fields omitted */ } |
62 | /// |
63 | /// # fn main() -> PyResult<()> { |
64 | /// Python::with_gil(|py| { |
65 | /// let foo = Bound::new(py, Foo {})?; |
66 | /// let weakref = PyWeakrefReference::new(&foo)?; |
67 | /// assert!( |
68 | /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>` |
69 | /// weakref.upgrade() |
70 | /// .map_or(false, |obj| obj.is(&foo)) |
71 | /// ); |
72 | /// |
73 | /// let weakref2 = PyWeakrefReference::new(&foo)?; |
74 | /// assert!(weakref.is(&weakref2)); |
75 | /// |
76 | /// drop(foo); |
77 | /// |
78 | /// assert!(weakref.upgrade().is_none()); |
79 | /// Ok(()) |
80 | /// }) |
81 | /// # } |
82 | /// ``` |
83 | 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 | /// Deprecated name for [`PyWeakrefReference::new`]. |
94 | #[deprecated (since = "0.23.0" , note = "renamed to `PyWeakrefReference::new`" )] |
95 | #[inline ] |
96 | pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefReference>> { |
97 | Self::new(object) |
98 | } |
99 | |
100 | /// Constructs a new Weak Reference (`weakref.ref`/`weakref.ReferenceType`) for the given object with a callback. |
101 | /// |
102 | /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag) or if the `callback` is not callable or None. |
103 | /// |
104 | /// # Examples |
105 | #[cfg_attr ( |
106 | not(all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9))))), |
107 | doc = "```rust,ignore" |
108 | )] |
109 | #[cfg_attr ( |
110 | all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9)))), |
111 | doc = "```rust" |
112 | )] |
113 | /// use pyo3::prelude::*; |
114 | /// use pyo3::types::PyWeakrefReference; |
115 | /// use pyo3::ffi::c_str; |
116 | /// |
117 | /// #[pyclass(weakref)] |
118 | /// struct Foo { /* fields omitted */ } |
119 | /// |
120 | /// #[pyfunction] |
121 | /// fn callback(wref: Bound<'_, PyWeakrefReference>) -> PyResult<()> { |
122 | /// let py = wref.py(); |
123 | /// assert!(wref.upgrade_as::<Foo>()?.is_none()); |
124 | /// py.run(c_str!("counter = 1" ), None, None) |
125 | /// } |
126 | /// |
127 | /// # fn main() -> PyResult<()> { |
128 | /// Python::with_gil(|py| { |
129 | /// py.run(c_str!("counter = 0" ), None, None)?; |
130 | /// assert_eq!(py.eval(c_str!("counter" ), None, None)?.extract::<u32>()?, 0); |
131 | /// let foo = Bound::new(py, Foo{})?; |
132 | /// |
133 | /// // This is fine. |
134 | /// let weakref = PyWeakrefReference::new_with(&foo, py.None())?; |
135 | /// assert!(weakref.upgrade_as::<Foo>()?.is_some()); |
136 | /// assert!( |
137 | /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>` |
138 | /// weakref.upgrade() |
139 | /// .map_or(false, |obj| obj.is(&foo)) |
140 | /// ); |
141 | /// assert_eq!(py.eval(c_str!("counter" ), None, None)?.extract::<u32>()?, 0); |
142 | /// |
143 | /// let weakref2 = PyWeakrefReference::new_with(&foo, wrap_pyfunction!(callback, py)?)?; |
144 | /// assert!(!weakref.is(&weakref2)); // Not the same weakref |
145 | /// assert!(weakref.eq(&weakref2)?); // But Equal, since they point to the same object |
146 | /// |
147 | /// drop(foo); |
148 | /// |
149 | /// assert!(weakref.upgrade_as::<Foo>()?.is_none()); |
150 | /// assert_eq!(py.eval(c_str!("counter" ), None, None)?.extract::<u32>()?, 1); |
151 | /// Ok(()) |
152 | /// }) |
153 | /// # } |
154 | /// ``` |
155 | pub fn new_with<'py, C>( |
156 | object: &Bound<'py, PyAny>, |
157 | callback: C, |
158 | ) -> PyResult<Bound<'py, PyWeakrefReference>> |
159 | where |
160 | C: IntoPyObject<'py>, |
161 | { |
162 | fn inner<'py>( |
163 | object: &Bound<'py, PyAny>, |
164 | callback: Borrowed<'_, 'py, PyAny>, |
165 | ) -> PyResult<Bound<'py, PyWeakrefReference>> { |
166 | unsafe { |
167 | Bound::from_owned_ptr_or_err( |
168 | object.py(), |
169 | ffi::PyWeakref_NewRef(object.as_ptr(), callback.as_ptr()), |
170 | ) |
171 | .downcast_into_unchecked() |
172 | } |
173 | } |
174 | |
175 | let py = object.py(); |
176 | inner( |
177 | object, |
178 | callback |
179 | .into_pyobject_or_pyerr(py)? |
180 | .into_any() |
181 | .as_borrowed(), |
182 | ) |
183 | } |
184 | |
185 | /// Deprecated name for [`PyWeakrefReference::new_with`]. |
186 | #[deprecated (since = "0.23.0" , note = "renamed to `PyWeakrefReference::new_with`" )] |
187 | #[allow (deprecated)] |
188 | #[inline ] |
189 | pub fn new_bound_with<'py, C>( |
190 | object: &Bound<'py, PyAny>, |
191 | callback: C, |
192 | ) -> PyResult<Bound<'py, PyWeakrefReference>> |
193 | where |
194 | C: crate::ToPyObject, |
195 | { |
196 | Self::new_with(object, callback.to_object(object.py())) |
197 | } |
198 | } |
199 | |
200 | impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefReference> { |
201 | fn upgrade(&self) -> Option<Bound<'py, PyAny>> { |
202 | let mut obj: *mut ffi::PyObject = std::ptr::null_mut(); |
203 | match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } { |
204 | std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ReferenceType' instance should be valid (non-null and actually a weakref reference)" ), |
205 | 0 => None, |
206 | 1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }), |
207 | } |
208 | } |
209 | } |
210 | |
211 | #[cfg (test)] |
212 | mod tests { |
213 | use crate::types::any::{PyAny, PyAnyMethods}; |
214 | use crate::types::weakref::{PyWeakrefMethods, PyWeakrefReference}; |
215 | use crate::{Bound, PyResult, Python}; |
216 | |
217 | #[cfg (all(not(Py_LIMITED_API), Py_3_10))] |
218 | const CLASS_NAME: &str = "<class 'weakref.ReferenceType'>" ; |
219 | #[cfg (all(not(Py_LIMITED_API), not(Py_3_10)))] |
220 | const CLASS_NAME: &str = "<class 'weakref'>" ; |
221 | |
222 | fn check_repr( |
223 | reference: &Bound<'_, PyWeakrefReference>, |
224 | object: Option<(&Bound<'_, PyAny>, &str)>, |
225 | ) -> PyResult<()> { |
226 | let repr = reference.repr()?.to_string(); |
227 | let (first_part, second_part) = repr.split_once("; " ).unwrap(); |
228 | |
229 | { |
230 | let (msg, addr) = first_part.split_once("0x" ).unwrap(); |
231 | |
232 | assert_eq!(msg, "<weakref at " ); |
233 | assert!(addr |
234 | .to_lowercase() |
235 | .contains(format!("{:x?}" , reference.as_ptr()).split_at(2).1)); |
236 | } |
237 | |
238 | match object { |
239 | Some((object, class)) => { |
240 | let (msg, addr) = second_part.split_once("0x" ).unwrap(); |
241 | |
242 | // Avoid testing on reprs directly since they the quoting and full path vs class name tends to be changedi undocumented. |
243 | assert!(msg.starts_with("to '" )); |
244 | assert!(msg.contains(class)); |
245 | assert!(msg.ends_with("' at " )); |
246 | |
247 | assert!(addr |
248 | .to_lowercase() |
249 | .contains(format!("{:x?}" , object.as_ptr()).split_at(2).1)); |
250 | } |
251 | None => { |
252 | assert_eq!(second_part, "dead>" ) |
253 | } |
254 | } |
255 | |
256 | Ok(()) |
257 | } |
258 | |
259 | mod python_class { |
260 | use super::*; |
261 | use crate::ffi; |
262 | use crate::{py_result_ext::PyResultExt, types::PyType}; |
263 | use std::ptr; |
264 | |
265 | fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> { |
266 | py.run(ffi::c_str!("class A: \n pass \n" ), None, None)?; |
267 | py.eval(ffi::c_str!("A" ), None, None) |
268 | .downcast_into::<PyType>() |
269 | } |
270 | |
271 | #[test ] |
272 | fn test_weakref_reference_behavior() -> PyResult<()> { |
273 | Python::with_gil(|py| { |
274 | let class = get_type(py)?; |
275 | let object = class.call0()?; |
276 | let reference = PyWeakrefReference::new(&object)?; |
277 | |
278 | assert!(!reference.is(&object)); |
279 | assert!(reference.upgrade().unwrap().is(&object)); |
280 | |
281 | #[cfg (not(Py_LIMITED_API))] |
282 | assert_eq!(reference.get_type().to_string(), CLASS_NAME); |
283 | |
284 | #[cfg (not(Py_LIMITED_API))] |
285 | assert_eq!(reference.getattr("__class__" )?.to_string(), CLASS_NAME); |
286 | |
287 | #[cfg (not(Py_LIMITED_API))] |
288 | check_repr(&reference, Some((object.as_any(), "A" )))?; |
289 | |
290 | assert!(reference |
291 | .getattr("__callback__" ) |
292 | .map_or(false, |result| result.is_none())); |
293 | |
294 | assert!(reference.call0()?.is(&object)); |
295 | |
296 | drop(object); |
297 | |
298 | assert!(reference.upgrade().is_none()); |
299 | #[cfg (not(Py_LIMITED_API))] |
300 | assert_eq!(reference.getattr("__class__" )?.to_string(), CLASS_NAME); |
301 | check_repr(&reference, None)?; |
302 | |
303 | assert!(reference |
304 | .getattr("__callback__" ) |
305 | .map_or(false, |result| result.is_none())); |
306 | |
307 | assert!(reference.call0()?.is_none()); |
308 | |
309 | Ok(()) |
310 | }) |
311 | } |
312 | |
313 | #[test ] |
314 | fn test_weakref_upgrade_as() -> PyResult<()> { |
315 | Python::with_gil(|py| { |
316 | let class = get_type(py)?; |
317 | let object = class.call0()?; |
318 | let reference = PyWeakrefReference::new(&object)?; |
319 | |
320 | { |
321 | // This test is a bit weird but ok. |
322 | let obj = reference.upgrade_as::<PyAny>(); |
323 | |
324 | assert!(obj.is_ok()); |
325 | let obj = obj.unwrap(); |
326 | |
327 | assert!(obj.is_some()); |
328 | assert!( |
329 | obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) |
330 | && obj.is_exact_instance(&class)) |
331 | ); |
332 | } |
333 | |
334 | drop(object); |
335 | |
336 | { |
337 | // This test is a bit weird but ok. |
338 | let obj = reference.upgrade_as::<PyAny>(); |
339 | |
340 | assert!(obj.is_ok()); |
341 | let obj = obj.unwrap(); |
342 | |
343 | assert!(obj.is_none()); |
344 | } |
345 | |
346 | Ok(()) |
347 | }) |
348 | } |
349 | |
350 | #[test ] |
351 | fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { |
352 | Python::with_gil(|py| { |
353 | let class = get_type(py)?; |
354 | let object = class.call0()?; |
355 | let reference = PyWeakrefReference::new(&object)?; |
356 | |
357 | { |
358 | // This test is a bit weird but ok. |
359 | let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() }; |
360 | |
361 | assert!(obj.is_some()); |
362 | assert!( |
363 | obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) |
364 | && obj.is_exact_instance(&class)) |
365 | ); |
366 | } |
367 | |
368 | drop(object); |
369 | |
370 | { |
371 | // This test is a bit weird but ok. |
372 | let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() }; |
373 | |
374 | assert!(obj.is_none()); |
375 | } |
376 | |
377 | Ok(()) |
378 | }) |
379 | } |
380 | |
381 | #[test ] |
382 | fn test_weakref_upgrade() -> PyResult<()> { |
383 | Python::with_gil(|py| { |
384 | let class = get_type(py)?; |
385 | let object = class.call0()?; |
386 | let reference = PyWeakrefReference::new(&object)?; |
387 | |
388 | assert!(reference.call0()?.is(&object)); |
389 | assert!(reference.upgrade().is_some()); |
390 | assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); |
391 | |
392 | drop(object); |
393 | |
394 | assert!(reference.call0()?.is_none()); |
395 | assert!(reference.upgrade().is_none()); |
396 | |
397 | Ok(()) |
398 | }) |
399 | } |
400 | |
401 | #[test ] |
402 | #[allow (deprecated)] |
403 | fn test_weakref_get_object() -> PyResult<()> { |
404 | Python::with_gil(|py| { |
405 | let class = get_type(py)?; |
406 | let object = class.call0()?; |
407 | let reference = PyWeakrefReference::new(&object)?; |
408 | |
409 | assert!(reference.call0()?.is(&object)); |
410 | assert!(reference.get_object().is(&object)); |
411 | |
412 | drop(object); |
413 | |
414 | assert!(reference.call0()?.is(&reference.get_object())); |
415 | assert!(reference.call0()?.is_none()); |
416 | assert!(reference.get_object().is_none()); |
417 | |
418 | Ok(()) |
419 | }) |
420 | } |
421 | } |
422 | |
423 | // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. |
424 | #[cfg (all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9)))))] |
425 | mod pyo3_pyclass { |
426 | use super::*; |
427 | use crate::{pyclass, Py}; |
428 | use std::ptr; |
429 | |
430 | #[pyclass(weakref, crate = "crate" )] |
431 | struct WeakrefablePyClass {} |
432 | |
433 | #[test ] |
434 | fn test_weakref_reference_behavior() -> PyResult<()> { |
435 | Python::with_gil(|py| { |
436 | let object: Bound<'_, WeakrefablePyClass> = Bound::new(py, WeakrefablePyClass {})?; |
437 | let reference = PyWeakrefReference::new(&object)?; |
438 | |
439 | assert!(!reference.is(&object)); |
440 | assert!(reference.upgrade().unwrap().is(&object)); |
441 | #[cfg (not(Py_LIMITED_API))] |
442 | assert_eq!(reference.get_type().to_string(), CLASS_NAME); |
443 | |
444 | #[cfg (not(Py_LIMITED_API))] |
445 | assert_eq!(reference.getattr("__class__" )?.to_string(), CLASS_NAME); |
446 | #[cfg (not(Py_LIMITED_API))] |
447 | check_repr(&reference, Some((object.as_any(), "WeakrefablePyClass" )))?; |
448 | |
449 | assert!(reference |
450 | .getattr("__callback__" ) |
451 | .map_or(false, |result| result.is_none())); |
452 | |
453 | assert!(reference.call0()?.is(&object)); |
454 | |
455 | drop(object); |
456 | |
457 | assert!(reference.upgrade().is_none()); |
458 | #[cfg (not(Py_LIMITED_API))] |
459 | assert_eq!(reference.getattr("__class__" )?.to_string(), CLASS_NAME); |
460 | check_repr(&reference, None)?; |
461 | |
462 | assert!(reference |
463 | .getattr("__callback__" ) |
464 | .map_or(false, |result| result.is_none())); |
465 | |
466 | assert!(reference.call0()?.is_none()); |
467 | |
468 | Ok(()) |
469 | }) |
470 | } |
471 | |
472 | #[test ] |
473 | fn test_weakref_upgrade_as() -> PyResult<()> { |
474 | Python::with_gil(|py| { |
475 | let object = Py::new(py, WeakrefablePyClass {})?; |
476 | let reference = PyWeakrefReference::new(object.bind(py))?; |
477 | |
478 | { |
479 | let obj = reference.upgrade_as::<WeakrefablePyClass>(); |
480 | |
481 | assert!(obj.is_ok()); |
482 | let obj = obj.unwrap(); |
483 | |
484 | assert!(obj.is_some()); |
485 | assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); |
486 | } |
487 | |
488 | drop(object); |
489 | |
490 | { |
491 | let obj = reference.upgrade_as::<WeakrefablePyClass>(); |
492 | |
493 | assert!(obj.is_ok()); |
494 | let obj = obj.unwrap(); |
495 | |
496 | assert!(obj.is_none()); |
497 | } |
498 | |
499 | Ok(()) |
500 | }) |
501 | } |
502 | |
503 | #[test ] |
504 | fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { |
505 | Python::with_gil(|py| { |
506 | let object = Py::new(py, WeakrefablePyClass {})?; |
507 | let reference = PyWeakrefReference::new(object.bind(py))?; |
508 | |
509 | { |
510 | let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() }; |
511 | |
512 | assert!(obj.is_some()); |
513 | assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); |
514 | } |
515 | |
516 | drop(object); |
517 | |
518 | { |
519 | let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() }; |
520 | |
521 | assert!(obj.is_none()); |
522 | } |
523 | |
524 | Ok(()) |
525 | }) |
526 | } |
527 | |
528 | #[test ] |
529 | fn test_weakref_upgrade() -> PyResult<()> { |
530 | Python::with_gil(|py| { |
531 | let object = Py::new(py, WeakrefablePyClass {})?; |
532 | let reference = PyWeakrefReference::new(object.bind(py))?; |
533 | |
534 | assert!(reference.call0()?.is(&object)); |
535 | assert!(reference.upgrade().is_some()); |
536 | assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); |
537 | |
538 | drop(object); |
539 | |
540 | assert!(reference.call0()?.is_none()); |
541 | assert!(reference.upgrade().is_none()); |
542 | |
543 | Ok(()) |
544 | }) |
545 | } |
546 | |
547 | #[test ] |
548 | #[allow (deprecated)] |
549 | fn test_weakref_get_object() -> PyResult<()> { |
550 | Python::with_gil(|py| { |
551 | let object = Py::new(py, WeakrefablePyClass {})?; |
552 | let reference = PyWeakrefReference::new(object.bind(py))?; |
553 | |
554 | assert!(reference.call0()?.is(&object)); |
555 | assert!(reference.get_object().is(&object)); |
556 | |
557 | drop(object); |
558 | |
559 | assert!(reference.call0()?.is(&reference.get_object())); |
560 | assert!(reference.call0()?.is_none()); |
561 | assert!(reference.get_object().is_none()); |
562 | |
563 | Ok(()) |
564 | }) |
565 | } |
566 | } |
567 | } |
568 | |