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::{try_new_from_iter, PySetMethods},
26        PyDict, PyFrozenSet, PySet,
27    },
28    Bound, FromPyObject, PyAny, PyErr, PyResult, Python,
29};
30use std::{cmp, hash};
31
32impl<'py, K, V, H> IntoPyObject<'py> for hashbrown::HashMap<K, V, H>
33where
34    K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
35    V: IntoPyObject<'py>,
36    H: hash::BuildHasher,
37{
38    type Target = PyDict;
39    type Output = Bound<'py, Self::Target>;
40    type Error = PyErr;
41
42    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
43        let dict = PyDict::new(py);
44        for (k, v) in self {
45            dict.set_item(k, v)?;
46        }
47        Ok(dict)
48    }
49}
50
51impl<'a, 'py, K, V, H> IntoPyObject<'py> for &'a hashbrown::HashMap<K, V, H>
52where
53    &'a K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
54    &'a V: IntoPyObject<'py>,
55    H: hash::BuildHasher,
56{
57    type Target = PyDict;
58    type Output = Bound<'py, Self::Target>;
59    type Error = PyErr;
60
61    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
62        let dict = PyDict::new(py);
63        for (k, v) in self {
64            dict.set_item(k, v)?;
65        }
66        Ok(dict)
67    }
68}
69
70impl<'py, K, V, S> FromPyObject<'py> for hashbrown::HashMap<K, V, S>
71where
72    K: FromPyObject<'py> + cmp::Eq + hash::Hash,
73    V: FromPyObject<'py>,
74    S: hash::BuildHasher + Default,
75{
76    fn extract_bound(ob: &Bound<'py, PyAny>) -> Result<Self, PyErr> {
77        let dict = ob.downcast::<PyDict>()?;
78        let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
79        for (k, v) in dict {
80            ret.insert(k.extract()?, v.extract()?);
81        }
82        Ok(ret)
83    }
84}
85
86impl<'py, K, H> IntoPyObject<'py> for hashbrown::HashSet<K, H>
87where
88    K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
89    H: hash::BuildHasher,
90{
91    type Target = PySet;
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        try_new_from_iter(py, self)
97    }
98}
99
100impl<'a, 'py, K, H> IntoPyObject<'py> for &'a hashbrown::HashSet<K, H>
101where
102    &'a K: IntoPyObject<'py> + cmp::Eq + hash::Hash,
103    H: hash::BuildHasher,
104{
105    type Target = PySet;
106    type Output = Bound<'py, Self::Target>;
107    type Error = PyErr;
108
109    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
110        try_new_from_iter(py, self)
111    }
112}
113
114impl<'py, K, S> FromPyObject<'py> for hashbrown::HashSet<K, S>
115where
116    K: FromPyObject<'py> + cmp::Eq + hash::Hash,
117    S: hash::BuildHasher + Default,
118{
119    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
120        match ob.downcast::<PySet>() {
121            Ok(set) => set.iter().map(|any| any.extract()).collect(),
122            Err(err) => {
123                if let Ok(frozen_set) = ob.downcast::<PyFrozenSet>() {
124                    frozen_set.iter().map(|any| any.extract()).collect()
125                } else {
126                    Err(PyErr::from(err))
127                }
128            }
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::types::IntoPyDict;
137    use std::collections::hash_map::RandomState;
138
139    #[test]
140    fn test_hashbrown_hashmap_into_pyobject() {
141        Python::with_gil(|py| {
142            let mut map =
143                hashbrown::HashMap::<i32, i32, RandomState>::with_hasher(RandomState::new());
144            map.insert(1, 1);
145
146            let py_map = (&map).into_pyobject(py).unwrap();
147
148            assert!(py_map.len() == 1);
149            assert!(
150                py_map
151                    .get_item(1)
152                    .unwrap()
153                    .unwrap()
154                    .extract::<i32>()
155                    .unwrap()
156                    == 1
157            );
158            assert_eq!(map, py_map.extract().unwrap());
159        });
160    }
161
162    #[test]
163    fn test_hashbrown_hashmap_into_dict() {
164        Python::with_gil(|py| {
165            let mut map =
166                hashbrown::HashMap::<i32, i32, RandomState>::with_hasher(RandomState::new());
167            map.insert(1, 1);
168
169            let py_map = map.into_py_dict(py).unwrap();
170
171            assert_eq!(py_map.len(), 1);
172            assert_eq!(
173                py_map
174                    .get_item(1)
175                    .unwrap()
176                    .unwrap()
177                    .extract::<i32>()
178                    .unwrap(),
179                1
180            );
181        });
182    }
183
184    #[test]
185    fn test_extract_hashbrown_hashset() {
186        Python::with_gil(|py| {
187            let set = PySet::new(py, [1, 2, 3, 4, 5]).unwrap();
188            let hash_set: hashbrown::HashSet<usize, RandomState> = set.extract().unwrap();
189            assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
190
191            let set = PyFrozenSet::new(py, [1, 2, 3, 4, 5]).unwrap();
192            let hash_set: hashbrown::HashSet<usize, RandomState> = set.extract().unwrap();
193            assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
194        });
195    }
196
197    #[test]
198    fn test_hashbrown_hashset_into_pyobject() {
199        Python::with_gil(|py| {
200            let hs: hashbrown::HashSet<u64, RandomState> =
201                [1, 2, 3, 4, 5].iter().cloned().collect();
202
203            let hso = hs.clone().into_pyobject(py).unwrap();
204
205            assert_eq!(hs, hso.extract().unwrap());
206        });
207    }
208}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here