1use crate::conversion::IntoPyObject;
2use crate::err::PyResult;
3use crate::ffi_ptr_ext::FfiPtrExt;
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::{PyStaticExpr, type_hint_identifier};
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::{Py, Python, err, ffi};
13
14#[repr(transparent)]
22pub struct PyMapping(PyAny);
23
24pyobject_native_type_named!(PyMapping);
25
26unsafe impl PyTypeInfo for PyMapping {
27 #[cfg(feature = "experimental-inspect")]
28 const TYPE_HINT: PyStaticExpr = type_hint_identifier!("collections.abc", "Mapping");
29
30 #[inline]
31 #[allow(clippy::redundant_closure_call)]
32 fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject {
33 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
34 TYPE.import(py, "collections.abc", "Mapping")
35 .unwrap()
36 .as_type_ptr()
37 }
38
39 #[inline]
40 fn is_type_of(object: &Bound<'_, PyAny>) -> bool {
41 PyDict::is_type_of(object)
44 || object
45 .is_instance(&Self::type_object(object.py()).into_any())
46 .unwrap_or_else(|err| {
47 err.write_unraisable(object.py(), Some(object));
48 false
49 })
50 }
51}
52
53impl PyMapping {
54 pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
58 let ty = T::type_object(py);
59 Self::type_object(py).call_method1("register", (ty,))?;
60 Ok(())
61 }
62}
63
64#[doc(alias = "PyMapping")]
70pub trait PyMappingMethods<'py>: crate::sealed::Sealed {
71 fn len(&self) -> PyResult<usize>;
75
76 fn is_empty(&self) -> PyResult<bool>;
78
79 fn contains<K>(&self, key: K) -> PyResult<bool>
83 where
84 K: IntoPyObject<'py>;
85
86 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
92 where
93 K: IntoPyObject<'py>;
94
95 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
99 where
100 K: IntoPyObject<'py>,
101 V: IntoPyObject<'py>;
102
103 fn del_item<K>(&self, key: K) -> PyResult<()>
107 where
108 K: IntoPyObject<'py>;
109
110 fn keys(&self) -> PyResult<Bound<'py, PyList>>;
112
113 fn values(&self) -> PyResult<Bound<'py, PyList>>;
115
116 fn items(&self) -> PyResult<Bound<'py, PyList>>;
118}
119
120impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
121 #[inline]
122 fn len(&self) -> PyResult<usize> {
123 let v = unsafe { ffi::PyMapping_Size(self.as_ptr()) };
124 err::error_on_negative(self.py(), v)?;
125 Ok(v as usize)
126 }
127
128 #[inline]
129 fn is_empty(&self) -> PyResult<bool> {
130 self.len().map(|l| l == 0)
131 }
132
133 fn contains<K>(&self, key: K) -> PyResult<bool>
134 where
135 K: IntoPyObject<'py>,
136 {
137 PyAnyMethods::contains(&**self, key)
138 }
139
140 #[inline]
141 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
142 where
143 K: IntoPyObject<'py>,
144 {
145 PyAnyMethods::get_item(&**self, key)
146 }
147
148 #[inline]
149 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
150 where
151 K: IntoPyObject<'py>,
152 V: IntoPyObject<'py>,
153 {
154 PyAnyMethods::set_item(&**self, key, value)
155 }
156
157 #[inline]
158 fn del_item<K>(&self, key: K) -> PyResult<()>
159 where
160 K: IntoPyObject<'py>,
161 {
162 PyAnyMethods::del_item(&**self, key)
163 }
164
165 #[inline]
166 fn keys(&self) -> PyResult<Bound<'py, PyList>> {
167 unsafe {
168 ffi::PyMapping_Keys(self.as_ptr())
169 .assume_owned_or_err(self.py())
170 .cast_into_unchecked()
171 }
172 }
173
174 #[inline]
175 fn values(&self) -> PyResult<Bound<'py, PyList>> {
176 unsafe {
177 ffi::PyMapping_Values(self.as_ptr())
178 .assume_owned_or_err(self.py())
179 .cast_into_unchecked()
180 }
181 }
182
183 #[inline]
184 fn items(&self) -> PyResult<Bound<'py, PyList>> {
185 unsafe {
186 ffi::PyMapping_Items(self.as_ptr())
187 .assume_owned_or_err(self.py())
188 .cast_into_unchecked()
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use crate::platform::collections::HashMap;
196 use crate::{exceptions::PyKeyError, types::PyTuple};
197
198 use super::*;
199 use crate::conversion::IntoPyObject;
200
201 #[test]
202 fn test_len() {
203 Python::attach(|py| {
204 let mut v = HashMap::<i32, i32>::new();
205 let ob = (&v).into_pyobject(py).unwrap();
206 let mapping = ob.cast::<PyMapping>().unwrap();
207 assert_eq!(0, mapping.len().unwrap());
208 assert!(mapping.is_empty().unwrap());
209
210 v.insert(7, 32);
211 let ob = v.into_pyobject(py).unwrap();
212 let mapping2 = ob.cast::<PyMapping>().unwrap();
213 assert_eq!(1, mapping2.len().unwrap());
214 assert!(!mapping2.is_empty().unwrap());
215 });
216 }
217
218 #[test]
219 fn test_contains() {
220 Python::attach(|py| {
221 let mut v = HashMap::new();
222 v.insert("key0", 1234);
223 let ob = v.into_pyobject(py).unwrap();
224 let mapping = ob.cast::<PyMapping>().unwrap();
225 mapping.set_item("key1", "foo").unwrap();
226
227 assert!(mapping.contains("key0").unwrap());
228 assert!(mapping.contains("key1").unwrap());
229 assert!(!mapping.contains("key2").unwrap());
230 });
231 }
232
233 #[test]
234 fn test_get_item() {
235 Python::attach(|py| {
236 let mut v = HashMap::new();
237 v.insert(7, 32);
238 let ob = v.into_pyobject(py).unwrap();
239 let mapping = ob.cast::<PyMapping>().unwrap();
240 assert_eq!(
241 32,
242 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
243 );
244 assert!(
245 mapping
246 .get_item(8i32)
247 .unwrap_err()
248 .is_instance_of::<PyKeyError>(py)
249 );
250 });
251 }
252
253 #[test]
254 fn test_set_item() {
255 Python::attach(|py| {
256 let mut v = HashMap::new();
257 v.insert(7, 32);
258 let ob = v.into_pyobject(py).unwrap();
259 let mapping = ob.cast::<PyMapping>().unwrap();
260 assert!(mapping.set_item(7i32, 42i32).is_ok()); assert!(mapping.set_item(8i32, 123i32).is_ok()); assert_eq!(
263 42i32,
264 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
265 );
266 assert_eq!(
267 123i32,
268 mapping.get_item(8i32).unwrap().extract::<i32>().unwrap()
269 );
270 });
271 }
272
273 #[test]
274 fn test_del_item() {
275 Python::attach(|py| {
276 let mut v = HashMap::new();
277 v.insert(7, 32);
278 let ob = v.into_pyobject(py).unwrap();
279 let mapping = ob.cast::<PyMapping>().unwrap();
280 assert!(mapping.del_item(7i32).is_ok());
281 assert_eq!(0, mapping.len().unwrap());
282 assert!(
283 mapping
284 .get_item(7i32)
285 .unwrap_err()
286 .is_instance_of::<PyKeyError>(py)
287 );
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}