pyo3/conversions/
hashbrown.rs

1#![cfg(feature = "hashbrown")]
2
3//!  Conversions to and from [hashbrown](https://docs.rs/hashbrown/)’s
4//! `HashMap` and `HashSet`.
5//!
6//! # Setup
7//!
8//! To use this feature, add this to your **`Cargo.toml`**:
9//!
10//! ```toml
11//! [dependencies]
12//! # change * to the latest versions
13//! hashbrown = "*"
14#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"),  "\", features = [\"hashbrown\"] }")]
15//! ```
16//!
17//! Note that you must use compatible versions of hashbrown and PyO3.
18//! The required hashbrown version may vary based on the version of PyO3.
19use crate::{
20    conversion::IntoPyObject,
21    types::{
22        any::PyAnyMethods,
23        dict::PyDictMethods,
24        frozenset::PyFrozenSetMethods,
25        set::{new_from_iter, try_new_from_iter, PySetMethods},
26        PyDict, PyFrozenSet, PySet,
27    },
28    Bound, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python,
29};
30#[allow(deprecated)]
31use crate::{IntoPy, ToPyObject};
32use std::{cmp, hash};
33
34#[allow(deprecated)]
35impl<K, V, H> ToPyObject for hashbrown::HashMap<K, V, H>
36where
37    K: hash::Hash + cmp::Eq + ToPyObject,
38    V: ToPyObject,
39    H: hash::BuildHasher,
40{
41    fn to_object(&self, py: Python<'_>) -> PyObject {
42        let dict = PyDict::new(py);
43        for (k, v) in self {
44            dict.set_item(k.to_object(py), v.to_object(py)).unwrap();
45        }
46        dict.into_any().unbind()
47    }
48}
49
50#[allow(deprecated)]
51impl<K, V, H> IntoPy<PyObject> for hashbrown::HashMap<K, V, H>
52where
53    K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
54    V: IntoPy<PyObject>,
55    H: hash::BuildHasher,
56{
57    fn into_py(self, py: Python<'_>) -> PyObject {
58        let dict = PyDict::new(py);
59        for (k, v) in self {
60            dict.set_item(k.into_py(py), v.into_py(py)).unwrap();
61        }
62        dict.into_any().unbind()
63    }
64}
65
66impl<'py, K, V, H> IntoPyObject<'py> for hashbrown::HashMap<K, V, H>
67where
68    K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
69    V: IntoPyObject<'py>,
70    H: hash::BuildHasher,
71{
72    type Target = PyDict;
73    type Output = Bound<'py, Self::Target>;
74    type Error = PyErr;
75
76    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
77        let dict = PyDict::new(py);
78        for (k, v) in self {
79            dict.set_item(k, v)?;
80        }
81        Ok(dict)
82    }
83}
84
85impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a hashbrown::HashMap<K, V, H>
86where
87    &'a K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
88    &'a V: IntoPyObject<'py>,
89    H: hash::BuildHasher,
90{
91    type Target = PyDict;
92    type Output = Bound<'py, Self::Target>;
93    type Error = PyErr;
94
95    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
96        let dict = PyDict::new(py);
97        for (k, v) in self {
98            dict.set_item(k, v)?;
99        }
100        Ok(dict)
101    }
102}
103
104impl<'py, K, V, S> FromPyObject<'py> for hashbrown::HashMap<K, V, S>
105where
106    K: FromPyObject<'py> + cmp::Eq + hash::Hash,
107    V: FromPyObject<'py>,
108    S: hash::BuildHasher + Default,
109{
110    fn extract_bound(ob: &Bound<'py, PyAny>) -> Result<Self, PyErr> {
111        let dict = ob.downcast::<PyDict>()?;
112        let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
113        for (k, v) in dict {
114            ret.insert(k.extract()?, v.extract()?);
115        }
116        Ok(ret)
117    }
118}
119
120#[allow(deprecated)]
121impl<T> ToPyObject for hashbrown::HashSet<T>
122where
123    T: hash::Hash + Eq + ToPyObject,
124{
125    fn to_object(&self, py: Python<'_>) -> PyObject {
126        new_from_iter(py, self)
127            .expect("Failed to create Python set from hashbrown::HashSet")
128            .into()
129    }
130}
131
132#[allow(deprecated)]
133impl<K, S> IntoPy<PyObject> for hashbrown::HashSet<K, S>
134where
135    K: IntoPy<PyObject> + Eq + hash::Hash,
136    S: hash::BuildHasher + Default,
137{
138    fn into_py(self, py: Python<'_>) -> PyObject {
139        new_from_iter(py, self.into_iter().map(|item| item.into_py(py)))
140            .expect("Failed to create Python set from hashbrown::HashSet")
141            .into()
142    }
143}
144
145impl<'py, K, H> IntoPyObject<'py> for hashbrown::HashSet<K, H>
146where
147    K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
148    H: hash::BuildHasher,
149{
150    type Target = PySet;
151    type Output = Bound<'py, Self::Target>;
152    type Error = PyErr;
153
154    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
155        try_new_from_iter(py, self)
156    }
157}
158
159impl<'a, 'py, K, H> IntoPyObject<'py> for &'a hashbrown::HashSet<K, H>
160where
161    &'a K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
162    H: hash::BuildHasher,
163{
164    type Target = PySet;
165    type Output = Bound<'py, Self::Target>;
166    type Error = PyErr;
167
168    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
169        try_new_from_iter(py, self)
170    }
171}
172
173impl<'py, K, S> FromPyObject<'py> for hashbrown::HashSet<K, S>
174where
175    K: FromPyObject<'py> + cmp::Eq + hash::Hash,
176    S: hash::BuildHasher + Default,
177{
178    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
179        match ob.downcast::<PySet>() {
180            Ok(set) => set.iter().map(|any| any.extract()).collect(),
181            Err(err) => {
182                if let Ok(frozen_set) = ob.downcast::<PyFrozenSet>() {
183                    frozen_set.iter().map(|any| any.extract()).collect()
184                } else {
185                    Err(PyErr::from(err))
186                }
187            }
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::types::IntoPyDict;
196
197    #[test]
198    fn test_hashbrown_hashmap_into_pyobject() {
199        Python::with_gil(|py| {
200            let mut map = hashbrown::HashMap::<i32, i32>::new();
201            map.insert(1, 1);
202
203            let py_map = (&map).into_pyobject(py).unwrap();
204
205            assert!(py_map.len() == 1);
206            assert!(
207                py_map
208                    .get_item(1)
209                    .unwrap()
210                    .unwrap()
211                    .extract::<i32>()
212                    .unwrap()
213                    == 1
214            );
215            assert_eq!(map, py_map.extract().unwrap());
216        });
217    }
218
219    #[test]
220    fn test_hashbrown_hashmap_into_dict() {
221        Python::with_gil(|py| {
222            let mut map = hashbrown::HashMap::<i32, i32>::new();
223            map.insert(1, 1);
224
225            let py_map = map.into_py_dict(py).unwrap();
226
227            assert_eq!(py_map.len(), 1);
228            assert_eq!(
229                py_map
230                    .get_item(1)
231                    .unwrap()
232                    .unwrap()
233                    .extract::<i32>()
234                    .unwrap(),
235                1
236            );
237        });
238    }
239
240    #[test]
241    fn test_extract_hashbrown_hashset() {
242        Python::with_gil(|py| {
243            let set = PySet::new(py, [1, 2, 3, 4, 5]).unwrap();
244            let hash_set: hashbrown::HashSet<usize> = set.extract().unwrap();
245            assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
246
247            let set = PyFrozenSet::new(py, [1, 2, 3, 4, 5]).unwrap();
248            let hash_set: hashbrown::HashSet<usize> = set.extract().unwrap();
249            assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
250        });
251    }
252
253    #[test]
254    fn test_hashbrown_hashset_into_pyobject() {
255        Python::with_gil(|py| {
256            let hs: hashbrown::HashSet<u64> = [1, 2, 3, 4, 5].iter().cloned().collect();
257
258            let hso = hs.clone().into_pyobject(py).unwrap();
259
260            assert_eq!(hs, hso.extract().unwrap());
261        });
262    }
263}