pyo3/types/
mapping.rs
1use crate::conversion::IntoPyObject;
2use crate::err::PyResult;
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::instance::Bound;
5use crate::py_result_ext::PyResultExt;
6use crate::sync::GILOnceCell;
7use crate::type_object::PyTypeInfo;
8use crate::types::any::PyAnyMethods;
9use crate::types::{PyAny, PyDict, PyList, PyType};
10use crate::{ffi, Py, PyTypeCheck, Python};
11
12#[repr(transparent)]
20pub struct PyMapping(PyAny);
21pyobject_native_type_named!(PyMapping);
22
23impl PyMapping {
24 pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
28 let ty = T::type_object(py);
29 get_mapping_abc(py)?.call_method1("register", (ty,))?;
30 Ok(())
31 }
32}
33
34#[doc(alias = "PyMapping")]
40pub trait PyMappingMethods<'py>: crate::sealed::Sealed {
41 fn len(&self) -> PyResult<usize>;
45
46 fn is_empty(&self) -> PyResult<bool>;
48
49 fn contains<K>(&self, key: K) -> PyResult<bool>
53 where
54 K: IntoPyObject<'py>;
55
56 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
62 where
63 K: IntoPyObject<'py>;
64
65 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
69 where
70 K: IntoPyObject<'py>,
71 V: IntoPyObject<'py>;
72
73 fn del_item<K>(&self, key: K) -> PyResult<()>
77 where
78 K: IntoPyObject<'py>;
79
80 fn keys(&self) -> PyResult<Bound<'py, PyList>>;
82
83 fn values(&self) -> PyResult<Bound<'py, PyList>>;
85
86 fn items(&self) -> PyResult<Bound<'py, PyList>>;
88}
89
90impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
91 #[inline]
92 fn len(&self) -> PyResult<usize> {
93 let v = unsafe { ffi::PyMapping_Size(self.as_ptr()) };
94 crate::err::error_on_minusone(self.py(), v)?;
95 Ok(v as usize)
96 }
97
98 #[inline]
99 fn is_empty(&self) -> PyResult<bool> {
100 self.len().map(|l| l == 0)
101 }
102
103 fn contains<K>(&self, key: K) -> PyResult<bool>
104 where
105 K: IntoPyObject<'py>,
106 {
107 PyAnyMethods::contains(&**self, key)
108 }
109
110 #[inline]
111 fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
112 where
113 K: IntoPyObject<'py>,
114 {
115 PyAnyMethods::get_item(&**self, key)
116 }
117
118 #[inline]
119 fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
120 where
121 K: IntoPyObject<'py>,
122 V: IntoPyObject<'py>,
123 {
124 PyAnyMethods::set_item(&**self, key, value)
125 }
126
127 #[inline]
128 fn del_item<K>(&self, key: K) -> PyResult<()>
129 where
130 K: IntoPyObject<'py>,
131 {
132 PyAnyMethods::del_item(&**self, key)
133 }
134
135 #[inline]
136 fn keys(&self) -> PyResult<Bound<'py, PyList>> {
137 unsafe {
138 ffi::PyMapping_Keys(self.as_ptr())
139 .assume_owned_or_err(self.py())
140 .downcast_into_unchecked()
141 }
142 }
143
144 #[inline]
145 fn values(&self) -> PyResult<Bound<'py, PyList>> {
146 unsafe {
147 ffi::PyMapping_Values(self.as_ptr())
148 .assume_owned_or_err(self.py())
149 .downcast_into_unchecked()
150 }
151 }
152
153 #[inline]
154 fn items(&self) -> PyResult<Bound<'py, PyList>> {
155 unsafe {
156 ffi::PyMapping_Items(self.as_ptr())
157 .assume_owned_or_err(self.py())
158 .downcast_into_unchecked()
159 }
160 }
161}
162
163fn get_mapping_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
164 static MAPPING_ABC: GILOnceCell<Py<PyType>> = GILOnceCell::new();
165
166 MAPPING_ABC.import(py, "collections.abc", "Mapping")
167}
168
169impl PyTypeCheck for PyMapping {
170 const NAME: &'static str = "Mapping";
171
172 #[inline]
173 fn type_check(object: &Bound<'_, PyAny>) -> bool {
174 PyDict::is_type_of(object)
177 || get_mapping_abc(object.py())
178 .and_then(|abc| object.is_instance(abc))
179 .unwrap_or_else(|err| {
180 err.write_unraisable(object.py(), Some(object));
181 false
182 })
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use std::collections::HashMap;
189
190 use crate::{exceptions::PyKeyError, types::PyTuple};
191
192 use super::*;
193 use crate::conversion::IntoPyObject;
194
195 #[test]
196 fn test_len() {
197 Python::with_gil(|py| {
198 let mut v = HashMap::<i32, i32>::new();
199 let ob = (&v).into_pyobject(py).unwrap();
200 let mapping = ob.downcast::<PyMapping>().unwrap();
201 assert_eq!(0, mapping.len().unwrap());
202 assert!(mapping.is_empty().unwrap());
203
204 v.insert(7, 32);
205 let ob = v.into_pyobject(py).unwrap();
206 let mapping2 = ob.downcast::<PyMapping>().unwrap();
207 assert_eq!(1, mapping2.len().unwrap());
208 assert!(!mapping2.is_empty().unwrap());
209 });
210 }
211
212 #[test]
213 fn test_contains() {
214 Python::with_gil(|py| {
215 let mut v = HashMap::new();
216 v.insert("key0", 1234);
217 let ob = v.into_pyobject(py).unwrap();
218 let mapping = ob.downcast::<PyMapping>().unwrap();
219 mapping.set_item("key1", "foo").unwrap();
220
221 assert!(mapping.contains("key0").unwrap());
222 assert!(mapping.contains("key1").unwrap());
223 assert!(!mapping.contains("key2").unwrap());
224 });
225 }
226
227 #[test]
228 fn test_get_item() {
229 Python::with_gil(|py| {
230 let mut v = HashMap::new();
231 v.insert(7, 32);
232 let ob = v.into_pyobject(py).unwrap();
233 let mapping = ob.downcast::<PyMapping>().unwrap();
234 assert_eq!(
235 32,
236 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
237 );
238 assert!(mapping
239 .get_item(8i32)
240 .unwrap_err()
241 .is_instance_of::<PyKeyError>(py));
242 });
243 }
244
245 #[test]
246 fn test_set_item() {
247 Python::with_gil(|py| {
248 let mut v = HashMap::new();
249 v.insert(7, 32);
250 let ob = v.into_pyobject(py).unwrap();
251 let mapping = ob.downcast::<PyMapping>().unwrap();
252 assert!(mapping.set_item(7i32, 42i32).is_ok()); assert!(mapping.set_item(8i32, 123i32).is_ok()); assert_eq!(
255 42i32,
256 mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
257 );
258 assert_eq!(
259 123i32,
260 mapping.get_item(8i32).unwrap().extract::<i32>().unwrap()
261 );
262 });
263 }
264
265 #[test]
266 fn test_del_item() {
267 Python::with_gil(|py| {
268 let mut v = HashMap::new();
269 v.insert(7, 32);
270 let ob = v.into_pyobject(py).unwrap();
271 let mapping = ob.downcast::<PyMapping>().unwrap();
272 assert!(mapping.del_item(7i32).is_ok());
273 assert_eq!(0, mapping.len().unwrap());
274 assert!(mapping
275 .get_item(7i32)
276 .unwrap_err()
277 .is_instance_of::<PyKeyError>(py));
278 });
279 }
280
281 #[test]
282 fn test_items() {
283 Python::with_gil(|py| {
284 let mut v = HashMap::new();
285 v.insert(7, 32);
286 v.insert(8, 42);
287 v.insert(9, 123);
288 let ob = v.into_pyobject(py).unwrap();
289 let mapping = ob.downcast::<PyMapping>().unwrap();
290 let mut key_sum = 0;
292 let mut value_sum = 0;
293 for el in mapping.items().unwrap().try_iter().unwrap() {
294 let tuple = el.unwrap().downcast_into::<PyTuple>().unwrap();
295 key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
296 value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
297 }
298 assert_eq!(7 + 8 + 9, key_sum);
299 assert_eq!(32 + 42 + 123, value_sum);
300 });
301 }
302
303 #[test]
304 fn test_keys() {
305 Python::with_gil(|py| {
306 let mut v = HashMap::new();
307 v.insert(7, 32);
308 v.insert(8, 42);
309 v.insert(9, 123);
310 let ob = v.into_pyobject(py).unwrap();
311 let mapping = ob.downcast::<PyMapping>().unwrap();
312 let mut key_sum = 0;
314 for el in mapping.keys().unwrap().try_iter().unwrap() {
315 key_sum += el.unwrap().extract::<i32>().unwrap();
316 }
317 assert_eq!(7 + 8 + 9, key_sum);
318 });
319 }
320
321 #[test]
322 fn test_values() {
323 Python::with_gil(|py| {
324 let mut v = HashMap::new();
325 v.insert(7, 32);
326 v.insert(8, 42);
327 v.insert(9, 123);
328 let ob = v.into_pyobject(py).unwrap();
329 let mapping = ob.downcast::<PyMapping>().unwrap();
330 let mut values_sum = 0;
332 for el in mapping.values().unwrap().try_iter().unwrap() {
333 values_sum += el.unwrap().extract::<i32>().unwrap();
334 }
335 assert_eq!(32 + 42 + 123, values_sum);
336 });
337 }
338}