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/_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.apply(enumerate)
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.apply(itertools.combinations, r)
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.apply(itertools.permutations, r)
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.apply(itertools.combinations_with_replacement, r)
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.apply(itertools.pairwise)
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.apply(partial(map, cz.functoolz.juxt(*funcs)))
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.apply(partial(mit.adjacent, predicate, distance=distance))
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
- This function is analogous to unique_everseen and is subject to the same performance considerations.
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
- return self.apply(mit.classify_unique)
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.1
4
- Summary: Add your description here
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 collections for eager operations.
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 `Collection` (`list`, `tuple`, `set`...), and represents **eagerly** evaluated data.
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)
@@ -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())