python-iterutils 0.0.8__tar.gz → 0.1.0__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.
@@ -1,27 +1,26 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-iterutils
3
- Version: 0.0.8
3
+ Version: 0.1.0
4
4
  Summary: Python another itertools.
5
5
  Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
6
6
  License: MIT
7
7
  Keywords: iterable,iterutils
8
8
  Author: ChenyangGao
9
9
  Author-email: wosiwujm@gmail.com
10
- Requires-Python: >=3.10,<4.0
10
+ Requires-Python: >=3.12,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.10
18
- Classifier: Programming Language :: Python :: 3.11
19
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3 :: Only
21
20
  Classifier: Topic :: Software Development
22
21
  Classifier: Topic :: Software Development :: Libraries
23
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Dist: python-asynctools
23
+ Requires-Dist: python-asynctools (>=0.0.9)
25
24
  Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
26
25
  Description-Content-Type: text/markdown
27
26
 
@@ -0,0 +1,567 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
+ __version__ = (0, 1, 0)
6
+ __all__ = [
7
+ "iterable", "async_iterable", "foreach", "async_foreach", "through", "async_through",
8
+ "flatten", "async_flatten", "chunked", "wrap_iter", "wrap_aiter", "acc_step",
9
+ "cut_iter", "run_gen_step", "run_gen_step_iter", "Return", "Yield", "YieldFrom",
10
+ ]
11
+
12
+
13
+ from abc import ABC, abstractmethod
14
+ from asyncio import to_thread
15
+ from collections.abc import (
16
+ AsyncIterable, AsyncIterator, Buffer, Callable, Generator,
17
+ Iterable, Iterator, Sequence,
18
+ )
19
+ from dataclasses import dataclass
20
+ from itertools import batched, pairwise
21
+ from inspect import isawaitable
22
+ from typing import overload, Any, Literal
23
+
24
+ from asynctools import async_map, async_zip, async_batched, ensure_async, ensure_aiter
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(iterable, /) -> bool:
52
+ try:
53
+ return isinstance(iter(iterable), Iterable)
54
+ except TypeError:
55
+ return False
56
+
57
+
58
+ def async_iterable(iterable, /) -> bool:
59
+ try:
60
+ return isinstance(iter(iterable), AsyncIterable)
61
+ except TypeError:
62
+ return False
63
+
64
+
65
+ def foreach(
66
+ value: Callable,
67
+ iterable: Iterable | AsyncIterable,
68
+ /,
69
+ *iterables: Iterable | AsyncIterable,
70
+ ):
71
+ if not (isinstance(iterable, Iterable) and all(isinstance(it, Iterable) for it in iterables)):
72
+ return async_foreach(value, iterable, *iterables)
73
+ if iterables:
74
+ for args in zip(iterable, *iterables):
75
+ value(*args)
76
+ else:
77
+ for arg in iterable:
78
+ value(arg)
79
+
80
+
81
+ async def async_foreach(
82
+ value: Callable,
83
+ iterable: Iterable | AsyncIterable,
84
+ /,
85
+ *iterables: Iterable | AsyncIterable,
86
+ threaded: bool = True,
87
+ ):
88
+ value = ensure_async(value, threaded=threaded)
89
+ if iterables:
90
+ async for args in async_zip(iterable, *iterables, threaded=threaded):
91
+ await value(*args)
92
+ else:
93
+ async for arg in ensure_aiter(iterable, threaded=threaded):
94
+ await value(arg)
95
+
96
+
97
+ def through(
98
+ iterable: Iterable | AsyncIterable,
99
+ /,
100
+ take_while: None | Callable = None,
101
+ ):
102
+ if not isinstance(iterable, Iterable):
103
+ return async_through(iterable, take_while)
104
+ if take_while is None:
105
+ for _ in iterable:
106
+ pass
107
+ else:
108
+ for v in map(take_while, iterable):
109
+ if not v:
110
+ break
111
+
112
+
113
+ async def async_through(
114
+ iterable: Iterable | AsyncIterable,
115
+ /,
116
+ take_while: None | Callable = None,
117
+ threaded: bool = True,
118
+ ):
119
+ iterable = ensure_aiter(iterable, threaded=threaded)
120
+ if take_while is None:
121
+ async for _ in iterable:
122
+ pass
123
+ elif take_while is bool:
124
+ async for v in iterable:
125
+ if not v:
126
+ break
127
+ else:
128
+ async for v in async_map(take_while, iterable):
129
+ if not v:
130
+ break
131
+
132
+
133
+ @overload
134
+ def flatten(
135
+ iterable: Iterable,
136
+ /,
137
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
138
+ ) -> Iterable:
139
+ ...
140
+ @overload
141
+ def flatten(
142
+ iterable: AsyncIterable,
143
+ /,
144
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
145
+ ) -> AsyncIterable:
146
+ ...
147
+ def flatten(
148
+ iterable: Iterable | AsyncIterable,
149
+ /,
150
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
151
+ ) -> Iterable | AsyncIterable:
152
+ if not isinstance(iterable, Iterable):
153
+ return async_flatten(iterable, exclude_types)
154
+ def gen(iterable):
155
+ for e in iterable:
156
+ if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
157
+ yield from gen(e)
158
+ else:
159
+ yield e
160
+ return gen(iterable)
161
+
162
+
163
+ async def async_flatten(
164
+ iterable: Iterable | AsyncIterable,
165
+ /,
166
+ exclude_types: type | tuple[type, ...] = (Buffer, str),
167
+ threaded: bool = True,
168
+ ) -> AsyncIterator:
169
+ async for e in ensure_aiter(iterable, threaded=threaded):
170
+ if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
171
+ async for e in async_flatten(e, exclude_types, threaded=threaded):
172
+ yield e
173
+ else:
174
+ yield e
175
+
176
+
177
+ @overload
178
+ def chunked[T](
179
+ iterable: Iterable[T],
180
+ n: int = 1,
181
+ /,
182
+ ) -> Iterator[Sequence[T]]:
183
+ ...
184
+ @overload
185
+ def chunked[T](
186
+ iterable: AsyncIterable[T],
187
+ n: int = 1,
188
+ /,
189
+ ) -> AsyncIterator[Sequence[T]]:
190
+ ...
191
+ def chunked[T](
192
+ iterable: Iterable[T] | AsyncIterable[T],
193
+ n: int = 1,
194
+ /,
195
+ ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
196
+ if n < 0:
197
+ n = 1
198
+ if isinstance(iterable, Sequence):
199
+ if n == 1:
200
+ return ((e,) for e in iterable)
201
+ return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
202
+ elif isinstance(iterable, Iterable):
203
+ return batched(iterable, n)
204
+ else:
205
+ return async_batched(iterable, n)
206
+
207
+
208
+ @overload
209
+ def wrap_iter[T](
210
+ iterable: Iterable[T],
211
+ /,
212
+ callprev: None | Callable[[T], Any] = None,
213
+ callnext: None | Callable[[T], Any] = None,
214
+ callenter: None | Callable[[Iterable[T]], Any] = None,
215
+ callexit: None | Callable[[Iterable[T], None | BaseException], Any] = None,
216
+ ) -> Iterator[T]:
217
+ ...
218
+ @overload
219
+ def wrap_iter[T](
220
+ iterable: AsyncIterable[T],
221
+ /,
222
+ callprev: None | Callable[[T], Any] = None,
223
+ callnext: None | Callable[[T], Any] = None,
224
+ callenter: None | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
225
+ callexit: None | Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] = None,
226
+ ) -> AsyncIterator[T]:
227
+ ...
228
+ def wrap_iter[T](
229
+ iterable: Iterable[T] | AsyncIterable[T],
230
+ /,
231
+ callprev: None | Callable[[T], Any] = None,
232
+ callnext: None | Callable[[T], Any] = None,
233
+ callenter: None | Callable[[Iterable[T]], Any] | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
234
+ callexit: ( None | Callable[[Iterable[T], None | BaseException], Any] |
235
+ Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] ) = None,
236
+ ) -> Iterator[T] | AsyncIterator[T]:
237
+ if not isinstance(iterable, Iterable):
238
+ return wrap_aiter(
239
+ iterable,
240
+ callprev=callprev,
241
+ callnext=callnext,
242
+ callenter=callenter, # type: ignore
243
+ callexit=callexit, # type: ignore
244
+ )
245
+ if not callable(callprev):
246
+ callprev = None
247
+ if not callable(callnext):
248
+ callnext = None
249
+ def gen():
250
+ try:
251
+ if callable(callenter):
252
+ callenter(iterable)
253
+ for e in iterable:
254
+ if callprev:
255
+ try:
256
+ callprev(e)
257
+ except (StopIteration, GeneratorExit):
258
+ break
259
+ yield e
260
+ if callnext:
261
+ try:
262
+ callnext(e)
263
+ except (StopIteration, GeneratorExit):
264
+ break
265
+ except BaseException as e:
266
+ if callable(callexit):
267
+ if not callexit(iterable, e):
268
+ raise
269
+ else:
270
+ raise
271
+ finally:
272
+ if callable(callexit):
273
+ callexit(iterable, None)
274
+ return gen()
275
+
276
+
277
+ async def wrap_aiter[T](
278
+ iterable: Iterable[T] | AsyncIterable[T],
279
+ /,
280
+ callprev: None | Callable[[T], Any] = None,
281
+ callnext: None | Callable[[T], Any] = None,
282
+ callenter: None | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
283
+ callexit: None | Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] = None,
284
+ threaded: bool = True,
285
+ ) -> AsyncIterator[T]:
286
+ callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
287
+ callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
288
+ try:
289
+ async for e in ensure_aiter(iterable, threaded=threaded):
290
+ if callprev:
291
+ try:
292
+ await callprev(e)
293
+ except (StopAsyncIteration, GeneratorExit):
294
+ break
295
+ yield e
296
+ if callnext:
297
+ try:
298
+ await callnext(e)
299
+ except (StopAsyncIteration, GeneratorExit):
300
+ break
301
+ except BaseException as e:
302
+ if callable(callexit):
303
+ if not await ensure_async(callexit, threaded=threaded)(iterable, e):
304
+ raise
305
+ else:
306
+ raise
307
+ finally:
308
+ if callable(callexit):
309
+ await ensure_async(callexit, threaded=threaded)(iterable, None)
310
+
311
+
312
+ def acc_step(
313
+ start: int,
314
+ stop: None | int = None,
315
+ step: int = 1,
316
+ ) -> Iterator[tuple[int, int, int]]:
317
+ if stop is None:
318
+ start, stop = 0, start
319
+ for i in range(start + step, stop, step):
320
+ yield start, (start := i), step
321
+ if start != stop:
322
+ yield start, stop, stop - start
323
+
324
+
325
+ def cut_iter(
326
+ start: int,
327
+ stop: None | int = None,
328
+ step: int = 1,
329
+ ) -> Iterator[tuple[int, int]]:
330
+ if stop is None:
331
+ start, stop = 0, start
332
+ for start in range(start + step, stop, step):
333
+ yield start, step
334
+ if start != stop:
335
+ yield stop, stop - start
336
+
337
+
338
+ def run_gen_step[T](
339
+ gen_step: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
340
+ *,
341
+ async_: bool = False,
342
+ threaded: bool = False,
343
+ as_iter: bool = False,
344
+ ) -> T:
345
+ if callable(gen_step):
346
+ gen = gen_step()
347
+ close = gen.close
348
+ else:
349
+ gen = gen_step
350
+ close = None
351
+ send = gen.send
352
+ throw = gen.throw
353
+ if async_:
354
+ async def process():
355
+ try:
356
+ if threaded:
357
+ value = await to_thread(send, None)
358
+ else:
359
+ value = send(None)
360
+ while True:
361
+ if isinstance(value, YieldBase):
362
+ raise StopIteration(value)
363
+ try:
364
+ if callable(value):
365
+ value = value()
366
+ if isawaitable(value):
367
+ value = await value
368
+ except BaseException as e:
369
+ if threaded:
370
+ value = await to_thread(throw, e)
371
+ else:
372
+ value = throw(e)
373
+ else:
374
+ if threaded:
375
+ value = await to_thread(send, value)
376
+ else:
377
+ value = send(value)
378
+ except StopIteration as e:
379
+ value = e.value
380
+ identity = False
381
+ try_call_me = True
382
+ if isinstance(value, YieldBase):
383
+ identity = value.identity
384
+ try_call_me = value.try_call_me
385
+ value = value.value
386
+ if callable(value) and try_call_me:
387
+ value = value()
388
+ if not identity and isawaitable(value):
389
+ value = await value
390
+ return value
391
+ finally:
392
+ if close is not None:
393
+ if threaded:
394
+ await to_thread(close)
395
+ else:
396
+ close()
397
+ result = process()
398
+ if as_iter:
399
+ async def wrap(result):
400
+ iterable = await result
401
+ try:
402
+ iterable = aiter(iterable)
403
+ except TypeError:
404
+ for val in iter(iterable):
405
+ if isawaitable(val):
406
+ val = await val
407
+ yield val
408
+ else:
409
+ async for val in iterable:
410
+ yield val
411
+ result = wrap(result)
412
+ return result
413
+ else:
414
+ try:
415
+ value = send(None)
416
+ while True:
417
+ if isinstance(value, YieldBase):
418
+ raise StopIteration(value)
419
+ try:
420
+ if callable(value):
421
+ value = value()
422
+ except BaseException as e:
423
+ value = throw(e)
424
+ else:
425
+ value = send(value)
426
+ except StopIteration as e:
427
+ value = e.value
428
+ try_call_me = True
429
+ if isinstance(value, YieldBase):
430
+ try_call_me = value.try_call_me
431
+ value = value.value
432
+ if callable(value) and try_call_me:
433
+ value = value()
434
+ if as_iter:
435
+ value = iter(value)
436
+ return value
437
+ finally:
438
+ if close is not None:
439
+ close()
440
+
441
+
442
+ @overload
443
+ def run_gen_step_iter(
444
+ gen_step: Generator | Callable[[], Generator],
445
+ threaded: bool = False,
446
+ *,
447
+ async_: Literal[False] = False,
448
+ ) -> Iterator:
449
+ ...
450
+ @overload
451
+ def run_gen_step_iter(
452
+ gen_step: Generator | Callable[[], Generator],
453
+ threaded: bool = False,
454
+ *,
455
+ async_: Literal[True],
456
+ ) -> AsyncIterator:
457
+ ...
458
+ def run_gen_step_iter(
459
+ gen_step: Generator | Callable[[], Generator],
460
+ threaded: bool = False,
461
+ *,
462
+ async_: bool = False,
463
+ ) -> Iterator | AsyncIterator:
464
+ if callable(gen_step):
465
+ gen = gen_step()
466
+ close = gen.close
467
+ else:
468
+ gen = gen_step
469
+ close = None
470
+ send = gen.send
471
+ throw = gen.throw
472
+ if async_:
473
+ async def process():
474
+ async def extract(value):
475
+ identity = False
476
+ try_call_me = True
477
+ yield_type = -1
478
+ if isinstance(value, YieldBase):
479
+ identity = value.identity
480
+ try_call_me = value.try_call_me
481
+ yield_type = value.yield_type
482
+ value = value.value
483
+ if try_call_me and callable(value):
484
+ value = value()
485
+ if not identity and isawaitable(value):
486
+ value = await value
487
+ return yield_type, value
488
+ try:
489
+ if threaded:
490
+ value = await to_thread(send, None)
491
+ else:
492
+ value = send(None)
493
+ while True:
494
+ try:
495
+ yield_type, value = await extract(value)
496
+ match yield_type:
497
+ case 1:
498
+ yield value
499
+ case 2:
500
+ async for val in ensure_aiter(value, threaded=threaded):
501
+ yield val
502
+ except BaseException as e:
503
+ if threaded:
504
+ value = await to_thread(throw, e)
505
+ else:
506
+ value = throw(e)
507
+ else:
508
+ if threaded:
509
+ value = await to_thread(send, value)
510
+ else:
511
+ value = send(value)
512
+ except StopIteration as e:
513
+ yield_type, value = await extract(e.value)
514
+ match yield_type:
515
+ case 1:
516
+ yield value
517
+ case 2:
518
+ async for val in ensure_aiter(value, threaded=threaded):
519
+ yield val
520
+ finally:
521
+ if close is not None:
522
+ if threaded:
523
+ await to_thread(close)
524
+ else:
525
+ close()
526
+ else:
527
+ def process():
528
+ def extract(value, /):
529
+ try_call_me = True
530
+ yield_type = -1
531
+ if isinstance(value, YieldBase):
532
+ try_call_me = value.try_call_me
533
+ yield_type = value.yield_type
534
+ value = value.value
535
+ if try_call_me and callable(value):
536
+ value = value()
537
+ return yield_type, value
538
+ try:
539
+ value = send(None)
540
+ while True:
541
+ try:
542
+ yield_type, value = extract(value)
543
+ match yield_type:
544
+ case 0:
545
+ return value
546
+ case 1:
547
+ yield value
548
+ case 2:
549
+ yield from value
550
+ except BaseException as e:
551
+ value = throw(e)
552
+ else:
553
+ value = send(value)
554
+ except StopIteration as e:
555
+ yield_type, value = extract(e.value)
556
+ match yield_type:
557
+ case 1:
558
+ yield value
559
+ case 2:
560
+ yield from value
561
+ case _:
562
+ return value
563
+ finally:
564
+ if close is not None:
565
+ close()
566
+ return process()
567
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.0.8"
3
+ version = "0.1.0"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -13,7 +13,7 @@ classifiers = [
13
13
  "Development Status :: 5 - Production/Stable",
14
14
  "Programming Language :: Python",
15
15
  "Programming Language :: Python :: 3",
16
- "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.12",
17
17
  "Programming Language :: Python :: 3 :: Only",
18
18
  "Operating System :: OS Independent",
19
19
  "Intended Audience :: Developers",
@@ -26,8 +26,8 @@ include = [
26
26
  ]
27
27
 
28
28
  [tool.poetry.dependencies]
29
- python = "^3.10"
30
- python-asynctools = "*"
29
+ python = "^3.12"
30
+ python-asynctools = ">=0.0.9"
31
31
 
32
32
  [build-system]
33
33
  requires = ["poetry-core"]
@@ -1,416 +0,0 @@
1
- #!/usr/bin/env python3
2
- # encoding: utf-8
3
-
4
- __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 0, 8)
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
- "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 Yield(YieldBase):
40
- yield_type = 1
41
-
42
-
43
- class YieldFrom(YieldBase):
44
- yield_type = 2
45
-
46
-
47
- def iterable(it, /) -> bool:
48
- try:
49
- return isinstance(iter(it), Iterable)
50
- except TypeError:
51
- return False
52
-
53
-
54
- def async_iterable(it, /) -> bool:
55
- try:
56
- return isinstance(iter(it), AsyncIterable)
57
- except TypeError:
58
- return False
59
-
60
-
61
- def foreach(ret: Callable, iterable, /, *iterables):
62
- if iterables:
63
- for args in zip(iterable, *iterables):
64
- ret(*args)
65
- else:
66
- for arg in iterable:
67
- ret(arg)
68
-
69
-
70
- async def async_foreach(ret: Callable, iterable, /, *iterables, threaded: bool = True):
71
- ret = ensure_async(ret, threaded=threaded)
72
- if iterables:
73
- async for args in async_zip(iterable, *iterables, threaded=threaded):
74
- await ret(*args)
75
- else:
76
- async for arg in ensure_aiter(iterable, threaded=threaded):
77
- await ret(arg)
78
-
79
-
80
- def through(it: Iterable, /, take_while: None | Callable = None):
81
- if take_while is None:
82
- for _ in it:
83
- pass
84
- else:
85
- for v in map(take_while, it):
86
- if not v:
87
- break
88
-
89
-
90
- async def async_through(
91
- it: Iterable | AsyncIterable,
92
- /,
93
- take_while: None | Callable = None,
94
- threaded: bool = True,
95
- ):
96
- it = ensure_aiter(it, threaded=threaded)
97
- if take_while is None:
98
- async for _ in it:
99
- pass
100
- else:
101
- async for v in async_map(take_while, it):
102
- if not v:
103
- break
104
-
105
-
106
- def wrap_iter(
107
- it: Iterable[T],
108
- /,
109
- callprev: None | Callable[[T], Any] = None,
110
- callnext: None | Callable[[T], Any] = None,
111
- callenter: None | Callable[[Iterable[T]], Any] = None,
112
- callexit: None | Callable[[Iterable[T], None | BaseException], Any] = None,
113
- ) -> Iterator[T]:
114
- if not callable(callprev):
115
- callprev = None
116
- if not callable(callnext):
117
- callnext = None
118
- try:
119
- if callable(callenter):
120
- callenter(it)
121
- for e in it:
122
- if callprev:
123
- try:
124
- callprev(e)
125
- except (StopIteration, GeneratorExit):
126
- break
127
- yield e
128
- if callnext:
129
- try:
130
- callnext(e)
131
- except (StopIteration, GeneratorExit):
132
- break
133
- except BaseException as e:
134
- if callable(callexit):
135
- if not callexit(it, e):
136
- raise
137
- else:
138
- raise
139
- finally:
140
- if callable(callexit):
141
- callexit(it, None)
142
-
143
-
144
- async def wrap_aiter(
145
- it: Iterable[T] | AsyncIterable[T],
146
- /,
147
- callprev: None | Callable[[T], Any] = None,
148
- callnext: None | Callable[[T], Any] = None,
149
- callenter: None | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
150
- callexit: None | Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] = None,
151
- threaded: bool = True,
152
- ) -> AsyncIterator[T]:
153
- callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
154
- callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
155
- try:
156
- async for e in ensure_aiter(it, threaded=threaded):
157
- if callprev:
158
- try:
159
- await callprev(e)
160
- except (StopAsyncIteration, GeneratorExit):
161
- break
162
- yield e
163
- if callnext:
164
- try:
165
- await callnext(e)
166
- except (StopAsyncIteration, GeneratorExit):
167
- break
168
- except BaseException as e:
169
- if callable(callexit):
170
- if not await ensure_async(callexit, threaded=threaded)(it, e):
171
- raise
172
- else:
173
- raise
174
- finally:
175
- if callable(callexit):
176
- await ensure_async(callexit, threaded=threaded)(it, None)
177
-
178
-
179
- def acc_step(
180
- start: int,
181
- stop: None | int = None,
182
- step: int = 1,
183
- ) -> Iterator[tuple[int, int, int]]:
184
- if stop is None:
185
- start, stop = 0, start
186
- for i in range(start + step, stop, step):
187
- yield start, (start := i), step
188
- if start != stop:
189
- yield start, stop, stop - start
190
-
191
-
192
- def cut_iter(
193
- start: int,
194
- stop: None | int = None,
195
- step: int = 1,
196
- ) -> Iterator[tuple[int, int]]:
197
- if stop is None:
198
- start, stop = 0, start
199
- for start in range(start + step, stop, step):
200
- yield start, step
201
- if start != stop:
202
- yield stop, stop - start
203
-
204
-
205
- def run_gen_step(
206
- gen_step: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
207
- *,
208
- async_: bool = False,
209
- threaded: bool = False,
210
- as_iter: bool = False,
211
- ) -> T:
212
- if callable(gen_step):
213
- gen = gen_step()
214
- close = gen.close
215
- else:
216
- gen = gen_step
217
- close = None
218
- send = gen.send
219
- throw = gen.throw
220
- if async_:
221
- async def process():
222
- try:
223
- if threaded:
224
- ret = await to_thread(send, None)
225
- else:
226
- ret = send(None)
227
- while True:
228
- try:
229
- if callable(ret):
230
- ret = ret()
231
- if isawaitable(ret):
232
- ret = await ret
233
- except BaseException as e:
234
- if threaded:
235
- ret = await to_thread(throw, e)
236
- else:
237
- ret = throw(e)
238
- else:
239
- if threaded:
240
- ret = await to_thread(send, ret)
241
- else:
242
- ret = send(ret)
243
- except StopIteration as e:
244
- return e.value
245
- finally:
246
- if close is not None:
247
- if threaded:
248
- await to_thread(close)
249
- else:
250
- close()
251
- result = process()
252
- if as_iter:
253
- async def wrap(result):
254
- it = await result
255
- try:
256
- it = aiter(it)
257
- except TypeError:
258
- for val in iter(it):
259
- if isawaitable(val):
260
- val = await val
261
- yield val
262
- else:
263
- async for val in it:
264
- yield val
265
- result = wrap(result)
266
- return result
267
- else:
268
- try:
269
- ret = send(None)
270
- while True:
271
- try:
272
- if callable(ret):
273
- ret = ret()
274
- except BaseException as e:
275
- ret = throw(e)
276
- else:
277
- ret = send(ret)
278
- except StopIteration as e:
279
- result = e.value
280
- if as_iter:
281
- result = iter(result)
282
- return result
283
- finally:
284
- if close is not None:
285
- close()
286
-
287
-
288
- @overload
289
- def run_gen_step_iter(
290
- gen_step: Generator | Callable[[], Generator],
291
- threaded: bool = False,
292
- *,
293
- async_: Literal[False] = False,
294
- ) -> Iterator:
295
- ...
296
- @overload
297
- def run_gen_step_iter(
298
- gen_step: Generator | Callable[[], Generator],
299
- threaded: bool = False,
300
- *,
301
- async_: Literal[True],
302
- ) -> AsyncIterator:
303
- ...
304
- def run_gen_step_iter(
305
- gen_step: Generator | Callable[[], Generator],
306
- threaded: bool = False,
307
- *,
308
- async_: bool = False,
309
- ) -> Iterator | AsyncIterator:
310
- if callable(gen_step):
311
- gen = gen_step()
312
- close = gen.close
313
- else:
314
- gen = gen_step
315
- close = None
316
- send = gen.send
317
- throw = gen.throw
318
- if async_:
319
- async def process():
320
- async def extract(ret):
321
- yield_type = 0
322
- identity = False
323
- try_call_me = True
324
- if isinstance(ret, YieldBase):
325
- identity = ret.identity
326
- try_call_me = ret.try_call_me
327
- yield_type = ret.yield_type
328
- ret = ret.value
329
- if not identity:
330
- if try_call_me and callable(ret):
331
- ret = ret()
332
- if isawaitable(ret):
333
- ret = await ret
334
- return yield_type, ret
335
- try:
336
- if threaded:
337
- ret = await to_thread(send, None)
338
- else:
339
- ret = send(None)
340
- while True:
341
- try:
342
- yield_type, ret = await extract(ret)
343
- match yield_type:
344
- case 1:
345
- yield ret
346
- case 2:
347
- async for val in ensure_aiter(ret, threaded=threaded):
348
- yield val
349
- except BaseException as e:
350
- if threaded:
351
- ret = await to_thread(throw, e)
352
- else:
353
- ret = throw(e)
354
- else:
355
- if threaded:
356
- ret = await to_thread(send, ret)
357
- else:
358
- ret = send(ret)
359
- except StopIteration as e:
360
- ret = e.value
361
- if isinstance(ret, YieldBase):
362
- yield_type, ret = await extract(ret)
363
- match yield_type:
364
- case 1:
365
- yield ret
366
- case 2:
367
- async for val in ensure_aiter(ret, threaded=threaded):
368
- yield val
369
- finally:
370
- if close is not None:
371
- if threaded:
372
- await to_thread(close)
373
- else:
374
- close()
375
- else:
376
- def process():
377
- def extract(ret):
378
- yield_type = 0
379
- identity = False
380
- try_call_me = True
381
- if isinstance(ret, YieldBase):
382
- identity = ret.identity
383
- try_call_me = ret.try_call_me
384
- yield_type = ret.yield_type
385
- ret = ret.value
386
- if not identity and try_call_me and callable(ret):
387
- ret = ret()
388
- return yield_type, ret
389
- try:
390
- ret = send(None)
391
- while True:
392
- try:
393
- yield_type, ret = extract(ret)
394
- match yield_type:
395
- case 1:
396
- yield ret
397
- case 2:
398
- yield from ret
399
- except BaseException as e:
400
- ret = throw(e)
401
- else:
402
- ret = send(ret)
403
- except StopIteration as e:
404
- ret = e.value
405
- if isinstance(ret, YieldBase):
406
- yield_type, ret = extract(ret)
407
- match yield_type:
408
- case 1:
409
- yield ret
410
- case 2:
411
- yield from ret
412
- finally:
413
- if close is not None:
414
- close()
415
- return process()
416
-