Skip to main content

pyo3/types/
mappingproxy.rs

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