Skip to content

Commit

Permalink
Merge pull request #3507 from orhun/feat/support_smallvec_conversion
Browse files Browse the repository at this point in the history
Add support for `SmallVec` in conversion traits (#3440)
  • Loading branch information
davidhewitt authored Oct 15, 2023
2 parents cabbca4 + 87cbd38 commit aa28cec
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ num-bigint = { version = "0.4", optional = true }
num-complex = { version = ">= 0.2, < 0.5", optional = true }
rust_decimal = { version = "1.0.0", default-features = false, optional = true }
serde = { version = "1.0", optional = true }
smallvec = { version = "1.0", optional = true }

[dev-dependencies]
assert_approx_eq = "1.1.0"
Expand Down Expand Up @@ -104,6 +105,7 @@ full = [
"num-bigint",
"num-complex",
"hashbrown",
"smallvec",
"serde",
"indexmap",
"eyre",
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3507.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `smallvec` feature to add `ToPyObject`, `IntoPy` and `FromPyObject` implementations for `smallvec::SmallVec`.
1 change: 1 addition & 0 deletions src/conversions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pub mod num_bigint;
pub mod num_complex;
pub mod rust_decimal;
pub mod serde;
pub mod smallvec;
mod std;
140 changes: 140 additions & 0 deletions src/conversions/smallvec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#![cfg(feature = "smallvec")]

//! Conversions to and from [smallvec](https://docs.rs/smallvec/).
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! # change * to the latest versions
//! smallvec = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"smallvec\"] }")]
//! ```
//!
//! Note that you must use compatible versions of smallvec and PyO3.
//! The required smallvec version may vary based on the version of PyO3.
use crate::exceptions::PyTypeError;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::types::TypeInfo;
use crate::types::list::new_from_iter;
use crate::types::{PySequence, PyString};
use crate::{
ffi, FromPyObject, IntoPy, PyAny, PyDowncastError, PyObject, PyResult, Python, ToPyObject,
};
use smallvec::{Array, SmallVec};

impl<A> ToPyObject for SmallVec<A>
where
A: Array,
A::Item: ToPyObject,
{
fn to_object(&self, py: Python<'_>) -> PyObject {
self.as_slice().to_object(py)
}
}

impl<A> IntoPy<PyObject> for SmallVec<A>
where
A: Array,
A::Item: IntoPy<PyObject>,
{
fn into_py(self, py: Python<'_>) -> PyObject {
let mut iter = self.into_iter().map(|e| e.into_py(py));
let list = new_from_iter(py, &mut iter);
list.into()
}

#[cfg(feature = "experimental-inspect")]
fn type_output() -> TypeInfo {
TypeInfo::list_of(A::Item::type_output())
}
}

impl<'a, A> FromPyObject<'a> for SmallVec<A>
where
A: Array,
A::Item: FromPyObject<'a>,
{
fn extract(obj: &'a PyAny) -> PyResult<Self> {
if obj.is_instance_of::<PyString>() {
return Err(PyTypeError::new_err("Can't extract `str` to `SmallVec`"));
}
extract_sequence(obj)
}

#[cfg(feature = "experimental-inspect")]
fn type_input() -> TypeInfo {
TypeInfo::sequence_of(A::Item::type_input())
}
}

fn extract_sequence<'s, A>(obj: &'s PyAny) -> PyResult<SmallVec<A>>
where
A: Array,
A::Item: FromPyObject<'s>,
{
// Types that pass `PySequence_Check` usually implement enough of the sequence protocol
// to support this function and if not, we will only fail extraction safely.
let seq: &PySequence = unsafe {
if ffi::PySequence_Check(obj.as_ptr()) != 0 {
obj.downcast_unchecked()
} else {
return Err(PyDowncastError::new(obj, "Sequence").into());
}
};

let mut sv = SmallVec::with_capacity(seq.len().unwrap_or(0));
for item in seq.iter()? {
sv.push(item?.extract::<A::Item>()?);
}
Ok(sv)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::types::{PyDict, PyList};

#[test]
fn test_smallvec_into_py() {
Python::with_gil(|py| {
let sv: SmallVec<[u64; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
let hso: PyObject = sv.clone().into_py(py);
let l = PyList::new(py, [1, 2, 3, 4, 5]);
assert!(l.eq(hso).unwrap());
});
}

#[test]
fn test_smallvec_from_py_object() {
Python::with_gil(|py| {
let l = PyList::new(py, [1, 2, 3, 4, 5]);
let sv: SmallVec<[u64; 8]> = l.extract().unwrap();
assert_eq!(sv.as_slice(), [1, 2, 3, 4, 5]);
});
}

#[test]
fn test_smallvec_from_py_object_fails() {
Python::with_gil(|py| {
let dict = PyDict::new(py);
let sv: PyResult<SmallVec<[u64; 8]>> = dict.extract();
assert_eq!(
sv.unwrap_err().to_string(),
"TypeError: 'dict' object cannot be converted to 'Sequence'"
);
});
}

#[test]
fn test_smallvec_to_object() {
Python::with_gil(|py| {
let sv: SmallVec<[u64; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
let hso: PyObject = sv.to_object(py);
let l = PyList::new(py, [1, 2, 3, 4, 5]);
assert!(l.eq(hso).unwrap());
});
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
//! - [`eyre`]: Enables a conversion from [eyre]’s [`Report`] type to [`PyErr`].
//! - [`hashbrown`]: Enables conversions between Python objects and [hashbrown]'s [`HashMap`] and
//! [`HashSet`] types.
//! - [`smallvec`][smallvec]: Enables conversions between Python list and [smallvec]'s [`SmallVec`].
//! - [`indexmap`][indexmap_feature]: Enables conversions between Python dictionary and [indexmap]'s [`IndexMap`].
//! - [`num-bigint`]: Enables conversions between Python objects and [num-bigint]'s [`BigInt`] and
//! [`BigUint`] types.
Expand Down Expand Up @@ -256,6 +257,7 @@
//! [inventory]: https://docs.rs/inventory
//! [`HashMap`]: https://docs.rs/hashbrown/latest/hashbrown/struct.HashMap.html
//! [`HashSet`]: https://docs.rs/hashbrown/latest/hashbrown/struct.HashSet.html
//! [`SmallVec`]: https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html
//! [`IndexMap`]: https://docs.rs/indexmap/latest/indexmap/map/struct.IndexMap.html
//! [`BigInt`]: https://docs.rs/num-bigint/latest/num_bigint/struct.BigInt.html
//! [`BigUint`]: https://docs.rs/num-bigint/latest/num_bigint/struct.BigUint.html
Expand All @@ -282,6 +284,7 @@
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/features.html "Features - The Cargo Book"
//! [global interpreter lock]: https://docs.python.org/3/glossary.html#term-global-interpreter-lock
//! [hashbrown]: https://docs.rs/hashbrown
//! [smallvec]: https://docs.rs/smallvec
//! [indexmap]: https://docs.rs/indexmap
//! [manual_builds]: https://pyo3.rs/latest/building_and_distribution.html#manual-builds "Manual builds - Building and Distribution - PyO3 user guide"
//! [num-bigint]: https://docs.rs/num-bigint
Expand Down

0 comments on commit aa28cec

Please sign in to comment.