python-iterutils 0.1.7__tar.gz → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-iterutils
3
- Version: 0.1.7
3
+ Version: 0.1.9
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
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
20
20
  Classifier: Topic :: Software Development
21
21
  Classifier: Topic :: Software Development :: Libraries
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
- Requires-Dist: python-asynctools (>=0.0.10)
23
+ Requires-Dist: python-asynctools (>=0.1.2)
24
24
  Requires-Dist: python-decotools (>=0.0.2)
25
25
  Requires-Dist: python-texttools (>=0.0.3)
26
26
  Requires-Dist: python-undefined (>=0.0.3)
@@ -2,24 +2,27 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 1, 7)
5
+ __version__ = (0, 1, 9)
6
6
  __all__ = [
7
7
  "Return", "Yield", "YieldFrom", "iterable", "async_iterable", "foreach", "async_foreach",
8
- "through", "async_through", "flatten", "async_flatten", "map", "filter", "reduce", "zip",
9
- "chunked", "iter_unique", "async_iter_unique", "wrap_iter", "wrap_aiter", "acc_step",
10
- "cut_iter", "run_gen_step", "run_gen_step_iter", "as_gen_step", "bfs_gen", "with_iter_next",
11
- "backgroud_loop",
8
+ "through", "async_through", "flatten", "async_flatten", "collect", "async_collect",
9
+ "group_collect", "async_group_collect", "map", "filter", "reduce", "zip", "chunked",
10
+ "iter_unique", "async_iter_unique", "wrap_iter", "wrap_aiter", "acc_step", "cut_iter",
11
+ "iter_gen_step", "iter_gen_step_async", "run_gen_step", "run_gen_step_iter",
12
+ "as_gen_step", "bfs_gen", "with_iter_next", "backgroud_loop",
12
13
  ]
13
14
 
14
15
  from abc import ABC, abstractmethod
15
16
  from asyncio import create_task, sleep as async_sleep, to_thread
16
17
  from builtins import map as _map, filter as _filter, zip as _zip
17
- from collections import deque
18
+ from collections import defaultdict, deque
18
19
  from collections.abc import (
19
- AsyncIterable, AsyncIterator, Awaitable, Buffer, Callable, Coroutine, Generator,
20
- Iterable, Iterator, MutableSet, Sequence,
20
+ AsyncIterable, AsyncIterator, Awaitable, Buffer, Callable, Collection, Container,
21
+ Coroutine, Generator, Iterable, Iterator, Mapping, MutableMapping, MutableSet,
22
+ MutableSequence, Sequence, ValuesView,
21
23
  )
22
24
  from contextlib import asynccontextmanager, contextmanager, ExitStack, AsyncExitStack
25
+ from copy import copy
23
26
  from dataclasses import dataclass
24
27
  from itertools import batched, pairwise
25
28
  from inspect import isawaitable, iscoroutinefunction
@@ -27,10 +30,11 @@ from sys import _getframe
27
30
  from _thread import start_new_thread
28
31
  from time import sleep, time
29
32
  from types import FrameType
30
- from typing import overload, Any, AsyncContextManager, ContextManager, Literal
33
+ from typing import cast, overload, Any, AsyncContextManager, ContextManager, Literal
31
34
 
32
35
  from asynctools import (
33
36
  async_filter, async_map, async_reduce, async_zip, async_batched, ensure_async, ensure_aiter,
37
+ collect as async_collect,
34
38
  )
35
39
  from decotools import optional
36
40
  from texttools import format_time
@@ -49,6 +53,12 @@ class YieldBase(ABC):
49
53
  ...
50
54
 
51
55
 
56
+ class Reraised(BaseException):
57
+
58
+ def __init__(self, exc: BaseException, /):
59
+ self.exception = exc
60
+
61
+
52
62
  class Return(YieldBase):
53
63
  yield_type = 0
54
64
 
@@ -96,7 +106,7 @@ async def async_foreach(
96
106
  iterable: Iterable | AsyncIterable,
97
107
  /,
98
108
  *iterables: Iterable | AsyncIterable,
99
- threaded: bool = True,
109
+ threaded: bool = False,
100
110
  ):
101
111
  value = ensure_async(value, threaded=threaded)
102
112
  if iterables:
