python-iterutils 0.2.3__py3-none-any.whl → 0.2.4__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.
iterutils/__init__.py CHANGED
@@ -2,21 +2,20 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 2, 3)
5
+ __version__ = (0, 2, 4)
6
6
  __all__ = [
7
- "Return", "Yield", "YieldFrom", "iterable", "async_iterable",
8
- "foreach", "async_foreach", "through", "async_through", "flatten",
9
- "async_flatten", "collect", "async_collect", "group_collect",
10
- "async_group_collect", "map", "filter", "reduce", "zip", "chunked",
11
- "iter_unique", "async_iter_unique", "wrap_iter", "wrap_aiter",
12
- "acc_step", "cut_iter", "iter_gen_step", "iter_gen_step_async",
13
- "run_gen_step", "run_gen_step_sync_iter", "run_gen_step_async_iter",
14
- "run_gen_step_iter", "as_gen_step", "as_gen_step_iter", "bfs_gen",
15
- "with_iter_next", "backgroud_loop",
7
+ "Yield", "YieldFrom", "iterable", "async_iterable",
8
+ "run_gen_step", "run_gen_step_iter", "as_gen_step",
9
+ "as_gen_step_iter", "split_cm, ""with_iter_next",
10
+ "map", "filter", "reduce", "zip", "chunked",
11
+ "foreach", "async_foreach", "through", "async_through",
12
+ "flatten", "async_flatten", "collect", "async_collect",
13
+ "group_collect", "async_group_collect", "iter_unique",
14
+ "async_iter_unique", "wrap_iter", "wrap_aiter", "acc_step",
15
+ "cut_iter", "bfs_gen", "context", "backgroud_loop",
16
16
  ]
17
17
 
18
- from abc import ABC, abstractmethod
19
- from asyncio import create_task, sleep as async_sleep, to_thread
18
+ from asyncio import create_task, sleep as async_sleep
20
19
  from builtins import map as _map, filter as _filter, zip as _zip
21
20
  from collections import defaultdict, deque
