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