1use 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#[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 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#[doc(alias = "PyMappingProxy")]
42pub trait PyMappingProxyMethods<'py, 'a>: crate::sealed::Sealed {
43 fn is_empty(&self) -> PyResult<bool>;
45
46 fn keys(&self) -> PyResult<Bound<'py, PyList>>;
48
49 fn values(&self) -> PyResult<Bound<'py, PyList>>;
51
52 fn items(&self) -> PyResult<Bound<'py, PyList>>;
54
55 fn as_mapping(&self) -> &Bound<'py, PyMapping>;
57
58 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 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 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 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}