@@ -127,7 +137,7 @@ async def async_through(
127
137
  iterable: Iterable | AsyncIterable,
128
138
  /,
129
139
  take_while: None | Callable = None,
130
- threaded: bool = True,
140
+ threaded: bool = False,
131
141
  ):
132
142
  iterable = ensure_aiter(iterable, threaded=threaded)
133
143
  if take_while is None:
@@ -177,7 +187,7 @@ async def async_flatten(
177
187
  iterable: Iterable | AsyncIterable,
178
188
  /,
179
189
  exclude_types: type | tuple[type, ...] = (Buffer, str),
180
- threaded: bool = True,
190
+ threaded: bool = False,
181
191
  ) -> AsyncIterator:
182
192
  async for e in ensure_aiter(iterable, threaded=threaded):
183
193
  if isinstance(e, (Iterable, AsyncIterable)) and not isinstance(e, exclude_types):
@@ -187,6 +197,168 @@ async def async_flatten(
187
197
  yield e
188
198
 
189
199
 
200
+ @overload
201
+ def collect[K, V](
202
+ iterable: Iterable[tuple[K, V]] | Mapping[K, V],
203
+ /,
204
+ rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
205
+ ) -> MutableMapping[K, V]:
206
+ ...
207
+ @overload
208
+ def collect[T](
209
+ iterable: Iterable[T],
210
+ /,
211
+ rettype: Callable[[Iterable[T]], Collection[T]] = list,
212
+ ) -> Collection[T]:
213
+ ...
214
+ @overload
215
+ def collect[K, V](
216
+ iterable: AsyncIterable[tuple[K, V]],
217
+ /,
218
+ rettype: Callable[[Iterable[tuple[K, V]]], MutableMapping[K, V]],
219
+ ) -> Coroutine[Any, Any, MutableMapping[K, V]]:
220
+ ...
221
+ @overload
222
+ def collect[T](
223
+ iterable: AsyncIterable[T],
224
+ /,
225
+ rettype: Callable[[Iterable[T]], Collection[T]] = list,
226
+ ) -> Coroutine[Any, Any, Collection[T]]:
227
+ ...
228
+ def collect(
229
+ iterable: Iterable | AsyncIterable | Mapping,
230
+ /,
231
+ rettype: Callable[[Iterable], Collection] = list,
232
+ ) -> Collection | Coroutine[Any, Any, Collection]:
233
+ if not isinstance(iterable, Iterable):
234
+ return async_collect(iterable, rettype)
235
+ return rettype(iterable)
236
+
237
+
238
+ @overload
239
+ def group_collect[K, V, C: Container](
240
+ iterable: Iterable[tuple[K, V]],
241
+ mapping: None = None,
242
+ factory: None | C | Callable[[], C] = None,
243
+ ) -> dict[K, C]:
244
+ ...
245
+ @overload
246
+ def group_collect[K, V, C: Container, M: MutableMapping](
247
+ iterable: Iterable[tuple[K, V]],
248
+ mapping: M,
249
+ factory: None | C | Callable[[], C] = None,
250
+ ) -> M:
251
+ ...
252
+ @overload
253
+ def group_collect[K, V, C: Container](
254
+ iterable: AsyncIterable[tuple[K, V]],
255
+ mapping: None = None,
256
+ factory: None | C | Callable[[], C] = None,
257
+ ) -> Coroutine[Any, Any, dict[K, C]]:
258
+ ...
259
+ @overload
260
+ def group_collect[K, V, C: Container, M: MutableMapping](
261
+ iterable: AsyncIterable[tuple[K, V]],
262
+ mapping: M,
263
+ factory: None | C | Callable[[], C] = None,
264
+ ) -> Coroutine[Any, Any, M]:
265
+ ...
266
+ def group_collect[K, V, C: Container, M: MutableMapping](
267
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
268
+ mapping: None | M = None,
269
+ factory: None | C | Callable[[], C] = None,
270
+ ) -> dict[K, C] | M | Coroutine[Any, Any, dict[K, C]] | Coroutine[Any, Any, M]:
271
+ if not isinstance(iterable, Iterable):
272
+ return async_group_collect(iterable, mapping, factory)
273
+ if factory is None:
274
+ if isinstance(mapping, defaultdict):
275
+ factory = mapping.default_factory
276
+ elif mapping:
277
+ factory = type(next(iter(ValuesView(mapping))))
278
+ else:
279
+ factory = cast(type[C], list)
280
+ elif callable(factory):
281
+ pass
282
+ elif isinstance(factory, Container):
283
+ factory = cast(Callable[[], C], lambda _obj=factory: copy(_obj))
284
+ else:
285
+ raise ValueError("can't determine factory")
286
+ factory = cast(Callable[[], C], factory)
287
+ if isinstance(factory, type):
288
+ factory_type = factory
289
+ else:
290
+ factory_type = type(factory())
291
+ if issubclass(factory_type, MutableSequence):
292
+ add = getattr(factory_type, "append")
293
+ else:
294
+ add = getattr(factory_type, "add")
295
+ if mapping is None:
296
+ mapping = cast(M, {})
297
+ for k, v in iterable:
298
+ try:
299
+ c = mapping[k]
300
+ except LookupError:
301
+ c = mapping[k] = factory()
302
+ add(c, v)
303
+ return mapping
304
+
305
+
306
+ @overload
307
+ async def async_group_collect[K, V, C: Container](
308
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
309
+ mapping: None = None,
310
+ factory: None | C | Callable[[], C] = None,
311
+ threaded: bool = False,
312
+ ) -> dict[K, C]:
313
+ ...
314
+ @overload
315
+ async def async_group_collect[K, V, C: Container, M: MutableMapping](
316
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
317
+ mapping: M,
318
+ factory: None | C | Callable[[], C] = None,
319
+ threaded: bool = False,
320
+ ) -> M:
321
+ ...
322
+ async def async_group_collect[K, V, C: Container, M: MutableMapping](
323
+ iterable: Iterable[tuple[K, V]] | AsyncIterable[tuple[K, V]],
324
+ mapping: None | M = None,
325
+ factory: None | C | Callable[[], C] = None,
326
+ threaded: bool = False,
327
+ ) -> dict[K, C] | M:
328
+ iterable = ensure_aiter(iterable, threaded=threaded)
329
+ if factory is None:
330
+ if isinstance(mapping, defaultdict):
331
+ factory = mapping.default_factory
332
+ elif mapping:
333
+ factory = type(next(iter(ValuesView(mapping))))
334
+ else:
335
+ factory = cast(type[C], list)
336
+ elif callable(factory):
337
+ pass
338
+ elif isinstance(factory, Container):
339
+ factory = cast(Callable[[], C], lambda _obj=factory: copy(_obj))
340
+ else:
341
+ raise ValueError("can't determine factory")
342
+ factory = cast(Callable[[], C], factory)
343
+ if isinstance(factory, type):
344
+ factory_type = factory
345
+ else:
346
+ factory_type = type(factory())
347
+ if issubclass(factory_type, MutableSequence):
348
+ add = getattr(factory_type, "append")
349
+ else:
350
+ add = getattr(factory_type, "add")
351
+ if mapping is None:
352
+ mapping = cast(M, {})
353
+ async for k, v in iterable:
354
+ try:
355
+ c = mapping[k]
356
+ except LookupError:
357
+ c = mapping[k] = factory()
358
+ add(c, v)
359
+ return mapping
360
+
361
+
190
362
  def map(
191
363
  function: None | Callable,
192
364
  iterable: Iterable | AsyncIterable,
@@ -400,7 +572,7 @@ async def wrap_aiter[T](
400
572
  callnext: None | Callable[[T], Any] = None,
401
573
  callenter: None | Callable[[Iterable[T] | AsyncIterable[T]], Any] = None,
402
574
  callexit: None | Callable[[Iterable[T] | AsyncIterable[T], None | BaseException], Any] = None,
403
- threaded: bool = True,
575
+ threaded: bool = False,
404
576
  ) -> AsyncIterator[T]:
405
577
  callprev = ensure_async(callprev, threaded=threaded) if callable(callprev) else None
406
578
  callnext = ensure_async(callnext, threaded=threaded) if callable(callnext) else None
@@ -471,110 +643,124 @@ def _get_async(back: int = 2) -> bool:
471
643
  return False
472
644
 
473
645
 
474
- def run_gen_step[T](
475
- gen_step: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
476
- *,
477
- async_: None | bool = None,
478
- threaded: bool = False,
479
- as_iter: bool = False,
480
- ) -> T:
481
- if async_ is None:
482
- async_ = _get_async()
646
+ def iter_gen_step(
647
+ gen_step: Generator | Callable[[], Generator],
648
+ ):
483
649
  if callable(gen_step):
484
650
  gen = gen_step()
485
- close = gen.close
486
651
  else:
487
652
  gen = gen_step
488
- close = None
489
653
  send = gen.send
490
654
  throw = gen.throw
491
- if async_:
492
- async def process():
655
+ close = gen.close
656
+ try:
657
+ value = send(None)
658
+ while True:
659
+ if isinstance(value, YieldBase):
660
+ raise StopIteration(value)
661
+ try:
662
+ if callable(value):
663
+ value = value()
664
+ except BaseException as e:
665
+ value = throw(e)
666
+ else:
667
+ value = send(value)
668
+ yield value
669
+ except StopIteration as e:
670
+ value = e.value
671
+ try_call_me = True
672
+ if isinstance(value, YieldBase):
673
+ try_call_me = value.try_call_me
674
+ value = value.value
675
+ if callable(value) and try_call_me:
676
+ try:
677
+ value = value()
678
+ except BaseException as e:
679
+ try:
680
+ value = throw(e)
681
+ except BaseException as e:
682
+ raise Reraised(e) from e
683
+ yield value
684
+ finally:
685
+ close()
686
+
687
+
688
+ async def iter_gen_step_async(
689
+ gen_step: Generator | Callable[[], Generator],
690
+ threaded: bool = False,
691
+ ):
692
+ if callable(gen_step):
693
+ gen = gen_step()
694
+ else:
695
+ gen = gen_step
696
+ send = ensure_async(gen.send, threaded=threaded)
697
+ throw = ensure_async(gen.throw, threaded=threaded)
698
+ close = ensure_async(gen.close, threaded=threaded)
699
+ try:
700
+ value = await send(None)
701
+ while True:
702
+ if isinstance(value, YieldBase):
703
+ raise StopIteration(value)
493
704
  try:
705
+ if callable(value):
706
+ value = await ensure_async(value, threaded=threaded)()
707
+ except BaseException as e:
708
+ value = await throw(e)
709
+ else:
710
+ value = await send(value)
711
+ yield value
712
+ except StopIteration as e:
713
+ value = e.value
714
+ identity = False
715
+ try_call_me = True
716
+ if isinstance(value, YieldBase):
717
+ identity = value.identity
718
+ try_call_me = value.try_call_me
719
+ value = value.value
720
+ try:
721
+ if callable(value) and try_call_me:
494
722
  if threaded:
495
- value = await to_thread(send, None)
723
+ value = await to_thread(value)
496
724
  else:
497
- value = send(None)
498
- while True:
499
- if isinstance(value, YieldBase):
500
- raise StopIteration(value)
501
- try:
502
- if callable(value):
503
- value = value()
504
- if isawaitable(value):
505
- value = await value
506
- except BaseException as e:
507
- if threaded:
508
- value = await to_thread(throw, e)
509
- else:
510
- value = throw(e)
511
- else:
512
- if threaded:
513
- value = await to_thread(send, value)
514
- else:
515
- value = send(value)
516
- except StopIteration as e:
517
- value = e.value
518
- identity = False
519
- try_call_me = True
520
- if isinstance(value, YieldBase):
521
- identity = value.identity
522
- try_call_me = value.try_call_me
523
- value = value.value
524
- if callable(value) and try_call_me:
525
725
  value = value()
526
- if not identity and isawaitable(value):
527
- value = await value
726
+ if not identity and isawaitable(value):
727
+ value = await value
728
+ except BaseException as e:
729
+ try:
730
+ value = await throw(e)
731
+ except BaseException as e:
732
+ raise Reraised(e) from e
733
+ yield value
734
+ except StopAsyncIteration as e:
735
+ raise Reraised(e) from e
736
+ finally:
737
+ await close()
738
+
739
+
740
+ def run_gen_step[T](
741
+ gen_step: Generator | Callable[[], Generator],
742
+ *,
743
+ async_: None | bool = None,
744
+ threaded: bool = False,
745
+ ):
746
+ if async_ is None:
747
+ async_ = _get_async()
748
+ if async_:
749
+ async def process():
750
+ try:
751
+ async for value in iter_gen_step_async(gen_step, threaded=threaded):
752
+ pass
528
753
  return value
529
- finally:
530
- if close is not None:
531
- if threaded:
532
- await to_thread(close)
533
- else:
534
- close()
535
- result = process()
536
- if as_iter:
537
- async def wrap(result):
538
- iterable = await result
539
- try:
540
- iterable = aiter(iterable)
541
- except TypeError:
542
- for val in iter(iterable):
543
- if isawaitable(val):
544
- val = await val
545
- yield val
546
- else:
547
- async for val in iterable:
548
- yield val
549
- result = wrap(result)
550
- return result
754
+ except Reraised as e:
755
+ raise e.exception
756
+ return process()
551
757
  else:
552
758
  try:
553
- value = send(None)
554
- while True:
555
- if isinstance(value, YieldBase):
556
- raise StopIteration(value)
557
- try:
558
- if callable(value):
559
- value = value()
560
- except BaseException as e:
561
- value = throw(e)
562
- else:
563
- value = send(value)
564
- except StopIteration as e:
565
- value = e.value
566
- try_call_me = True
567
- if isinstance(value, YieldBase):
568
- try_call_me = value.try_call_me
569
- value = value.value
570
- if callable(value) and try_call_me:
571
- value = value()
572
- if as_iter:
573
- value = iter(value)
759
+ for value in iter_gen_step(gen_step):
760
+ pass
574
761
  return value
575
- finally:
576
- if close is not None:
577
- close()
762
+ except Reraised as e:
763
+ raise e.exception
578
764
 
579
765
 
580
766
  @overload
@@ -611,12 +797,11 @@ def run_gen_step_iter(
611
797
  async_ = _get_async()
612
798
  if callable(gen_step):
613
799
  gen = gen_step()
614
- close = gen.close
615
800
  else:
616
801
  gen = gen_step
617
- close = None
618
802
  send = gen.send
619
803
  throw = gen.throw
804
+ close = gen.close
620
805
  if async_:
621
806
  async def process():
622
807
  async def extract(value):
@@ -658,19 +843,24 @@ def run_gen_step_iter(
658
843
  else:
659
844
  value = send(value)
660
845
  except StopIteration as e:
661
- yield_type, value = await extract(e.value)
662
- match yield_type:
663
- case 1:
664
- yield value
665
- case 2:
666
- async for val in ensure_aiter(value, threaded=threaded):
667
- yield val
668
- finally:
669
- if close is not None:
846
+ try:
847
+ yield_type, value = await extract(e.value)
848
+ match yield_type:
849
+ case 1:
850
+ yield value
851
+ case 2:
852
+ async for val in ensure_aiter(value, threaded=threaded):
853
+ yield val
854
+ except BaseException as e:
670
855
  if threaded:
671
- await to_thread(close)
856
+ await to_thread(throw, e)
672
857
  else:
673
- close()
858
+ throw(e)
859
+ finally:
860
+ if threaded:
861
+ await to_thread(close)
862
+ else:
863
+ close()
674
864
  else:
675
865
  def process():
676
866
  def extract(value, /):
@@ -700,17 +890,19 @@ def run_gen_step_iter(
700
890
  else:
701
891
  value = send(value)
702
892
  except StopIteration as e:
703
- yield_type, value = extract(e.value)
704
- match yield_type:
705
- case 1:
706
- yield value
707
- case 2:
708
- yield from value
709
- case _:
710
- return value
893
+ try:
894
+ yield_type, value = extract(e.value)
895
+ match yield_type:
896
+ case 1:
897
+ yield value
898
+ case 2:
899
+ yield from value
900
+ case _:
901
+ return value
902
+ except BaseException as e:
903
+ throw(e)
711
904
  finally:
712
- if close is not None:
713
- close()
905
+ close()
714
906
  return process()
715
907
 
716
908
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-iterutils"
3
- version = "0.1.7"
3
+ version = "0.1.9"
4
4
  description = "Python another itertools."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -27,7 +27,7 @@ include = [
27
27
 
28
28
  [tool.poetry.dependencies]
29
29
  python = "^3.12"
30
- python-asynctools = ">=0.0.10"
30
+ python-asynctools = ">=0.1.2"
31
31
  python-decotools = ">=0.0.2"
32
32
  python-texttools = ">=0.0.3"
33
33
  python-undefined = ">=0.0.3"