Skip to main content

pyo3/conversions/
smallvec.rs

1#![cfg(feature = "smallvec")]
2
3//!  Conversions to and from [smallvec](https://docs.rs/smallvec/).
4//!
5//! # Setup
6//!
7//! To use this feature, add this to your **`Cargo.toml`**:
8//!
9//! ```toml
10//! [dependencies]
11//! # change * to the latest versions
12//! smallvec = "*"
13#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"),  "\", features = [\"smallvec\"] }")]
14//! ```
15//!
16//! Note that you must use compatible versions of smallvec and PyO3.
17//! The required smallvec version may vary based on the version of PyO3.
18use crate::conversion::{FromPyObjectOwned, IntoPyObject};
19use crate::exceptions::PyTypeError;
20#[cfg(feature = "experimental-inspect")]
21use crate::inspect::PyStaticExpr;
22#[cfg(feature = "experimental-inspect")]
23use crate::type_hint_subscript;
24use crate::types::any::PyAnyMethods;
25use crate::types::{PySequence, PyString};
26use crate::{
27    err::CastError, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo, Python,
28};
29use smallvec::{Array, SmallVec};
30
31impl<'py, A> IntoPyObject<'py> for SmallVec<A>
32where
33    A: Array,
34    A::Item: IntoPyObject<'py>,
35{
36    type Target = PyAny;
37    type Output = Bound<'py, Self::Target>;
38    type Error = PyErr;
39
40    #[cfg(feature = "experimental-inspect")]
41    const OUTPUT_TYPE: PyStaticExpr = A::Item::SEQUENCE_OUTPUT_TYPE;
42
43    /// Turns [`SmallVec<u8>`] into [`PyBytes`], all other `T`s will be turned into a [`PyList`]
44    ///
45    /// [`PyBytes`]: crate::types::PyBytes
46    /// [`PyList`]: crate::types::PyList
47    #[inline]
48    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
49        <A::Item>::owned_sequence_into_pyobject(self, py, crate::conversion::private::Token)
50    }
51}
52
53impl<'a, 'py, A> IntoPyObject<'py> for &'a SmallVec<A>
54where
55    A: Array,
56    &'a A::Item: IntoPyObject<'py>,
57{
58    type Target = PyAny;
59    type Output = Bound<'py, Self::Target>;
60    type Error = PyErr;
61
62    #[cfg(feature = "experimental-inspect")]
63    const OUTPUT_TYPE: PyStaticExpr = <&[A::Item]>::OUTPUT_TYPE;
64
65    #[inline]
66    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
67        self.as_slice().into_pyobject(py)
68    }
69}
70
71impl<'py, A> FromPyObject<'_, 'py> for SmallVec<A>
72where
73    A: Array,
74    A::Item: FromPyObjectOwned<'py>,
75{
76    type Error = PyErr;
77
78    #[cfg(feature = "experimental-inspect")]
79    const INPUT_TYPE: PyStaticExpr =
80        type_hint_subscript!(PySequence::TYPE_HINT, A::Item::INPUT_TYPE);
81
82    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
83        if obj.is_instance_of::<PyString>() {
84            return Err(PyTypeError::new_err("Can't extract `str` to `SmallVec`"));
85        }
86        extract_sequence(obj)
87    }
88}
89
90fn extract_sequence<'py, A>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<SmallVec<A>>
91where
92    A: Array,
93    A::Item: FromPyObjectOwned<'py>,
94{
95    // Types that pass `PySequence_Check` usually implement enough of the sequence protocol
96    // to support this function and if not, we will only fail extraction safely.
97    if unsafe { ffi::PySequence_Check(obj.as_ptr()) } == 0 {
98        return Err(CastError::new(obj, PySequence::type_object(obj.py()).into_any()).into());
99    }
100
101    let mut sv = SmallVec::with_capacity(obj.len().unwrap_or(0));
102    for item in obj.try_iter()? {
103        sv.push(item?.extract::<A::Item>().map_err(Into::into)?);
104    }
105    Ok(sv)
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::types::{PyBytes, PyBytesMethods, PyDict, PyList};
112
113    #[test]
114    fn test_smallvec_from_py_object() {
115        Python::attach(|py| {
116            let l = PyList::new(py, [1, 2, 3, 4, 5]).unwrap();
117            let sv: SmallVec<[u64; 8]> = l.extract().unwrap();
118            assert_eq!(sv.as_slice(), [1, 2, 3, 4, 5]);
119        });
120    }
121
122    #[test]
123    fn test_smallvec_from_py_object_fails() {
124        Python::attach(|py| {
125            let dict = PyDict::new(py);
126            let sv: PyResult<SmallVec<[u64; 8]>> = dict.extract();
127            assert_eq!(
128                sv.unwrap_err().to_string(),
129                "TypeError: 'dict' object is not an instance of 'Sequence'"
130            );
131        });
132    }
133
134    #[test]
135    fn test_smallvec_into_pyobject() {
136        Python::attach(|py| {
137            let sv: SmallVec<[u64; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
138            let hso = sv.into_pyobject(py).unwrap();
139            let l = PyList::new(py, [1, 2, 3, 4, 5]).unwrap();
140            assert!(l.eq(hso).unwrap());
141        });
142    }
143
144    #[test]
145    fn test_smallvec_intopyobject_impl() {
146        Python::attach(|py| {
147            let bytes: SmallVec<[u8; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
148            let obj = bytes.clone().into_pyobject(py).unwrap();
149            assert!(obj.is_instance_of::<PyBytes>());
150            let obj = obj.cast_into::<PyBytes>().unwrap();
151            assert_eq!(obj.as_bytes(), &*bytes);
152
153            let nums: SmallVec<[u16; 8]> = [1, 2, 3, 4, 5].iter().cloned().collect();
154            let obj = nums.into_pyobject(py).unwrap();
155            assert!(obj.is_instance_of::<PyList>());
156        });
157    }
158}