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