1use crate::conversion::IntoPyObject;
2use crate::err::PyResult;
3use crate::ffi_ptr_ext::FfiPtrExt;
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::TypeHint;
6use crate::instance::Bound;
7use crate::py_result_ext::PyResultExt;
8use crate::sync::PyOnceLock;
9use crate::type_object::PyTypeInfo;
10use crate::types::any::PyAnyMethods;
11use crate::types::{PyAny, PyDict, PyList, PyType, PyTypeMethods};
12use crate::{ffi, Py, Python};
13
14#[repr(transparent)]
22pub struct PyMapping(PyAny);
23
24pyobject_native_type_named!(PyMapping);
25
26unsafe impl PyTypeInfo for PyMapping {
27 const NAME: &'static str = "Mapping";
28 const MODULE: Option<&'static str> = Some("collections.abc");
29
30 #[cfg(feature = "experimental-inspect")]
31 const TYPE_HINT: TypeHint = TypeHint::module_attr("collections.abc", "Mapping");
32
33 #[inline]
34 #[allow(clippy::redundant_closure_call)]
35 fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject {
36 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
37 TYPE.import(py, "collections.abc", "Mapping")
38 .unwrap()
39 .as_type_ptr()
40 }
41
42 #[inline]
43 fn is_type_of(object: &Bound<'_, PyAny>) -> bool {
44 PyDict::is_type_of(object)
47 || object
48 .is_instance(&Self::type_object(object.py()).into_any())
49 .unwrap_or_else(|err| {
50 err.write_unraisable(object.py(), Some(object));
51 false
52 })
53 }
54}
55
56impl PyMapping {
57 pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
61 let ty = T::type_object(py);
62 Self::type_object(py).call_method1("register", (ty,))?;
63 Ok(())
64 }
65}
66
67#[doc(alias = "PyMapping")]
73pub trait PyMappingMethods<'py>: crate::sealed::Sealed {
74 fn len(&self) -> PyResult<usize>;
78
79 fn is_empty(&self) -> PyResult<bool>;
81
82 fn contains<K>(&self, key: K) -> PyResult<bool>
86 where
87 K: IntoPyObject<'py>;
88
89 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
95 where
96 K: IntoPyObject<'py>;
97
98 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
102 where
103 K: IntoPyObject<'py>,
104 V: IntoPyObject<'py>;
105
106 fn del_item<K>(&self, key: K) -> PyResult<()>
110 where
111 K: IntoPyObject<'py>;
112
113 fn keys(&self) -> PyResult<Bound<'py, PyList>>;
115
116 fn values(&self) -> PyResult<Bound<'py, PyList>>;
118
119 fn items(&self) -> PyResult<Bound<'py, PyList>>;
121}
122
123impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
124 #[inline]
125 fn len(&self) -> PyResult<usize> {
126 let v = unsafe { ffi::PyMapping_Size(self.as_ptr()) };
127 crate::err::error_on_minusone(self.py(), v)?;
128 Ok(v as usize)
129 }
130
131 #[inline]
132 fn is_empty(&self) -> PyResult<bool> {
133 self.len().map(|l| l == 0)
134 }
135
136 fn contains<K>(&self, key: K) -> PyResult<bool>
137 where
138 K: IntoPyObject<'py>,
139 {
140 PyAnyMethods::contains(&**self, key)
141 }
142
143 #[inline]
144 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
145 where
146 K: IntoPyObject<'py>,
147 {
148 PyAnyMethods::get_item(&**self, key)
149 }
150
151 #[inline]
152 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
153 where
154 K: IntoPyObject<'py>,
155 V: IntoPyObject<'py>,
156 {
157 PyAnyMethods::set_item(&**self, key, value)
158 }
159
160 #[inline]
161 fn del_item<K>(&self, key: K) -> PyResult<()>
162 where
163 K: IntoPyObject<'py>,
164 {
165 PyAnyMethods::del_item(&**self, key)
166 }
167
168 #[inline]
169 fn keys(&self) -> PyResult<Bound<'py, PyList>> {
170 unsafe {
171 ffi::PyMapping_Keys(self.as_ptr())
172 .assume_owned_or_err(self.py())
173 .cast_into_unchecked()
174 }
175 }
176
177 #[inline]
178 fn values(&self) -> PyResult<Bound<'py, PyList>> {
179 unsafe {
180 ffi::PyMapping_Values(self.as_ptr())
181 .assume_owned_or_err(self.py())
182 .cast_into_unchecked()
183 }
184 }
185
186 #[inline]
187 fn items(&self) -> PyResult<Bound<'py, PyList>> {
188 unsafe {
189 ffi::PyMapping_Items(self.as_ptr())
190 .assume_owned_or_err(self.py())
191 .cast_into_unchecked()
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use std::collections::HashMap;
199
200 use crate::{exceptions::PyKeyError, types::PyTuple};
201
202 use super::*;
203 use crate::conversion::IntoPyObject;
204
205 #[test]
206 fn test_len() {
207 Python::attach(|py| {
208 let mut v = HashMap::<i32, i32>::new();
209 let ob = (&v).into_pyobject(py).unwrap();
210 let mapping = ob.cast::<PyMapping>().unwrap();
211 assert_eq!(0, mapping.len().unwrap());
212 assert!(mapping.is_empty().unwrap());
213
214 v.insert(7, 32);
215 let ob = v.into_pyobject(py).unwrap();
216 let mapping2 = ob.cast::<PyMapping>().unwrap();
217 assert_eq!(1, mapping2.len().unwrap());
218 assert!(!mapping2.is_empty().unwrap());
219 });
220 }
221
222 #[test]
223 fn test_contains() {
224 Python::attach(|py| {
225 let mut v = HashMap::new();
226 v.insert("key0", 1234);
227 let ob = v.into_pyobject(py).unwrap();
228 let mapping = ob.cast::<PyMapping>().unwrap();
229 mapping.set_item("key1", "foo").unwrap();
230
231 assert!(mapping.contains("key0").unwrap());
232 assert!(mapping.contains("key1").unwrap());
233 assert!(!mapping.contains("key2").unwrap());
234 });
235 }
236
237 #[test]
238 fn test_get_item() {
239 Python::attach(|py| {
240 let mut v = HashMap::new();
241 v.insert(7, 32);
242 let ob = v.into_pyobject(py).unwrap();
243 let mapping = ob.cast::<PyMapping>().unwrap();
244 assert_eq!(
245 32,
246 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
247 );
248 assert!(mapping
249 .get_item(8i32)
250 .unwrap_err()
251 .is_instance_of::<PyKeyError>(py));
252 });
253 }
254
255 #[test]
256 fn test_set_item() {
257 Python::attach(|py| {
258 let mut v = HashMap::new();
259 v.insert(7, 32);
260 let ob = v.into_pyobject(py).unwrap();
261 let mapping = ob.cast::<PyMapping>().unwrap();
262 assert!(mapping.set_item(7i32, 42i32).is_ok()); assert!(mapping.set_item(8i32, 123i32).is_ok()); assert_eq!(
265 42i32,
266 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
267 );
268 assert_eq!(
269 123i32,
270 mapping.get_item(8i32).unwrap().extract::<i32>().unwrap()
271 );
272 });
273 }
274
275 #[test]
276 fn test_del_item() {
277 Python::attach(|py| {
278 let mut v = HashMap::new();
279 v.insert(7, 32);
280 let ob = v.into_pyobject(py).unwrap();
281 let mapping = ob.cast::<PyMapping>().unwrap();
282 assert!(mapping.del_item(7i32).is_ok());
283 assert_eq!(0, mapping.len().unwrap());
284 assert!(mapping
285 .get_item(7i32)
286 .unwrap_err()
287 .is_instance_of::<PyKeyError>(py));
288 });
289 }
290
291 #[test]
292 fn test_items() {
293 Python::attach(|py| {
294 let mut v = HashMap::new();
295 v.insert(7, 32);
296 v.insert(8, 42);
297 v.insert(9, 123);
298 let ob = v.into_pyobject(py).unwrap();
299 let mapping = ob.cast::<PyMapping>().unwrap();
300 let mut key_sum = 0;
302 let mut value_sum = 0;
303 for el in mapping.items().unwrap().try_iter().unwrap() {
304 let tuple = el.unwrap().cast_into::<PyTuple>().unwrap();
305 key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
306 value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
307 }
308 assert_eq!(7 + 8 + 9, key_sum);
309 assert_eq!(32 + 42 + 123, value_sum);
310 });
311 }
312
313 #[test]
314 fn test_keys() {
315 Python::attach(|py| {
316 let mut v = HashMap::new();
317 v.insert(7, 32);
318 v.insert(8, 42);
319 v.insert(9, 123);
320 let ob = v.into_pyobject(py).unwrap();
321 let mapping = ob.cast::<PyMapping>().unwrap();
322 let mut key_sum = 0;
324 for el in mapping.keys().unwrap().try_iter().unwrap() {
325 key_sum += el.unwrap().extract::<i32>().unwrap();
326 }
327 assert_eq!(7 + 8 + 9, key_sum);
328 });
329 }
330
331 #[test]
332 fn test_values() {
333 Python::attach(|py| {
334 let mut v = HashMap::new();
335 v.insert(7, 32);
336 v.insert(8, 42);
337 v.insert(9, 123);
338 let ob = v.into_pyobject(py).unwrap();
339 let mapping = ob.cast::<PyMapping>().unwrap();
340 let mut values_sum = 0;
342 for el in mapping.values().unwrap().try_iter().unwrap() {
343 values_sum += el.unwrap().extract::<i32>().unwrap();
344 }
345 assert_eq!(32 + 42 + 123, values_sum);
346 });
347 }
348
349 #[test]
350 fn test_type_object() {
351 Python::attach(|py| {
352 let abc = PyMapping::type_object(py);
353 assert!(PyDict::new(py).is_instance(&abc).unwrap());
354 })
355 }
356}