Skip to content

Commit a0f00d1

Browse files
committed
wip
1 parent a65111c commit a0f00d1

File tree

10 files changed

+828
-427
lines changed

10 files changed

+828
-427
lines changed

src/input/generic_iterable.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use super::parse_json::{JsonInput, JsonObject};
2+
use pyo3::{
3+
exceptions::PyTypeError,
4+
types::{
5+
PyByteArray, PyBytes, PyDict, PyFrozenSet, PyIterator, PyList, PyMapping, PySequence, PySet, PyString, PyTuple,
6+
},
7+
PyAny, PyResult, Python, ToPyObject,
8+
};
9+
10+
#[derive(Debug)]
11+
pub enum GenericIterable<'a> {
12+
List(&'a PyList),
13+
Tuple(&'a PyTuple),
14+
Set(&'a PySet),
15+
FrozenSet(&'a PyFrozenSet),
16+
Dict(&'a PyDict),
17+
// Treat dict values / keys / items as generic iterators
18+
// since PyPy doesn't export the concrete types
19+
DictKeys(&'a PyIterator),
20+
DictValues(&'a PyIterator),
21+
DictItems(&'a PyIterator),
22+
Mapping(&'a PyMapping),
23+
String(&'a PyString),
24+
Bytes(&'a PyBytes),
25+
PyByteArray(&'a PyByteArray),
26+
Sequence(&'a PySequence),
27+
Iterator(&'a PyIterator),
28+
JsonArray(&'a [JsonInput]),
29+
JsonObject(&'a JsonObject),
30+
}
31+
32+
type PyMappingItems<'a> = (&'a PyAny, &'a PyAny);
33+
34+
#[inline(always)]
35+
fn extract_items(item: PyResult<&PyAny>) -> PyResult<PyMappingItems<'_>> {
36+
match item {
37+
Ok(v) => v.extract::<PyMappingItems>(),
38+
Err(e) => Err(e),
39+
}
40+
}
41+
42+
impl<'a, 'py: 'a> GenericIterable<'a> {
43+
pub fn into_sequence_iterator(
44+
self,
45+
py: Python<'py>,
46+
) -> PyResult<Box<dyn Iterator<Item = PyResult<&'a PyAny>> + 'a>> {
47+
match self {
48+
GenericIterable::List(iter) => Ok(Box::new(iter.iter().map(Ok))),
49+
GenericIterable::Tuple(iter) => Ok(Box::new(iter.iter().map(Ok))),
50+
GenericIterable::Set(iter) => Ok(Box::new(iter.iter().map(Ok))),
51+
GenericIterable::FrozenSet(iter) => Ok(Box::new(iter.iter().map(Ok))),
52+
// Note that this iterates over only the keys, just like doing iter({}) in Python
53+
GenericIterable::Dict(iter) => Ok(Box::new(iter.iter().map(|(k, _)| Ok(k)))),
54+
GenericIterable::DictKeys(iter) => Ok(Box::new(iter)),
55+
GenericIterable::DictValues(iter) => Ok(Box::new(iter)),
56+
GenericIterable::DictItems(iter) => Ok(Box::new(iter)),
57+
// Note that this iterates over only the keys, just like doing iter({}) in Python
58+
GenericIterable::Mapping(iter) => Ok(Box::new(iter.keys()?.iter()?)),
59+
GenericIterable::String(iter) => Ok(Box::new(iter.iter()?)),
60+
GenericIterable::Bytes(iter) => Ok(Box::new(iter.iter()?)),
61+
GenericIterable::PyByteArray(iter) => Ok(Box::new(iter.iter()?)),
62+
GenericIterable::Sequence(iter) => Ok(Box::new(iter.iter()?)),
63+
GenericIterable::Iterator(iter) => Ok(Box::new(iter)),
64+
GenericIterable::JsonArray(iter) => Ok(Box::new(iter.iter().map(move |v| {
65+
let v = v.to_object(py);
66+
Ok(v.into_ref(py))
67+
}))),
68+
// Note that this iterates over only the keys, just like doing iter({}) in Python, just for consistency
69+
GenericIterable::JsonObject(iter) => Ok(Box::new(
70+
iter.iter().map(move |(k, _)| Ok(k.to_object(py).into_ref(py))),
71+
)),
72+
}
73+
}
74+
75+
pub fn into_mapping_items_iterator(
76+
self,
77+
py: Python<'py>,
78+
) -> PyResult<Box<dyn Iterator<Item = PyResult<PyMappingItems<'a>>> + 'a>> {
79+
match self {
80+
GenericIterable::List(iter) => Ok(Box::new(iter.iter().map(|v| extract_items(Ok(v))))),
81+
GenericIterable::Tuple(iter) => Ok(Box::new(iter.iter().map(|v| extract_items(Ok(v))))),
82+
GenericIterable::Set(iter) => Ok(Box::new(iter.iter().map(|v| extract_items(Ok(v))))),
83+
GenericIterable::FrozenSet(iter) => Ok(Box::new(iter.iter().map(|v| extract_items(Ok(v))))),
84+
// Note that we iterate over (key, value), unlike doing iter({}) in Python
85+
GenericIterable::Dict(iter) => Ok(Box::new(iter.iter().map(Ok))),
86+
// Keys or values can be tuples
87+
GenericIterable::DictKeys(iter) => Ok(Box::new(iter.map(extract_items))),
88+
GenericIterable::DictValues(iter) => Ok(Box::new(iter.map(extract_items))),
89+
GenericIterable::DictItems(iter) => Ok(Box::new(iter.map(extract_items))),
90+
// Note that we iterate over (key, value), unlike doing iter({}) in Python
91+
GenericIterable::Mapping(iter) => Ok(Box::new(iter.items()?.iter()?.map(extract_items))),
92+
// In Python if you do dict("foobar") you get "dictionary update sequence element #0 has length 1; 2 is required"
93+
// This is similar but arguably a better error message
94+
GenericIterable::String(_) => Err(PyTypeError::new_err(
95+
"Expected an iterable of (key, value) pairs, got a string",
96+
)),
97+
GenericIterable::Bytes(_) => Err(PyTypeError::new_err(
98+
"Expected an iterable of (key, value) pairs, got a bytes",
99+
)),
100+
GenericIterable::PyByteArray(_) => Err(PyTypeError::new_err(
101+
"Expected an iterable of (key, value) pairs, got a bytearray",
102+
)),
103+
// Obviously these may be things that are not convertible to a tuple of (Hashable, Any)
104+
// Python fails with a similar error message to above, ours will be slightly different (PyO3 will fail to extract) but similar enough
105+
GenericIterable::Sequence(iter) => Ok(Box::new(iter.iter()?.map(extract_items))),
106+
GenericIterable::Iterator(iter) => Ok(Box::new(iter.iter()?.map(extract_items))),
107+
GenericIterable::JsonArray(iter) => Ok(Box::new(
108+
iter.iter()
109+
.map(move |v| extract_items(Ok(v.to_object(py).into_ref(py)))),
110+
)),
111+
// Note that we iterate over (key, value), unlike doing iter({}) in Python
112+
GenericIterable::JsonObject(iter) => Ok(Box::new(iter.iter().map(move |(k, v)| {
113+
let k = PyString::new(py, k).as_ref();
114+
let v = v.to_object(py).into_ref(py);
115+
Ok((k, v))
116+
}))),
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)