Merge pull request #1863 from davidhewitt/more-conversions

conversions: move more features inside the module
This commit is contained in:
David Hewitt 2021-09-07 22:19:46 +01:00 committed by GitHub
commit 0c5ac220de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 189 additions and 169 deletions

View File

@ -0,0 +1,178 @@
#![cfg(feature = "hashbrown")]
#![cfg_attr(docsrs, doc(cfg(feature = "hashbrown")))]
//! Conversions to and from [hashbrown](https://docs.rs/hashbrown/)s
//! `HashMap` and `HashSet`.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! # change * to the latest versions
//! hashbrown = "*"
// workaround for `extended_key_value_attributes`: https://github.com/rust-lang/rust/issues/82768#issuecomment-803935643
#![cfg_attr(docsrs, cfg_attr(docsrs, doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"hashbrown\"] }")))]
#![cfg_attr(
not(docsrs),
doc = "pyo3 = { version = \"*\", features = [\"hashbrown\"] }"
)]
//! ```
//!
//! Note that you must use compatible versions of hashbrown and PyO3.
//! The required hashbrown version may vary based on the version of PyO3.
use crate::{
types::{IntoPyDict, PyDict, PySet},
FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, PyTryFrom, Python, ToPyObject,
};
use std::{cmp, hash};
impl<K, V, H> ToPyObject for hashbrown::HashMap<K, V, H>
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<K, V, H> IntoPy<PyObject> for hashbrown::HashMap<K, V, H>
where
K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
V: IntoPy<PyObject>,
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<K, V, S>
where
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
V: FromPyObject<'source>,
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
let dict = <PyDict as PyTryFrom>::try_from(ob)?;
let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict.iter() {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
}
}
impl<T> ToPyObject for hashbrown::HashSet<T>
where
T: hash::Hash + Eq + ToPyObject,
{
fn to_object(&self, py: Python) -> PyObject {
let set = PySet::new::<T>(py, &[]).expect("Failed to construct empty set");
{
for val in self {
set.add(val).expect("Failed to add to set");
}
}
set.into()
}
}
impl<K, S> IntoPy<PyObject> for hashbrown::HashSet<K, S>
where
K: IntoPy<PyObject> + 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<K, S>
where
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> PyResult<Self> {
let set: &PySet = ob.downcast()?;
set.iter().map(K::extract).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hashbrown_hashmap_to_python() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::new();
map.insert(1, 1);
let m = map.to_object(py);
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
assert!(py_map.len() == 1);
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
assert_eq!(map, py_map.extract().unwrap());
});
}
#[test]
fn test_hashbrown_hashmap_into_python() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::new();
map.insert(1, 1);
let m: PyObject = map.into_py(py);
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
assert!(py_map.len() == 1);
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
});
}
#[test]
fn test_hashbrown_hashmap_into_dict() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::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::<i32>().unwrap(), 1);
});
}
#[test]
fn test_extract_hashbrown_hashset() {
Python::with_gil(|py| {
let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap();
let hash_set: hashbrown::HashSet<usize> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
});
}
#[test]
fn test_hashbrown_hashset_into_py() {
Python::with_gil(|py| {
let hs: hashbrown::HashSet<u64> = [1, 2, 3, 4, 5].iter().cloned().collect();
let hso: PyObject = hs.clone().into_py(py);
assert_eq!(hs, hso.extract(py).unwrap());
});
}
}

View File

@ -1,3 +1,6 @@
#![cfg(feature = "indexmap")]
#![cfg_attr(docsrs, doc(cfg(feature = "indexmap")))]
//! Conversions to and from [indexmap](https://docs.rs/indexmap/)s
//! `IndexMap`.
//!

View File

@ -1,8 +1,9 @@
//! This module contains conversions between various Rust object and their representation in Python.
mod array;
#[cfg(feature = "indexmap")]
#[cfg_attr(docsrs, doc(cfg(feature = "indexmap")))]
pub mod hashbrown;
pub mod indexmap;
pub mod num_bigint;
pub mod num_complex;
mod osstr;
mod path;

View File

@ -7,6 +7,7 @@
docsrs,
doc(cfg(all(feature = "num-bigint", not(any(Py_LIMITED_API, PyPy)))))
)]
//! Conversions to and from [num-bigint](https://docs.rs/num-bigint)s [`BigInt`] and [`BigUint`] types.
//!
//! This is useful for converting Python integers when they may not fit in Rust's built-in integer types.

View File

