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