python-iterutils 0.2.10.2__py3-none-any.whl → 0.3.1__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/dynamic.py ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __all__ = ["dynamic_async", "dynamic_async_iter"]
5
+
6
+ from asyncio import AbstractEventLoop
7
+ from collections.abc import AsyncIterable, Callable, Iterable
8
+ from inspect import isawaitable
9
+
10
+ from argtools import has_keyword_arg
11
+ from asynctools import (
12
+ ensure_async, in_async, iter_async, run_async, to_coroutine,
13
+ to_aiter,
14
+ )
15
+ from decotools import optional
16
+
17
+
18
+ @optional
19
+ def dynamic_async(
20
+ func: Callable,
21
+ /,
22
+ threaded: bool = False,
23
+ async_: None | bool = None,
24
+ loop: None | AbstractEventLoop = None,
25
+ ):
26
+ has_async_arg = has_keyword_arg(func, "async_")
27
+ def wrapper(*args, async_: None | bool = async_, **kwds):
28
+ if async_ is None:
29
+ async_ = in_async()
30
+ if has_async_arg:
31
+ kwds["async_"] = async_
32
+ if async_:
33
+ if threaded:
34
+ r = ensure_async(func, threaded=threaded)(*args, **kwds)
35
+ else:
36
+ r = func(*args, **kwds)
37
+ if not isawaitable(r):
38
+ r = to_coroutine(r)
39
+ else:
40
+ r = func(*args, **kwds)
41
+ if isawaitable(r):
42
+ r = run_async(r, loop)
43
+ return r
44
+ return wrapper
45
+
46
+
47
+ @optional
48
+ def dynamic_async_iter(
49
+ func: Callable[..., Iterable] | Callable[..., AsyncIterable],
50
+ /,
51
+ threaded: bool = False,
52
+ async_: None | bool = None,
53
+ loop: None | AbstractEventLoop = None,
54
+ ):
55
+ has_async_arg = has_keyword_arg(func, "async_")
56
+ def wrapper(*args, async_: None | bool = async_, **kwds):
57
+ if async_ is None:
58
+ async_ = in_async()
59
+ if has_async_arg:
60
+ kwds["async_"] = async_
61
+ it = func(*args, **kwds)
62
+ if async_:
63
+ if not isinstance(it, AsyncIterable):
64
+ it = to_aiter(it, threaded=threaded)
65
+ elif isinstance(it, AsyncIterable):
66
+ it = iter_async(it, loop)
67
+ return it
68
+ return wrapper
69
+
iterutils/gen_step.py ADDED
@@ -0,0 +1,447 @@
1
+ #!/usr/bin/env python3
2
+ # encoding: utf-8
3
+
4
+ __all__ = [
5
+ "iter_gen_step", "run_gen_step", "run_gen_step_iter",
6
+ "as_gen_step", "with_iter_next", "with_lock", "split_cm",
7
+ "Yield", "YieldFrom",
8
+ ]
9
+
10
+ from asyncio import CancelledError as AsyncCancelledError
11
+ from collections.abc import (
12
+ AsyncGenerator, AsyncIterable, AsyncIterator, Awaitable, Callable,
13
+ Coroutine, Generator, Iterable, Iterator,
14
+ )
15
+ from concurrent.futures import CancelledError
16
+ from contextlib import contextmanager, AbstractContextManager, AbstractAsyncContextManager
17
+ from dataclasses import dataclass
18
+ from inspect import isawaitable
19
+ from sys import exc_info
20
+ from typing import (
21
+ cast, overload, runtime_checkable, Any, ContextManager, Literal, Protocol,
22
+ )
23
+
24
+ from argtools import has_keyword_arg
25
+ from asynctools import in_async
26
+ from decotools import optional
27
+
28
+
29
+ @runtime_checkable
30
+ class SupportsBool(Protocol):
31
+ def __bool__(self, /) -> bool:
32
+ ...
33
+
34
+
35
+ @dataclass(slots=True, frozen=True)
36
+ class Yield:
37
+ """专供 `run_gen_step_iter`,说明值需要 yield 给用户
38
+ """
39
+ value: Any
40
+
41
+
42
+ @dataclass(slots=True, frozen=True)
43
+ class YieldFrom:
44
+ """专供 `run_gen_step_iter`,说明值需要解包后逐个 yield 给用户
45
+ """
46
+ value: Any
47
+
48
+
49
+ def iter_gen_step_sync[Y](
50
+ gen: Generator[Y, None | Y, Y],
51
+ /,
52
+ ) -> Generator[Y, Any, Any]:
53
+ send = gen.send
54
+ try:
55
+ v = None
56
+ while True:
57
+ yield (v := send(v))
58
+ except StopIteration as e:
59
+ yield e.value
60
+ finally:
61
+ gen.close()
62
+
63
+
64
+ async def iter_gen_step_async[Y](
65
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y],
66
+ /,
67
+ ) -> AsyncGenerator[Y, Any]:
68
+ send = gen.send
69
+ throw = gen.throw
70
+ try:
71
+ r = send(None)
72
+ while True:
73
+ try:
74
+ yield (v := await r)
75
+ except BaseException as e:
76
+ r = throw(e)
77
+ else:
78
+ r = send(v)
79
+ raise AsyncCancelledError
80
+ except StopIteration as e:
81
+ v = e.value
82
+ if isawaitable(v):
83
+ v = await v
84
+ yield v
85
+ finally:
86
+ gen.close()
87
+
88
+
89
+ @overload
90
+ def iter_gen_step[Y](
91
+ gen: Generator[Y, None | Y, Y] | Callable[[], Generator[Y, None | Y, Y]],
92
+ /,
93
+ async_: Literal[False] = False,
94
+ ) -> Generator[Y, Any, Any]:
95
+ ...
96
+ @overload
97
+ def iter_gen_step[Y](
98
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y] | Callable[[], Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y]],
99
+ /,
100
+ async_: Literal[True],
101
+ ) -> AsyncGenerator[Y, Any]:
102
+ ...
103
+ def iter_gen_step[Y](
104
+ gen: Generator[Y, None | Y, Y] | Callable[[], Generator[Y, None | Y, Y]] | Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y] | Callable[[], Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y]],
105
+ /,
106
+ async_: Literal[False, True] = False,
107
+ ) -> Generator[Y, Any, Any] | AsyncGenerator[Y, Any]:
108
+ if not isinstance(gen, Generator):
109
+ gen = gen()
110
+ if async_:
111
+ gen = cast(Generator[Awaitable[Y], None | Y, Awaitable[Y] | Y], gen)
112
+ return iter_gen_step_async(gen)
113
+ else:
114
+ gen = cast(Generator[Y, None | Y, Y], gen)
115
+ return iter_gen_step_sync(gen)
116
+
117
+
118
+ def run_gen_step_sync[Y, T](
119
+ gen: Generator[Y, None | Y, T],
120
+ /,
121
+ running: SupportsBool = True,
122
+ ) -> T:
123
+ send = gen.send
124
+ try:
125
+ v = None
126
+ while running:
127
+ v = send(v)
128
+ raise CancelledError
129
+ except StopIteration as e:
130
+ return e.value
131
+ finally:
132
+ gen.close()
133
+
134
+
135
+ async def run_gen_step_async[Y, T](
136
+ gen: Generator[Awaitable[Y], None | Y, Awaitable[T] | T],
137
+ /,
138
+ running: SupportsBool = True,
139
+ ) -> T:
140
+ send = gen.send
141
+ throw = gen.throw
142
+ try:
143
+ r = send(None)
144
+ while running:
145
+ try:
146
+ v = await r
147
+ except BaseException as e:
148
+ r = throw(e)
149
+ else:
150
+ r = send(v)
151
+ raise AsyncCancelledError
152
+ except StopIteration as e:
153
+ v = e.value
154
+ if isawaitable(v):
155
+ v = await v
156
+ return v
157
+ finally:
158
+ gen.close()
159
+
160
+
161
+ @overload
162
+ def run_gen_step[T](
163
+ gen: Generator[Any, Any, T] | Callable[[], Generator[Any, Any, T]],
164
+ /,
165
+ async_: Literal[False] = False,
166
+ running: SupportsBool = True,
167
+ ) -> T:
168
+ ...
169
+ @overload
170
+ def run_gen_step[T](
171
+ gen: Generator[Awaitable, Any, Awaitable[T] | T] | Callable[[], Generator[Awaitable, Any, Awaitable[T] | T]],
172
+ /,
173
+ async_: Literal[True],
174
+ running: SupportsBool = True,
175
+ ) -> Coroutine[Any, Any, T]:
176
+ ...
177
+ def run_gen_step(
178
+ gen,
179
+ /,
180
+ async_: Literal[False, True] = False,
181
+ running: SupportsBool = True,
182
+ ):
183
+ if not isinstance(gen, Generator):
184
+ gen = gen()
185
+ if async_:
186
+ return run_gen_step_async(gen, running)
187
+ else:
188
+ return run_gen_step_sync(gen, running)
189
+
190
+
191
+ def run_gen_step_iter_sync(
192
+ gen: Generator,
193
+ /,
194
+ running: SupportsBool = True,
195
+ ) -> Iterator:
196
+ send = gen.send
197
+ try:
198
+ v: Any = None
199
+ while running:
200
+ v = send(v)
201
+ if isinstance(v, Yield):
202
+ v = v.value
203
+ yield v
204
+ elif isinstance(v, YieldFrom):
205
+ v = yield from v.value
206
+ raise CancelledError
207
+ except StopIteration as e:
208
+ return e.value
209
+ finally:
210
+ gen.close()
211
+
212
+
213
+ async def run_gen_step_iter_async(
214
+ gen: Generator,
215
+ /,
216
+ running: SupportsBool = True,
217
+ ) -> AsyncIterator:
218
+ send = gen.send
219
+ throw = gen.throw
220
+ try:
221
+ v = send(None)
222
+ while running:
223
+ try:
224
+ if isinstance(v, Yield):
225
+ yield_type = 1
226
+ v = v.value
227
+ elif isinstance(v, YieldFrom):
228
+ yield_type = 2
229
+ v = v.value
230
+ else:
231
+ yield_type = 0
232
+ if isawaitable(v):
233
+ v = await v
234
+ match yield_type:
235
+ case 1:
236
+ yield v
237
+ case 2:
238
+ if isinstance(v, AsyncIterable):
239
+ async for e in v:
240
+ yield e
241
+ else:
242
+ for e in v:
243
+ yield e
244
+ except BaseException as e:
245
+ v = throw(e)
246
+ else:
247
+ v = send(v)
248
+ except StopIteration as e:
249
+ v = e.value
250
+ if isawaitable(v):
251
+ v = await v
252
+ #return v
253
+ finally:
254
+ gen.close()
255
+
256
+
257
+ @overload
258
+ def run_gen_step_iter(
259
+ gen: Generator | Callable[[], Generator],
260
+ /,
261
+ async_: Literal[False] = False,
262
+ running: SupportsBool = True,
263
+ ) -> Iterator:
264
+ ...
265
+ @overload
266
+ def run_gen_step_iter(
267
+ gen: Generator | Callable[[], Generator],
268
+ /,
269
+ async_: Literal[True],
270
+ running: SupportsBool = True,
271
+ ) -> AsyncIterator:
272
+ ...
273
+ def run_gen_step_iter(
274
+ gen: Generator | Callable[[], Generator],
275
+ /,
276
+ async_: Literal[False, True] = False,
277
+ running: SupportsBool = True,
278
+ ) -> Iterator | AsyncIterator:
279
+ if not isinstance(gen, Generator):
280
+ gen = gen()
281
+ if async_:
282
+ return run_gen_step_iter_async(gen, running)
283
+ else:
284
+ return run_gen_step_iter_sync(gen, running)
285
+
286
+
287
+ @optional
288
+ def as_gen_step(
289
+ func: Callable[..., Generator],
290
+ /,
291
+ async_: None | bool = None,
292
+ is_iter: bool = False,
293
+ ):
294
+ has_async_arg = has_keyword_arg(func, "async_")
295
+ if is_iter:
296
+ run: Callable = run_gen_step_iter
297
+ else:
298
+ run = run_gen_step
299
+ def wrapper(*args, async_: None | bool = async_, **kwds):
300
+ if async_ is None:
301
+ async_ = in_async()
302
+ if has_async_arg:
303
+ kwds["async_"] = async_
304
+ return run(func(*args, **kwds), async_)
305
+ return wrapper
306
+
307
+
308
+ @overload
309
+ def with_iter_next[T](
310
+ iterable: Iterable[T],
311
+ /,
312
+ ) -> ContextManager[Callable[[], T]]:
313
+ ...
314
+ @overload
315
+ def with_iter_next[T](
316
+ iterable: AsyncIterable[T],
317
+ /,
318
+ ) -> ContextManager[Callable[[], Awaitable[T]]]:
319
+ ...
320
+ @contextmanager
321
+ def with_iter_next[T](
322
+ iterable: Iterable[T] | AsyncIterable[T],
323
+ /,
324
+ ):
325
+ """包装迭代器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
326
+
327
+ .. code:: python
328
+
329
+ if async_:
330
+ async def process():
331
+ async for e in iterable:
332
+ do_what_you_want(e)
333
+ return process()
334
+ else:
335
+ for e in iterable:
336
+ do_what_you_want(e)
337
+
338
+ 大概相当于
339
+
340
+ .. code:: python
341
+
342
+ def gen_step():
343
+ with with_iter_next(iterable) as do_next:
344
+ while True:
345
+ e = yield do_next()
346
+ do_what_you_want(e)
347
+
348
+ run_gen_step(gen_step, async_)
349
+ """
350
+ if isinstance(iterable, AsyncIterable):
351
+ try:
352
+ yield aiter(iterable).__anext__
353
+ except StopAsyncIteration:
354
+ pass
355
+ else:
356
+ try:
357
+ yield iter(iterable).__next__
358
+ except StopIteration:
359
+ pass
360
+
361
+
362
+ @contextmanager
363
+ def with_lock[T](lock, /):
364
+ """包装锁,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
365
+
366
+ .. code:: python
367
+
368
+ if async_:
369
+ async def process():
370
+ async with lock:
371
+ ...
372
+ return process()
373
+ else:
374
+ with lock:
375
+ ...
376
+
377
+ 大概相当于
378
+
379
+ .. code:: python
380
+
381
+ def gen_step():
382
+ with with_lock(lock) as r:
383
+ yield r
384
+ ...
385
+
386
+ run_gen_step(gen_step, async_)
387
+ """
388
+ try:
389
+ yield lock.acquire()
390
+ finally:
391
+ lock.release()
392
+
393
+
394
+ @overload
395
+ def split_cm[T](
396
+ cm: AbstractContextManager[T],
397
+ /,
398
+ ) -> tuple[Callable[[], T], Callable[[], Any]]:
399
+ ...
400
+ @overload
401
+ def split_cm[T](
402
+ cm: AbstractAsyncContextManager[T],
403
+ /,
404
+ ) -> tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]:
405
+ ...
406
+ def split_cm[T](
407
+ cm: AbstractContextManager[T] | AbstractAsyncContextManager[T],
408
+ /,
409
+ ) -> (
410
+ tuple[Callable[[], T], Callable[[], Any]] |
411
+ tuple[Callable[[], Coroutine[Any, Any, T]], Callable[[], Coroutine]]
412
+ ):
413
+ """拆分上下文管理器,以供 `run_gen_step` 和 `run_gen_step_iter` 使用
414
+
415
+ .. code:: python
416
+
417
+ if async_:
418
+ async def process():
419
+ async with cm as obj:
420
+ do_what_you_want(obj)
421
+ return process()
422
+ else:
423
+ with cm as obj:
424
+ do_what_you_want(obj)
425
+
426
+ 大概相当于
427
+
428
+ .. code:: python
429
+
430
+ def gen_step():
431
+ enter, exit = split_cm(cm)
432
+ obj = yield enter()
433
+ try:
434
+ do_what_you_want(obj)
435
+ finally:
436
+ yield exit()
437
+
438
+ run_gen_step(gen_step, async_)
439
+ """
440
+ if isinstance(cm, AbstractAsyncContextManager):
441
+ enter: Callable = cm.__aenter__
442
+ exit: Callable = cm.__aexit__
443
+ else:
444
+ enter = cm.__enter__
445
+ exit = cm.__exit__
446
+ return enter, lambda: exit(*exc_info())
447
+