@ -69,12 +69,12 @@
//! so that your module can also be used with statically linked Python interpreters.
//! Use this feature when building an extension module.
//
//! - `hashbrown`: Enables conversions between Python objects and
//! - [`hashbrown`](./hashbrown/index.html): Enables conversions between Python objects and
//! [hashbrown](https://docs.rs/hashbrown)'s
//! [`HashMap`](https://docs.rs/hashbrown/latest/hashbrown/struct.HashMap.html) and
//! [`HashSet`](https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html) types.
//
//! - [`indexmap`](crate::indexmap): Enables conversions between Python dictionary and
//! - [`indexmap`](./indexmap/index.html): Enables conversions between Python dictionary and
//! [indexmap](https://docs.rs/indexmap)'s
//! [`IndexMap`](https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html).
//
@ -88,7 +88,7 @@
//! [`BigInt`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html) and
//! [`BigUint`](https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html) types.
//
//! - [`num-complex`](crate::num_complex): Enables conversions between Python objects and
//! - [`num-complex`](./num_complex/index.html): Enables conversions between Python objects and
//! [num-complex](https://docs.rs/num-complex)'s
//! [`Complex`](https://docs.rs/num-complex/latest/num_complex/struct.Complex.html) type.
//
@ -331,13 +331,7 @@ mod python;
pub mod type_object;
pub mod types;
pub mod num_bigint;
pub mod num_complex;
#[cfg_attr(docsrs, doc(cfg(feature = "indexmap")))]
#[cfg(feature = "indexmap")]
pub use crate::conversions::indexmap;
pub use crate::conversions::*;
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[cfg(feature = "serde")]

View File

@ -364,94 +364,6 @@ where
}
}
#[cfg(feature = "hashbrown")]
mod hashbrown_hashmap_conversion {
use super::*;
use crate::{FromPyObject, PyErr, PyObject, ToPyObject};
impl<K, V, H> ToPyObject for hashbrown::HashMap<K, V, H>
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<K, V, H> IntoPy<PyObject> for hashbrown::HashMap<K, V, H>
where
K: hash::Hash + cmp::Eq + IntoPy<PyObject>,
V: IntoPy<PyObject>,
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<K, V, S>
where
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
V: FromPyObject<'source>,
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
let dict = <PyDict as PyTryFrom>::try_from(ob)?;
let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict.iter() {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
}
}
#[test]
fn test_hashbrown_hashmap_to_python() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::new();
map.insert(1, 1);
let m = map.to_object(py);
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
assert!(py_map.len() == 1);
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
assert_eq!(map, py_map.extract().unwrap());
});
}
#[test]
fn test_hashbrown_hashmap_into_python() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::new();
map.insert(1, 1);
let m: PyObject = map.into_py(py);
let py_map = <PyDict as PyTryFrom>::try_from(m.as_ref(py)).unwrap();
assert!(py_map.len() == 1);
assert!(py_map.get_item(1).unwrap().extract::<i32>().unwrap() == 1);
});
}
#[test]
fn test_hashbrown_hashmap_into_dict() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::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::<i32>().unwrap(), 1);
});
}
}
#[cfg(test)]
mod tests {
use crate::conversion::IntoPy;

View File

@ -332,75 +332,6 @@ impl<'a> std::iter::IntoIterator for &'a PyFrozenSet {
}
}
#[cfg(feature = "hashbrown")]
#[cfg_attr(docsrs, doc(cfg(feature = "hashbrown")))]
mod hashbrown_hashset_conversion {
use super::*;
use crate::{FromPyObject, PyObject, PyResult, ToPyObject};
impl<T> ToPyObject for hashbrown::HashSet<T>
where
T: hash::Hash + Eq + ToPyObject,
{
fn to_object(&self, py: Python) -> PyObject {
let set = PySet::new::<T>(py, &[]).expect("Failed to construct empty set");
{
for val in self {
set.add(val).expect("Failed to add to set");
}
}
set.into()
}
}
impl<K, S> IntoPy<PyObject> for hashbrown::HashSet<K, S>
where
K: IntoPy<PyObject> + 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<K, S>
where
K: FromPyObject<'source> + cmp::Eq + hash::Hash,
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> PyResult<Self> {
let set: &PySet = ob.downcast()?;
set.iter().map(K::extract).collect()
}
}
#[test]
fn test_extract_hashbrown_hashset() {
Python::with_gil(|py| {
let set = PySet::new(py, &[1, 2, 3, 4, 5]).unwrap();
let hash_set: hashbrown::HashSet<usize> = set.extract().unwrap();
assert_eq!(hash_set, [1, 2, 3, 4, 5].iter().copied().collect());
});
}
#[test]
fn test_hashbrown_hashset_into_py() {
Python::with_gil(|py| {
let hs: hashbrown::HashSet<u64> = [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 tests {
use super::{PyFrozenSet, PySet};