diff --git a/Cargo.toml b/Cargo.toml index f50846ba..e9e14fa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ num-complex = { version = "0.3", optional = true } paste = { version = "0.1.6", optional = true } pyo3cls = { path = "pyo3cls", version = "=0.11.1", optional = true } unindent = { version = "0.1.4", optional = true } +hashbrown = { version = "0.8", optional = true } [dev-dependencies] assert_approx_eq = "1.1.0" diff --git a/src/types/dict.rs b/src/types/dict.rs index 3bbc7841..de0abc3f 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -354,6 +354,97 @@ where } } +#[cfg(feature = "hashbrown")] +mod hashbrown_hashmap_conversion { + use super::*; + use crate::{FromPyObject, PyErr, PyObject, ToPyObject}; + + impl ToPyObject for hashbrown::HashMap + where + K: hash::Hash + cmp::Eq + ToPyObject, + V: ToPyObject, + H: hash::BuildHasher, + { + fn to_object(&self, py: Python) -> PyObject { + IntoPyDict::into_py_dict(self, py).into() + } + } + + impl IntoPy for hashbrown::HashMap + where + K: hash::Hash + cmp::Eq + IntoPy, + V: IntoPy, + H: hash::BuildHasher, + { + fn into_py(self, py: Python) -> PyObject { + let iter = self + .into_iter() + .map(|(k, v)| (k.into_py(py), v.into_py(py))); + IntoPyDict::into_py_dict(iter, py).into() + } + } + + impl<'source, K, V, S> FromPyObject<'source> for hashbrown::HashMap + where + K: FromPyObject<'source> + cmp::Eq + hash::Hash, + V: FromPyObject<'source>, + S: hash::BuildHasher + Default, + { + fn extract(ob: &'source PyAny) -> Result { + let dict = ::try_from(ob)?; + let mut ret = hashbrown::HashMap::default(); + for (k, v) in dict.iter() { + ret.insert(K::extract(k)?, V::extract(v)?); + } + Ok(ret) + } + } + + #[test] + fn test_hashbrown_hashmap_to_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = hashbrown::HashMap::::new(); + map.insert(1, 1); + + let m = map.to_object(py); + let py_map = ::try_from(m.as_ref(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + assert_eq!(map, py_map.extract().unwrap()); + } + #[test] + fn test_hashbrown_hashmap_into_python() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = hashbrown::HashMap::::new(); + map.insert(1, 1); + + let m: PyObject = map.into_py(py); + let py_map = ::try_from(m.as_ref(py)).unwrap(); + + assert!(py_map.len() == 1); + assert!(py_map.get_item(1).unwrap().extract::().unwrap() == 1); + } + + #[test] + fn test_hashbrown_hashmap_into_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let mut map = hashbrown::HashMap::::new(); + map.insert(1, 1); + + let py_map = map.into_py_dict(py); + + assert_eq!(py_map.len(), 1); + assert_eq!(py_map.get_item(1).unwrap().extract::().unwrap(), 1); + } +} + #[cfg(test)] mod test { use crate::conversion::IntoPy; diff --git a/src/types/set.rs b/src/types/set.rs index ae5d14c7..09417534 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -300,6 +300,80 @@ impl<'a> std::iter::IntoIterator for &'a PyFrozenSet { } } +#[cfg(feature = "hashbrown")] +mod hashbrown_hashset_conversion { + use super::*; + use crate::{FromPyObject, PyObject, PyResult, ToPyObject}; + + impl ToPyObject for hashbrown::HashSet + where + T: hash::Hash + Eq + ToPyObject, + { + fn to_object(&self, py: Python) -> PyObject { + let set = PySet::new::(py, &[]).expect("Failed to construct empty set"); + { + for val in self { + set.add(val).expect("Failed to add to set"); + } + } + set.into() + } + } + + impl IntoPy for hashbrown::HashSet + where + K: IntoPy + Eq + hash::Hash, + S: hash::BuildHasher + Default, + { + fn into_py(self, py: Python) -> PyObject { + let set = PySet::empty(py).expect("Failed to construct empty set"); + { + for val in self { + set.add(val.into_py(py)).expect("Failed to add to set"); + } + } + set.into() + } + } + + impl<'source, K, S> FromPyObject<'source> for hashbrown::HashSet + where + K: FromPyObject<'source> + cmp::Eq + hash::Hash, + S: hash::BuildHasher + Default, + { + fn extract(ob: &'source PyAny) -> PyResult { + let set: &PySet = ob.downcast()?; + set.iter().map(K::extract).collect() + } + } + + #[test] + fn test_extract_hashbrown_hashset() { + use std::iter::FromIterator; + let gil = Python::acquire_gil(); + let py = gil.python(); + + let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap(); + let hash_set: hashbrown::HashSet = set.extract().unwrap(); + assert_eq!( + hash_set, + hashbrown::HashSet::from_iter([1, 2, 3, 4, 5].iter().copied()) + ); + } + + #[test] + fn test_hashbrown_hashset_into_py() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let hs: hashbrown::HashSet = [1, 2, 3, 4, 5].iter().cloned().collect(); + + let hso: PyObject = hs.clone().into_py(py); + + assert_eq!(hs, hso.extract(py).unwrap()); + } +} + #[cfg(test)] mod test { use super::{PyFrozenSet, PySet};