1use crate::err::PyResult;
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::instance::Bound;
4use crate::py_result_ext::PyResultExt;
5use crate::sync::GILOnceCell;
6use crate::type_object::PyTypeInfo;
7use crate::types::any::PyAnyMethods;
8use crate::types::{PyAny, PyDict, PySequence, PyType};
9#[cfg(feature = "gil-refs")]
10use crate::{err::PyDowncastError, PyNativeType};
11use crate::{ffi, Py, PyTypeCheck, Python, ToPyObject};
12
13#[repr(transparent)]
21pub struct PyMapping(PyAny);
22pyobject_native_type_named!(PyMapping);
23pyobject_native_type_extract!(PyMapping);
24
25impl PyMapping {
26 pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
30 let ty = T::type_object_bound(py);
31 get_mapping_abc(py)?.call_method1("register", (ty,))?;
32 Ok(())
33 }
34}
35
36#[cfg(feature = "gil-refs")]
37impl PyMapping {
38 #[inline]
42 pub fn len(&self) -> PyResult<usize> {
43 self.as_borrowed().len()
44 }
45
46 #[inline]
48 pub fn is_empty(&self) -> PyResult<bool> {
49 self.as_borrowed().is_empty()
50 }
51
52 pub fn contains<K>(&self, key: K) -> PyResult<bool>
56 where
57 K: ToPyObject,
58 {
59 self.as_borrowed().contains(key)
60 }
61
62 #[inline]
68 pub fn get_item<K>(&self, key: K) -> PyResult<&PyAny>
69 where
70 K: ToPyObject,
71 {
72 self.as_borrowed().get_item(key).map(Bound::into_gil_ref)
73 }
74
75 #[inline]
79 pub fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
80 where
81 K: ToPyObject,
82 V: ToPyObject,
83 {
84 self.as_borrowed().set_item(key, value)
85 }
86
87 #[inline]
91 pub fn del_item<K>(&self, key: K) -> PyResult<()>
92 where
93 K: ToPyObject,
94 {
95 self.as_borrowed().del_item(key)
96 }
97
98 #[inline]
100 pub fn keys(&self) -> PyResult<&PySequence> {
101 self.as_borrowed().keys().map(Bound::into_gil_ref)
102 }
103
104 #[inline]
106 pub fn values(&self) -> PyResult<&PySequence> {
107 self.as_borrowed().values().map(Bound::into_gil_ref)
108 }
109
110 #[inline]
112 pub fn items(&self) -> PyResult<&PySequence> {
113 self.as_borrowed().items().map(Bound::into_gil_ref)
114 }
115}
116
117#[doc(alias = "PyMapping")]
123pub trait PyMappingMethods<'py>: crate::sealed::Sealed {
124 fn len(&self) -> PyResult<usize>;
128
129 fn is_empty(&self) -> PyResult<bool>;
131
132 fn contains<K>(&self, key: K) -> PyResult<bool>
136 where
137 K: ToPyObject;
138
139 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
145 where
146 K: ToPyObject;
147
148 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
152 where
153 K: ToPyObject,
154 V: ToPyObject;
155
156 fn del_item<K>(&self, key: K) -> PyResult<()>
160 where
161 K: ToPyObject;
162
163 fn keys(&self) -> PyResult<Bound<'py, PySequence>>;
165
166 fn values(&self) -> PyResult<Bound<'py, PySequence>>;
168
169 fn items(&self) -> PyResult<Bound<'py, PySequence>>;
171}
172
173impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
174 #[inline]
175 fn len(&self) -> PyResult<usize> {
176 let v = unsafe { ffi::PyMapping_Size(self.as_ptr()) };
177 crate::err::error_on_minusone(self.py(), v)?;
178 Ok(v as usize)
179 }
180
181 #[inline]
182 fn is_empty(&self) -> PyResult<bool> {
183 self.len().map(|l| l == 0)
184 }
185
186 fn contains<K>(&self, key: K) -> PyResult<bool>
187 where
188 K: ToPyObject,
189 {
190 PyAnyMethods::contains(&**self, key)
191 }
192
193 #[inline]
194 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
195 where
196 K: ToPyObject,
197 {
198 PyAnyMethods::get_item(&**self, key)
199 }
200
201 #[inline]
202 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
203 where
204 K: ToPyObject,
205 V: ToPyObject,
206 {
207 PyAnyMethods::set_item(&**self, key, value)
208 }
209
210 #[inline]
211 fn del_item<K>(&self, key: K) -> PyResult<()>
212 where
213 K: ToPyObject,
214 {
215 PyAnyMethods::del_item(&**self, key)
216 }
217
218 #[inline]
219 fn keys(&self) -> PyResult<Bound<'py, PySequence>> {
220 unsafe {
221 ffi::PyMapping_Keys(self.as_ptr())
222 .assume_owned_or_err(self.py())
223 .downcast_into_unchecked()
224 }
225 }
226
227 #[inline]
228 fn values(&self) -> PyResult<Bound<'py, PySequence>> {
229 unsafe {
230 ffi::PyMapping_Values(self.as_ptr())
231 .assume_owned_or_err(self.py())
232 .downcast_into_unchecked()
233 }
234 }
235
236 #[inline]
237 fn items(&self) -> PyResult<Bound<'py, PySequence>> {
238 unsafe {
239 ffi::PyMapping_Items(self.as_ptr())
240 .assume_owned_or_err(self.py())
241 .downcast_into_unchecked()
242 }
243 }
244}
245
246fn get_mapping_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
247 static MAPPING_ABC: GILOnceCell<Py<PyType>> = GILOnceCell::new();
248
249 MAPPING_ABC.get_or_try_init_type_ref(py, "collections.abc", "Mapping")
250}
251
252impl PyTypeCheck for PyMapping {
253 const NAME: &'static str = "Mapping";
254
255 #[inline]
256 fn type_check(object: &Bound<'_, PyAny>) -> bool {
257 PyDict::is_type_of_bound(object)
260 || get_mapping_abc(object.py())
261 .and_then(|abc| object.is_instance(abc))
262 .unwrap_or_else(|err| {
263 err.write_unraisable_bound(object.py(), Some(&object.as_borrowed()));
264 false
265 })
266 }
267}
268
269#[cfg(feature = "gil-refs")]
270#[allow(deprecated)]
271impl<'v> crate::PyTryFrom<'v> for PyMapping {
272 fn try_from<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
276 let value = value.into();
277
278 if PyMapping::type_check(&value.as_borrowed()) {
279 unsafe { return Ok(value.downcast_unchecked()) }
280 }
281
282 Err(PyDowncastError::new(value, "Mapping"))
283 }
284
285 #[inline]
286 fn try_from_exact<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
287 value.into().downcast()
288 }
289
290 #[inline]
291 unsafe fn try_from_unchecked<V: Into<&'v PyAny>>(value: V) -> &'v PyMapping {
292 let ptr = value.into() as *const _ as *const PyMapping;
293 &*ptr
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use std::collections::HashMap;
300
301 use crate::{exceptions::PyKeyError, types::PyTuple};
302
303 use super::*;
304
305 #[test]
306 fn test_len() {
307 Python::with_gil(|py| {
308 let mut v = HashMap::new();
309 let ob = v.to_object(py);
310 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
311 assert_eq!(0, mapping.len().unwrap());
312 assert!(mapping.is_empty().unwrap());
313
314 v.insert(7, 32);
315 let ob = v.to_object(py);
316 let mapping2 = ob.downcast_bound::<PyMapping>(py).unwrap();
317 assert_eq!(1, mapping2.len().unwrap());
318 assert!(!mapping2.is_empty().unwrap());
319 });
320 }
321
322 #[test]
323 fn test_contains() {
324 Python::with_gil(|py| {
325 let mut v = HashMap::new();
326 v.insert("key0", 1234);
327 let ob = v.to_object(py);
328 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
329 mapping.set_item("key1", "foo").unwrap();
330
331 assert!(mapping.contains("key0").unwrap());
332 assert!(mapping.contains("key1").unwrap());
333 assert!(!mapping.contains("key2").unwrap());
334 });
335 }
336
337 #[test]
338 fn test_get_item() {
339 Python::with_gil(|py| {
340 let mut v = HashMap::new();
341 v.insert(7, 32);
342 let ob = v.to_object(py);
343 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
344 assert_eq!(
345 32,
346 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
347 );
348 assert!(mapping
349 .get_item(8i32)
350 .unwrap_err()
351 .is_instance_of::<PyKeyError>(py));
352 });
353 }
354
355 #[test]
356 fn test_set_item() {
357 Python::with_gil(|py| {
358 let mut v = HashMap::new();
359 v.insert(7, 32);
360 let ob = v.to_object(py);
361 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
362 assert!(mapping.set_item(7i32, 42i32).is_ok()); assert!(mapping.set_item(8i32, 123i32).is_ok()); assert_eq!(
365 42i32,
366 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
367 );
368 assert_eq!(
369 123i32,
370 mapping.get_item(8i32).unwrap().extract::<i32>().unwrap()
371 );
372 });
373 }
374
375 #[test]
376 fn test_del_item() {
377 Python::with_gil(|py| {
378 let mut v = HashMap::new();
379 v.insert(7, 32);
380 let ob = v.to_object(py);
381 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
382 assert!(mapping.del_item(7i32).is_ok());
383 assert_eq!(0, mapping.len().unwrap());
384 assert!(mapping
385 .get_item(7i32)
386 .unwrap_err()
387 .is_instance_of::<PyKeyError>(py));
388 });
389 }
390
391 #[test]
392 fn test_items() {
393 Python::with_gil(|py| {
394 let mut v = HashMap::new();
395 v.insert(7, 32);
396 v.insert(8, 42);
397 v.insert(9, 123);
398 let ob = v.to_object(py);
399 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
400 let mut key_sum = 0;
402 let mut value_sum = 0;
403 for el in mapping.items().unwrap().iter().unwrap() {
404 let tuple = el.unwrap().downcast_into::<PyTuple>().unwrap();
405 key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
406 value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
407 }
408 assert_eq!(7 + 8 + 9, key_sum);
409 assert_eq!(32 + 42 + 123, value_sum);
410 });
411 }
412
413 #[test]
414 fn test_keys() {
415 Python::with_gil(|py| {
416 let mut v = HashMap::new();
417 v.insert(7, 32);
418 v.insert(8, 42);
419 v.insert(9, 123);
420 let ob = v.to_object(py);
421 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
422 let mut key_sum = 0;
424 for el in mapping.keys().unwrap().iter().unwrap() {
425 key_sum += el.unwrap().extract::<i32>().unwrap();
426 }
427 assert_eq!(7 + 8 + 9, key_sum);
428 });
429 }
430
431 #[test]
432 fn test_values() {
433 Python::with_gil(|py| {
434 let mut v = HashMap::new();
435 v.insert(7, 32);
436 v.insert(8, 42);
437 v.insert(9, 123);
438 let ob = v.to_object(py);
439 let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
440 let mut values_sum = 0;
442 for el in mapping.values().unwrap().iter().unwrap() {
443 values_sum += el.unwrap().extract::<i32>().unwrap();
444 }
445 assert_eq!(32 + 42 + 123, values_sum);
446 });
447 }
448
449 #[test]
450 #[cfg(feature = "gil-refs")]
451 #[allow(deprecated)]
452 fn test_mapping_try_from() {
453 use crate::PyTryFrom;
454 Python::with_gil(|py| {
455 let dict = PyDict::new(py);
456 let _ = <PyMapping as PyTryFrom>::try_from(dict).unwrap();
457 let _ = PyMapping::try_from_exact(dict).unwrap();
458 });
459 }
460}