1 | use crate::err::PyResult; |
2 | use crate::ffi_ptr_ext::FfiPtrExt; |
3 | use crate::py_result_ext::PyResultExt; |
4 | use crate::type_object::PyTypeCheck; |
5 | use crate::types::any::PyAny; |
6 | use crate::{ffi, Borrowed, Bound, BoundObject, IntoPyObject, IntoPyObjectExt}; |
7 | |
8 | use super::PyWeakrefMethods; |
9 | |
10 | /// Represents any Python `weakref` Proxy type. |
11 | /// |
12 | /// In Python this is created by calling `weakref.proxy`. |
13 | /// This is either a `weakref.ProxyType` or a `weakref.CallableProxyType` (`weakref.ProxyTypes`). |
14 | #[repr (transparent)] |
15 | pub struct PyWeakrefProxy(PyAny); |
16 | |
17 | pyobject_native_type_named!(PyWeakrefProxy); |
18 | |
19 | // TODO: We known the layout but this cannot be implemented, due to the lack of public typeobject pointers. And it is 2 distinct types |
20 | // #[cfg(not(Py_LIMITED_API))] |
21 | // pyobject_native_type_sized!(PyWeakrefProxy, ffi::PyWeakReference); |
22 | |
23 | impl PyTypeCheck for PyWeakrefProxy { |
24 | const NAME: &'static str = "weakref.ProxyTypes" ; |
25 | |
26 | fn type_check(object: &Bound<'_, PyAny>) -> bool { |
27 | unsafe { ffi::PyWeakref_CheckProxy(op:object.as_ptr()) > 0 } |
28 | } |
29 | } |
30 | |
31 | /// TODO: UPDATE DOCS |
32 | impl PyWeakrefProxy { |
33 | /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object. |
34 | /// |
35 | /// Returns a `TypeError` if `object` is not weak referenceable (Most native types and PyClasses without `weakref` flag). |
36 | /// |
37 | /// # Examples |
38 | #[cfg_attr ( |
39 | not(all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9))))), |
40 | doc = "```rust,ignore" |
41 | )] |
42 | #[cfg_attr ( |
43 | all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9)))), |
44 | doc = "```rust" |
45 | )] |
46 | /// use pyo3::prelude::*; |
47 | /// use pyo3::types::PyWeakrefProxy; |
48 | /// |
49 | /// #[pyclass(weakref)] |
50 | /// struct Foo { /* fields omitted */ } |
51 | /// |
52 | /// # fn main() -> PyResult<()> { |
53 | /// Python::with_gil(|py| { |
54 | /// let foo = Bound::new(py, Foo {})?; |
55 | /// let weakref = PyWeakrefProxy::new(&foo)?; |
56 | /// assert!( |
57 | /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>` |
58 | /// weakref.upgrade() |
59 | /// .map_or(false, |obj| obj.is(&foo)) |
60 | /// ); |
61 | /// |
62 | /// let weakref2 = PyWeakrefProxy::new(&foo)?; |
63 | /// assert!(weakref.is(&weakref2)); |
64 | /// |
65 | /// drop(foo); |
66 | /// |
67 | /// assert!(weakref.upgrade().is_none()); |
68 | /// Ok(()) |
69 | /// }) |
70 | /// # } |
71 | /// ``` |
72 | #[inline ] |
73 | pub fn new<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefProxy>> { |
74 | unsafe { |
75 | Bound::from_owned_ptr_or_err( |
76 | object.py(), |
77 | ffi::PyWeakref_NewProxy(object.as_ptr(), ffi::Py_None()), |
78 | ) |
79 | .downcast_into_unchecked() |
80 | } |
81 | } |
82 | |
83 | /// Deprecated name for [`PyWeakrefProxy::new`]. |
84 | #[deprecated (since = "0.23.0" , note = "renamed to `PyWeakrefProxy::new`" )] |
85 | #[inline ] |
86 | pub fn new_bound<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyWeakrefProxy>> { |
87 | Self::new(object) |
88 | } |
89 | |
90 | /// Constructs a new Weak Reference (`weakref.proxy`/`weakref.ProxyType`/`weakref.CallableProxyType`) for the given object with a callback. |
91 | /// |
92 | /// 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. |
93 | /// |
94 | /// # Examples |
95 | #[cfg_attr ( |
96 | not(all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9))))), |
97 | doc = "```rust,ignore" |
98 | )] |
99 | #[cfg_attr ( |
100 | all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9)))), |
101 | doc = "```rust" |
102 | )] |
103 | /// use pyo3::prelude::*; |
104 | /// use pyo3::types::PyWeakrefProxy; |
105 | /// use pyo3::ffi::c_str; |
106 | /// |
107 | /// #[pyclass(weakref)] |
108 | /// struct Foo { /* fields omitted */ } |
109 | /// |
110 | /// #[pyfunction] |
111 | /// fn callback(wref: Bound<'_, PyWeakrefProxy>) -> PyResult<()> { |
112 | /// let py = wref.py(); |
113 | /// assert!(wref.upgrade_as::<Foo>()?.is_none()); |
114 | /// py.run(c_str!("counter = 1" ), None, None) |
115 | /// } |
116 | /// |
117 | /// # fn main() -> PyResult<()> { |
118 | /// Python::with_gil(|py| { |
119 | /// py.run(c_str!("counter = 0" ), None, None)?; |
120 | /// assert_eq!(py.eval(c_str!("counter" ), None, None)?.extract::<u32>()?, 0); |
121 | /// let foo = Bound::new(py, Foo{})?; |
122 | /// |
123 | /// // This is fine. |
124 | /// let weakref = PyWeakrefProxy::new_with(&foo, py.None())?; |
125 | /// assert!(weakref.upgrade_as::<Foo>()?.is_some()); |
126 | /// assert!( |
127 | /// // In normal situations where a direct `Bound<'py, Foo>` is required use `upgrade::<Foo>` |
128 | /// weakref.upgrade() |
129 | /// .map_or(false, |obj| obj.is(&foo)) |
130 | /// ); |
131 | /// assert_eq!(py.eval(c_str!("counter" ), None, None)?.extract::<u32>()?, 0); |
132 | /// |
133 | /// let weakref2 = PyWeakrefProxy::new_with(&foo, wrap_pyfunction!(callback, py)?)?; |
134 | /// assert!(!weakref.is(&weakref2)); // Not the same weakref |
135 | /// assert!(weakref.eq(&weakref2)?); // But Equal, since they point to the same object |
136 | /// |
137 | /// drop(foo); |
138 | /// |
139 | /// assert!(weakref.upgrade_as::<Foo>()?.is_none()); |
140 | /// assert_eq!(py.eval(c_str!("counter" ), None, None)?.extract::<u32>()?, 1); |
141 | /// Ok(()) |
142 | /// }) |
143 | /// # } |
144 | /// ``` |
145 | #[inline ] |
146 | pub fn new_with<'py, C>( |
147 | object: &Bound<'py, PyAny>, |
148 | callback: C, |
149 | ) -> PyResult<Bound<'py, PyWeakrefProxy>> |
150 | where |
151 | C: IntoPyObject<'py>, |
152 | { |
153 | fn inner<'py>( |
154 | object: &Bound<'py, PyAny>, |
155 | callback: Borrowed<'_, 'py, PyAny>, |
156 | ) -> PyResult<Bound<'py, PyWeakrefProxy>> { |
157 | unsafe { |
158 | Bound::from_owned_ptr_or_err( |
159 | object.py(), |
160 | ffi::PyWeakref_NewProxy(object.as_ptr(), callback.as_ptr()), |
161 | ) |
162 | .downcast_into_unchecked() |
163 | } |
164 | } |
165 | |
166 | let py = object.py(); |
167 | inner( |
168 | object, |
169 | callback |
170 | .into_pyobject_or_pyerr(py)? |
171 | .into_any() |
172 | .as_borrowed(), |
173 | ) |
174 | } |
175 | |
176 | /// Deprecated name for [`PyWeakrefProxy::new_with`]. |
177 | #[deprecated (since = "0.23.0" , note = "renamed to `PyWeakrefProxy::new_with`" )] |
178 | #[allow (deprecated)] |
179 | #[inline ] |
180 | pub fn new_bound_with<'py, C>( |
181 | object: &Bound<'py, PyAny>, |
182 | callback: C, |
183 | ) -> PyResult<Bound<'py, PyWeakrefProxy>> |
184 | where |
185 | C: crate::ToPyObject, |
186 | { |
187 | Self::new_with(object, callback.to_object(object.py())) |
188 | } |
189 | } |
190 | |
191 | impl<'py> PyWeakrefMethods<'py> for Bound<'py, PyWeakrefProxy> { |
192 | fn upgrade(&self) -> Option<Bound<'py, PyAny>> { |
193 | let mut obj: *mut ffi::PyObject = std::ptr::null_mut(); |
194 | match unsafe { ffi::compat::PyWeakref_GetRef(self.as_ptr(), &mut obj) } { |
195 | std::os::raw::c_int::MIN..=-1 => panic!("The 'weakref.ProxyType' (or `weakref.CallableProxyType`) instance should be valid (non-null and actually a weakref reference)" ), |
196 | 0 => None, |
197 | 1..=std::os::raw::c_int::MAX => Some(unsafe { obj.assume_owned_unchecked(self.py()) }), |
198 | } |
199 | } |
200 | } |
201 | |
202 | #[cfg (test)] |
203 | mod tests { |
204 | use crate::exceptions::{PyAttributeError, PyReferenceError, PyTypeError}; |
205 | use crate::types::any::{PyAny, PyAnyMethods}; |
206 | use crate::types::weakref::{PyWeakrefMethods, PyWeakrefProxy}; |
207 | use crate::{Bound, PyResult, Python}; |
208 | |
209 | #[cfg (all(Py_3_13, not(Py_LIMITED_API)))] |
210 | const DEADREF_FIX: Option<&str> = None; |
211 | #[cfg (all(not(Py_3_13), not(Py_LIMITED_API)))] |
212 | const DEADREF_FIX: Option<&str> = Some("NoneType" ); |
213 | |
214 | #[cfg (not(Py_LIMITED_API))] |
215 | fn check_repr( |
216 | reference: &Bound<'_, PyWeakrefProxy>, |
217 | object: &Bound<'_, PyAny>, |
218 | class: Option<&str>, |
219 | ) -> PyResult<()> { |
220 | let repr = reference.repr()?.to_string(); |
221 | |
222 | #[cfg (Py_3_13)] |
223 | let (first_part, second_part) = repr.split_once(';' ).unwrap(); |
224 | #[cfg (not(Py_3_13))] |
225 | let (first_part, second_part) = repr.split_once(" to " ).unwrap(); |
226 | |
227 | { |
228 | let (msg, addr) = first_part.split_once("0x" ).unwrap(); |
229 | |
230 | assert_eq!(msg, "<weakproxy at " ); |
231 | assert!(addr |
232 | .to_lowercase() |
233 | .contains(format!("{:x?}" , reference.as_ptr()).split_at(2).1)); |
234 | } |
235 | |
236 | if let Some(class) = class.or(DEADREF_FIX) { |
237 | let (msg, addr) = second_part.split_once("0x" ).unwrap(); |
238 | |
239 | // Avoids not succeeding at unreliable quotation (Python 3.13-dev adds ' around classname without documenting) |
240 | #[cfg (Py_3_13)] |
241 | assert!(msg.starts_with(" to '" )); |
242 | assert!(msg.contains(class)); |
243 | assert!(msg.ends_with(" at " )); |
244 | |
245 | assert!(addr |
246 | .to_lowercase() |
247 | .contains(format!("{:x?}" , object.as_ptr()).split_at(2).1)); |
248 | } else { |
249 | assert!(second_part.contains("dead" )); |
250 | } |
251 | |
252 | Ok(()) |
253 | } |
254 | |
255 | mod proxy { |
256 | use super::*; |
257 | |
258 | #[cfg (all(not(Py_LIMITED_API), Py_3_10))] |
259 | const CLASS_NAME: &str = "'weakref.ProxyType'" ; |
260 | #[cfg (all(not(Py_LIMITED_API), not(Py_3_10)))] |
261 | const CLASS_NAME: &str = "'weakproxy'" ; |
262 | |
263 | mod python_class { |
264 | use super::*; |
265 | use crate::ffi; |
266 | use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType}; |
267 | use std::ptr; |
268 | |
269 | fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> { |
270 | let globals = PyDict::new(py); |
271 | py.run(ffi::c_str!("class A: \n pass \n" ), Some(&globals), None)?; |
272 | py.eval(ffi::c_str!("A" ), Some(&globals), None) |
273 | .downcast_into::<PyType>() |
274 | } |
275 | |
276 | #[test ] |
277 | fn test_weakref_proxy_behavior() -> PyResult<()> { |
278 | Python::with_gil(|py| { |
279 | let class = get_type(py)?; |
280 | let object = class.call0()?; |
281 | let reference = PyWeakrefProxy::new(&object)?; |
282 | |
283 | assert!(!reference.is(&object)); |
284 | assert!(reference.upgrade().unwrap().is(&object)); |
285 | |
286 | #[cfg (not(Py_LIMITED_API))] |
287 | assert_eq!( |
288 | reference.get_type().to_string(), |
289 | format!("<class {}>" , CLASS_NAME) |
290 | ); |
291 | |
292 | assert_eq!(reference.getattr("__class__" )?.to_string(), "<class 'A'>" ); |
293 | #[cfg (not(Py_LIMITED_API))] |
294 | check_repr(&reference, &object, Some("A" ))?; |
295 | |
296 | assert!(reference |
297 | .getattr("__callback__" ) |
298 | .err() |
299 | .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py))); |
300 | |
301 | assert!(reference.call0().err().map_or(false, |err| { |
302 | let result = err.is_instance_of::<PyTypeError>(py); |
303 | #[cfg(not(Py_LIMITED_API))] |
304 | let result = result |
305 | & (err.value(py).to_string() |
306 | == format!("{} object is not callable" , CLASS_NAME)); |
307 | result |
308 | })); |
309 | |
310 | drop(object); |
311 | |
312 | assert!(reference.upgrade().is_none()); |
313 | assert!(reference |
314 | .getattr("__class__" ) |
315 | .err() |
316 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
317 | #[cfg (not(Py_LIMITED_API))] |
318 | check_repr(&reference, py.None().bind(py), None)?; |
319 | |
320 | assert!(reference |
321 | .getattr("__callback__" ) |
322 | .err() |
323 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
324 | |
325 | assert!(reference.call0().err().map_or(false, |err| { |
326 | let result = err.is_instance_of::<PyTypeError>(py); |
327 | #[cfg(not(Py_LIMITED_API))] |
328 | let result = result |
329 | & (err.value(py).to_string() |
330 | == format!("{} object is not callable" , CLASS_NAME)); |
331 | result |
332 | })); |
333 | |
334 | Ok(()) |
335 | }) |
336 | } |
337 | |
338 | #[test ] |
339 | fn test_weakref_upgrade_as() -> PyResult<()> { |
340 | Python::with_gil(|py| { |
341 | let class = get_type(py)?; |
342 | let object = class.call0()?; |
343 | let reference = PyWeakrefProxy::new(&object)?; |
344 | |
345 | { |
346 | // This test is a bit weird but ok. |
347 | let obj = reference.upgrade_as::<PyAny>(); |
348 | |
349 | assert!(obj.is_ok()); |
350 | let obj = obj.unwrap(); |
351 | |
352 | assert!(obj.is_some()); |
353 | assert!( |
354 | obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) |
355 | && obj.is_exact_instance(&class)) |
356 | ); |
357 | } |
358 | |
359 | drop(object); |
360 | |
361 | { |
362 | // This test is a bit weird but ok. |
363 | let obj = reference.upgrade_as::<PyAny>(); |
364 | |
365 | assert!(obj.is_ok()); |
366 | let obj = obj.unwrap(); |
367 | |
368 | assert!(obj.is_none()); |
369 | } |
370 | |
371 | Ok(()) |
372 | }) |
373 | } |
374 | |
375 | #[test ] |
376 | fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { |
377 | Python::with_gil(|py| { |
378 | let class = get_type(py)?; |
379 | let object = class.call0()?; |
380 | let reference = PyWeakrefProxy::new(&object)?; |
381 | |
382 | { |
383 | // This test is a bit weird but ok. |
384 | let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() }; |
385 | |
386 | assert!(obj.is_some()); |
387 | assert!( |
388 | obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) |
389 | && obj.is_exact_instance(&class)) |
390 | ); |
391 | } |
392 | |
393 | drop(object); |
394 | |
395 | { |
396 | // This test is a bit weird but ok. |
397 | let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() }; |
398 | |
399 | assert!(obj.is_none()); |
400 | } |
401 | |
402 | Ok(()) |
403 | }) |
404 | } |
405 | |
406 | #[test ] |
407 | fn test_weakref_upgrade() -> PyResult<()> { |
408 | Python::with_gil(|py| { |
409 | let class = get_type(py)?; |
410 | let object = class.call0()?; |
411 | let reference = PyWeakrefProxy::new(&object)?; |
412 | |
413 | assert!(reference.upgrade().is_some()); |
414 | assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); |
415 | |
416 | drop(object); |
417 | |
418 | assert!(reference.upgrade().is_none()); |
419 | |
420 | Ok(()) |
421 | }) |
422 | } |
423 | |
424 | #[test ] |
425 | fn test_weakref_get_object() -> PyResult<()> { |
426 | Python::with_gil(|py| { |
427 | let class = get_type(py)?; |
428 | let object = class.call0()?; |
429 | let reference = PyWeakrefProxy::new(&object)?; |
430 | |
431 | assert!(reference.upgrade().unwrap().is(&object)); |
432 | |
433 | drop(object); |
434 | |
435 | assert!(reference.upgrade().is_none()); |
436 | |
437 | Ok(()) |
438 | }) |
439 | } |
440 | } |
441 | |
442 | // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. |
443 | #[cfg (all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9)))))] |
444 | mod pyo3_pyclass { |
445 | use super::*; |
446 | use crate::{pyclass, Py}; |
447 | use std::ptr; |
448 | |
449 | #[pyclass(weakref, crate = "crate" )] |
450 | struct WeakrefablePyClass {} |
451 | |
452 | #[test ] |
453 | fn test_weakref_proxy_behavior() -> PyResult<()> { |
454 | Python::with_gil(|py| { |
455 | let object: Bound<'_, WeakrefablePyClass> = |
456 | Bound::new(py, WeakrefablePyClass {})?; |
457 | let reference = PyWeakrefProxy::new(&object)?; |
458 | |
459 | assert!(!reference.is(&object)); |
460 | assert!(reference.upgrade().unwrap().is(&object)); |
461 | #[cfg (not(Py_LIMITED_API))] |
462 | assert_eq!( |
463 | reference.get_type().to_string(), |
464 | format!("<class {}>" , CLASS_NAME) |
465 | ); |
466 | |
467 | assert_eq!( |
468 | reference.getattr("__class__" )?.to_string(), |
469 | "<class 'builtins.WeakrefablePyClass'>" |
470 | ); |
471 | #[cfg (not(Py_LIMITED_API))] |
472 | check_repr(&reference, object.as_any(), Some("WeakrefablePyClass" ))?; |
473 | |
474 | assert!(reference |
475 | .getattr("__callback__" ) |
476 | .err() |
477 | .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py))); |
478 | |
479 | assert!(reference.call0().err().map_or(false, |err| { |
480 | let result = err.is_instance_of::<PyTypeError>(py); |
481 | #[cfg(not(Py_LIMITED_API))] |
482 | let result = result |
483 | & (err.value(py).to_string() |
484 | == format!("{} object is not callable" , CLASS_NAME)); |
485 | result |
486 | })); |
487 | |
488 | drop(object); |
489 | |
490 | assert!(reference.upgrade().is_none()); |
491 | assert!(reference |
492 | .getattr("__class__" ) |
493 | .err() |
494 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
495 | #[cfg (not(Py_LIMITED_API))] |
496 | check_repr(&reference, py.None().bind(py), None)?; |
497 | |
498 | assert!(reference |
499 | .getattr("__callback__" ) |
500 | .err() |
501 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
502 | |
503 | assert!(reference.call0().err().map_or(false, |err| { |
504 | let result = err.is_instance_of::<PyTypeError>(py); |
505 | #[cfg(not(Py_LIMITED_API))] |
506 | let result = result |
507 | & (err.value(py).to_string() |
508 | == format!("{} object is not callable" , CLASS_NAME)); |
509 | result |
510 | })); |
511 | |
512 | Ok(()) |
513 | }) |
514 | } |
515 | |
516 | #[test ] |
517 | fn test_weakref_upgrade_as() -> PyResult<()> { |
518 | Python::with_gil(|py| { |
519 | let object = Py::new(py, WeakrefablePyClass {})?; |
520 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
521 | |
522 | { |
523 | let obj = reference.upgrade_as::<WeakrefablePyClass>(); |
524 | |
525 | assert!(obj.is_ok()); |
526 | let obj = obj.unwrap(); |
527 | |
528 | assert!(obj.is_some()); |
529 | assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); |
530 | } |
531 | |
532 | drop(object); |
533 | |
534 | { |
535 | let obj = reference.upgrade_as::<WeakrefablePyClass>(); |
536 | |
537 | assert!(obj.is_ok()); |
538 | let obj = obj.unwrap(); |
539 | |
540 | assert!(obj.is_none()); |
541 | } |
542 | |
543 | Ok(()) |
544 | }) |
545 | } |
546 | |
547 | #[test ] |
548 | fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { |
549 | Python::with_gil(|py| { |
550 | let object = Py::new(py, WeakrefablePyClass {})?; |
551 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
552 | |
553 | { |
554 | let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() }; |
555 | |
556 | assert!(obj.is_some()); |
557 | assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); |
558 | } |
559 | |
560 | drop(object); |
561 | |
562 | { |
563 | let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() }; |
564 | |
565 | assert!(obj.is_none()); |
566 | } |
567 | |
568 | Ok(()) |
569 | }) |
570 | } |
571 | |
572 | #[test ] |
573 | fn test_weakref_upgrade() -> PyResult<()> { |
574 | Python::with_gil(|py| { |
575 | let object = Py::new(py, WeakrefablePyClass {})?; |
576 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
577 | |
578 | assert!(reference.upgrade().is_some()); |
579 | assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); |
580 | |
581 | drop(object); |
582 | |
583 | assert!(reference.upgrade().is_none()); |
584 | |
585 | Ok(()) |
586 | }) |
587 | } |
588 | |
589 | #[test ] |
590 | #[allow (deprecated)] |
591 | fn test_weakref_get_object() -> PyResult<()> { |
592 | Python::with_gil(|py| { |
593 | let object = Py::new(py, WeakrefablePyClass {})?; |
594 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
595 | |
596 | assert!(reference.get_object().is(&object)); |
597 | |
598 | drop(object); |
599 | |
600 | assert!(reference.get_object().is_none()); |
601 | |
602 | Ok(()) |
603 | }) |
604 | } |
605 | } |
606 | } |
607 | |
608 | mod callable_proxy { |
609 | use super::*; |
610 | |
611 | #[cfg (all(not(Py_LIMITED_API), Py_3_10))] |
612 | const CLASS_NAME: &str = "<class 'weakref.CallableProxyType'>" ; |
613 | #[cfg (all(not(Py_LIMITED_API), not(Py_3_10)))] |
614 | const CLASS_NAME: &str = "<class 'weakcallableproxy'>" ; |
615 | |
616 | mod python_class { |
617 | use super::*; |
618 | use crate::ffi; |
619 | use crate::{py_result_ext::PyResultExt, types::PyDict, types::PyType}; |
620 | use std::ptr; |
621 | |
622 | fn get_type(py: Python<'_>) -> PyResult<Bound<'_, PyType>> { |
623 | let globals = PyDict::new(py); |
624 | py.run( |
625 | ffi::c_str!("class A: \n def __call__(self): \n return 'This class is callable!' \n" ), |
626 | Some(&globals), |
627 | None, |
628 | )?; |
629 | py.eval(ffi::c_str!("A" ), Some(&globals), None) |
630 | .downcast_into::<PyType>() |
631 | } |
632 | |
633 | #[test ] |
634 | fn test_weakref_proxy_behavior() -> PyResult<()> { |
635 | Python::with_gil(|py| { |
636 | let class = get_type(py)?; |
637 | let object = class.call0()?; |
638 | let reference = PyWeakrefProxy::new(&object)?; |
639 | |
640 | assert!(!reference.is(&object)); |
641 | assert!(reference.upgrade().unwrap().is(&object)); |
642 | #[cfg (not(Py_LIMITED_API))] |
643 | assert_eq!(reference.get_type().to_string(), CLASS_NAME); |
644 | |
645 | assert_eq!(reference.getattr("__class__" )?.to_string(), "<class 'A'>" ); |
646 | #[cfg (not(Py_LIMITED_API))] |
647 | check_repr(&reference, &object, Some("A" ))?; |
648 | |
649 | assert!(reference |
650 | .getattr("__callback__" ) |
651 | .err() |
652 | .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py))); |
653 | |
654 | assert_eq!(reference.call0()?.to_string(), "This class is callable!" ); |
655 | |
656 | drop(object); |
657 | |
658 | assert!(reference.upgrade().is_none()); |
659 | assert!(reference |
660 | .getattr("__class__" ) |
661 | .err() |
662 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
663 | #[cfg (not(Py_LIMITED_API))] |
664 | check_repr(&reference, py.None().bind(py), None)?; |
665 | |
666 | assert!(reference |
667 | .getattr("__callback__" ) |
668 | .err() |
669 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
670 | |
671 | assert!(reference |
672 | .call0() |
673 | .err() |
674 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py) |
675 | & (err.value(py).to_string() |
676 | == "weakly-referenced object no longer exists" ))); |
677 | |
678 | Ok(()) |
679 | }) |
680 | } |
681 | |
682 | #[test ] |
683 | fn test_weakref_upgrade_as() -> PyResult<()> { |
684 | Python::with_gil(|py| { |
685 | let class = get_type(py)?; |
686 | let object = class.call0()?; |
687 | let reference = PyWeakrefProxy::new(&object)?; |
688 | |
689 | { |
690 | // This test is a bit weird but ok. |
691 | let obj = reference.upgrade_as::<PyAny>(); |
692 | |
693 | assert!(obj.is_ok()); |
694 | let obj = obj.unwrap(); |
695 | |
696 | assert!(obj.is_some()); |
697 | assert!( |
698 | obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) |
699 | && obj.is_exact_instance(&class)) |
700 | ); |
701 | } |
702 | |
703 | drop(object); |
704 | |
705 | { |
706 | // This test is a bit weird but ok. |
707 | let obj = reference.upgrade_as::<PyAny>(); |
708 | |
709 | assert!(obj.is_ok()); |
710 | let obj = obj.unwrap(); |
711 | |
712 | assert!(obj.is_none()); |
713 | } |
714 | |
715 | Ok(()) |
716 | }) |
717 | } |
718 | |
719 | #[test ] |
720 | fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { |
721 | Python::with_gil(|py| { |
722 | let class = get_type(py)?; |
723 | let object = class.call0()?; |
724 | let reference = PyWeakrefProxy::new(&object)?; |
725 | |
726 | { |
727 | // This test is a bit weird but ok. |
728 | let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() }; |
729 | |
730 | assert!(obj.is_some()); |
731 | assert!( |
732 | obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()) |
733 | && obj.is_exact_instance(&class)) |
734 | ); |
735 | } |
736 | |
737 | drop(object); |
738 | |
739 | { |
740 | // This test is a bit weird but ok. |
741 | let obj = unsafe { reference.upgrade_as_unchecked::<PyAny>() }; |
742 | |
743 | assert!(obj.is_none()); |
744 | } |
745 | |
746 | Ok(()) |
747 | }) |
748 | } |
749 | |
750 | #[test ] |
751 | fn test_weakref_upgrade() -> PyResult<()> { |
752 | Python::with_gil(|py| { |
753 | let class = get_type(py)?; |
754 | let object = class.call0()?; |
755 | let reference = PyWeakrefProxy::new(&object)?; |
756 | |
757 | assert!(reference.upgrade().is_some()); |
758 | assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); |
759 | |
760 | drop(object); |
761 | |
762 | assert!(reference.upgrade().is_none()); |
763 | |
764 | Ok(()) |
765 | }) |
766 | } |
767 | |
768 | #[test ] |
769 | #[allow (deprecated)] |
770 | fn test_weakref_get_object() -> PyResult<()> { |
771 | Python::with_gil(|py| { |
772 | let class = get_type(py)?; |
773 | let object = class.call0()?; |
774 | let reference = PyWeakrefProxy::new(&object)?; |
775 | |
776 | assert!(reference.get_object().is(&object)); |
777 | |
778 | drop(object); |
779 | |
780 | assert!(reference.get_object().is_none()); |
781 | |
782 | Ok(()) |
783 | }) |
784 | } |
785 | } |
786 | |
787 | // under 'abi3-py37' and 'abi3-py38' PyClass cannot be weakreferencable. |
788 | #[cfg (all(feature = "macros" , not(all(Py_LIMITED_API, not(Py_3_9)))))] |
789 | mod pyo3_pyclass { |
790 | use super::*; |
791 | use crate::{pyclass, pymethods , Py}; |
792 | use std::ptr; |
793 | |
794 | #[pyclass(weakref, crate = "crate" )] |
795 | struct WeakrefablePyClass {} |
796 | |
797 | #[pymethods(crate = "crate" )] |
798 | impl WeakrefablePyClass { |
799 | fn __call__(&self) -> &str { |
800 | "This class is callable!" |
801 | } |
802 | } |
803 | |
804 | #[test ] |
805 | fn test_weakref_proxy_behavior() -> PyResult<()> { |
806 | Python::with_gil(|py| { |
807 | let object: Bound<'_, WeakrefablePyClass> = |
808 | Bound::new(py, WeakrefablePyClass {})?; |
809 | let reference = PyWeakrefProxy::new(&object)?; |
810 | |
811 | assert!(!reference.is(&object)); |
812 | assert!(reference.upgrade().unwrap().is(&object)); |
813 | #[cfg (not(Py_LIMITED_API))] |
814 | assert_eq!(reference.get_type().to_string(), CLASS_NAME); |
815 | |
816 | assert_eq!( |
817 | reference.getattr("__class__" )?.to_string(), |
818 | "<class 'builtins.WeakrefablePyClass'>" |
819 | ); |
820 | #[cfg (not(Py_LIMITED_API))] |
821 | check_repr(&reference, object.as_any(), Some("WeakrefablePyClass" ))?; |
822 | |
823 | assert!(reference |
824 | .getattr("__callback__" ) |
825 | .err() |
826 | .map_or(false, |err| err.is_instance_of::<PyAttributeError>(py))); |
827 | |
828 | assert_eq!(reference.call0()?.to_string(), "This class is callable!" ); |
829 | |
830 | drop(object); |
831 | |
832 | assert!(reference.upgrade().is_none()); |
833 | assert!(reference |
834 | .getattr("__class__" ) |
835 | .err() |
836 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
837 | #[cfg (not(Py_LIMITED_API))] |
838 | check_repr(&reference, py.None().bind(py), None)?; |
839 | |
840 | assert!(reference |
841 | .getattr("__callback__" ) |
842 | .err() |
843 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py))); |
844 | |
845 | assert!(reference |
846 | .call0() |
847 | .err() |
848 | .map_or(false, |err| err.is_instance_of::<PyReferenceError>(py) |
849 | & (err.value(py).to_string() |
850 | == "weakly-referenced object no longer exists" ))); |
851 | |
852 | Ok(()) |
853 | }) |
854 | } |
855 | |
856 | #[test ] |
857 | fn test_weakref_upgrade_as() -> PyResult<()> { |
858 | Python::with_gil(|py| { |
859 | let object = Py::new(py, WeakrefablePyClass {})?; |
860 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
861 | |
862 | { |
863 | let obj = reference.upgrade_as::<WeakrefablePyClass>(); |
864 | |
865 | assert!(obj.is_ok()); |
866 | let obj = obj.unwrap(); |
867 | |
868 | assert!(obj.is_some()); |
869 | assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); |
870 | } |
871 | |
872 | drop(object); |
873 | |
874 | { |
875 | let obj = reference.upgrade_as::<WeakrefablePyClass>(); |
876 | |
877 | assert!(obj.is_ok()); |
878 | let obj = obj.unwrap(); |
879 | |
880 | assert!(obj.is_none()); |
881 | } |
882 | |
883 | Ok(()) |
884 | }) |
885 | } |
886 | |
887 | #[test ] |
888 | fn test_weakref_upgrade_as_unchecked() -> PyResult<()> { |
889 | Python::with_gil(|py| { |
890 | let object = Py::new(py, WeakrefablePyClass {})?; |
891 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
892 | |
893 | { |
894 | let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() }; |
895 | |
896 | assert!(obj.is_some()); |
897 | assert!(obj.map_or(false, |obj| ptr::eq(obj.as_ptr(), object.as_ptr()))); |
898 | } |
899 | |
900 | drop(object); |
901 | |
902 | { |
903 | let obj = unsafe { reference.upgrade_as_unchecked::<WeakrefablePyClass>() }; |
904 | |
905 | assert!(obj.is_none()); |
906 | } |
907 | |
908 | Ok(()) |
909 | }) |
910 | } |
911 | |
912 | #[test ] |
913 | fn test_weakref_upgrade() -> PyResult<()> { |
914 | Python::with_gil(|py| { |
915 | let object = Py::new(py, WeakrefablePyClass {})?; |
916 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
917 | |
918 | assert!(reference.upgrade().is_some()); |
919 | assert!(reference.upgrade().map_or(false, |obj| obj.is(&object))); |
920 | |
921 | drop(object); |
922 | |
923 | assert!(reference.upgrade().is_none()); |
924 | |
925 | Ok(()) |
926 | }) |
927 | } |
928 | |
929 | #[test ] |
930 | #[allow (deprecated)] |
931 | fn test_weakref_get_object() -> PyResult<()> { |
932 | Python::with_gil(|py| { |
933 | let object = Py::new(py, WeakrefablePyClass {})?; |
934 | let reference = PyWeakrefProxy::new(object.bind(py))?; |
935 | |
936 | assert!(reference.get_object().is(&object)); |
937 | |
938 | drop(object); |
939 | |
940 | assert!(reference.get_object().is_none()); |
941 | |
942 | Ok(()) |
943 | }) |
944 | } |
945 | } |
946 | } |
947 | } |
948 | |