python-iterutils 0.0.9__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ChenyangGao <https://github.com/ChenyangGao>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-iterutils
3
+ Version: 0.0.9
4
+ Summary: Python another itertools.
5
+ Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
6
+ License: MIT
7
+ Keywords: iterable,iterutils
8
+ Author: ChenyangGao
9
+ Author-email: wosiwujm@gmail.com
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Topic :: Software Development
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Requires-Dist: python-asynctools (>=0.0.8.2)
26
+ Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Python another itertools.
30
+
31
+ ## Installation
32
+
33
+ You can install from [pypi](https://pypi.org/project/python-iterutils/)
34
+
35
+ ```console
36
+ pip install -U python-iterutils
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```python
42
+ import iterutils
43
+ ```
44
+
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
+ __version__ = (0, 0, 9)
6
+ __all__ = [
7
+ "iterable", "async_iterable", "foreach", "async_foreach", "through", "async_through",
8
+ "wrap_iter", "wrap_aiter", "acc_step", "cut_iter", "run_gen_step", "run_gen_step_iter",
9
+ "Return", "Yield", "YieldFrom",
10
+ ]
11
+
12
+ from abc import ABC, abstractmethod
13
+ from asyncio import to_thread
14
+ from collections.abc import (
15
+ AsyncIterable, AsyncIterator, Callable, Generator, Iterable, Iterator,
16
+ )
17
+ from dataclasses import dataclass
18
+ from inspect import isawaitable
19
+ from typing import overload, Any, Literal, TypeVar
20
+
21
+ from asynctools import async_map, async_zip, ensure_async, ensure_aiter
22
+
23
+
24
+ T = TypeVar("T")
25
+
26
+
27
+ @dataclass(slots=True, frozen=True)
28
+ class YieldBase(ABC):
29
+ value: Any
30
+ identity: bool = False
31
+ try_call_me: bool = True
32
+
33
+ @property
34
+ @abstractmethod
35
+ def yield_type(self, /) -> int:
36
+ ...
37
+
38
+
39
+ class Return(YieldBase):
40
+ yield_type = 0
41
+
42
+
43
+ class Yield(YieldBase):
44
+ yield_type = 1
45
+
46
+
47
+ class YieldFrom(YieldBase):
48
+ yield_type = 2
49
+
50
+
51
+ def iterable(it, /) -> bool:
52
+ try:
53
+ return isinstance(iter(it), Iterable)
54
+ except TypeError:
55
+ return False
56
+
57
+
58
+ def async_iterable(it, /) -> bool:
59
+ try:
60
+ return isinstance(iter(it), AsyncIterable)
61
+ except TypeError:
62
+ return False
63
+
64
+
65
+ def foreach(value: Callable, iterable, /, *iterables):
66
+ if iterables:
67
+ for args in zip(iterable, *iterables):
68
+ value(*args)
69
+ else:
70
+ for arg in iterable:
71
+ value(arg)
72
+
73
+
74
+ async def async_foreach(value: Callable, iterable, /, *iterables, threaded: bool = True):
75
+ value = ensure_async(value, threaded=threaded)
76
+ if iterables:
77
+ async for args in async_zip(iterable, *iterables, threaded=threaded):
78
+ await value(*args)
79
+ else:
80
+ async for arg in ensure_aiter(iterable, threaded=threaded):
81
+ await value(arg)
82
+
83
+
84
+ def through(it: Iterable, /, take_while: None | Callable = None):
85
+ if take_while is None:
86
+ for _ in it:
87
+ pass
88
+ else:
89
+ for v in map(take_while, it):
90
+ if not v:
91
+ break
92
+
93
+
94
+ async def async_through(
95
+ it: Iterable | AsyncIterable,
96
+ /,
97
+ take_while: None | Callable = None,
98
+ threaded: bool = True,
99
+ ):
100
+ it = ensure_aiter(it, threaded=threaded)
101
+ if take_while is None:
102
+ async for _ in it:
103
+ pass
104
+ elif take_while is bool:
105
+ async for v in it:
106
+ if not v:
107
+ break
108
+ else:
109
+ async for v in async_map(take_while, it):
110
+ if not v:
111
+ break
112
+
113
+
114
+ def wrap_iter(
115
+ it: Iterable[T],
116
+ /,
117
+ callprev: None | Callable[[T], Any] = None,
118
+ callnext: None | Callable[[T], Any] = None,
119
+ callenter: None | Callable[[Iterable[T]], Any] = None,
120
+ callexit: None | Callable[[Iterable[T], None | BaseException], Any] = None,
121
+ ) -> Iterator[T]:
122
+ if not callable(callprev):
123
+ callprev = None
124
+ if not callable(callnext):
125
+ callnext = None
126
+ try:
127
+ if callable(callenter):
128
+ callenter(it)
129
+ for e in it:
130
+ if callprev:
131
+ try:
132
+ callprev(e)
133
+ except (StopIteration, GeneratorExit):
134
+ break
135
+ yield e
136
+ if callnext:
137
+ try:
138
+ callnext(e)
139
+ except (StopIteration, GeneratorExit):
140
+ break
141
+ except BaseException as e:
142
+ if callable(callexit):
143
+ if not callexit(it, e):
144
+ raise
145
+ else:
146
+ raise
147
+ finally:
148
+ if callable(callexit):
149
+ callexit(it, None)
150
+
151
+
152
+ async def wrap_aiter(
153
+ it: Iterable[T] | AsyncIterable[T],
154
+ /,
155
+ callprev: None | Callable[[T], Any] = None,
156
+ callnext: None | Callable[[T], Any] = None,
157
+ callenter: None | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
158
+ callexit: None | Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] = None,
159
+ threaded: bool = True,
160
+ ) -> AsyncIterator[T]:
161
+ callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
162
+ callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
163
+ try:
164
+ async for e in ensure_aiter(it, threaded=threaded):
165
+ if callprev:
166
+ try:
167
+ await callprev(e)
168
+ except (StopAsyncIteration, GeneratorExit):
169
+ break
170
+ yield e
171
+ if callnext:
172
+ try:
173
+ await callnext(e)
174
+ except (StopAsyncIteration, GeneratorExit):
175
+ break
176
+ except BaseException as e:
177
+ if callable(callexit):
178
+ if not await ensure_async(callexit, threaded=threaded)(it, e):
179
+ raise
180
+ else:
181
+ raise
182
+ finally:
183
+ if callable(callexit):
184
+ await ensure_async(callexit, threaded=threaded)(it, None)
185
+
186
+
187
+ def acc_step(
188
+ start: int,
189
+ stop: None | int = None,
190
+ step: int = 1,
191
+ ) -> Iterator[tuple[int, int, int]]:
192
+ if stop is None:
193
+ start, stop = 0, start
194
+ for i in range(start + step, stop, step):
195
+ yield start, (start := i), step
196
+ if start != stop:
197
+ yield start, stop, stop - start
198
+
199
+
200
+ def cut_iter(
201
+ start: int,
202
+ stop: None | int = None,
203
+ step: int = 1,
204
+ ) -> Iterator[tuple[int, int]]:
205
+ if stop is None:
206
+ start, stop = 0, start
207
+ for start in range(start + step, stop, step):
208
+ yield start, step
209
+ if start != stop:
210
+ yield stop, stop - start
211
+
212
+
213
+ def run_gen_step(
214
+ gen_step: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
215
+ *,
216
+ async_: bool = False,
217
+ threaded: bool = False,
218
+ as_iter: bool = False,
219
+ ) -> T:
220
+ if callable(gen_step):
221
+ gen = gen_step()
222
+ close = gen.close
223
+ else:
224
+ gen = gen_step
225
+ close = None
226
+ send = gen.send
227
+ throw = gen.throw
228
+ if async_:
229
+ async def process():
230
+ try:
231
+ if threaded:
232
+ value = await to_thread(send, None)
233
+ else:
234
+ value = send(None)
235
+ while True:
236
+ if isinstance(value, YieldBase):
237
+ raise StopIteration(value)
238
+ try:
239
+ if callable(value):
240
+ value = value()
241
+ if isawaitable(value):
242
+ value = await value
243
+ except BaseException as e:
244
+ if threaded:
245
+ value = await to_thread(throw, e)
246
+ else:
247
+ value = throw(e)
248
+ else:
249
+ if threaded:
250
+ value = await to_thread(send, value)
251
+ else:
252
+ value = send(value)
253
+ except StopIteration as e:
254
+ value = e.value
255
+ identity = False
256
+ try_call_me = True
257
+ if isinstance(value, YieldBase):
258
+ identity = value.identity
259
+ try_call_me = value.try_call_me
260
+ value = value.value
261
+ if callable(value) and try_call_me:
262
+ value = value()
263
+ if not identity and isawaitable(value):
264
+ value = await value
265
+ return value
266
+ finally:
267
+ if close is not None:
268
+ if threaded:
269
+ await to_thread(close)
270
+ else:
271
+ close()
272
+ result = process()
273
+ if as_iter:
274
+ async def wrap(result):
275
+ it = await result
276
+ try:
277
+ it = aiter(it)
278
+ except TypeError:
279
+ for val in iter(it):
280
+ if isawaitable(val):
281
+ val = await val
282
+ yield val
283
+ else:
284
+ async for val in it:
285
+ yield val
286
+ result = wrap(result)
287
+ return result
288
+ else:
289
+ try:
290
+ value = send(None)
291
+ while True:
292
+ if isinstance(value, YieldBase):
293
+ raise StopIteration(value)
294
+ try:
295
+ if callable(value):
296
+ value = value()
297
+ except BaseException as e:
298
+ value = throw(e)
299
+ else:
300
+ value = send(value)
301
+ except StopIteration as e:
302
+ value = e.value
303
+ try_call_me = True
304
+ if isinstance(value, YieldBase):
305
+ try_call_me = value.try_call_me
306
+ value = value.value
307
+ if callable(value) and try_call_me:
308
+ value = value()
309
+ if as_iter:
310
+ value = iter(value)
311
+ return value
312
+ finally:
313
+ if close is not None:
314
+ close()
315
+
316
+
317
+ @overload
318
+ def run_gen_step_iter(
319
+ gen_step: Generator | Callable[[], Generator],
320
+ threaded: bool = False,
321
+ *,
322
+ async_: Literal[False] = False,
323
+ ) -> Iterator:
324
+ ...
325
+ @overload
326
+ def run_gen_step_iter(
327
+ gen_step: Generator | Callable[[], Generator],
328
+ threaded: bool = False,
329
+ *,
330
+ async_: Literal[True],
331
+ ) -> AsyncIterator:
332
+ ...
333
+ def run_gen_step_iter(
334
+ gen_step: Generator | Callable[[], Generator],
335
+ threaded: bool = False,
336
+ *,
337
+ async_: bool = False,
338
+ ) -> Iterator | AsyncIterator:
339
+ if callable(gen_step):
340
+ gen = gen_step()
341
+ close = gen.close
342
+ else:
343
+ gen = gen_step
344
+ close = None
345
+ send = gen.send
346
+ throw = gen.throw
347
+ if async_:
348
+ async def process():
349
+ async def extract(value):
350
+ identity = False
351
+ try_call_me = True
352
+ yield_type = -1
353
+ if isinstance(value, YieldBase):
354
+ identity = value.identity
355
+ try_call_me = value.try_call_me
356
+ yield_type = value.yield_type
357
+ value = value.value
358
+ if try_call_me and callable(value):
359
+ value = value()
360
+ if not identity and isawaitable(value):
361
+ value = await value
362
+ return yield_type, value
363
+ try:
364
+ if threaded:
365
+ value = await to_thread(send, None)
366
+ else:
367
+ value = send(None)
368
+ while True:
369
+ try:
370
+ yield_type, value = await extract(value)
371
+ match yield_type:
372
+ case 1:
373
+ yield value
374
+ case 2:
375
+ async for val in ensure_aiter(value, threaded=threaded):
376
+ yield val
377
+ except BaseException as e:
378
+ if threaded:
379
+ value = await to_thread(throw, e)
380
+ else:
381
+ value = throw(e)
382
+ else:
383
+ if threaded:
384
+ value = await to_thread(send, value)
385
+ else:
386
+ value = send(value)
387
+ except StopIteration as e:
388
+ yield_type, value = await extract(e.value)
389
+ match yield_type:
390
+ case 1:
391
+ yield value
392
+ case 2:
393
+ async for val in ensure_aiter(value, threaded=threaded):
394
+ yield val
395
+ finally:
396
+ if close is not None:
397
+ if threaded:
398
+ await to_thread(close)
399
+ else:
400
+ close()
401
+ else:
402
+ def process():
403
+ def extract(value, /):
404
+ try_call_me = True
405
+ yield_type = -1
406
+ if isinstance(value, YieldBase):
407
+ try_call_me = value.try_call_me
408
+ yield_type = value.yield_type
409
+ value = value.value
410
+ if try_call_me and callable(value):
411
+ value = value()
412
+ return yield_type, value
413
+ try:
414
+ value = send(None)
415
+ while True:
416
+ try:
417
+ yield_type, value = extract(value)
418
+ match yield_type:
419
+ case 0:
420
+ return value
421
+ case 1:
422
+ yield value
423
+ case 2:
424
+ yield from value
425
+ except BaseException as e:
426
+ value = throw(e)
427
+ else:
428
+ value = send(value)
429
+ except StopIteration as e:
430
+ yield_type, value = extract(e.value)
431
+ match yield_type:
432
+ case 1:
433
+ yield value
434
+ case 2:
435
+ yield from value
436
+ case _:
437
+ return value
438
+ finally:
439
+ if close is not None:
440
+ close()
441
+ return process()
442
+
File without changes
@@ -0,0 +1,37 @@
1
+ [tool.poetry]
2
+ name = "python-iterutils"
3
+ version = "0.0.9"
4
+ description = "Python another itertools."
5
+ authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
+ license = "MIT"
7
+ readme = "readme.md"
8
+ homepage = "https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils"
9
+ repository = "https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils"
10
+ keywords = ["iterable", "iterutils"]
11
+ classifiers = [
12
+ "License :: OSI Approved :: MIT License",
13
+ "Development Status :: 5 - Production/Stable",
14
+ "Programming Language :: Python",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Operating System :: OS Independent",
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Software Development",
21
+ "Topic :: Software Development :: Libraries",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+ include = [
25
+ "LICENSE",
26
+ ]
27
+
28
+ [tool.poetry.dependencies]
29
+ python = "^3.10"
30
+ python-asynctools = ">=0.0.8.2"
31
+
32
+ [build-system]
33
+ requires = ["poetry-core"]
34
+ build-backend = "poetry.core.masonry.api"
35
+
36
+ [[tool.poetry.packages]]
37
+ include = "iterutils"
@@ -0,0 +1,15 @@
1
+ # Python another itertools.
2
+
3
+ ## Installation
4
+
5
+ You can install from [pypi](https://pypi.org/project/python-iterutils/)
6
+
7
+ ```console
8
+ pip install -U python-iterutils
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ import iterutils
15
+ ```