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
11use std::ffi::c_int;
12
13#[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 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#[doc(alias = "PyMappingProxy")]
50pub trait PyMappingProxyMethods<'py, 'a>: crate::sealed::Sealed {
51 fn is_empty(&self) -> PyResult<bool>;
53
54 fn keys(&self) -> PyResult<Bound<'py, PyList>>;
56
57 fn values(&self) -> PyResult<Bound<'py, PyList>>;
59
60 fn items(&self) -> PyResult<Bound<'py, PyList>>;
62
63 fn as_mapping(&self) -> &Bound<'py, PyMapping>;
65
66 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 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 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 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}