22
21
  from collections.abc import (
@@ -27,95 +26,472 @@ from collections.abc import (
27
26
  )
28
27
  from contextlib import (
29
28
  asynccontextmanager, contextmanager, ExitStack, AsyncExitStack,
29
+ AbstractContextManager, AbstractAsyncContextManager,
30
30
  )
31
31
  from copy import copy
32
32
  from dataclasses import dataclass
33
33
  from itertools import batched, pairwise
34
- from inspect import isawaitable, iscoroutinefunction
35
- from sys import _getframe
34
+ from inspect import isawaitable, iscoroutinefunction, signature
35
+ from sys import _getframe, exc_info
36
36
  from _thread import start_new_thread
37
37
  from time import sleep, time
38
38
  from types import FrameType
39
39
  from typing import (
40
40
  cast, overload, Any, AsyncContextManager, ContextManager, Literal,
41
- Protocol,
42
41
  )
43
42
 
44
43
  from asynctools import (
45
44
  async_filter, async_map, async_reduce, async_zip, async_batched,
46
45
  ensure_async, ensure_aiter, collect as async_collect,
47
46
  )
48
- from decotools import optional
49
47
  from texttools import format_time
50
48
  from undefined import undefined
51
49
 
52
50
 
53
- class SupportsBool(Protocol):
51
+ @dataclass(slots=True, frozen=True, unsafe_hash=True)
52
+ class Yield:
53
+ """专供 `run_gen_step_iter`,说明值需要 yield 给用户
54
54
  """
55
+ value: Any
56
+
57
+
58
+ @dataclass(slots=True, frozen=True, unsafe_hash=True)
59
+ class YieldFrom:
60
+ """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
55
61
  """
56
- def __bool__(self, /) -> bool: ...
62
+ value: Any
57
63
 
58
64
 
59
- class Reraised(BaseException):
65
+ def iterable(obj, /) -> bool:
66
+ """判断对象是不是 Iterable
60
67
  """
68
+ return isinstance(obj, Iterable)
69
+
70
+
71
+ def async_iterable(obj, /) -> bool:
72
+ """判断对象是不是 AsyncIterable
61
73
  """
62
- def __init__(self, exc: BaseException, /):
63
- if isinstance(exc, Reraised):
64
- exc = exc.exception
65
- self.exception: BaseException = exc
74
+ return isinstance(obj, AsyncIterable)
66
75
 
67
76
 
68
- @dataclass(slots=True, frozen=True, unsafe_hash=True)
69
- class YieldBase(ABC):
77
+ def _get_async(back: int = 2, /) -> bool:
78
+ """往上查找,从最近的调用栈的命名空间中获取 `async_` 的值
70
79
  """
80
+ f: None | FrameType
81
+ f = _getframe(back)
82
+ f_globals = f.f_globals
83
+ f_locals = f.f_locals
84
+ if f_locals is f_globals:
85
+ return f_locals.get("async_") or False
86
+ while f_locals is not None and f_locals is not f_globals:
87
+ if "async_" in f_locals:
88
+ return f_locals["async_"] or False
89
+ f = f.f_back
90
+ if f is None:
91
+ break
92
+ f_locals = f.f_locals
93
+ return False
94
+
95
+
96
+ def _run_gen_step(gen: Generator, /):
97
+ send = gen.send
98
+ try:
99
+ value: Any = send(None)
100
+ while True:
101
+ value = send(value)
102
+ except StopIteration as e:
103
+ return e.value
104
+ finally:
105
+ gen.close()
106
+
107
+
108
+ async def _run_gen_step_async(gen: Generator, /):
109
+ send = gen.send
110
+ throw = gen.throw
111
+ try:
112
+ ret: Awaitable = send(None)
113
+ while True:
114
+ try:
115
+ value: Any = await ret
116
+ except BaseException as e:
117
+ ret = throw(e)
118
+ else:
119
+ ret = send(value)
120
+ except StopIteration as e:
121
+ if isawaitable(e.value):
122
+ return await e.value
123
+ return e.value
124
+ finally:
125
+ gen.close()
126
+
127
+
128
+ def run_gen_step[**Args](
129
+ gen_step: Generator | Callable[Args, Generator],
130
+ async_: None | Literal[False, True] = None,
131
+ /,
132
+ *args: Args.args,
133
+ **kwds: Args.kwargs,
134
+ ):
135
+ """驱动生成器运行,并返回其结果
71
136
  """
72
- value: Any
73
- may_await: None | bool | Literal[1] = False
74
- may_call: None | bool | Literal[1] = None
137
+ if async_ is None:
138
+ async_ = _get_async()
139
+ if not isinstance(gen_step, Generator):
140
+ params = signature(gen_step).parameters
141
+ if ((param := params.get("async_")) and
142
+ (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
143
+ ):
144
+ kwds.setdefault("async_", async_)
145
+ gen_step = gen_step(*args, **kwds)
146
+ if async_:
147
+ return _run_gen_step_async(gen_step)
148
+ else:
149
+ return _run_gen_step(gen_step)
150
+
151
+
152
+ def _run_gen_step_iter(gen: Generator, /) -> Iterator:
153
+ send = gen.send
154
+ try:
155
+ value: Any = send(None)
156
+ while True:
157
+ if isinstance(value, Yield):
158
+ yield value.value
159
+ elif isinstance(value, YieldFrom):
160
+ yield from value.value
161
+ value = send(value)
162
+ except StopIteration as e:
163
+ value = e.value
164
+ if isinstance(value, Yield):
165
+ yield value.value
166
+ elif isinstance(value, YieldFrom):
167
+ yield from value.value
168
+ finally:
169
+ gen.close()
170
+
171
+
172
+ async def _run_gen_step_async_iter(gen: Generator, /) -> AsyncIterator:
173
+ send = gen.send
174
+ throw = gen.throw
175
+ try:
176
+ ret: Awaitable | Yield | YieldFrom = send(None)
177
+ while True:
178
+ try:
179
+ if isinstance(ret, Awaitable):
180
+ value: Any = await ret
181
+ else:
182
+ value = ret.value
183
+ if isawaitable(value):
184
+ value = await value
185
+ if isinstance(ret, Yield):
186
+ yield value
187
+ elif isinstance(value, AsyncIterable):
188
+ async for e in value:
189
+ yield e
190
+ else:
191
+ for e in value:
192
+ yield e
193
+ except BaseException as e:
194
+ ret = throw(e)
195
+ else:
196
+ ret = send(value)
197
+ except StopIteration as e:
198
+ val = e.value
199
+ if isinstance(val, (Yield, YieldFrom)):
200
+ value = val.value
201
+ if isawaitable(value):
202
+ value = await value
203
+ if isinstance(ret, Yield):
204
+ yield value
205
+ elif isinstance(value, AsyncIterable):
206
+ async for e in value:
207
+ yield e
208
+ else:
209
+ for e in value:
210
+ yield e
211
+ elif isawaitable(val):
212
+ await val
213
+ finally:
214
+ gen.close()
215
+
216
+
217
+ @overload
218
+ def run_gen_step_iter[**Args](
219
+ gen_step: Generator | Callable[Args, Generator],
220
+ async_: None = None,
221
+ /,
222
+ *args: Args.args,
223
+ **kwds: Args.kwargs,
224
+ ) -> Iterator | AsyncIterator:
225
+ ...
226
+ @overload
227
+ def run_gen_step_iter[**Args](
228
+ gen_step: Generator | Callable[Args, Generator],
229
+ async_: Literal[False] = False,
230
+ /,
231
+ *args: Args.args,
232
+ **kwds: Args.kwargs,
233
+ ) -> Iterator:
234
+ ...
235
+ @overload
236
+ def run_gen_step_iter[**Args](
237
+ gen_step: Generator | Callable[Args, Generator],
238
+ async_: Literal[True],
239
+ /,
240
+ *args: Args.args,
241
+ **kwds: Args.kwargs,
242
+ ) -> AsyncIterator:
243
+ ...
244
+ def run_gen_step_iter[**Args](
245
+ gen_step: Generator | Callable[Args, Generator],
246
+ async_: None | Literal[False, True] = None,
247
+ /,
248
+ *args: Args.args,
249
+ **kwds: Args.kwargs,
250
+ ) -> Iterator | AsyncIterator:
251
+ """驱动生成器运行,并从中返回可迭代而出的值
252
+ """
253
+ if async_ is None:
254
+ async_ = _get_async()
255
+ if not isinstance(gen_step, Generator):
256
+ params = signature(gen_step).parameters
257
+ if ((param := params.get("async_")) and
258
+ (param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY))
259
+ ):
260
+ kwds.setdefault("async_", async_)
261
+ gen_step = gen_step(*args, **kwds)
262
+ if async_:
263
+ return _run_gen_step_async_iter(gen_step)
264
+ else:
265
+ return _run_gen_step_iter(gen_step)
266
+
267
+
268
+ def as_gen_step[**Args](
269
+ gen_step: Callable[Args, Generator],
270
+ /,
271
+ ) -> Callable[Args, Any]:
272
+ def wrapper(*args: Args.args, **kwds: Args.kwargs):
273
+ async_ = cast(None | Literal[False, True], kwds.pop("async_", None))
274
+ return run_gen_step(gen_step, async_, *args, **kwds)
275
+ return wrapper
276
+
277
+
278
+ def as_gen_step_iter[**Args](
279
+ gen_step: Callable[Args, Generator],
280
+ /,
281
+ ) -> Callable[Args, Iterable | AsyncIterable]:
282
+ def wrapper(*args: Args.args, **kwds: Args.kwargs):
283
+ async_ = cast(None | Literal[False, True], kwds.pop("async_", None))
284
+ return run_gen_step_iter(gen_step, async_, *args, **kwds)
285
+ return wrapper
286
+
287
+
288
+ @overload
289
+ def split_cm[T](
290
+ cm: AbstractContextManager[T],
291
+ /,
292
+ ) -> tuple[Callable[[], T], Callable[[], Any]]:
293
+ ...
294
+ @overload
295
+ def split_cm[T](
296
+ cm: AbstractAsyncContextManager[T],
297
+ /,
298
+ ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
299
+ ...
300
+ def split_cm[T](
301
+ cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
302
+ /,
303
+ ) -> (
304
+ tuple[Callable[[], T], Callable[[], Any]] |
305
+ tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
306
+ ):
307
+ """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
308
+
309
+ .. code:: python
310
+
311
+ if async_:
312
+ async def process():
313
+ async with cm as obj:
314
+ do_what_you_want()
315
+ return process()
316
+ else:
317
+ with cm as obj:
318
+ do_what_you_want()
319
+
320
+ 大概相当于
321
+
322
+ .. code:: python
323
+
324
+ def gen_step():
325
+ enter, exit = split_cm(cm)
326
+ obj = yield enter()
327
+ try:
328
+ do_what_you_want()
329
+ finally:
330
+ yield exit()
331
+
332
+ run_gen_step(gen_step, async_=async_)
333
+ """
334
+ if isinstance(cm, AbstractAsyncContextManager):
335
+ enter: Callable = cm.__aenter__
336
+ exit: Callable = cm.__aexit__
337
+ else:
338
+ enter = cm.__enter__
339
+ exit = cm.__exit__
340
+ return enter, lambda: exit(*exc_info())
341
+
342
+
343
+ @overload
344
+ def with_iter_next[T](
345
+ iterable: Iterable[T],
346
+ /,
347
+ ) -> ContextManager[Callable[[], T]]:
348
+ ...
349
+ @overload
350
+ def with_iter_next[T](
351
+ iterable: AsyncIterable[T],
352
+ /,
353
+ ) -> ContextManager[Callable[[], Awaitable[T]]]:
354
+ ...
355
+ @contextmanager
356
+ def with_iter_next[T](
357
+ iterable: Iterable[T] | AsyncIterable[T],
358
+ /,
359
+ ):
360
+ """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
361
+
362
+ .. code:: python
363
+
364
+ if async_:
365
+ async def process():
366
+ async for e in iterable:
367
+ do_what_you_want()
368
+ return process()
369
+ else:
370
+ for e in iterable:
371
+ do_what_you_want()
372
+
373
+ 大概相当于
374
+
375
+ .. code:: python
75
376
 
76
- @property
77
- @abstractmethod
78
- def yield_type(self, /) -> int:
79
- ...
377
+ def gen_step():
378
+ with with_iter_next(iterable) as do_next:
379
+ while True:
380
+ e = yield do_next()
381
+ do_what_you_want()
80
382
 
383
+ run_gen_step(gen_step, async_=async_)
384
+ """
385
+ if isinstance(iterable, AsyncIterable):
386
+ try:
387
+ yield aiter(iterable).__anext__
388
+ except StopAsyncIteration:
389
+ pass
390
+ else:
391
+ try:
392
+ yield iter(iterable).__next__
393
+ except StopIteration:
394
+ pass
81
395
 
82
- class Return(YieldBase):
396
+
397
+ def map(
398
+ function: None | Callable,
399
+ iterable: Iterable | AsyncIterable,
400
+ /,
401
+ *iterables: Iterable | AsyncIterable,
402
+ ):
83
403
  """
84
404
  """
85
- __slots__ = ()
86
- yield_type = 0
405
+ if (
406
+ iscoroutinefunction(function) or
407
+ isinstance(iterable, AsyncIterable) or
408
+ any(isinstance(i, AsyncIterable) for i in iterables)
409
+ ):
410
+ if function is None:
411
+ if iterables:
412
+ return async_zip(iterable, *iterables)
413
+ else:
414
+ return iterable
415
+ return async_map(function, iterable, *iterables)
416
+ if function is None:
417
+ if iterables:
418
+ return _zip(iterable, *iterables)
419
+ else:
420
+ return iterable
421
+ return _map(function, iterable, *iterables)
87
422
 
88
423
 
89
- class Yield(YieldBase):
424
+ def filter(
425
+ function: None | Callable,
426
+ iterable: Iterable | AsyncIterable,
427
+ /,
428
+ ):
90
429
  """
91
430
  """
92
- __slots__ = ()
93
- yield_type = 1
431
+ if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
432
+ return async_filter(function, iterable)
433
+ return _filter(function, iterable)
94
434
 
95
435
 
96
- class YieldFrom(YieldBase):
436
+ def reduce(
437
+ function: Callable,
438
+ iterable: Iterable | AsyncIterable,
439
+ initial: Any = undefined,
440
+ /,
441
+ ):
97
442
  """
98
443
  """
99
- __slots__ = ()
100
- yield_type = 2
444
+ if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
445
+ return async_reduce(function, iterable, initial)
446
+ from functools import reduce
447
+ if initial is undefined:
448
+ return reduce(function, iterable)
449
+ return reduce(function, iterable, initial)
101
450
 
102
451
 
103
- def iterable(iterable, /) -> bool:
452
+ def zip(
453
+ iterable: Iterable | AsyncIterable,
454
+ /,
455
+ *iterables: Iterable | AsyncIterable,
456
+ ):
104
457
  """
105
458
  """
106
- try:
107
- return isinstance(iter(iterable), Iterable)
108
- except TypeError:
109
- return False
459
+ if isinstance(iterable, AsyncIterable) or any(isinstance(i, AsyncIterable) for i in iterables):
460
+ return async_zip(iterable, *iterables)
461
+ return _zip(iterable, *iterables)
110
462
 
111
463
 
112
- def async_iterable(iterable, /) -> bool:
464
+ @overload
465
+ def chunked[T](
466
+ iterable: Iterable[T],
467
+ n: int = 1,
468
+ /,
469
+ ) -> Iterator[Sequence[T]]:
470
+ ...
471
+ @overload
472
+ def chunked[T](
473
+ iterable: AsyncIterable[T],
474
+ n: int = 1,
475
+ /,
476
+ ) -> AsyncIterator[Sequence[T]]:
477
+ ...
478
+ def chunked[T](
479
+ iterable: Iterable[T] | AsyncIterable[T],
480
+ n: int = 1,
481
+ /,
482
+ ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
113
483
  """
114
484
  """
115
- try:
116
- return isinstance(iter(iterable), AsyncIterable)
117
- except TypeError:
118
- return False
485
+ if n < 0:
486
+ n = 1
487
+ if isinstance(iterable, Sequence):
488
+ if n == 1:
489
+ return ((e,) for e in iterable)
490
+ return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
491
+ elif isinstance(iterable, Iterable):
492
+ return batched(iterable, n)
493
+ else:
494
+ return async_batched(iterable, n)
119
495
 
120
496
 
121
497
  def foreach(
@@ -410,109 +786,9 @@ async def async_group_collect[K, V, C: Container, M: MutableMapping](
410
786
  return mapping
411
787
 
412
788
 
413
- def map(
414
- function: None | Callable,
415
- iterable: Iterable | AsyncIterable,
416
- /,
417
- *iterables: Iterable | AsyncIterable,
418
- ):
419
- """
420
- """
421
- if (
422
- iscoroutinefunction(function) or
423
- isinstance(iterable, AsyncIterable) or
424
- any(isinstance(i, AsyncIterable) for i in iterables)
425
- ):
426
- if function is None:
427
- if iterables:
428
- return async_zip(iterable, *iterables)
429
- else:
430
- return iterable
431
- return async_map(function, iterable, *iterables)
432
- if function is None:
433
- if iterables:
434
- return _zip(iterable, *iterables)
435
- else:
436
- return iterable
437
- return _map(function, iterable, *iterables)
438
-
439
-
440
- def filter(
441
- function: None | Callable,
442
- iterable: Iterable | AsyncIterable,
443
- /,
444
- ):
445
- """
446
- """
447
- if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
448
- return async_filter(function, iterable)
449
- return _filter(function, iterable)
450
-
451
-
452
- def reduce(
453
- function: Callable,
454
- iterable: Iterable | AsyncIterable,
455
- initial: Any = undefined,
456
- /,
457
- ):
458
- """
459
- """
460
- if iscoroutinefunction(function) or isinstance(iterable, AsyncIterable):
461
- return async_reduce(function, iterable, initial)
462
- from functools import reduce
463
- if initial is undefined:
464
- return reduce(function, iterable)
465
- return reduce(function, iterable, initial)
466
-
467
-
468
- def zip(
469
- iterable: Iterable | AsyncIterable,
470
- /,
471
- *iterables: Iterable | AsyncIterable,
472
- ):
473
- """
474
- """
475
- if isinstance(iterable, AsyncIterable) or any(isinstance(i, AsyncIterable) for i in iterables):
476
- return async_zip(iterable, *iterables)
477
- return _zip(iterable, *iterables)
478
-
479
-
480
- @overload
481
- def chunked[T](
482
- iterable: Iterable[T],
483
- n: int = 1,
484
- /,
485
- ) -> Iterator[Sequence[T]]:
486
- ...
487
- @overload
488
- def chunked[T](
489
- iterable: AsyncIterable[T],
490
- n: int = 1,
491
- /,
492
- ) -> AsyncIterator[Sequence[T]]:
493
- ...
494
- def chunked[T](
495
- iterable: Iterable[T] | AsyncIterable[T],
496
- n: int = 1,
497
- /,
498
- ) -> Iterator[Sequence[T]] | AsyncIterator[Sequence[T]]:
499
- """
500
- """
501
- if n < 0:
502
- n = 1
503
- if isinstance(iterable, Sequence):
504
- if n == 1:
505
- return ((e,) for e in iterable)
506
- return (iterable[i:j] for i, j in pairwise(range(0, len(iterable)+n, n)))
507
- elif isinstance(iterable, Iterable):
508
- return batched(iterable, n)
509
- else:
510
- return async_batched(iterable, n)
511
-
512
-
513
- @overload
514
- def iter_unique[T](
515
- iterable: Iterable[T],
789
+ @overload
790
+ def iter_unique[T](
791
+ iterable: Iterable[T],
516
792
  /,
517
793
  seen: None | MutableSet = None,
518
794
  ) -> Iterator[T]:
@@ -650,493 +926,6 @@ def cut_iter(
650
926
  yield stop, stop - start
651
927
 
652
928
 
653
- def _get_async(back: int = 2) -> bool:
654
- f: None | FrameType
655
- f = _getframe(back)
656
- f_globals = f.f_globals
657
- f_locals = f.f_locals
658
- if f_locals is f_globals:
659
- return f_locals.get("async_") or False
660
- while f_locals is not None and f_locals is not f_globals:
661
- if "async_" in f_locals:
662
- return f_locals["async_"] or False
663
- f = f.f_back
664
- if f is None:
665
- break
666
- f_locals = f.f_locals
667
- return False
668
-
669
-
670
- @overload
671
- def call_as_async[**Args, T: Coroutine](
672
- func: Callable[Args, T], /,
673
- may_await: bool | Literal[1] = False,
674
- threaded: bool = False,
675
- ) -> Callable[Args, T]:
676
- ...
677
- @overload
678
- def call_as_async[**Args, T](
679
- func: Callable[Args, T], /,
680
- may_await: bool | Literal[1] = False,
681
- threaded: bool = False,
682
- ) -> Callable[Args, Coroutine[Any, Any, T]]:
683
- ...
684
- def call_as_async[**Args, T](
685
- func: Callable[Args, T],
686
- /,
687
- may_await: bool | Literal[1] = False,
688
- threaded: bool = False,
689
- ) -> Callable[Args, T] | Callable[Args, Coroutine[Any, Any, T]]:
690
- """
691
- """
692
- if iscoroutinefunction(func):
693
- return func
694
- def wraps(*args, **kwds):
695
- try:
696
- return func(*args, **kwds)
697
- except (StopIteration, StopAsyncIteration) as e:
698
- raise Reraised(e) from e
699
- async def wrapper(*args, **kwds):
700
- if threaded:
701
- value = await to_thread(wraps, *args, **kwds)
702
- else:
703
- value = wraps(*args, **kwds)
704
- if may_await is 1 or may_await and isawaitable(value):
705
- value = await value
706
- return value
707
- return wrapper
708
-
709
-
710
- def iter_gen_step(
711
- gen_step: Generator | Callable[[], Generator],
712
- may_call: bool | Literal[1] = True,
713
- ):
714
- """
715
- """
716
- if callable(gen_step):
717
- gen_step = gen_step()
718
- send = gen_step.send
719
- throw = gen_step.throw
720
- close = gen_step.close
721
- value: Any = None
722
- try:
723
- while True:
724
- if isinstance(value, YieldBase):
725
- raise StopIteration(value)
726
- try:
727
- if may_call is 1 or may_call and callable(value):
728
- value = value()
729
- except BaseException as e:
730
- value = throw(e)
731
- else:
732
- value = send(value)
733
- yield value
734
- except StopIteration as e:
735
- value = e.value
736
- if isinstance(value, YieldBase):
737
- maybe_callable = value.may_call
738
- if maybe_callable is not None:
739
- may_call = maybe_callable
740
- value = value.value
741
- if may_call is 1 or may_call and callable(value):
742
- try:
743
- value = value()
744
- except BaseException as e:
745
- try:
746
- value = throw(e)
747
- except BaseException as e:
748
- raise Reraised(e) from e
749
- yield value
750
- finally:
751
- close()
752
-
753
-
754
- async def iter_gen_step_async(
755
- gen_step: Generator | Callable[[], Generator],
756
- may_await: bool | Literal[1] = True,
757
- may_call: bool | Literal[1] = True,
758
- threaded: bool = False,
759
- ):
760
- """
761
- """
762
- if callable(gen_step):
763
- gen_step = gen_step()
764
- send: Callable = call_as_async(gen_step.send, threaded=threaded)
765
- throw: Callable = call_as_async(gen_step.throw, threaded=threaded)
766
- close: Callable = call_as_async(gen_step.close, threaded=threaded)
767
- value: Any = None
768
- try:
769
- while True:
770
- if isinstance(value, YieldBase):
771
- raise StopIteration(value)
772
- try:
773
- if may_call is 1 or may_call and callable(value):
774
- value = await call_as_async(
775
- value, may_await=may_await, threaded=threaded)()
776
- elif may_await is 1 or may_await and isawaitable(value):
777
- value = await value
778
- except BaseException as e:
779
- if isinstance(e, Reraised):
780
- e = e.exception
781
- value = await throw(e)
782
- else:
783
- value = await send(value)
784
- yield value
785
- except BaseException as e:
786
- if isinstance(e, Reraised):
787
- e = e.exception
788
- if isinstance(e, StopIteration):
789
- value = e.value
790
- if isinstance(value, YieldBase):
791
- maybe_awaitable = value.may_await
792
- if maybe_awaitable is not None:
793
- may_await = maybe_awaitable
794
- maybe_callable = value.may_call
795
- if maybe_callable is not None:
796
- may_call = maybe_callable
797
- value = value.value
798
- try:
799
- if may_call is 1 or may_call and callable(value):
800
- value = await call_as_async(
801
- value, may_await=may_await, threaded=threaded)()
802
- elif may_await is 1 or may_await and isawaitable(value):
803
- value = await value
804
- except BaseException as e:
805
- try:
806
- value = await throw(e)
807
- except BaseException as e:
808
- raise Reraised(e) from e
809
- yield value
810
- else:
811
- raise
812
- finally:
813
- await close()
814
-
815
-
816
- @overload
817
- def run_gen_step(
818
- gen_step: Generator | Callable[[], Generator],
819
- may_await: bool | Literal[1] = True,
820
- may_call: bool | Literal[1] = True,
821
- threaded: bool = False,
822
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
823
- *,
824
- async_: None = None,
825
- ):
826
- ...
827
- @overload
828
- def run_gen_step(
829
- gen_step: Generator | Callable[[], Generator],
830
- may_await: bool | Literal[1] = True,
831
- may_call: bool | Literal[1] = True,
832
- threaded: bool = False,
833
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
834
- *,
835
- async_: Literal[False],
836
- ):
837
- ...
838
- @overload
839
- def run_gen_step(
840
- gen_step: Generator | Callable[[], Generator],
841
- may_await: bool | Literal[1] = True,
842
- may_call: bool | Literal[1] = True,
843
- threaded: bool = False,
844
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
845
- *,
846
- async_: Literal[True],
847
- ) -> Coroutine:
848
- ...
849
- def run_gen_step(
850
- gen_step: Generator | Callable[[], Generator],
851
- may_await: bool | Literal[1] = True,
852
- may_call: bool | Literal[1] = True,
853
- threaded: bool = False,
854
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
855
- *,
856
- async_: None | Literal[False, True] = None,
857
- ):
858
- """
859
- """
860
- if async_ is None:
861
- async_ = _get_async()
862
- if async_:
863
- async def process():
864
- gen = iter_gen_step_async(
865
- gen_step,
866
- may_await=may_await,
867
- may_call=may_call,
868
- threaded=threaded,
869
- )
870
- try:
871
- if running_flag is None:
872
- async for value in gen:
873
- pass
874
- elif callable(running_flag):
875
- pred = call_as_async(running_flag, threaded=threaded)
876
- if not await pred():
877
- raise RuntimeError("stop before starting", gen) from StopIteration()
878
- async for value in gen:
879
- if not await pred():
880
- raise RuntimeError("stop midway", gen) from StopIteration(value)
881
- else:
882
- if not running_flag:
883
- raise RuntimeError("stop before starting", gen) from StopIteration()
884
- async for value in gen:
885
- if not running_flag:
886
- raise RuntimeError("stop midway", gen) from StopIteration(value)
887
- return value
888
- except Reraised as e:
889
- raise e.exception
890
- except KeyboardInterrupt as e:
891
- e.args = gen,
892
- e.add_note(f"stop iterating gen_step: {gen!r}")
893
- raise
894
- return process()
895
- else:
896
- gen = iter_gen_step(gen_step, may_call=may_call)
897
- try:
898
- if running_flag is None:
899
- for value in gen:
900
- pass
901
- else:
902
- if not callable(running_flag):
903
- running_flag = running_flag.__bool__
904
- if not running_flag():
905
- raise RuntimeError("stop before starting", gen) from StopIteration()
906
- for value in gen:
907
- if not running_flag():
908
- raise RuntimeError("stop midway", gen) from StopIteration(value)
909
- return value
910
- except Reraised as e:
911
- raise e.exception
912
- except KeyboardInterrupt as e:
913
- e.args = gen,
914
- e.add_note(f"stop iterating gen_step: {gen!r}")
915
- raise
916
-
917
-
918
- def run_gen_step_sync_iter(
919
- gen_step: Generator | Callable[[], Generator],
920
- may_call: bool | Literal[1] = True,
921
- ) -> Iterator:
922
- """
923
- """
924
- if callable(gen_step):
925
- gen_step = gen_step()
926
- send: Callable = gen_step.send
927
- throw: Callable = gen_step.throw
928
- close: Callable = gen_step.close
929
- def extract(value, may_call=may_call, /):
930
- yield_type = -1
931
- if isinstance(value, YieldBase):
932
- yield_type = value.yield_type
933
- maybe_callable = value.may_call
934
- if maybe_callable is not None:
935
- may_call = maybe_callable
936
- value = value.value
937
- if may_call is 1 or may_call and callable(value):
938
- value = value()
939
- return yield_type, value
940
- try:
941
- value = send(None)
942
- while True:
943
- try:
944
- yield_type, value = extract(value)
945
- match yield_type:
946
- case 0:
947
- return value
948
- case 1:
949
- yield value
950
- case 2:
951
- yield from value
952
- except BaseException as e:
953
- value = throw(e)
954
- else:
955
- value = send(value)
956
- except StopIteration as e:
957
- try:
958
- yield_type, value = extract(e.value)
959
- match yield_type:
960
- case 1:
961
- yield value
962
- case 2:
963
- yield from value
964
- case _:
965
- return value
966
- except BaseException as e:
967
- throw(e)
968
- finally:
969
- close()
970
-
971
-
972
- async def run_gen_step_async_iter(
973
- gen_step: Generator | Callable[[], Generator],
974
- may_await: bool | Literal[1] = True,
975
- may_call: bool | Literal[1] = True,
976
- threaded: bool = False,
977
- ) -> AsyncIterator:
978
- """
979
- """
980
- if callable(gen_step):
981
- gen_step = gen_step()
982
- send: Callable = call_as_async(gen_step.send, threaded=threaded)
983
- throw: Callable = call_as_async(gen_step.throw, threaded=threaded)
984
- close: Callable = call_as_async(gen_step.close, threaded=threaded)
985
- async def extract(value, may_await=may_await, may_call=may_call, /):
986
- yield_type = -1
987
- if isinstance(value, YieldBase):
988
- yield_type = value.yield_type
989
- maybe_awaitable = value.may_await
990
- if maybe_awaitable is not None:
991
- may_await = maybe_awaitable
992
- maybe_callable = value.may_call
993
- if maybe_callable is not None:
994
- may_call = maybe_callable
995
- value = value.value
996
- if may_call is 1 or may_call and callable(value):
997
- value = await call_as_async(
998
- value, may_await=may_await, threaded=threaded)()
999
- elif may_await is 1 or may_await and isawaitable(value):
1000
- value = await value
1001
- return yield_type, value
1002
- try:
1003
- value = await send(None)
1004
- while True:
1005
- try:
1006
- yield_type, value = await extract(value)
1007
- match yield_type:
1008
- case 0:
1009
- return
1010
- case 1:
1011
- yield value
1012
- case 2:
1013
- async for val in ensure_aiter(value, threaded=threaded):
1014
- yield val
1015
- except BaseException as e:
1016
- if isinstance(e, Reraised):
1017
- e = e.exception
1018
- value = await throw(e)
1019
- else:
1020
- value = await send(value)
1021
- except BaseException as e:
1022
- if isinstance(e, Reraised):
1023
- e = e.exception
1024
- if isinstance(e, StopIteration):
1025
- try:
1026
- yield_type, value = await extract(e.value)
1027
- match yield_type:
1028
- case 1:
1029
- yield value
1030
- case 2:
1031
- async for val in ensure_aiter(value, threaded=threaded):
1032
- yield val
1033
- except BaseException as e:
1034
- if isinstance(e, Reraised):
1035
- e = e.exception
1036
- await throw(e)
1037
- else:
1038
- raise
1039
- finally:
1040
- await close()
1041
-
1042
-
1043
- @overload
1044
- def run_gen_step_iter(
1045
- gen_step: Generator | Callable[[], Generator],
1046
- may_await: bool | Literal[1] = True,
1047
- may_call: bool | Literal[1] = True,
1048
- threaded: bool = False,
1049
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1050
- *,
1051
- async_: None = None,
1052
- ) -> Iterator | AsyncIterator:
1053
- ...
1054
- @overload
1055
- def run_gen_step_iter(
1056
- gen_step: Generator | Callable[[], Generator],
1057
- may_await: bool | Literal[1] = True,
1058
- may_call: bool | Literal[1] = True,
1059
- threaded: bool = False,
1060
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1061
- *,
1062
- async_: Literal[False],
1063
- ) -> Iterator:
1064
- ...
1065
- @overload
1066
- def run_gen_step_iter(
1067
- gen_step: Generator | Callable[[], Generator],
1068
- may_await: bool | Literal[1] = True,
1069
- may_call: bool | Literal[1] = True,
1070
- threaded: bool = False,
1071
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1072
- *,
1073
- async_: Literal[True],
1074
- ) -> AsyncIterator:
1075
- ...
1076
- def run_gen_step_iter(
1077
- gen_step: Generator | Callable[[], Generator],
1078
- may_await: bool | Literal[1] = True,
1079
- may_call: bool | Literal[1] = True,
1080
- threaded: bool = False,
1081
- running_flag: None | SupportsBool | Callable[[], SupportsBool] | Callable[[], Awaitable[SupportsBool]] = None,
1082
- *,
1083
- async_: None | Literal[False, True] = None,
1084
- ) -> Iterator | AsyncIterator:
1085
- """
1086
- """
1087
- if async_ is None:
1088
- async_ = _get_async()
1089
- if async_:
1090
- return run_gen_step_async_iter(
1091
- gen_step,
1092
- may_await=may_await,
1093
- may_call=may_call,
1094
- threaded=threaded,
1095
- )
1096
- else:
1097
- return run_gen_step_sync_iter(gen_step, may_call=may_call)
1098
-
1099
-
1100
- @optional
1101
- def as_gen_step(
1102
- func: Callable[..., Generator],
1103
- /,
1104
- *deco_args,
1105
- iter: bool = False,
1106
- **deco_kwds,
1107
- ) -> Callable:
1108
- """装饰器,把生成器封装成 gen_step 函数
1109
- """
1110
- call = run_gen_step_iter if iter else run_gen_step
1111
- def wrapper(*args, async_: Literal[False, True] = False, **kwds):
1112
- return call(
1113
- func(*args, async_=async_, **kwds),
1114
- *deco_args,
1115
- async_=async_,
1116
- **deco_kwds,
1117
- )
1118
- return wrapper
1119
-
1120
-
1121
- @optional
1122
- def as_gen_step_iter(
1123
- func: Callable[..., Generator],
1124
- /,
1125
- *deco_args,
1126
- **deco_kwds,
1127
- ) -> Callable:
1128
- """装饰器,把生成器封装成 gen_step 函数
1129
- """
1130
- def wrapper(*args, async_: Literal[False, True] = False, **kwds):
1131
- return run_gen_step_iter(
1132
- func(*args, async_=async_, **kwds),
1133
- *deco_args,
1134
- async_=async_,
1135
- **deco_kwds,
1136
- )
1137
- return wrapper
1138
-
1139
-
1140
929
  @overload
1141
930
  def bfs_gen[T](
1142
931
  initial: T,
@@ -1179,59 +968,6 @@ def bfs_gen[T](
1179
968
  args = yield val
1180
969
 
1181
970
 
1182
- @overload
1183
- def with_iter_next[T](
1184
- iterable: Iterable[T],
1185
- /,
1186
- async_: Literal[False],
1187
- ) -> ContextManager[Callable[[], T]]:
1188
- ...
1189
- @overload
1190
- def with_iter_next[T](
1191
- iterable: Iterable[T] | AsyncIterable[T],
1192
- /,
1193
- async_: Literal[True],
1194
- ) -> ContextManager[Callable[[], Awaitable[T]]]:
1195
- ...
1196
- @overload
1197
- def with_iter_next[T](
1198
- iterable: Iterable[T] | AsyncIterable[T],
1199
- /,
1200
- async_: None = None,
1201
- ) -> ContextManager[Callable[[], T]] | ContextManager[Callable[[], Awaitable[T]]]:
1202
- ...
1203
- @contextmanager
1204
- def with_iter_next[T](
1205
- iterable: Iterable[T] | AsyncIterable[T],
1206
- /,
1207
- async_: None | Literal[False, True] = None,
1208
- ):
1209
- """
1210
- """
1211
- if async_ is None:
1212
- if not isinstance(iterable, Iterable):
1213
- async_ = True
1214
- else:
1215
- async_ = _get_async()
1216
- if async_:
1217
- get_next: Callable[[], T] | Callable[[], Awaitable[T]] = ensure_aiter(iterable).__anext__
1218
- elif isinstance(iterable, Iterable):
1219
- get_next = iter(iterable).__next__
1220
- else:
1221
- get_next = aiter(iterable).__anext__
1222
- async_ = True
1223
- if async_:
1224
- try:
1225
- yield get_next
1226
- except StopAsyncIteration:
1227
- pass
1228
- else:
1229
- try:
1230
- yield get_next
1231
- except StopIteration:
1232
- pass
1233
-
1234
-
1235
971
  @overload
1236
972
  def context[T](
1237
973
  func: Callable[..., T],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-iterutils
3
- Version: 0.2.3
3
+ Version: 0.2.4
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
@@ -21,7 +21,6 @@ Classifier: Topic :: Software Development
21
21
  Classifier: Topic :: Software Development :: Libraries
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Requires-Dist: python-asynctools (>=0.1.2)
24
- Requires-Dist: python-decotools (>=0.0.2)
25
24
  Requires-Dist: python-texttools (>=0.0.3)
26
25
  Requires-Dist: python-undefined (>=0.0.3)
27
26
  Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-iterutils
@@ -0,0 +1,7 @@
1
+ LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
+ iterutils/__init__.py,sha256=1DAr8LAf7Gge6iCv7YTDLAu_uI4EqWbuzWBfyAT76nE,30237
3
+ iterutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ python_iterutils-0.2.4.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
5
+ python_iterutils-0.2.4.dist-info/METADATA,sha256=z2H59Xf9TIn6I8XRGLHxenhfVyMGXOMErl5x0dooOWU,1425
6
+ python_iterutils-0.2.4.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
7
+ python_iterutils-0.2.4.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
- iterutils/__init__.py,sha256=egZxWU804STaONEuyAjvI2R-nUbTXx1wU4AECgYu0jg,39456
3
- iterutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- python_iterutils-0.2.3.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
5
- python_iterutils-0.2.3.dist-info/METADATA,sha256=ZdhvscMtLIfrgiGyeXC30ZTSXg2v0J98cLu9YpVt7uo,1467
6
- python_iterutils-0.2.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
7
- python_iterutils-0.2.3.dist-info/RECORD,,