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