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#[cfg(RustPython)]
11use crate::{
12 sync::PyOnceLock,
13 types::{PyType, PyTypeMethods},
14 Py,
15};
16
17#[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 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#[doc(alias = "PyMappingProxy")]
62pub trait PyMappingProxyMethods<'py, 'a>: crate::sealed::Sealed {
63 fn is_empty(&self) -> PyResult<bool>;
65
66 fn keys(&self) -> PyResult<Bound<'py, PyList>>;
68
69 fn values(&self) -> PyResult<Bound<'py, PyList>>;
71
72 fn items(&self) -> PyResult<Bound<'py, PyList>>;
74
75 fn as_mapping(&self) -> &Bound<'py, PyMapping>;
77
78 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 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 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 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}