pyochain 0.5.1__py3-none-any.whl → 0.5.32__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyochain might be problematic. Click here for more details.
- pyochain/__init__.py +2 -2
- pyochain/_core/__init__.py +3 -1
- pyochain/_core/_format.py +34 -0
- pyochain/_core/_main.py +65 -44
- pyochain/_core/_protocols.py +2 -7
- pyochain/_dict/__init__.py +1 -2
- pyochain/_dict/_filters.py +38 -53
- pyochain/_dict/_groups.py +7 -8
- pyochain/_dict/_iter.py +52 -9
- pyochain/_dict/_joins.py +11 -9
- pyochain/_dict/_main.py +32 -226
- pyochain/_dict/_maps.py +142 -0
- pyochain/_dict/_nested.py +119 -65
- pyochain/_dict/_process.py +40 -7
- pyochain/_iter/_aggregations.py +1 -0
- pyochain/_iter/_booleans.py +3 -0
- pyochain/_iter/_dicts.py +243 -0
- pyochain/_iter/_eager.py +60 -22
- pyochain/_iter/_filters.py +40 -49
- pyochain/_iter/_joins.py +13 -16
- pyochain/_iter/_lists.py +11 -9
- pyochain/_iter/_main.py +297 -60
- pyochain/_iter/_maps.py +55 -39
- pyochain/_iter/_partitions.py +11 -14
- pyochain/_iter/_process.py +26 -44
- pyochain/_iter/_rolling.py +22 -28
- pyochain/_iter/_tuples.py +119 -14
- {pyochain-0.5.1.dist-info → pyochain-0.5.32.dist-info}/METADATA +8 -42
- pyochain-0.5.32.dist-info/RECORD +32 -0
- pyochain/_dict/_exprs.py +0 -115
- pyochain/_dict/_funcs.py +0 -62
- pyochain/_iter/_constructors.py +0 -155
- pyochain/_iter/_groups.py +0 -264
- pyochain-0.5.1.dist-info/RECORD +0 -33
- {pyochain-0.5.1.dist-info → pyochain-0.5.32.dist-info}/WHEEL +0 -0
pyochain/_iter/_tuples.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
5
5
|
from functools import partial
|
|
6
|
-
from typing import TYPE_CHECKING, Literal, overload
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
7
7
|
|
|
8
8
|
import cytoolz as cz
|
|
9
9
|
import more_itertools as mit
|
|
@@ -25,7 +25,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
"""
|
|
28
|
-
return self.
|
|
28
|
+
return self._lazy(enumerate)
|
|
29
29
|
|
|
30
30
|
@overload
|
|
31
31
|
def combinations(self, r: Literal[2]) -> Iter[tuple[T, T]]: ...
|
|
@@ -41,7 +41,6 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
41
41
|
|
|
42
42
|
Args:
|
|
43
43
|
r: Length of each combination.
|
|
44
|
-
|
|
45
44
|
```python
|
|
46
45
|
>>> import pyochain as pc
|
|
47
46
|
>>> pc.Iter.from_([1, 2, 3]).combinations(2).into(list)
|
|
@@ -49,7 +48,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
49
48
|
|
|
50
49
|
```
|
|
51
50
|
"""
|
|
52
|
-
return self.
|
|
51
|
+
return self._lazy(itertools.combinations, r)
|
|
53
52
|
|
|
54
53
|
@overload
|
|
55
54
|
def permutations(self, r: Literal[2]) -> Iter[tuple[T, T]]: ...
|
|
@@ -65,7 +64,6 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
65
64
|
|
|
66
65
|
Args:
|
|
67
66
|
r: Length of each permutation. Defaults to the length of the iterable.
|
|
68
|
-
|
|
69
67
|
```python
|
|
70
68
|
>>> import pyochain as pc
|
|
71
69
|
>>> pc.Iter.from_([1, 2, 3]).permutations(2).into(list)
|
|
@@ -73,7 +71,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
73
71
|
|
|
74
72
|
```
|
|
75
73
|
"""
|
|
76
|
-
return self.
|
|
74
|
+
return self._lazy(itertools.permutations, r)
|
|
77
75
|
|
|
78
76
|
@overload
|
|
79
77
|
def combinations_with_replacement(self, r: Literal[2]) -> Iter[tuple[T, T]]: ...
|
|
@@ -93,7 +91,6 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
93
91
|
|
|
94
92
|
Args:
|
|
95
93
|
r: Length of each combination.
|
|
96
|
-
|
|
97
94
|
```python
|
|
98
95
|
>>> import pyochain as pc
|
|
99
96
|
>>> pc.Iter.from_([1, 2, 3]).combinations_with_replacement(2).into(list)
|
|
@@ -101,7 +98,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
101
98
|
|
|
102
99
|
```
|
|
103
100
|
"""
|
|
104
|
-
return self.
|
|
101
|
+
return self._lazy(itertools.combinations_with_replacement, r)
|
|
105
102
|
|
|
106
103
|
def pairwise(self) -> Iter[tuple[T, T]]:
|
|
107
104
|
"""
|
|
@@ -113,7 +110,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
113
110
|
|
|
114
111
|
```
|
|
115
112
|
"""
|
|
116
|
-
return self.
|
|
113
|
+
return self._lazy(itertools.pairwise)
|
|
117
114
|
|
|
118
115
|
@overload
|
|
119
116
|
def map_juxt[R1, R2](
|
|
@@ -156,7 +153,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
156
153
|
|
|
157
154
|
```
|
|
158
155
|
"""
|
|
159
|
-
return self.
|
|
156
|
+
return self._lazy(partial(map, cz.functoolz.juxt(*funcs)))
|
|
160
157
|
|
|
161
158
|
def adjacent(
|
|
162
159
|
self, predicate: Callable[[T], bool], distance: int = 1
|
|
@@ -195,7 +192,7 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
195
192
|
|
|
196
193
|
See also groupby_transform, which can be used with this function to group ranges of items with the same bool value.
|
|
197
194
|
"""
|
|
198
|
-
return self.
|
|
195
|
+
return self._lazy(partial(mit.adjacent, predicate, distance=distance))
|
|
199
196
|
|
|
200
197
|
def classify_unique(self) -> Iter[tuple[T, bool, bool]]:
|
|
201
198
|
"""
|
|
@@ -205,6 +202,9 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
205
202
|
- The element itself
|
|
206
203
|
- False if the element is equal to the one preceding it in the input, True otherwise (i.e. the equivalent of unique_justseen)
|
|
207
204
|
- False if this element has been seen anywhere in the input before, True otherwise (i.e. the equivalent of unique_everseen)
|
|
205
|
+
|
|
206
|
+
This function is analogous to unique_everseen and is subject to the same performance considerations.
|
|
207
|
+
|
|
208
208
|
```python
|
|
209
209
|
>>> import pyochain as pc
|
|
210
210
|
>>> pc.Iter.from_("otto").classify_unique().into(list)
|
|
@@ -215,7 +215,112 @@ class BaseTuples[T](IterWrapper[T]):
|
|
|
215
215
|
('o', True, False)]
|
|
216
216
|
|
|
217
217
|
```
|
|
218
|
+
"""
|
|
219
|
+
return self._lazy(mit.classify_unique)
|
|
218
220
|
|
|
219
|
-
|
|
221
|
+
@overload
|
|
222
|
+
def group_by_transform(
|
|
223
|
+
self,
|
|
224
|
+
keyfunc: None = None,
|
|
225
|
+
valuefunc: None = None,
|
|
226
|
+
reducefunc: None = None,
|
|
227
|
+
) -> Iter[tuple[T, Iterator[T]]]: ...
|
|
228
|
+
@overload
|
|
229
|
+
def group_by_transform[U](
|
|
230
|
+
self,
|
|
231
|
+
keyfunc: Callable[[T], U],
|
|
232
|
+
valuefunc: None,
|
|
233
|
+
reducefunc: None,
|
|
234
|
+
) -> Iter[tuple[U, Iterator[T]]]: ...
|
|
235
|
+
@overload
|
|
236
|
+
def group_by_transform[V](
|
|
237
|
+
self,
|
|
238
|
+
keyfunc: None,
|
|
239
|
+
valuefunc: Callable[[T], V],
|
|
240
|
+
reducefunc: None,
|
|
241
|
+
) -> Iter[tuple[T, Iterator[V]]]: ...
|
|
242
|
+
@overload
|
|
243
|
+
def group_by_transform[U, V](
|
|
244
|
+
self,
|
|
245
|
+
keyfunc: Callable[[T], U],
|
|
246
|
+
valuefunc: Callable[[T], V],
|
|
247
|
+
reducefunc: None,
|
|
248
|
+
) -> Iter[tuple[U, Iterator[V]]]: ...
|
|
249
|
+
@overload
|
|
250
|
+
def group_by_transform[W](
|
|
251
|
+
self,
|
|
252
|
+
keyfunc: None,
|
|
253
|
+
valuefunc: None,
|
|
254
|
+
reducefunc: Callable[[Iterator[T]], W],
|
|
255
|
+
) -> Iter[tuple[T, W]]: ...
|
|
256
|
+
@overload
|
|
257
|
+
def group_by_transform[U, W](
|
|
258
|
+
self,
|
|
259
|
+
keyfunc: Callable[[T], U],
|
|
260
|
+
valuefunc: None,
|
|
261
|
+
reducefunc: Callable[[Iterator[T]], W],
|
|
262
|
+
) -> Iter[tuple[U, W]]: ...
|
|
263
|
+
@overload
|
|
264
|
+
def group_by_transform[V, W](
|
|
265
|
+
self,
|
|
266
|
+
keyfunc: None,
|
|
267
|
+
valuefunc: Callable[[T], V],
|
|
268
|
+
reducefunc: Callable[[Iterator[V]], W],
|
|
269
|
+
) -> Iter[tuple[T, W]]: ...
|
|
270
|
+
@overload
|
|
271
|
+
def group_by_transform[U, V, W](
|
|
272
|
+
self,
|
|
273
|
+
keyfunc: Callable[[T], U],
|
|
274
|
+
valuefunc: Callable[[T], V],
|
|
275
|
+
reducefunc: Callable[[Iterator[V]], W],
|
|
276
|
+
) -> Iter[tuple[U, W]]: ...
|
|
277
|
+
def group_by_transform[U, V](
|
|
278
|
+
self,
|
|
279
|
+
keyfunc: Callable[[T], U] | None = None,
|
|
280
|
+
valuefunc: Callable[[T], V] | None = None,
|
|
281
|
+
reducefunc: Any = None,
|
|
282
|
+
) -> Iter[tuple[Any, ...]]:
|
|
220
283
|
"""
|
|
221
|
-
|
|
284
|
+
An extension of ``Iter.groupby`` that can apply transformations to the grouped data.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
keyfunc: Function to compute the key for grouping. Defaults to None.
|
|
288
|
+
valuefunc: Function to transform individual items after grouping. Defaults to None.
|
|
289
|
+
reducefunc: Function to transform each group of items. Defaults to None.
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
```python
|
|
293
|
+
>>> import pyochain as pc
|
|
294
|
+
>>> data = pc.Iter.from_("aAAbBBcCC")
|
|
295
|
+
>>> data.group_by_transform(
|
|
296
|
+
... lambda k: k.upper(), lambda v: v.lower(), lambda g: "".join(g)
|
|
297
|
+
... ).into(list)
|
|
298
|
+
[('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')]
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
Each optional argument defaults to an identity function if not specified.
|
|
302
|
+
|
|
303
|
+
group_by_transform is useful when grouping elements of an iterable using a separate iterable as the key.
|
|
304
|
+
|
|
305
|
+
To do this, zip the iterables and pass a keyfunc that extracts the first element and a valuefunc that extracts the second element:
|
|
306
|
+
|
|
307
|
+
Note that the order of items in the iterable is significant.
|
|
308
|
+
|
|
309
|
+
Only adjacent items are grouped together, so if you don't want any duplicate groups, you should sort the iterable by the key function.
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
```python
|
|
313
|
+
>>> from operator import itemgetter
|
|
314
|
+
>>> data = pc.Iter.from_([0, 0, 1, 1, 1, 2, 2, 2, 3])
|
|
315
|
+
>>> data.zip("abcdefghi").group_by_transform(itemgetter(0), itemgetter(1)).map(
|
|
316
|
+
... lambda kv: (kv[0], "".join(kv[1]))
|
|
317
|
+
... ).into(list)
|
|
318
|
+
[(0, 'ab'), (1, 'cde'), (2, 'fgh'), (3, 'i')]
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
def _group_by_transform(data: Iterable[T]) -> Iterator[tuple[Any, ...]]:
|
|
324
|
+
return mit.groupby_transform(data, keyfunc, valuefunc, reducefunc)
|
|
325
|
+
|
|
326
|
+
return self._lazy(_group_by_transform)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyochain
|
|
3
|
-
Version: 0.5.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.5.32
|
|
4
|
+
Summary: Method chaining for iterables and dictionaries in Python.
|
|
5
5
|
Requires-Dist: cytoolz>=1.0.1
|
|
6
6
|
Requires-Dist: more-itertools>=10.8.0
|
|
7
7
|
Requires-Dist: rolling>=0.5.0
|
|
@@ -16,6 +16,10 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
|
|
17
17
|
Manipulate data through composable chains of operations, enhancing readability and reducing boilerplate.
|
|
18
18
|
|
|
19
|
+
## Notice on Stability ⚠️
|
|
20
|
+
|
|
21
|
+
`pyochain` is currently in early development (< 1.0), and the API may undergo significant changes multiple times before reaching a stable 1.0 release.
|
|
22
|
+
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
21
25
|
```bash
|
|
@@ -33,7 +37,7 @@ The full API reference can be found at:
|
|
|
33
37
|
|
|
34
38
|
* **Declarative over Imperative:** Replace explicit `for` and `while` loops with sequences of high-level operations (map, filter, group, join...).
|
|
35
39
|
* **Fluent Chaining:** Each method transforms the data and returns a new wrapper instance, allowing for seamless chaining.
|
|
36
|
-
* **Lazy and Eager:** `Iter` operates lazily for efficiency on large or infinite sequences, while `Seq` represents materialized
|
|
40
|
+
* **Lazy and Eager:** `Iter` operates lazily for efficiency on large or infinite sequences, while `Seq` represents materialized sequences for eager operations.
|
|
37
41
|
* **100% Type-safe:** Extensive use of generics and overloads ensures type safety and improves developer experience.
|
|
38
42
|
* **Documentation-first:** Each method is thoroughly documented with clear explanations, and usage examples. Before any commit is made, each docstring is automatically tested to ensure accuracy. This also allows for a convenient experience in IDEs, where developers can easily access documentation with a simple hover of the mouse.
|
|
39
43
|
* **Functional paradigm:** Design encourages building complex data transformations by composing simple, reusable functions on known buildings blocks, rather than implementing customs classes each time.
|
|
@@ -41,7 +45,6 @@ The full API reference can be found at:
|
|
|
41
45
|
### Inspirations
|
|
42
46
|
|
|
43
47
|
* **Rust's language and Rust `Iterator` Trait:** Emulate naming conventions (`from_()`, `into()`) and leverage concepts from Rust's powerful iterator traits (method chaining, lazy evaluation) to bring similar expressiveness to Python.
|
|
44
|
-
* **Polars API:** The powerful expression API for `pyochain.Dict` (`select`, `with_fields`, `key`) mimics the expressive power of Polars for selecting, transforming, and reshaping nested dictionary data.
|
|
45
48
|
* **Python iterators libraries:** Libraries like `rolling`, `cytoolz`, and `more-itertools` provided ideas, inspiration, and implementations for many of the iterator methods.
|
|
46
49
|
* **PyFunctional:** Although not directly used (because I started writing pyochain before discovering it), also shares similar goals and ideas.
|
|
47
50
|
|
|
@@ -57,7 +60,7 @@ Provides a vast array of methods for transformation, filtering, aggregation, joi
|
|
|
57
60
|
|
|
58
61
|
#### `Seq[T]`
|
|
59
62
|
|
|
60
|
-
Wraps a Python `
|
|
63
|
+
Wraps a Python `Sequence` (`list`, `tuple`...), and represents **eagerly** evaluated data.
|
|
61
64
|
|
|
62
65
|
Exposes a subset of the `Iter` methods who operate on the full dataset (e.g., `sort`, `union`) or who aggregate it.
|
|
63
66
|
|
|
@@ -77,8 +80,6 @@ But `Dict` can work also as well as on "irregular" structures (e.g., `dict[Any,
|
|
|
77
80
|
|
|
78
81
|
* `pluck` to extract multiple fields at once.
|
|
79
82
|
* `flatten` to collapse nested structures into a single level.
|
|
80
|
-
* `schema` to infer the structure of the data by recursively analyzing keys and value types.
|
|
81
|
-
* `pyochain.key` expressions to compute/retrieve/select/create new fields from existing nested data in a declarative way.
|
|
82
83
|
|
|
83
84
|
#### `Wrapper[T]`
|
|
84
85
|
|
|
@@ -122,41 +123,6 @@ Each method and class make extensive use of generics, type hints, and overloads
|
|
|
122
123
|
|
|
123
124
|
Since there's much less need for intermediate variables, the developper don't have to annotate them as much, whilst still keeping a type-safe codebase.
|
|
124
125
|
|
|
125
|
-
Target: modern Python 3.13 syntax (PEP 695 generics, updated collections.abc types).
|
|
126
|
-
|
|
127
|
-
### Expressions for Dict ``pyochain.key``
|
|
128
|
-
|
|
129
|
-
Compute new fields from existing nested data with key() and Expr.apply(), either selecting a new dict or merging into the root.
|
|
130
|
-
|
|
131
|
-
```python
|
|
132
|
-
import pyochain as pc
|
|
133
|
-
|
|
134
|
-
# Build a compact view
|
|
135
|
-
data = pc.Dict(
|
|
136
|
-
{
|
|
137
|
-
"user": {"name": "Alice", "age": 30},
|
|
138
|
-
"scores": {"math": 18, "eng": 15},
|
|
139
|
-
}
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
view = data.select(
|
|
143
|
-
pc.key("user").key("name"),
|
|
144
|
-
pc.key("scores").key("math"),
|
|
145
|
-
pc.key("scores").key("eng"),
|
|
146
|
-
pc.key("user").key("age").apply(lambda x: x >= 18).alias("is_adult"),
|
|
147
|
-
)
|
|
148
|
-
# {"name": "Alice", "math": 18, "eng": 15, "is_adult": True}
|
|
149
|
-
merged = data.with_fields(
|
|
150
|
-
pc.key("scores").key("math").apply(lambda x: x * 10).alias("math_x10")
|
|
151
|
-
)
|
|
152
|
-
# {
|
|
153
|
-
# 'user': {'name': 'Alice', 'age': 30},
|
|
154
|
-
# 'scores': {'math': 18, 'eng': 15},
|
|
155
|
-
# 'math_x10': 180
|
|
156
|
-
# }
|
|
157
|
-
|
|
158
|
-
```
|
|
159
|
-
|
|
160
126
|
### Convenience mappers: itr and struct
|
|
161
127
|
|
|
162
128
|
Operate on iterables of iterables or iterables of dicts without leaving the chain.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
pyochain/__init__.py,sha256=IbaRq48Skr5RhltOmUkmwNbffziiFy9JltsLMDYlSEg,126
|
|
2
|
+
pyochain/_core/__init__.py,sha256=tBC30VIS-bRGGRbtT3NmFZ4kJ1Du84AGF9WJ17upJCY,505
|
|
3
|
+
pyochain/_core/_format.py,sha256=7H9sAlLRoUpaOw8gzKso7YAGrtcUs9dTcRvlb2oO6xo,900
|
|
4
|
+
pyochain/_core/_main.py,sha256=8LPnMRJnv8SDz2Q3rmngG1rx9c7fhkgAlqm5BzlUd34,5745
|
|
5
|
+
pyochain/_core/_protocols.py,sha256=UgjOCINuz6cJpkkj7--6S0ULbUudv860HVNYEquBZvM,833
|
|
6
|
+
pyochain/_dict/__init__.py,sha256=z3_hkXG_BrmS63WGjQocu7QXNuWZxeFF5wAnERmobsQ,44
|
|
7
|
+
pyochain/_dict/_filters.py,sha256=I6kmcmeUsoxjMg9mOJbRd2aKg7sjfBUUEKaiGQc2-_c,7927
|
|
8
|
+
pyochain/_dict/_groups.py,sha256=2QPQsfCV7GvUTEJfpW72EDKXGw5Yvw17Pcp9-X2iBo4,6076
|
|
9
|
+
pyochain/_dict/_iter.py,sha256=y8S6zAFu6A9NK-13f-u0Q3-lt16jE3kuMOl8awn_nnI,3702
|
|
10
|
+
pyochain/_dict/_joins.py,sha256=a6wzbr_xAmne0ohtL5tDpdccJ_89uUKgc3MnbZr39vk,4420
|
|
11
|
+
pyochain/_dict/_main.py,sha256=HNpClIk7mlkTLIU79mfLoRTM8YxaE01IMrXd9q132LI,3066
|
|
12
|
+
pyochain/_dict/_maps.py,sha256=pxwgPvgnRiQ1Kj8vTwQwG1OjVDTu0qKMeQPRFfFwHoM,3941
|
|
13
|
+
pyochain/_dict/_nested.py,sha256=VE2MisvxaOReevNvtyfTMCA_HYXBGnFDl7Tad9i-R68,9286
|
|
14
|
+
pyochain/_dict/_process.py,sha256=g5O_6Xwp_NPt1528KDPJTphyykmu6wYqztBx5dvMHn0,6381
|
|
15
|
+
pyochain/_iter/__init__.py,sha256=a8YS8Yx_UbLXdzM70YQrt6gyv1v7QW_16i2ydsyGGV8,56
|
|
16
|
+
pyochain/_iter/_aggregations.py,sha256=VkAYF9w4GwVBDYx1H5pL2dkMIWfodj3QsZsOc4AchlA,8584
|
|
17
|
+
pyochain/_iter/_booleans.py,sha256=KE4x-lxayHH_recHoX5ZbNz7JVdC9WuvA2ewNBpqUL0,7210
|
|
18
|
+
pyochain/_iter/_dicts.py,sha256=eA6WafYcOrQS-ZrUES2B-yX2HTqewSgvWMl6neqEDk8,7652
|
|
19
|
+
pyochain/_iter/_eager.py,sha256=ARC995qZaEE1v9kiyZNEieM1qJKEXiOUdkIRJTpOkJs,6880
|
|
20
|
+
pyochain/_iter/_filters.py,sha256=IRBhvopIT8YkBdH5UEMxHz7FyADMfhkLiAB8qdleces,15136
|
|
21
|
+
pyochain/_iter/_joins.py,sha256=ivvnTvfiw67U9kVWMIoy78PJNBwN0oZ4Ko9AyfxyGYM,13043
|
|
22
|
+
pyochain/_iter/_lists.py,sha256=TU-HjyyM_KqJTwA_0V0fCyJHl9L6wsRi1n4Sl8g2Gro,11103
|
|
23
|
+
pyochain/_iter/_main.py,sha256=ILGWDv6E0PWYDkeZEAwHE8chjow9xcosVH6M94Mjb5I,14986
|
|
24
|
+
pyochain/_iter/_maps.py,sha256=5PR7OGX9VewX_CDRyllcg0wIKEu81yaQZU7sRU6OCs4,11914
|
|
25
|
+
pyochain/_iter/_partitions.py,sha256=MYxlzQRrCBtfjnhtIVdMUhkNq5FCTpFV1R9sJ9LznsM,5095
|
|
26
|
+
pyochain/_iter/_process.py,sha256=P3Zw3uInZkOL-VlDUG4xfTnwek6lIa4j2a3IwxVaLD0,11039
|
|
27
|
+
pyochain/_iter/_rolling.py,sha256=YJ5X23eZTizXEJYneaZvn98zORbvJzLWXP8gX1BCvGY,6979
|
|
28
|
+
pyochain/_iter/_tuples.py,sha256=rcEeqrz3eio1CEYyZ0lt2CC5P_OW7ARLkTL0g7yf3ws,11137
|
|
29
|
+
pyochain/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
pyochain-0.5.32.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
31
|
+
pyochain-0.5.32.dist-info/METADATA,sha256=VX2CxM_dLgjRivIc5_LwCJ5QBQmjDjNFjwG2CYjGVXg,9985
|
|
32
|
+
pyochain-0.5.32.dist-info/RECORD,,
|
pyochain/_dict/_exprs.py
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Callable, Iterable
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from typing import Any, Self, TypeGuard
|
|
6
|
-
|
|
7
|
-
import cytoolz as cz
|
|
8
|
-
|
|
9
|
-
from .._core import Pipeable
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass(slots=True)
|
|
13
|
-
class Expr(Pipeable):
|
|
14
|
-
"""
|
|
15
|
-
Represents an expression in the pipeline.
|
|
16
|
-
|
|
17
|
-
An Expr encapsulates a sequence of operations to be applied to keys on a python dict.
|
|
18
|
-
|
|
19
|
-
Each Expr instance maintains:
|
|
20
|
-
- A list of tokens representing the keys to access in the dict (the first being the input given to the `key` function),
|
|
21
|
-
- A tuple of operations to apply to the accessed data
|
|
22
|
-
- An alias for the expression (default to the last token).
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
__tokens__: list[str]
|
|
26
|
-
__ops__: tuple[Callable[[object], object], ...]
|
|
27
|
-
_alias: str
|
|
28
|
-
|
|
29
|
-
def __repr__(self) -> str:
|
|
30
|
-
parts: list[str] = []
|
|
31
|
-
s_parts: list[str] = []
|
|
32
|
-
for t in self.__tokens__:
|
|
33
|
-
parts.append(f"field({t!r})")
|
|
34
|
-
if s_parts:
|
|
35
|
-
s_parts.append(".")
|
|
36
|
-
s_parts.append(str(t))
|
|
37
|
-
symbolic = ".".join(parts) if parts else "<root>"
|
|
38
|
-
lowered = "".join(s_parts) or "<root>"
|
|
39
|
-
base = f"Expr({symbolic} -> {lowered})"
|
|
40
|
-
return f"{base}.alias({self._alias!r})"
|
|
41
|
-
|
|
42
|
-
def _to_expr(self, op: Callable[[Any], Any]) -> Self:
|
|
43
|
-
return self.__class__(
|
|
44
|
-
self.__tokens__,
|
|
45
|
-
self.__ops__ + (op,),
|
|
46
|
-
self._alias,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
def key(self, name: str) -> Self:
|
|
50
|
-
"""
|
|
51
|
-
Add a key to the expression.
|
|
52
|
-
|
|
53
|
-
Allow to access nested keys in a dict.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
name: The key to access.
|
|
57
|
-
Example:
|
|
58
|
-
```python
|
|
59
|
-
>>> import pyochain as pc
|
|
60
|
-
>>> expr = pc.key("a").key("b").key("c")
|
|
61
|
-
>>> expr.__tokens__
|
|
62
|
-
['a', 'b', 'c']
|
|
63
|
-
>>> data = {"a": {"b": {"c": 42}}}
|
|
64
|
-
>>> pc.Dict(data).select(expr).unwrap()
|
|
65
|
-
{'c': 42}
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
"""
|
|
69
|
-
return self.__class__(
|
|
70
|
-
self.__tokens__ + [name],
|
|
71
|
-
self.__ops__,
|
|
72
|
-
name,
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
def alias(self, name: str) -> Self:
|
|
76
|
-
return self.__class__(self.__tokens__, self.__ops__, name)
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def name(self) -> str:
|
|
80
|
-
return self._alias
|
|
81
|
-
|
|
82
|
-
def apply(self, fn: Callable[[Any], Any]) -> Self:
|
|
83
|
-
"""
|
|
84
|
-
Applies the given function fn to the data within the current Expr instance
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
def _apply(data: Any) -> Any:
|
|
88
|
-
return fn(data)
|
|
89
|
-
|
|
90
|
-
return self._to_expr(_apply)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def key(name: str) -> Expr:
|
|
94
|
-
"""Create an Expr that accesses the given key."""
|
|
95
|
-
return Expr([name], (), name)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _expr_identity(obj: Any) -> TypeGuard[Expr]:
|
|
99
|
-
return hasattr(obj, "__tokens__")
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
type IntoExpr = Expr | str
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def compute_exprs(
|
|
106
|
-
exprs: Iterable[IntoExpr], data_in: dict[str, Any], data_out: dict[str, Any]
|
|
107
|
-
) -> dict[str, Any]:
|
|
108
|
-
for e in exprs:
|
|
109
|
-
if not _expr_identity(e):
|
|
110
|
-
e = key(e) # type: ignore
|
|
111
|
-
current: object = cz.dicttoolz.get_in(e.__tokens__, data_in)
|
|
112
|
-
for op in e.__ops__:
|
|
113
|
-
current = op(current)
|
|
114
|
-
data_out[e.name] = current
|
|
115
|
-
return data_out
|
pyochain/_dict/_funcs.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def dict_repr(
|
|
5
|
-
v: object,
|
|
6
|
-
depth: int = 0,
|
|
7
|
-
max_depth: int = 3,
|
|
8
|
-
max_items: int = 6,
|
|
9
|
-
max_str: int = 80,
|
|
10
|
-
indent: int = 2,
|
|
11
|
-
) -> str:
|
|
12
|
-
pad = " " * (depth * indent)
|
|
13
|
-
if depth > max_depth:
|
|
14
|
-
return "…"
|
|
15
|
-
match v:
|
|
16
|
-
case dict():
|
|
17
|
-
items: list[tuple[str, Any]] = list(v.items()) # type: ignore
|
|
18
|
-
shown: list[tuple[str, Any]] = items[:max_items]
|
|
19
|
-
if (
|
|
20
|
-
all(
|
|
21
|
-
not isinstance(val, dict) and not isinstance(val, list)
|
|
22
|
-
for _, val in shown
|
|
23
|
-
)
|
|
24
|
-
and len(shown) <= 2
|
|
25
|
-
):
|
|
26
|
-
body = ", ".join(
|
|
27
|
-
f"{k!r}: {dict_repr(val, depth + 1)}" for k, val in shown
|
|
28
|
-
)
|
|
29
|
-
if len(items) > max_items:
|
|
30
|
-
body += ", …"
|
|
31
|
-
return "{" + body + "}"
|
|
32
|
-
lines: list[str] = []
|
|
33
|
-
for k, val in shown:
|
|
34
|
-
lines.append(
|
|
35
|
-
f"{pad}{' ' * indent}{k!r}: {dict_repr(val, depth + 1, max_depth, max_items, max_str, indent)}"
|
|
36
|
-
)
|
|
37
|
-
if len(items) > max_items:
|
|
38
|
-
lines.append(f"{pad}{' ' * indent}…")
|
|
39
|
-
return "{\n" + ",\n".join(lines) + f"\n{pad}" + "}"
|
|
40
|
-
|
|
41
|
-
case list():
|
|
42
|
-
elems: list[Any] = v[:max_items] # type: ignore
|
|
43
|
-
if (
|
|
44
|
-
all(isinstance(x, (int, float, str, bool, type(None))) for x in elems)
|
|
45
|
-
and len(elems) <= 4
|
|
46
|
-
):
|
|
47
|
-
body = ", ".join(dict_repr(x, depth + 1) for x in elems)
|
|
48
|
-
if len(v) > max_items: # type: ignore
|
|
49
|
-
body += ", …"
|
|
50
|
-
return "[" + body + "]"
|
|
51
|
-
lines = [
|
|
52
|
-
f"{pad}{' ' * indent}{dict_repr(x, depth + 1, max_depth, max_items, max_str, indent)}"
|
|
53
|
-
for x in elems
|
|
54
|
-
]
|
|
55
|
-
if len(v) > max_items: # type: ignore
|
|
56
|
-
lines.append(f"{pad}{' ' * indent}…")
|
|
57
|
-
return "[\n" + ",\n".join(lines) + f"\n{pad}" + "]"
|
|
58
|
-
|
|
59
|
-
case str():
|
|
60
|
-
return repr(v if len(v) <= max_str else v[:max_str] + "…")
|
|
61
|
-
case _:
|
|
62
|
-
return repr(v)
|
pyochain/_iter/_constructors.py
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import itertools
|
|
4
|
-
from collections.abc import Callable, Iterable, Iterator
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
|
|
7
|
-
import cytoolz as cz
|
|
8
|
-
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from ._main import Iter
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class IterConstructors:
|
|
14
|
-
@staticmethod
|
|
15
|
-
def from_count(start: int = 0, step: int = 1) -> Iter[int]:
|
|
16
|
-
"""
|
|
17
|
-
Create an infinite iterator of evenly spaced values.
|
|
18
|
-
|
|
19
|
-
**Warning** ⚠️
|
|
20
|
-
This creates an infinite iterator.
|
|
21
|
-
Be sure to use `Iter.take()` or `Iter.slice()` to limit the number of items taken.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
start: Starting value of the sequence. Defaults to 0.
|
|
25
|
-
step: Difference between consecutive values. Defaults to 1.
|
|
26
|
-
Example:
|
|
27
|
-
```python
|
|
28
|
-
>>> import pyochain as pc
|
|
29
|
-
>>> pc.Iter.from_count(10, 2).take(3).into(list)
|
|
30
|
-
[10, 12, 14]
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
"""
|
|
34
|
-
from ._main import Iter
|
|
35
|
-
|
|
36
|
-
return Iter(itertools.count(start, step))
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def from_func[U](func: Callable[[U], U], input: U) -> Iter[U]:
|
|
40
|
-
"""
|
|
41
|
-
Create an infinite iterator by repeatedly applying a function on an original input.
|
|
42
|
-
|
|
43
|
-
**Warning** ⚠️
|
|
44
|
-
This creates an infinite iterator.
|
|
45
|
-
Be sure to use `Iter.take()` or `Iter.slice()` to limit the number of items taken.
|
|
46
|
-
|
|
47
|
-
Args:
|
|
48
|
-
func: Function to apply repeatedly.
|
|
49
|
-
input: Initial value to start the iteration.
|
|
50
|
-
|
|
51
|
-
Example:
|
|
52
|
-
```python
|
|
53
|
-
>>> import pyochain as pc
|
|
54
|
-
>>> pc.Iter.from_func(lambda x: x + 1, 0).take(3).into(list)
|
|
55
|
-
[0, 1, 2]
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
"""
|
|
59
|
-
from ._main import Iter
|
|
60
|
-
|
|
61
|
-
return Iter(cz.itertoolz.iterate(func, input))
|
|
62
|
-
|
|
63
|
-
@staticmethod
|
|
64
|
-
def from_[U](data: Iterable[U]) -> Iter[U]:
|
|
65
|
-
"""
|
|
66
|
-
Create an iterator from any Iterable.
|
|
67
|
-
|
|
68
|
-
- An Iterable is any object capable of returning its members one at a time, permitting it to be iterated over in a for-loop.
|
|
69
|
-
- An Iterator is an object representing a stream of data; returned by calling `iter()` on an Iterable.
|
|
70
|
-
- Once an Iterator is exhausted, it cannot be reused or reset.
|
|
71
|
-
|
|
72
|
-
If you need to reuse the data, consider collecting it into a list first with `.collect()`.
|
|
73
|
-
|
|
74
|
-
In general, avoid intermediate references when dealing with lazy iterators, and prioritize method chaining instead.
|
|
75
|
-
Args:
|
|
76
|
-
data: Iterable to convert into an iterator.
|
|
77
|
-
Example:
|
|
78
|
-
```python
|
|
79
|
-
>>> import pyochain as pc
|
|
80
|
-
>>> data: tuple[int, ...] = (1, 2, 3)
|
|
81
|
-
>>> iterator = pc.Iter.from_(data)
|
|
82
|
-
>>> iterator.unwrap().__class__.__name__
|
|
83
|
-
'tuple_iterator'
|
|
84
|
-
>>> mapped = iterator.map(lambda x: x * 2)
|
|
85
|
-
>>> mapped.unwrap().__class__.__name__
|
|
86
|
-
'map'
|
|
87
|
-
>>> mapped.collect(tuple).unwrap()
|
|
88
|
-
(2, 4, 6)
|
|
89
|
-
>>> # iterator is now exhausted
|
|
90
|
-
>>> iterator.collect().unwrap()
|
|
91
|
-
[]
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
"""
|
|
95
|
-
from ._main import Iter
|
|
96
|
-
|
|
97
|
-
return Iter(iter(data))
|
|
98
|
-
|
|
99
|
-
@staticmethod
|
|
100
|
-
def unfold[S, V](seed: S, generator: Callable[[S], tuple[V, S] | None]) -> Iter[V]:
|
|
101
|
-
"""
|
|
102
|
-
Create an iterator by repeatedly applying a generator function to an initial state.
|
|
103
|
-
|
|
104
|
-
The `generator` function takes the current state and must return:
|
|
105
|
-
- A tuple `(value, new_state)` to emit the `value` and continue with the `new_state`.
|
|
106
|
-
- `None` to stop the generation.
|
|
107
|
-
|
|
108
|
-
This is functionally equivalent to a state-based `while` loop.
|
|
109
|
-
|
|
110
|
-
**Warning** ⚠️
|
|
111
|
-
If the `generator` function never returns `None`, it creates an infinite iterator.
|
|
112
|
-
Be sure to use `Iter.take()` or `Iter.slice()` to limit the number of items taken if necessary.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
seed: Initial state for the generator.
|
|
116
|
-
generator: Function that generates the next value and state.
|
|
117
|
-
|
|
118
|
-
Example:
|
|
119
|
-
```python
|
|
120
|
-
>>> import pyochain as pc
|
|
121
|
-
>>> # Example 1: Simple counter up to 5
|
|
122
|
-
>>> def counter_generator(state: int) -> tuple[int, int] | None:
|
|
123
|
-
... if state < 5:
|
|
124
|
-
... return (state * 10, state + 1)
|
|
125
|
-
... return None
|
|
126
|
-
>>> pc.Iter.unfold(seed=0, generator=counter_generator).into(list)
|
|
127
|
-
[0, 10, 20, 30, 40]
|
|
128
|
-
>>> # Example 2: Fibonacci sequence up to 100
|
|
129
|
-
>>> type FibState = tuple[int, int]
|
|
130
|
-
>>> def fib_generator(state: FibState) -> tuple[int, FibState] | None:
|
|
131
|
-
... a, b = state
|
|
132
|
-
... if a > 100:
|
|
133
|
-
... return None
|
|
134
|
-
... return (a, (b, a + b))
|
|
135
|
-
>>> pc.Iter.unfold(seed=(0, 1), generator=fib_generator).into(list)
|
|
136
|
-
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
|
|
137
|
-
>>> # Example 3: Infinite iterator (requires take())
|
|
138
|
-
>>> pc.Iter.unfold(seed=1, generator=lambda s: (s, s * 2)).take(5).into(list)
|
|
139
|
-
[1, 2, 4, 8, 16]
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
"""
|
|
143
|
-
from ._main import Iter
|
|
144
|
-
|
|
145
|
-
def _unfold() -> Iterator[V]:
|
|
146
|
-
current_seed: S = seed
|
|
147
|
-
while True:
|
|
148
|
-
result: tuple[V, S] | None = generator(current_seed)
|
|
149
|
-
if result is None:
|
|
150
|
-
break
|
|
151
|
-
value, next_seed = result
|
|
152
|
-
yield value
|
|
153
|
-
current_seed = next_seed
|
|
154
|
-
|
|
155
|
-
return Iter(_unfold())
|