1 | // Copyright (c) 2017-present PyO3 Project and Contributors |
2 | |
3 | use super::PyMapping; |
4 | use crate::err::PyResult; |
5 | use crate::ffi_ptr_ext::FfiPtrExt; |
6 | use crate::instance::Bound; |
7 | use crate::types::any::PyAnyMethods; |
8 | use crate::types::{PyAny, PyIterator, PyList}; |
9 | use crate::{ffi, Python}; |
10 | |
11 | use std::os::raw::c_int; |
12 | |
13 | /// Represents a Python `mappingproxy`. |
14 | #[repr (transparent)] |
15 | pub struct PyMappingProxy(PyAny); |
16 | |
17 | #[inline ] |
18 | unsafe fn dict_proxy_check(op: *mut ffi::PyObject) -> c_int { |
19 | unsafe { ffi::Py_IS_TYPE(ob:op, tp:std::ptr::addr_of_mut!(ffi::PyDictProxy_Type)) } |
20 | } |
21 | |
22 | pyobject_native_type_core!( |
23 | PyMappingProxy, |
24 | pyobject_native_static_type_object!(ffi::PyDictProxy_Type), |
25 | #checkfunction=dict_proxy_check |
26 | ); |
27 | |
28 | impl PyMappingProxy { |
29 | /// Creates a mappingproxy from an object. |
30 | pub fn new<'py>( |
31 | py: Python<'py>, |
32 | elements: &Bound<'py, PyMapping>, |
33 | ) -> Bound<'py, PyMappingProxy> { |
34 | unsafe { |
35 | ffiBound<'_, PyAny>::PyDictProxy_New(arg1:elements.as_ptr()) |
36 | .assume_owned(py) |
37 | .downcast_into_unchecked() |
38 | } |
39 | } |
40 | } |
41 | |
42 | /// Implementation of functionality for [`PyMappingProxy`]. |
43 | /// |
44 | /// These methods are defined for the `Bound<'py, PyMappingProxy>` smart pointer, so to use method call |
45 | /// syntax these methods are separated into a trait, because stable Rust does not yet support |
46 | /// `arbitrary_self_types`. |
47 | #[doc (alias = "PyMappingProxy" )] |
48 | pub trait PyMappingProxyMethods<'py, 'a>: crate::sealed::Sealed { |
49 | /// Checks if the mappingproxy is empty, i.e. `len(self) == 0`. |
50 | fn is_empty(&self) -> PyResult<bool>; |
51 | |
52 | /// Returns a list containing all keys in the mapping. |
53 | fn keys(&self) -> PyResult<Bound<'py, PyList>>; |
54 | |
55 | /// Returns a list containing all values in the mapping. |
56 | fn values(&self) -> PyResult<Bound<'py, PyList>>; |
57 | |
58 | /// Returns a list of tuples of all (key, value) pairs in the mapping. |
59 | fn items(&self) -> PyResult<Bound<'py, PyList>>; |
60 | |
61 | /// Returns `self` cast as a `PyMapping`. |
62 | fn as_mapping(&self) -> &Bound<'py, PyMapping>; |
63 | |
64 | /// Takes an object and returns an iterator for it. Returns an error if the object is not |
65 | /// iterable. |
66 | fn try_iter(&'a self) -> PyResult<BoundMappingProxyIterator<'py, 'a>>; |
67 | } |
68 | |
69 | impl<'py, 'a> PyMappingProxyMethods<'py, 'a> for Bound<'py, PyMappingProxy> { |
70 | fn is_empty(&self) -> PyResult<bool> { |
71 | Ok(self.len()? == 0) |
72 | } |
73 | |
74 | #[inline ] |
75 | fn keys(&self) -> PyResult<Bound<'py, PyList>> { |
76 | unsafe { |
77 | Ok(ffi::PyMapping_Keys(self.as_ptr()) |
78 | .assume_owned_or_err(self.py())? |
79 | .downcast_into_unchecked()) |
80 | } |
81 | } |
82 | |
83 | #[inline ] |
84 | fn values(&self) -> PyResult<Bound<'py, PyList>> { |
85 | unsafe { |
86 | Ok(ffi::PyMapping_Values(self.as_ptr()) |
87 | .assume_owned_or_err(self.py())? |
88 | .downcast_into_unchecked()) |
89 | } |
90 | } |
91 | |
92 | #[inline ] |
93 | fn items(&self) -> PyResult<Bound<'py, PyList>> { |
94 | unsafe { |
95 | Ok(ffi::PyMapping_Items(self.as_ptr()) |
96 | .assume_owned_or_err(self.py())? |
97 | .downcast_into_unchecked()) |
98 | } |
99 | } |
100 | |
101 | fn as_mapping(&self) -> &Bound<'py, PyMapping> { |
102 | unsafe { self.downcast_unchecked() } |
103 | } |
104 | |
105 | fn try_iter(&'a self) -> PyResult<BoundMappingProxyIterator<'py, 'a>> { |
106 | Ok(BoundMappingProxyIterator { |
107 | iterator: PyIterator::from_object(self)?, |
108 | mappingproxy: self, |
109 | }) |
110 | } |
111 | } |
112 | |
113 | pub struct BoundMappingProxyIterator<'py, 'a> { |
114 | iterator: Bound<'py, PyIterator>, |
115 | mappingproxy: &'a Bound<'py, PyMappingProxy>, |
116 | } |
117 | |
118 | impl<'py> Iterator for BoundMappingProxyIterator<'py, '_> { |
119 | type Item = PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)>; |
120 | |
121 | #[inline ] |
122 | fn next(&mut self) -> Option<Self::Item> { |
123 | self.iterator.next().map(|key: Result, …>| match key { |
124 | Ok(key: Bound<'py, PyAny>) => match self.mappingproxy.get_item(&key) { |
125 | Ok(value: Bound<'py, PyAny>) => Ok((key, value)), |
126 | Err(e: PyErr) => Err(e), |
127 | }, |
128 | Err(e: PyErr) => Err(e), |
129 | }) |
130 | } |
131 | } |
132 | |
133 | #[cfg (test)] |
134 | mod tests { |
135 | |
136 | use super::*; |
137 | use crate::types::dict::*; |
138 | use crate::Python; |
139 | use crate::{ |
140 | exceptions::PyKeyError, |
141 | types::{PyInt, PyTuple}, |
142 | }; |
143 | use std::collections::{BTreeMap, HashMap}; |
144 | |
145 | #[test ] |
146 | fn test_new() { |
147 | Python::with_gil(|py| { |
148 | let pydict = [(7, 32)].into_py_dict(py).unwrap(); |
149 | let mappingproxy = PyMappingProxy::new(py, pydict.as_mapping()); |
150 | mappingproxy.get_item(7i32).unwrap(); |
151 | assert_eq!( |
152 | 32, |
153 | mappingproxy |
154 | .get_item(7i32) |
155 | .unwrap() |
156 | .extract::<i32>() |
157 | .unwrap() |
158 | ); |
159 | assert!(mappingproxy |
160 | .get_item(8i32) |
161 | .unwrap_err() |
162 | .is_instance_of::<PyKeyError>(py)); |
163 | }); |
164 | } |
165 | |
166 | #[test ] |
167 | fn test_len() { |
168 | Python::with_gil(|py| { |
169 | let mut v = HashMap::new(); |
170 | let dict = v.clone().into_py_dict(py).unwrap(); |
171 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
172 | assert_eq!(mappingproxy.len().unwrap(), 0); |
173 | v.insert(7, 32); |
174 | let dict2 = v.clone().into_py_dict(py).unwrap(); |
175 | let mp2 = PyMappingProxy::new(py, dict2.as_mapping()); |
176 | assert_eq!(mp2.len().unwrap(), 1); |
177 | }); |
178 | } |
179 | |
180 | #[test ] |
181 | fn test_contains() { |
182 | Python::with_gil(|py| { |
183 | let mut v = HashMap::new(); |
184 | v.insert(7, 32); |
185 | let dict = v.clone().into_py_dict(py).unwrap(); |
186 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
187 | assert!(mappingproxy.contains(7i32).unwrap()); |
188 | assert!(!mappingproxy.contains(8i32).unwrap()); |
189 | }); |
190 | } |
191 | |
192 | #[test ] |
193 | fn test_get_item() { |
194 | Python::with_gil(|py| { |
195 | let mut v = HashMap::new(); |
196 | v.insert(7, 32); |
197 | let dict = v.clone().into_py_dict(py).unwrap(); |
198 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
199 | assert_eq!( |
200 | 32, |
201 | mappingproxy |
202 | .get_item(7i32) |
203 | .unwrap() |
204 | .extract::<i32>() |
205 | .unwrap() |
206 | ); |
207 | assert!(mappingproxy |
208 | .get_item(8i32) |
209 | .unwrap_err() |
210 | .is_instance_of::<PyKeyError>(py)); |
211 | }); |
212 | } |
213 | |
214 | #[test ] |
215 | fn test_set_item_refcnt() { |
216 | Python::with_gil(|py| { |
217 | let cnt; |
218 | { |
219 | let none = py.None(); |
220 | cnt = none.get_refcnt(py); |
221 | let dict = [(10, none)].into_py_dict(py).unwrap(); |
222 | let _mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
223 | } |
224 | { |
225 | assert_eq!(cnt, py.None().get_refcnt(py)); |
226 | } |
227 | }); |
228 | } |
229 | |
230 | #[test ] |
231 | fn test_isempty() { |
232 | Python::with_gil(|py| { |
233 | let map: HashMap<usize, usize> = HashMap::new(); |
234 | let dict = map.into_py_dict(py).unwrap(); |
235 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
236 | assert!(mappingproxy.is_empty().unwrap()); |
237 | }); |
238 | } |
239 | |
240 | #[test ] |
241 | fn test_keys() { |
242 | Python::with_gil(|py| { |
243 | let mut v = HashMap::new(); |
244 | v.insert(7, 32); |
245 | v.insert(8, 42); |
246 | v.insert(9, 123); |
247 | let dict = v.into_py_dict(py).unwrap(); |
248 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
249 | // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. |
250 | let mut key_sum = 0; |
251 | for el in mappingproxy.keys().unwrap().try_iter().unwrap() { |
252 | key_sum += el.unwrap().extract::<i32>().unwrap(); |
253 | } |
254 | assert_eq!(7 + 8 + 9, key_sum); |
255 | }); |
256 | } |
257 | |
258 | #[test ] |
259 | fn test_values() { |
260 | Python::with_gil(|py| { |
261 | let mut v: HashMap<i32, i32> = HashMap::new(); |
262 | v.insert(7, 32); |
263 | v.insert(8, 42); |
264 | v.insert(9, 123); |
265 | let dict = v.into_py_dict(py).unwrap(); |
266 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
267 | // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. |
268 | let mut values_sum = 0; |
269 | for el in mappingproxy.values().unwrap().try_iter().unwrap() { |
270 | values_sum += el.unwrap().extract::<i32>().unwrap(); |
271 | } |
272 | assert_eq!(32 + 42 + 123, values_sum); |
273 | }); |
274 | } |
275 | |
276 | #[test ] |
277 | fn test_items() { |
278 | Python::with_gil(|py| { |
279 | let mut v = HashMap::new(); |
280 | v.insert(7, 32); |
281 | v.insert(8, 42); |
282 | v.insert(9, 123); |
283 | let dict = v.into_py_dict(py).unwrap(); |
284 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
285 | // Can't just compare against a vector of tuples since we don't have a guaranteed ordering. |
286 | let mut key_sum = 0; |
287 | let mut value_sum = 0; |
288 | for res in mappingproxy.items().unwrap().try_iter().unwrap() { |
289 | let el = res.unwrap(); |
290 | let tuple = el.downcast::<PyTuple>().unwrap(); |
291 | key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap(); |
292 | value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap(); |
293 | } |
294 | assert_eq!(7 + 8 + 9, key_sum); |
295 | assert_eq!(32 + 42 + 123, value_sum); |
296 | }); |
297 | } |
298 | |
299 | #[test ] |
300 | fn test_iter() { |
301 | Python::with_gil(|py| { |
302 | let mut v = HashMap::new(); |
303 | v.insert(7, 32); |
304 | v.insert(8, 42); |
305 | v.insert(9, 123); |
306 | let dict = v.into_py_dict(py).unwrap(); |
307 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
308 | let mut key_sum = 0; |
309 | let mut value_sum = 0; |
310 | for res in mappingproxy.try_iter().unwrap() { |
311 | let (key, value) = res.unwrap(); |
312 | key_sum += key.extract::<i32>().unwrap(); |
313 | value_sum += value.extract::<i32>().unwrap(); |
314 | } |
315 | assert_eq!(7 + 8 + 9, key_sum); |
316 | assert_eq!(32 + 42 + 123, value_sum); |
317 | }); |
318 | } |
319 | |
320 | #[test ] |
321 | fn test_hashmap_into_python() { |
322 | Python::with_gil(|py| { |
323 | let mut map = HashMap::<i32, i32>::new(); |
324 | map.insert(1, 1); |
325 | |
326 | let dict = map.clone().into_py_dict(py).unwrap(); |
327 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
328 | |
329 | assert_eq!(py_map.len().unwrap(), 1); |
330 | assert_eq!(py_map.get_item(1).unwrap().extract::<i32>().unwrap(), 1); |
331 | }); |
332 | } |
333 | |
334 | #[test ] |
335 | fn test_hashmap_into_mappingproxy() { |
336 | Python::with_gil(|py| { |
337 | let mut map = HashMap::<i32, i32>::new(); |
338 | map.insert(1, 1); |
339 | |
340 | let dict = map.clone().into_py_dict(py).unwrap(); |
341 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
342 | |
343 | assert_eq!(py_map.len().unwrap(), 1); |
344 | assert_eq!(py_map.get_item(1).unwrap().extract::<i32>().unwrap(), 1); |
345 | }); |
346 | } |
347 | |
348 | #[test ] |
349 | fn test_btreemap_into_py() { |
350 | Python::with_gil(|py| { |
351 | let mut map = BTreeMap::<i32, i32>::new(); |
352 | map.insert(1, 1); |
353 | |
354 | let dict = map.clone().into_py_dict(py).unwrap(); |
355 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
356 | |
357 | assert_eq!(py_map.len().unwrap(), 1); |
358 | assert_eq!(py_map.get_item(1).unwrap().extract::<i32>().unwrap(), 1); |
359 | }); |
360 | } |
361 | |
362 | #[test ] |
363 | fn test_btreemap_into_mappingproxy() { |
364 | Python::with_gil(|py| { |
365 | let mut map = BTreeMap::<i32, i32>::new(); |
366 | map.insert(1, 1); |
367 | |
368 | let dict = map.clone().into_py_dict(py).unwrap(); |
369 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
370 | |
371 | assert_eq!(py_map.len().unwrap(), 1); |
372 | assert_eq!(py_map.get_item(1).unwrap().extract::<i32>().unwrap(), 1); |
373 | }); |
374 | } |
375 | |
376 | #[test ] |
377 | fn test_vec_into_mappingproxy() { |
378 | Python::with_gil(|py| { |
379 | let vec = vec![("a" , 1), ("b" , 2), ("c" , 3)]; |
380 | let dict = vec.clone().into_py_dict(py).unwrap(); |
381 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
382 | |
383 | assert_eq!(py_map.len().unwrap(), 3); |
384 | assert_eq!(py_map.get_item("b" ).unwrap().extract::<i32>().unwrap(), 2); |
385 | }); |
386 | } |
387 | |
388 | #[test ] |
389 | fn test_slice_into_mappingproxy() { |
390 | Python::with_gil(|py| { |
391 | let arr = [("a" , 1), ("b" , 2), ("c" , 3)]; |
392 | |
393 | let dict = arr.into_py_dict(py).unwrap(); |
394 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
395 | |
396 | assert_eq!(py_map.len().unwrap(), 3); |
397 | assert_eq!(py_map.get_item("b" ).unwrap().extract::<i32>().unwrap(), 2); |
398 | }); |
399 | } |
400 | |
401 | #[test ] |
402 | fn mappingproxy_as_mapping() { |
403 | Python::with_gil(|py| { |
404 | let mut map = HashMap::<i32, i32>::new(); |
405 | map.insert(1, 1); |
406 | |
407 | let dict = map.clone().into_py_dict(py).unwrap(); |
408 | let py_map = PyMappingProxy::new(py, dict.as_mapping()); |
409 | |
410 | assert_eq!(py_map.as_mapping().len().unwrap(), 1); |
411 | assert_eq!( |
412 | py_map |
413 | .as_mapping() |
414 | .get_item(1) |
415 | .unwrap() |
416 | .extract::<i32>() |
417 | .unwrap(), |
418 | 1 |
419 | ); |
420 | }); |
421 | } |
422 | |
423 | #[cfg (not(PyPy))] |
424 | fn abc_mappingproxy(py: Python<'_>) -> Bound<'_, PyMappingProxy> { |
425 | let mut map = HashMap::<&'static str, i32>::new(); |
426 | map.insert("a" , 1); |
427 | map.insert("b" , 2); |
428 | map.insert("c" , 3); |
429 | let dict = map.clone().into_py_dict(py).unwrap(); |
430 | PyMappingProxy::new(py, dict.as_mapping()) |
431 | } |
432 | |
433 | #[test ] |
434 | #[cfg (not(PyPy))] |
435 | fn mappingproxy_keys_view() { |
436 | Python::with_gil(|py| { |
437 | let mappingproxy = abc_mappingproxy(py); |
438 | let keys = mappingproxy.call_method0("keys" ).unwrap(); |
439 | assert!(keys.is_instance(&py.get_type::<PyDictKeys>()).unwrap()); |
440 | }) |
441 | } |
442 | |
443 | #[test ] |
444 | #[cfg (not(PyPy))] |
445 | fn mappingproxy_values_view() { |
446 | Python::with_gil(|py| { |
447 | let mappingproxy = abc_mappingproxy(py); |
448 | let values = mappingproxy.call_method0("values" ).unwrap(); |
449 | assert!(values.is_instance(&py.get_type::<PyDictValues>()).unwrap()); |
450 | }) |
451 | } |
452 | |
453 | #[test ] |
454 | #[cfg (not(PyPy))] |
455 | fn mappingproxy_items_view() { |
456 | Python::with_gil(|py| { |
457 | let mappingproxy = abc_mappingproxy(py); |
458 | let items = mappingproxy.call_method0("items" ).unwrap(); |
459 | assert!(items.is_instance(&py.get_type::<PyDictItems>()).unwrap()); |
460 | }) |
461 | } |
462 | |
463 | #[test ] |
464 | fn get_value_from_mappingproxy_of_strings() { |
465 | Python::with_gil(|py: Python<'_>| { |
466 | let mut map = HashMap::new(); |
467 | map.insert("first key" .to_string(), "first value" .to_string()); |
468 | map.insert("second key" .to_string(), "second value" .to_string()); |
469 | map.insert("third key" .to_string(), "third value" .to_string()); |
470 | |
471 | let dict = map.clone().into_py_dict(py).unwrap(); |
472 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
473 | |
474 | assert_eq!( |
475 | map.into_iter().collect::<Vec<(String, String)>>(), |
476 | mappingproxy |
477 | .try_iter() |
478 | .unwrap() |
479 | .map(|object| { |
480 | let tuple = object.unwrap(); |
481 | ( |
482 | tuple.0.extract::<String>().unwrap(), |
483 | tuple.1.extract::<String>().unwrap(), |
484 | ) |
485 | }) |
486 | .collect::<Vec<(String, String)>>() |
487 | ); |
488 | }) |
489 | } |
490 | |
491 | #[test ] |
492 | fn get_value_from_mappingproxy_of_integers() { |
493 | Python::with_gil(|py: Python<'_>| { |
494 | const LEN: usize = 10_000; |
495 | let items: Vec<(usize, usize)> = (1..LEN).map(|i| (i, i - 1)).collect(); |
496 | |
497 | let dict = items.clone().into_py_dict(py).unwrap(); |
498 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
499 | |
500 | assert_eq!( |
501 | items, |
502 | mappingproxy |
503 | .clone() |
504 | .try_iter() |
505 | .unwrap() |
506 | .map(|object| { |
507 | let tuple = object.unwrap(); |
508 | ( |
509 | tuple |
510 | .0 |
511 | .downcast::<PyInt>() |
512 | .unwrap() |
513 | .extract::<usize>() |
514 | .unwrap(), |
515 | tuple |
516 | .1 |
517 | .downcast::<PyInt>() |
518 | .unwrap() |
519 | .extract::<usize>() |
520 | .unwrap(), |
521 | ) |
522 | }) |
523 | .collect::<Vec<(usize, usize)>>() |
524 | ); |
525 | for index in 1..LEN { |
526 | assert_eq!( |
527 | mappingproxy |
528 | .clone() |
529 | .get_item(index) |
530 | .unwrap() |
531 | .extract::<usize>() |
532 | .unwrap(), |
533 | index - 1 |
534 | ); |
535 | } |
536 | }) |
537 | } |
538 | |
539 | #[test ] |
540 | fn iter_mappingproxy_nosegv() { |
541 | Python::with_gil(|py| { |
542 | const LEN: usize = 1_000; |
543 | let items = (0..LEN as u64).map(|i| (i, i * 2)); |
544 | |
545 | let dict = items.clone().into_py_dict(py).unwrap(); |
546 | let mappingproxy = PyMappingProxy::new(py, dict.as_mapping()); |
547 | |
548 | let mut sum = 0; |
549 | for result in mappingproxy.try_iter().unwrap() { |
550 | let (k, _v) = result.unwrap(); |
551 | let i: u64 = k.extract().unwrap(); |
552 | sum += i; |
553 | } |
554 | assert_eq!(sum, 499_500); |
555 | }) |
556 | } |
557 | } |
558 | |