1use crate::conversion::IntoPyObject;
2use crate::err::PyResult;
3use crate::ffi_ptr_ext::FfiPtrExt;
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::{type_hint_identifier, PyStaticExpr};
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: PyStaticExpr = type_hint_identifier!("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 crate::platform::HashMap;
199 use crate::{exceptions::PyKeyError, types::PyTuple};
200
201 use super::*;
202 use crate::conversion::IntoPyObject;
203
204 #[test]
205 fn test_len() {
206 Python::attach(|py| {
207 let mut v = HashMap::<i32, i32>::new();
208 let ob = (&v).into_pyobject(py).unwrap();
209 let mapping = ob.cast::<PyMapping>().unwrap();
210 assert_eq!(0, mapping.len().unwrap());
211 assert!(mapping.is_empty().unwrap());
212
213 v.insert(7, 32);
214 let ob = v.into_pyobject(py).unwrap();
215 let mapping2 = ob.cast::<PyMapping>().unwrap();
216 assert_eq!(1, mapping2.len().unwrap());
217 assert!(!mapping2.is_empty().unwrap());
218 });
219 }
220
221 #[test]
222 fn test_contains() {
223 Python::attach(|py| {
224 let mut v = HashMap::new();
225 v.insert("key0", 1234);
226 let ob = v.into_pyobject(py).unwrap();
227 let mapping = ob.cast::<PyMapping>().unwrap();
228 mapping.set_item("key1", "foo").unwrap();
229
230 assert!(mapping.contains("key0").unwrap());
231 assert!(mapping.contains("key1").unwrap());
232 assert!(!mapping.contains("key2").unwrap());
233 });
234 }
235
236 #[test]
237 fn test_get_item() {
238 Python::attach(|py| {
239 let mut v = HashMap::new();
240 v.insert(7, 32);
241 let ob = v.into_pyobject(py).unwrap();
242 let mapping = ob.cast::<PyMapping>().unwrap();
243 assert_eq!(
244 32,
245 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
246 );
247 assert!(mapping
248 .get_item(8i32)
249 .unwrap_err()
250 .is_instance_of::<PyKeyError>(py));
251 });
252 }
253
254 #[test]
255 fn test_set_item() {
256 Python::attach(|py| {
257 let mut v = HashMap::new();
258 v.insert(7, 32);
259 let ob = v.into_pyobject(py).unwrap();
260 let mapping = ob.cast::<PyMapping>().unwrap();
261 assert!(mapping.set_item(7i32, 42i32).is_ok()); assert!(mapping.set_item(8i32, 123i32).is_ok()); assert_eq!(
264 42i32,
265 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
266 );
267 assert_eq!(
268 123i32,
269 mapping.get_item(8i32).unwrap().extract::<i32>().unwrap()
270 );
271 });
272 }
273
274 #[test]
275 fn test_del_item() {
276 Python::attach(|py| {
277 let mut v = HashMap::new();
278 v.insert(7, 32);
279 let ob = v.into_pyobject(py).unwrap();
280 let mapping = ob.cast::<PyMapping>().unwrap();
281 assert!(mapping.del_item(7i32).is_ok());
282 assert_eq!(0, mapping.len().unwrap());
283 assert!(mapping
284 .get_item(7i32)
285 .unwrap_err()
286 .is_instance_of::<PyKeyError>(py));
287 });
288 }
289
290 #[test]
291 fn test_items() {
292 Python::attach(|py| {
293 let mut v = HashMap::new();
294 v.insert(7, 32);
295 v.insert(8, 42);
296 v.insert(9, 123);
297 let ob = v.into_pyobject(py).unwrap();
298 let mapping = ob.cast::<PyMapping>().unwrap();
299 let mut key_sum = 0;
301 let mut value_sum = 0;
302 for el in mapping.items().unwrap().try_iter().unwrap() {
303 let tuple = el.unwrap().cast_into::<PyTuple>().unwrap();
304 key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
305 value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
306 }
307 assert_eq!(7 + 8 + 9, key_sum);
308 assert_eq!(32 + 42 + 123, value_sum);
309 });
310 }
311
312 #[test]
313 fn test_keys() {
314 Python::attach(|py| {
315 let mut v = HashMap::new();
316 v.insert(7, 32);
317 v.insert(8, 42);
318 v.insert(9, 123);
319 let ob = v.into_pyobject(py).unwrap();
320 let mapping = ob.cast::<PyMapping>().unwrap();
321 let mut key_sum = 0;
323 for el in mapping.keys().unwrap().try_iter().unwrap() {
324 key_sum += el.unwrap().extract::<i32>().unwrap();
325 }
326 assert_eq!(7 + 8 + 9, key_sum);
327 });
328 }
329
330 #[test]
331 fn test_values() {
332 Python::attach(|py| {
333 let mut v = HashMap::new();
334 v.insert(7, 32);
335 v.insert(8, 42);
336 v.insert(9, 123);
337 let ob = v.into_pyobject(py).unwrap();
338 let mapping = ob.cast::<PyMapping>().unwrap();
339 let mut values_sum = 0;
341 for el in mapping.values().unwrap().try_iter().unwrap() {
342 values_sum += el.unwrap().extract::<i32>().unwrap();
343 }
344 assert_eq!(32 + 42 + 123, values_sum);
345 });
346 }
347
348 #[test]
349 fn test_type_object() {
350 Python::attach(|py| {
351 let abc = PyMapping::type_object(py);
352 assert!(PyDict::new(py).is_instance(&abc).unwrap());
353 })
354 }
355}