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
11use std::os::raw::c_int;
12
13/// Represents a Python `mappingproxy`.
14#[repr(transparent)]
15pub struct PyMappingProxy(PyAny);
16
17#[inline]
18unsafe fn dict_proxy_check(op: *mut ffi::PyObject) -> c_int {
19    ffi::Py_IS_TYPE(op, std::ptr::addr_of_mut!(ffi::PyDictProxy_Type))
20}
21
22pyobject_native_type_core!(
23    PyMappingProxy,
24    pyobject_native_static_type_object!(ffi::PyDictProxy_Type),
25    #checkfunction=dict_proxy_check
26);
27
28impl 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            ffi::PyDictProxy_New(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")]
48pub 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
69impl<'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
113pub struct BoundMappingProxyIterator<'py, 'a> {
114    iterator: Bound<'py, PyIterator>,
115    mappingproxy: &'a Bound<'py, PyMappingProxy>,
116}
117
118impl<'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| match key {
124            Ok(key) => match self.mappingproxy.get_item(&key) {
125                Ok(value) => Ok((key, value)),
126                Err(e) => Err(e),
127            },
128            Err(e) => Err(e),
129        })
130    }
131}
132
133#[cfg(test)]
134mod 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}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here