omlish 0.0.0.dev412__py3-none-any.whl → 0.0.0.dev414__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.
- omlish/__about__.py +3 -3
- omlish/asyncs/bridge.py +1 -1
- omlish/lang/__init__.py +19 -10
- omlish/lang/asyncs.py +7 -14
- omlish/lang/descriptors.py +14 -0
- omlish/lang/maysyncs.py +40 -43
- omlish/lite/maysyncs.py +302 -504
- omlish/subprocesses/maysyncs.py +8 -103
- {omlish-0.0.0.dev412.dist-info → omlish-0.0.0.dev414.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev412.dist-info → omlish-0.0.0.dev414.dist-info}/RECORD +14 -14
- {omlish-0.0.0.dev412.dist-info → omlish-0.0.0.dev414.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev412.dist-info → omlish-0.0.0.dev414.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev412.dist-info → omlish-0.0.0.dev414.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev412.dist-info → omlish-0.0.0.dev414.dist-info}/top_level.txt +0 -0
omlish/lite/maysyncs.py
CHANGED
@@ -4,24 +4,20 @@
|
|
4
4
|
A system for writing a python function once which can then be effectively used in both sync and async contexts -
|
5
5
|
including IO, under any (or no) event loop.
|
6
6
|
|
7
|
-
Where an 'async fn' returns an 'awaitable', a 'maysync fn' returns a 'maywaitable', which
|
8
|
-
|
9
|
-
|
10
|
-
- `def s()` - to be called in sync contexts
|
11
|
-
- `async def a()` - to be called in async and maysync contexts
|
7
|
+
Where an 'async fn' returns an 'awaitable', a 'maysync fn' returns a 'maywaitable', which may be `await`'ed in async or
|
8
|
+
maysync contexts, or passed to `run_maysync` to be run sync.
|
12
9
|
|
13
10
|
For example, a maysync function `m_inc_int(x: int) -> int` would be used as such:
|
14
11
|
|
15
|
-
- `
|
16
|
-
- `assert await m_inc_int(5)
|
12
|
+
- `run_maysync(m_inc_int(5)) == 6` in sync contexts
|
13
|
+
- `assert await m_inc_int(5) == 6` in async and maysync contexts
|
17
14
|
|
18
15
|
Maysync fns may be constructed in two ways: either using `make_maysync`, providing an equivalent pair of sync and async
|
19
|
-
functions, or
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
code is fully efficiently usable in any context.
|
16
|
+
functions, or writing an 'maysync flavored' async python function. 'Maysync flavored' async functions are ones which
|
17
|
+
only call (await on) other maysync functions - the maysync machinery will ultimately call the appropriate 'leaf' sync or
|
18
|
+
async functions. Being regular python functions they are free to call whatever they like - for example doing sync IO -
|
19
|
+
but the point is to, ideally, route all IO through maysync functions such that the maysync code is fully efficiently
|
20
|
+
usable in any context.
|
25
21
|
|
26
22
|
Internally, it's not really correct to say that there is 'no event loop' in the maysync context - rather, each
|
27
23
|
'entrypoint' call to a maysync fn runs within its own tiny event loop.
|
@@ -37,9 +33,13 @@ TODO:
|
|
37
33
|
- works down to 3.8
|
38
34
|
- make_maysync_from_sync can run with asyncio.run_in_thread
|
39
35
|
- make_maysync_from_async can run with asyncio.run_soon
|
36
|
+
|
37
|
+
TODO OVERHAUL:
|
38
|
+
- no more sync/async context, just one Context, and it means sync
|
39
|
+
- make FpMaywaitable *not reusable*
|
40
|
+
- `cannot reuse already awaited coroutine`
|
40
41
|
"""
|
41
42
|
import abc
|
42
|
-
import functools
|
43
43
|
import inspect
|
44
44
|
import sys
|
45
45
|
import threading
|
@@ -47,16 +47,10 @@ import typing as ta
|
|
47
47
|
|
48
48
|
|
49
49
|
T = ta.TypeVar('T')
|
50
|
-
T_co = ta.TypeVar('T_co', covariant=True)
|
51
|
-
|
52
50
|
O = ta.TypeVar('O')
|
53
|
-
O_co = ta.TypeVar('O_co', covariant=True)
|
54
|
-
|
55
51
|
I = ta.TypeVar('I')
|
56
|
-
I_contra = ta.TypeVar('I_contra', contravariant=True)
|
57
52
|
|
58
53
|
_MaysyncX = ta.TypeVar('_MaysyncX')
|
59
|
-
|
60
54
|
_MaysyncRS = ta.TypeVar('_MaysyncRS')
|
61
55
|
_MaysyncRA = ta.TypeVar('_MaysyncRA')
|
62
56
|
|
@@ -64,323 +58,214 @@ _MaysyncRA = ta.TypeVar('_MaysyncRA')
|
|
64
58
|
##
|
65
59
|
|
66
60
|
|
67
|
-
class
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
def s(self) -> T_co:
|
79
|
-
...
|
80
|
-
|
81
|
-
def a(self) -> ta.Awaitable[T_co]:
|
82
|
-
...
|
83
|
-
|
84
|
-
|
85
|
-
class MaysyncGenerator(ta.Protocol[O_co, I_contra]):
|
86
|
-
"""
|
87
|
-
The maysync version of `AsyncGenerator[O, I]`. Generator maysync functions return a `MaysyncGenerator`, with the
|
88
|
-
following methods:
|
89
|
-
|
90
|
-
- `def s()` - to be called in sync contexts
|
91
|
-
- `async def a()` - to be called in async and maysync contexts
|
92
|
-
|
93
|
-
Only the proper method should be called in each context.
|
94
|
-
"""
|
95
|
-
|
96
|
-
def s(self) -> ta.Generator[O_co, I_contra, None]:
|
97
|
-
...
|
98
|
-
|
99
|
-
def a(self) -> ta.AsyncGenerator[O_co, I_contra]:
|
100
|
-
...
|
101
|
-
|
102
|
-
|
103
|
-
# The maysync equivalent of an async function
|
104
|
-
MaysyncFn = ta.Callable[..., Maywaitable[T]] # ta.TypeAlias # omlish-amalg-typing-no-move
|
105
|
-
|
106
|
-
# The maysync equivalent of an async generator function
|
107
|
-
MaysyncGeneratorFn = ta.Callable[..., MaysyncGenerator[O, I]] # ta.TypeAlias # omlish-amalg-typing-no-move
|
108
|
-
|
109
|
-
|
110
|
-
class Maysync_(abc.ABC): # noqa
|
111
|
-
"""
|
112
|
-
Abstract base class for maysync objects - either MaysyncFn's or MaysyncGeneratorFn's.
|
61
|
+
class AnyMaysyncFn(abc.ABC, ta.Generic[_MaysyncRS, _MaysyncRA]): # noqa
|
62
|
+
def __init__(
|
63
|
+
self,
|
64
|
+
s: ta.Callable[..., _MaysyncRS],
|
65
|
+
a: ta.Callable[..., _MaysyncRA],
|
66
|
+
) -> None:
|
67
|
+
if s is None:
|
68
|
+
raise TypeError(s)
|
69
|
+
if a is None:
|
70
|
+
raise TypeError(a)
|
71
|
+
self._s, self._a = s, a
|
113
72
|
|
114
|
-
|
115
|
-
|
116
|
-
"""
|
73
|
+
def __repr__(self) -> str:
|
74
|
+
return f'{self.__class__.__name__}@{id(self):x}({self._s!r}, {self._a!r})'
|
117
75
|
|
118
76
|
def __init_subclass__(cls, **kwargs):
|
119
|
-
if Maysync_ in cls.__bases__ and abc.ABC not in cls.__bases__:
|
120
|
-
raise TypeError(cls)
|
121
|
-
|
122
77
|
super().__init_subclass__(**kwargs)
|
123
78
|
|
79
|
+
if hasattr(inspect, 'markcoroutinefunction'):
|
80
|
+
inspect.markcoroutinefunction(cls.__call__)
|
81
|
+
|
124
82
|
class FnPair(ta.NamedTuple):
|
125
83
|
s: ta.Callable[..., ta.Any]
|
126
84
|
a: ta.Callable[..., ta.Any]
|
127
85
|
|
128
|
-
@abc.abstractmethod
|
129
86
|
def fn_pair(self) -> ta.Optional[FnPair]:
|
130
|
-
|
131
|
-
|
132
|
-
|
87
|
+
return AnyMaysyncFn.FnPair(
|
88
|
+
self._s,
|
89
|
+
self._a,
|
90
|
+
)
|
133
91
|
|
134
|
-
|
135
|
-
|
136
|
-
|
92
|
+
def __get__(self, instance, owner=None):
|
93
|
+
return self.__class__(
|
94
|
+
self._s.__get__(instance, owner), # noqa
|
95
|
+
self._a.__get__(instance, owner), # noqa
|
96
|
+
)
|
137
97
|
|
138
98
|
@abc.abstractmethod
|
139
99
|
def __call__(self, *args, **kwargs):
|
140
100
|
raise NotImplementedError
|
141
101
|
|
142
102
|
|
143
|
-
class
|
144
|
-
|
145
|
-
def cast(self) -> MaysyncFn[T]:
|
146
|
-
return ta.cast('MaysyncFn[T]', self)
|
103
|
+
class MaywaitableAlreadyConsumedError(Exception):
|
104
|
+
pass
|
147
105
|
|
148
106
|
|
149
|
-
class
|
107
|
+
class AnyMaywaitable(abc.ABC, ta.Generic[_MaysyncX]):
|
150
108
|
@ta.final
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
class _MaysyncThreadLocal(threading.local):
|
159
|
-
context: ta.Optional['_MaysyncContext'] = None
|
109
|
+
def __init__(
|
110
|
+
self,
|
111
|
+
x: _MaysyncX,
|
112
|
+
args: ta.Tuple[ta.Any, ...],
|
113
|
+
kwargs: ta.Mapping[str, ta.Any],
|
114
|
+
) -> None:
|
115
|
+
self._x, self._args, self._kwargs = x, args, kwargs
|
160
116
|
|
117
|
+
def __repr__(self) -> str:
|
118
|
+
return f'{self.__class__.__name__}@{id(self):x}({self._x!r})'
|
161
119
|
|
162
|
-
class _MaysyncContext(abc.ABC):
|
163
|
-
mode: ta.ClassVar[ta.Literal['s', 'a']]
|
164
120
|
|
165
|
-
|
166
|
-
def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
|
167
|
-
raise NotImplementedError
|
121
|
+
##
|
168
122
|
|
169
123
|
|
170
124
|
@ta.final
|
171
|
-
class
|
172
|
-
|
173
|
-
|
174
|
-
def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
|
175
|
-
prev = _MaysyncThreadLocal.context
|
176
|
-
_MaysyncThreadLocal.context = self
|
125
|
+
class MaysyncFn(AnyMaysyncFn[T, ta.Awaitable[T]], ta.Generic[T]): # noqa
|
126
|
+
def __call__(self, *args, **kwargs):
|
127
|
+
return Maywaitable(self, args, kwargs)
|
177
128
|
|
178
|
-
ph: ta.Any = sys.get_asyncgen_hooks()
|
179
|
-
if ph.firstiter is not None or ph.finalizer is not None:
|
180
|
-
sys.set_asyncgen_hooks(firstiter=None, finalizer=None)
|
181
|
-
else:
|
182
|
-
ph = None
|
183
129
|
|
184
|
-
|
185
|
-
|
130
|
+
@ta.final
|
131
|
+
class Maywaitable(AnyMaywaitable[MaysyncFn[T]], ta.Awaitable[T]):
|
132
|
+
_d: bool = False
|
186
133
|
|
187
|
-
|
188
|
-
|
189
|
-
|
134
|
+
def __run_maysync__(self) -> T:
|
135
|
+
if self._d or self._aw is not None:
|
136
|
+
raise MaywaitableAlreadyConsumedError(self)
|
190
137
|
|
191
|
-
|
138
|
+
self._d = True
|
139
|
+
return self._x._s(*self._args, **self._kwargs) # noqa
|
192
140
|
|
141
|
+
#
|
193
142
|
|
194
|
-
|
195
|
-
class _AsyncMaysyncContext(_MaysyncContext):
|
196
|
-
mode: ta.ClassVar[ta.Literal['a']] = 'a'
|
143
|
+
_aw: ta.Optional[ta.Awaitable[T]] = None
|
197
144
|
|
198
|
-
def
|
199
|
-
|
200
|
-
|
145
|
+
def __await__(self) -> ta.Generator[ta.Any, None, T]:
|
146
|
+
if (aw := self._aw) is None:
|
147
|
+
if self._d:
|
148
|
+
raise MaywaitableAlreadyConsumedError(self)
|
201
149
|
|
202
|
-
|
203
|
-
|
150
|
+
if _MaysyncThreadLocal.run_context is None:
|
151
|
+
aw = self._aw = self._x._a(*self._args, **self._kwargs) # noqa
|
152
|
+
else:
|
153
|
+
aw = self._aw = _MaysyncFuture(self)
|
204
154
|
|
205
|
-
|
206
|
-
_MaysyncThreadLocal.context = prev
|
155
|
+
return aw.__await__()
|
207
156
|
|
208
157
|
|
209
158
|
##
|
210
159
|
|
211
160
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
)
|
216
|
-
"""Abstract base class for the maysync versions of `Awaitable[T]` and `AsyncGenerator[O, I]`."""
|
161
|
+
@ta.final
|
162
|
+
class MaysyncGeneratorFn(AnyMaysyncFn, ta.Generic[O, I]): # noqa
|
163
|
+
def __call__(self, *args, **kwargs):
|
164
|
+
return MaysyncGenerator(self, args, kwargs)
|
217
165
|
|
218
|
-
@ta.final
|
219
|
-
def __init__(
|
220
|
-
self,
|
221
|
-
x: _MaysyncX,
|
222
|
-
args: ta.Tuple[ta.Any, ...],
|
223
|
-
kwargs: ta.Mapping[str, ta.Any],
|
224
|
-
) -> None:
|
225
|
-
self._x, self._args, self._kwargs = x, args, kwargs
|
226
166
|
|
227
|
-
|
228
|
-
|
167
|
+
@ta.final
|
168
|
+
class MaysyncGenerator(AnyMaywaitable[MaysyncGeneratorFn[O, I]], ta.AsyncGenerator[O, I]):
|
169
|
+
def _make_sg(self) -> ta.Generator[O, I, None]:
|
170
|
+
return self._x._s(*self._args, **self._kwargs) # noqa
|
229
171
|
|
172
|
+
_sg: ta.Optional[ta.Generator[O, I, None]] = None
|
230
173
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
)
|
236
|
-
|
174
|
+
def _get_sg(self) -> ta.Generator[O, I, None]:
|
175
|
+
if (sg := self._sg) is None:
|
176
|
+
if self._ag is not None:
|
177
|
+
raise MaywaitableAlreadyConsumedError(self)
|
178
|
+
sg = self._sg = self._make_sg()
|
179
|
+
return sg
|
237
180
|
|
181
|
+
def __run_maysync__(self) -> ta.Generator[O, I, None]:
|
182
|
+
return (self._sg if self._sg is not None else self._get_sg())
|
238
183
|
|
239
|
-
|
240
|
-
_MaywaitableLike[_MaysyncX],
|
241
|
-
abc.ABC,
|
242
|
-
ta.Generic[_MaysyncX, O, I],
|
243
|
-
):
|
244
|
-
pass
|
184
|
+
#
|
245
185
|
|
186
|
+
def _make_ag(self) -> ta.AsyncGenerator[O, I]:
|
187
|
+
if _MaysyncThreadLocal.run_context is None:
|
188
|
+
return self._x._a(*self._args, **self._kwargs) # noqa
|
246
189
|
|
247
|
-
|
190
|
+
async def inner():
|
191
|
+
g = self._x._s(*self._args, **self._kwargs) # noqa
|
248
192
|
|
193
|
+
i: ta.Any = None
|
194
|
+
e: ta.Any = None
|
249
195
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
)
|
254
|
-
|
196
|
+
while True:
|
197
|
+
try:
|
198
|
+
if e is not None:
|
199
|
+
o = g.throw(e)
|
200
|
+
else:
|
201
|
+
o = g.send(i)
|
202
|
+
except StopIteration as ex:
|
203
|
+
if ex.value is not None:
|
204
|
+
raise TypeError(ex) from None
|
205
|
+
return
|
255
206
|
|
256
|
-
|
257
|
-
|
258
|
-
s: ta.Callable[..., _MaysyncRS],
|
259
|
-
a: ta.Callable[..., _MaysyncRA],
|
260
|
-
) -> None:
|
261
|
-
if s is None:
|
262
|
-
raise TypeError(s)
|
263
|
-
if a is None:
|
264
|
-
raise TypeError(a)
|
265
|
-
self._s = s
|
266
|
-
self._a = a
|
207
|
+
i = None
|
208
|
+
e = None
|
267
209
|
|
268
|
-
|
269
|
-
|
210
|
+
try:
|
211
|
+
i = yield o
|
212
|
+
except StopIteration as ex: # noqa
|
213
|
+
raise NotImplementedError # noqa
|
214
|
+
except StopAsyncIteration as ex: # noqa
|
215
|
+
raise NotImplementedError # noqa
|
216
|
+
except BaseException as ex: # noqa
|
217
|
+
e = ex
|
270
218
|
|
271
|
-
|
272
|
-
return Maysync_.FnPair(
|
273
|
-
self._s,
|
274
|
-
self._a,
|
275
|
-
)
|
219
|
+
return inner()
|
276
220
|
|
277
|
-
|
278
|
-
return self.__class__(
|
279
|
-
self._s.__get__(instance, owner), # noqa
|
280
|
-
self._a.__get__(instance, owner), # noqa
|
281
|
-
)
|
221
|
+
_ag: ta.Optional[ta.AsyncGenerator[O, I]] = None
|
282
222
|
|
223
|
+
def _get_ag(self) -> ta.AsyncGenerator[O, I]:
|
224
|
+
if (ag := self._ag) is None:
|
225
|
+
if self._sg is not None:
|
226
|
+
raise MaywaitableAlreadyConsumedError(self)
|
227
|
+
ag = self._ag = self._make_ag()
|
228
|
+
return ag
|
283
229
|
|
284
|
-
|
285
|
-
|
286
|
-
_FpMaysyncFnLike[T, ta.Awaitable[T]],
|
287
|
-
MaysyncFn_[T],
|
288
|
-
ta.Generic[T],
|
289
|
-
):
|
290
|
-
def __call__(self, *args, **kwargs):
|
291
|
-
return _FpMaywaitable(self, args, kwargs)
|
230
|
+
def __anext__(self):
|
231
|
+
return (self._ag if self._ag is not None else self._get_ag()).__anext__()
|
292
232
|
|
233
|
+
def asend(self, value: I):
|
234
|
+
return (self._ag if self._ag is not None else self._get_ag()).asend(value)
|
293
235
|
|
294
|
-
|
295
|
-
|
296
|
-
_Maywaitable[_FpMaysyncFn[T], T],
|
297
|
-
):
|
298
|
-
def s(self) -> T:
|
299
|
-
return self._x._s(*self._args, **self._kwargs) # noqa
|
236
|
+
def athrow(self, typ, val=None, tb=None):
|
237
|
+
return (self._ag if self._ag is not None else self._get_ag()).athrow(typ, val, tb)
|
300
238
|
|
301
|
-
def
|
302
|
-
if
|
303
|
-
return _MaysyncFuture(self)
|
239
|
+
def aclose(self):
|
240
|
+
return (self._ag if self._ag is not None else self._get_ag()).aclose()
|
304
241
|
|
305
|
-
|
242
|
+
|
243
|
+
##
|
306
244
|
|
307
245
|
|
308
246
|
def make_maysync_fn(
|
309
247
|
s: ta.Callable[..., T],
|
310
248
|
a: ta.Callable[..., ta.Awaitable[T]],
|
311
|
-
) ->
|
249
|
+
) -> ta.Callable[..., ta.Awaitable[T]]:
|
312
250
|
"""Constructs a MaysyncFn from a (sync, async) function pair."""
|
313
251
|
|
314
|
-
return
|
315
|
-
|
316
|
-
|
317
|
-
@ta.final
|
318
|
-
class _FpMaysyncGeneratorFn(
|
319
|
-
_FpMaysyncFnLike[ta.Generator[O, I, None], ta.AsyncGenerator[O, I]],
|
320
|
-
MaysyncGeneratorFn_[O, I],
|
321
|
-
ta.Generic[O, I],
|
322
|
-
):
|
323
|
-
def __call__(self, *args, **kwargs):
|
324
|
-
return _FpMaysyncGenerator(self, args, kwargs)
|
325
|
-
|
326
|
-
|
327
|
-
@ta.final
|
328
|
-
class _FpMaysyncGenerator(
|
329
|
-
_MaysyncGenerator[_FpMaysyncGeneratorFn[O, I], O, I],
|
330
|
-
):
|
331
|
-
def s(self) -> ta.Generator[O, I, None]:
|
332
|
-
return self._x._s(*self._args, **self._kwargs) # noqa
|
333
|
-
|
334
|
-
def a(self) -> ta.AsyncGenerator[O, I]:
|
335
|
-
if (ctx := _MaysyncThreadLocal.context) is not None and ctx.mode == 's':
|
336
|
-
async def inner():
|
337
|
-
g = self._x._s(*self._args, **self._kwargs) # noqa
|
338
|
-
|
339
|
-
i: ta.Any = None
|
340
|
-
e: ta.Any = None
|
341
|
-
|
342
|
-
while True:
|
343
|
-
try:
|
344
|
-
if e is not None:
|
345
|
-
o = g.throw(e)
|
346
|
-
else:
|
347
|
-
o = g.send(i)
|
348
|
-
except StopIteration as ex:
|
349
|
-
if ex.value is not None:
|
350
|
-
raise TypeError(ex) from None
|
351
|
-
return
|
352
|
-
|
353
|
-
i = None
|
354
|
-
e = None
|
355
|
-
|
356
|
-
try:
|
357
|
-
i = yield o
|
358
|
-
except StopIteration as ex: # noqa
|
359
|
-
raise NotImplementedError # noqa
|
360
|
-
except StopAsyncIteration as ex: # noqa
|
361
|
-
raise NotImplementedError # noqa
|
362
|
-
except BaseException as ex: # noqa
|
363
|
-
e = ex
|
364
|
-
|
365
|
-
return inner()
|
366
|
-
|
367
|
-
return self._x._a(*self._args, **self._kwargs) # noqa
|
252
|
+
return MaysyncFn(s, a)
|
368
253
|
|
369
254
|
|
370
255
|
def make_maysync_generator_fn(
|
371
256
|
s: ta.Callable[..., ta.Generator[O, I, None]],
|
372
257
|
a: ta.Callable[..., ta.AsyncGenerator[O, I]],
|
373
|
-
) ->
|
258
|
+
) -> ta.Callable[..., ta.AsyncGenerator[O, I]]:
|
374
259
|
"""Constructs a MaysyncGeneratorFn from a (sync, async) generator function pair."""
|
375
260
|
|
376
|
-
return
|
261
|
+
return MaysyncGeneratorFn(s, a)
|
377
262
|
|
378
263
|
|
379
264
|
@ta.overload
|
380
265
|
def make_maysync(
|
381
266
|
s: ta.Callable[..., T],
|
382
267
|
a: ta.Callable[..., ta.Awaitable[T]],
|
383
|
-
) ->
|
268
|
+
) -> ta.Callable[..., ta.Awaitable[T]]:
|
384
269
|
...
|
385
270
|
|
386
271
|
|
@@ -388,7 +273,7 @@ def make_maysync(
|
|
388
273
|
def make_maysync(
|
389
274
|
s: ta.Callable[..., ta.Generator[O, I, None]],
|
390
275
|
a: ta.Callable[..., ta.AsyncGenerator[O, I]],
|
391
|
-
) ->
|
276
|
+
) -> ta.Callable[..., ta.AsyncGenerator[O, I]]:
|
392
277
|
...
|
393
278
|
|
394
279
|
|
@@ -399,63 +284,121 @@ def make_maysync(s, a):
|
|
399
284
|
"""
|
400
285
|
|
401
286
|
if inspect.isasyncgenfunction(a):
|
402
|
-
return
|
287
|
+
return MaysyncGeneratorFn(s, a)
|
403
288
|
else:
|
404
|
-
return
|
289
|
+
return MaysyncFn(s, a)
|
290
|
+
|
291
|
+
|
292
|
+
##
|
293
|
+
|
294
|
+
|
295
|
+
@ta.final
|
296
|
+
class _MaysyncThreadLocal(threading.local):
|
297
|
+
run_context: ta.Optional['_MaysyncRunContext'] = None
|
298
|
+
|
299
|
+
|
300
|
+
@ta.final
|
301
|
+
class _MaysyncRunContext:
|
302
|
+
def __init__(self, root: ta.Any) -> None:
|
303
|
+
self._root = root
|
304
|
+
|
305
|
+
def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
|
306
|
+
prev = _MaysyncThreadLocal.run_context
|
307
|
+
_MaysyncThreadLocal.run_context = self
|
308
|
+
|
309
|
+
ph: ta.Any = sys.get_asyncgen_hooks()
|
310
|
+
if ph.firstiter is not None or ph.finalizer is not None:
|
311
|
+
sys.set_asyncgen_hooks(firstiter=None, finalizer=None)
|
312
|
+
else:
|
313
|
+
ph = None
|
314
|
+
|
315
|
+
try:
|
316
|
+
return fn(*args, **kwargs)
|
317
|
+
|
318
|
+
finally:
|
319
|
+
if ph is not None:
|
320
|
+
sys.set_asyncgen_hooks(*ph)
|
321
|
+
|
322
|
+
if _MaysyncThreadLocal.run_context is not self:
|
323
|
+
raise RuntimeError
|
324
|
+
_MaysyncThreadLocal.run_context = prev
|
325
|
+
|
326
|
+
|
327
|
+
def is_running_maysync() -> bool:
|
328
|
+
return _MaysyncThreadLocal.run_context is not None
|
405
329
|
|
406
330
|
|
407
331
|
##
|
408
332
|
|
409
333
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
334
|
+
@ta.final
|
335
|
+
class _MaysyncFutureNotAwaitedError(RuntimeError):
|
336
|
+
pass
|
337
|
+
|
338
|
+
|
339
|
+
@ta.final
|
340
|
+
class _MaysyncFuture(ta.Generic[T]):
|
414
341
|
def __init__(
|
415
342
|
self,
|
416
|
-
|
343
|
+
x: Maywaitable[T],
|
417
344
|
) -> None:
|
418
|
-
self.
|
419
|
-
|
420
|
-
functools.update_wrapper(self, mg, updated=())
|
345
|
+
self._x = x
|
421
346
|
|
422
347
|
def __repr__(self) -> str:
|
423
|
-
return f'{self.__class__.__name__}@{id(self):x}({self.
|
348
|
+
return f'{self.__class__.__name__}@{id(self):x}({self._x!r}, done={self.done!r})'
|
424
349
|
|
425
|
-
|
426
|
-
|
350
|
+
done: bool = False
|
351
|
+
result: T
|
352
|
+
error: ta.Optional[BaseException] = None
|
427
353
|
|
428
|
-
def
|
429
|
-
|
430
|
-
self
|
431
|
-
|
354
|
+
def __await__(self) -> ta.Generator['_MaysyncFuture', None, T]:
|
355
|
+
if not self.done:
|
356
|
+
yield self
|
357
|
+
if not self.done:
|
358
|
+
raise _MaysyncFutureNotAwaitedError
|
359
|
+
if self.error is not None:
|
360
|
+
raise self.error
|
361
|
+
else:
|
362
|
+
return self.result
|
432
363
|
|
433
|
-
|
434
|
-
|
435
|
-
|
364
|
+
def s(self) -> None:
|
365
|
+
if self.done:
|
366
|
+
return
|
436
367
|
|
368
|
+
try:
|
369
|
+
self.result = self._x._x._s(*self._x._args, **self._x._kwargs) # noqa
|
370
|
+
except BaseException as ex: # noqa
|
371
|
+
self.error = ex
|
372
|
+
self.done = True
|
437
373
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
)
|
444
|
-
|
445
|
-
|
374
|
+
async def a(self) -> None:
|
375
|
+
if self.done:
|
376
|
+
return
|
377
|
+
|
378
|
+
try:
|
379
|
+
self.result = await self._x._x._a(*self._x._args, **self._x._kwargs) # noqa
|
380
|
+
except BaseException as ex: # noqa
|
381
|
+
self.error = ex
|
382
|
+
self.done = True
|
383
|
+
|
384
|
+
|
385
|
+
##
|
446
386
|
|
447
387
|
|
448
388
|
@ta.final
|
449
|
-
class
|
450
|
-
def __init__(
|
451
|
-
|
452
|
-
|
389
|
+
class _MaysyncDriver:
|
390
|
+
def __init__(
|
391
|
+
self,
|
392
|
+
ctx: _MaysyncRunContext,
|
393
|
+
coro: ta.Any,
|
394
|
+
) -> None:
|
395
|
+
self.ctx, self.coro = ctx, coro
|
453
396
|
|
454
397
|
value: ta.Any
|
455
398
|
|
456
399
|
def __iter__(self) -> ta.Generator['_MaysyncFuture', None, None]:
|
457
400
|
try:
|
458
|
-
a = self.
|
401
|
+
a = self.coro.__await__()
|
459
402
|
try:
|
460
403
|
g = iter(a)
|
461
404
|
try:
|
@@ -480,260 +423,115 @@ class _MgMaysyncDriver:
|
|
480
423
|
self.ctx.run(a.close)
|
481
424
|
|
482
425
|
finally:
|
483
|
-
self.ctx.run(self.
|
426
|
+
self.ctx.run(self.coro.close)
|
484
427
|
|
485
428
|
|
486
|
-
|
487
|
-
class _MgMaywaitable(
|
488
|
-
_Maywaitable[_MgMaysyncFn[T], T],
|
489
|
-
):
|
490
|
-
def _driver(self, ctx: _MaysyncContext) -> _MgMaysyncDriver:
|
491
|
-
return _MgMaysyncDriver(ctx, self._x._mg(*self._args, **self._kwargs)) # noqa
|
429
|
+
##
|
492
430
|
|
493
|
-
def s(self) -> T:
|
494
|
-
for f in (drv := self._driver(_SyncMaysyncContext())):
|
495
|
-
f.s()
|
496
|
-
del f
|
497
431
|
|
498
|
-
|
432
|
+
def run_maysync_fn(
|
433
|
+
m: ta.Awaitable[T],
|
434
|
+
) -> T:
|
435
|
+
if isinstance(m, Maywaitable):
|
436
|
+
return m.__run_maysync__()
|
499
437
|
|
500
|
-
|
501
|
-
|
502
|
-
|
438
|
+
for f in (drv := _MaysyncDriver(_MaysyncRunContext(m), m)):
|
439
|
+
f.s()
|
440
|
+
del f
|
503
441
|
|
504
|
-
|
505
|
-
for f in (drv := self._driver(_AsyncMaysyncContext())):
|
506
|
-
await f.a()
|
507
|
-
del f
|
442
|
+
return drv.value
|
508
443
|
|
509
|
-
return drv.value
|
510
444
|
|
511
|
-
|
445
|
+
def run_maysync_generator_fn(
|
446
|
+
m: ta.AsyncGenerator[O, I],
|
447
|
+
) -> ta.Generator[O, I, None]:
|
448
|
+
if isinstance(m, MaysyncGenerator):
|
449
|
+
return m.__run_maysync__()
|
512
450
|
|
451
|
+
def inner():
|
452
|
+
ctx = _MaysyncRunContext(m)
|
513
453
|
|
514
|
-
@ta.final
|
515
|
-
class _MgMaysyncGeneratorFn(
|
516
|
-
_MgMaysyncFnLike[ta.AsyncGenerator[O, I]],
|
517
|
-
MaysyncGeneratorFn_[O, I],
|
518
|
-
ta.Generic[O, I],
|
519
|
-
):
|
520
|
-
def __call__(self, *args, **kwargs):
|
521
|
-
return _MgMaysyncGenerator(self, args, kwargs)
|
522
|
-
|
523
|
-
|
524
|
-
@ta.final
|
525
|
-
class _MgMaysyncGeneratorDriver:
|
526
|
-
def __init__(self, ctx: _MaysyncContext, ag: ta.Any) -> None:
|
527
|
-
self.ctx = ctx
|
528
|
-
self.ag = ag
|
529
|
-
|
530
|
-
def __iter__(self) -> ta.Generator[
|
531
|
-
ta.Union[
|
532
|
-
ta.Tuple[ta.Literal['f'], '_MaysyncFuture'],
|
533
|
-
ta.Tuple[ta.Literal['o'], ta.Any],
|
534
|
-
],
|
535
|
-
ta.Union[
|
536
|
-
ta.Tuple[ta.Any, BaseException],
|
537
|
-
None,
|
538
|
-
],
|
539
|
-
None,
|
540
|
-
]:
|
541
454
|
try:
|
542
|
-
|
543
|
-
|
544
|
-
i: ta.Any = None
|
545
|
-
e: ta.Any = None
|
455
|
+
i: ta.Any = None
|
456
|
+
e: ta.Any = None
|
546
457
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
458
|
+
while True:
|
459
|
+
if e is not None:
|
460
|
+
coro = m.athrow(e)
|
461
|
+
else:
|
462
|
+
coro = m.asend(i)
|
552
463
|
|
553
|
-
|
554
|
-
|
464
|
+
i = None
|
465
|
+
e = None
|
555
466
|
|
556
|
-
|
557
|
-
|
558
|
-
|
467
|
+
drv = _MaysyncDriver(ctx, coro)
|
468
|
+
di = iter(drv)
|
469
|
+
try:
|
470
|
+
while True:
|
471
|
+
try:
|
472
|
+
f = next(di)
|
473
|
+
except StopAsyncIteration:
|
474
|
+
return
|
475
|
+
except StopIteration:
|
476
|
+
break
|
559
477
|
|
478
|
+
f.s()
|
560
479
|
del f
|
561
480
|
|
562
|
-
|
563
|
-
|
564
|
-
finally:
|
565
|
-
if ai is not self.ag:
|
566
|
-
for f in _MgMaysyncDriver(self.ctx, ai.aclose()):
|
567
|
-
yield ('f', f)
|
568
|
-
|
569
|
-
finally:
|
570
|
-
for f in _MgMaysyncDriver(self.ctx, self.ag.aclose()):
|
571
|
-
yield ('f', f)
|
572
|
-
|
573
|
-
|
574
|
-
@ta.final
|
575
|
-
class _MgMaysyncGenerator(
|
576
|
-
_MaysyncGenerator[_MgMaysyncGeneratorFn[O, I], O, I],
|
577
|
-
):
|
578
|
-
def _driver(self, ctx: _MaysyncContext) -> _MgMaysyncGeneratorDriver:
|
579
|
-
return _MgMaysyncGeneratorDriver(ctx, self._x._mg(*self._args, **self._kwargs)) # noqa
|
580
|
-
|
581
|
-
def s(self) -> ta.Generator[O, I, None]:
|
582
|
-
di = iter(self._driver(_SyncMaysyncContext()))
|
583
|
-
|
584
|
-
ie: ta.Any = None
|
585
|
-
|
586
|
-
while True:
|
587
|
-
try:
|
588
|
-
t, x = di.send(ie)
|
589
|
-
except StopAsyncIteration:
|
590
|
-
return
|
591
|
-
except StopIteration:
|
592
|
-
raise RuntimeError from None
|
593
|
-
|
594
|
-
ie = None
|
595
|
-
|
596
|
-
if t == 'f':
|
597
|
-
x.s()
|
481
|
+
finally:
|
482
|
+
di.close()
|
598
483
|
|
599
|
-
|
484
|
+
o = drv.value
|
600
485
|
try:
|
601
|
-
|
486
|
+
i = yield o
|
602
487
|
except BaseException as ex: # noqa
|
603
|
-
|
604
|
-
|
605
|
-
else:
|
606
|
-
raise RuntimeError((t, x))
|
607
|
-
|
608
|
-
del x
|
609
|
-
|
610
|
-
def a(self) -> ta.AsyncGenerator[O, I]:
|
611
|
-
if _MaysyncThreadLocal.context is not None:
|
612
|
-
return self._x._mg(*self._args, **self._kwargs) # noqa
|
488
|
+
e = ex
|
613
489
|
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
ie: ta.Any = None
|
618
|
-
|
619
|
-
while True:
|
620
|
-
try:
|
621
|
-
t, x = di.send(ie)
|
622
|
-
except StopAsyncIteration:
|
623
|
-
return
|
624
|
-
except StopIteration:
|
625
|
-
raise RuntimeError from None
|
626
|
-
|
627
|
-
ie = None
|
628
|
-
|
629
|
-
if t == 'f':
|
630
|
-
await x.a()
|
631
|
-
|
632
|
-
elif t == 'o':
|
633
|
-
try:
|
634
|
-
ie = ((yield x), None)
|
635
|
-
except BaseException as ex: # noqa
|
636
|
-
ie = (None, ex)
|
637
|
-
|
638
|
-
else:
|
639
|
-
raise RuntimeError((t, x))
|
640
|
-
|
641
|
-
del x
|
642
|
-
|
643
|
-
return inner()
|
644
|
-
|
645
|
-
|
646
|
-
def maysync_fn(
|
647
|
-
m: ta.Callable[..., ta.Awaitable[T]],
|
648
|
-
) -> MaysyncFn[T]:
|
649
|
-
"""Constructs a MaysyncFn from a 'maysync flavored' async function."""
|
650
|
-
|
651
|
-
return _MgMaysyncFn(m)
|
652
|
-
|
653
|
-
|
654
|
-
def maysync_generator_fn(
|
655
|
-
m: ta.Callable[..., ta.AsyncGenerator[O, I]],
|
656
|
-
) -> MaysyncGeneratorFn[O, I]:
|
657
|
-
"""Constructs a MaysyncGeneratorFn from a 'maysync flavored' async generator function."""
|
490
|
+
finally:
|
491
|
+
for f in _MaysyncDriver(ctx, m.aclose()):
|
492
|
+
f.s()
|
658
493
|
|
659
|
-
return
|
494
|
+
return inner()
|
660
495
|
|
661
496
|
|
662
497
|
@ta.overload
|
663
|
-
def
|
664
|
-
m: ta.
|
665
|
-
) ->
|
498
|
+
def run_maysync(
|
499
|
+
m: ta.Awaitable[T],
|
500
|
+
) -> T:
|
666
501
|
...
|
667
502
|
|
668
503
|
|
669
504
|
@ta.overload
|
670
|
-
def
|
671
|
-
m: ta.
|
672
|
-
) ->
|
505
|
+
def run_maysync(
|
506
|
+
m: ta.AsyncGenerator[O, I],
|
507
|
+
) -> ta.Generator[O, I, None]:
|
673
508
|
...
|
674
509
|
|
675
510
|
|
676
|
-
def
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
if inspect.isasyncgenfunction(m):
|
683
|
-
return maysync_generator_fn(m)
|
511
|
+
def run_maysync(m):
|
512
|
+
if hasattr(m, '__await__'):
|
513
|
+
return run_maysync_fn(m)
|
514
|
+
elif hasattr(m, '__aiter__'):
|
515
|
+
return run_maysync_generator_fn(m)
|
684
516
|
else:
|
685
|
-
|
517
|
+
raise TypeError(m)
|
686
518
|
|
687
519
|
|
688
520
|
##
|
689
521
|
|
690
522
|
|
691
|
-
|
692
|
-
class _MaysyncFutureNotAwaitedError(RuntimeError):
|
693
|
-
pass
|
523
|
+
_MAYSYNC_MARK_ATTR = '__maysync__'
|
694
524
|
|
695
525
|
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
) -> None:
|
702
|
-
self._x = x
|
703
|
-
|
704
|
-
def __repr__(self) -> str:
|
705
|
-
return f'{self.__class__.__name__}@{id(self):x}({self._x!r}, done={self.done!r})'
|
706
|
-
|
707
|
-
done: bool = False
|
708
|
-
result: T
|
709
|
-
error: ta.Optional[BaseException] = None
|
710
|
-
|
711
|
-
def __await__(self):
|
712
|
-
if not self.done:
|
713
|
-
yield self
|
714
|
-
if not self.done:
|
715
|
-
raise _MaysyncFutureNotAwaitedError
|
716
|
-
if self.error is not None:
|
717
|
-
raise self.error
|
718
|
-
else:
|
719
|
-
return self.result
|
526
|
+
def mark_maysync(o):
|
527
|
+
if not callable(o) or isinstance(o, AnyMaysyncFn):
|
528
|
+
raise TypeError(o)
|
529
|
+
setattr(o, _MAYSYNC_MARK_ATTR, True)
|
530
|
+
return o
|
720
531
|
|
721
|
-
def s(self) -> None:
|
722
|
-
if self.done:
|
723
|
-
return
|
724
532
|
|
725
|
-
|
726
|
-
|
727
|
-
except BaseException as ex: # noqa
|
728
|
-
self.error = ex
|
729
|
-
self.done = True
|
533
|
+
def is_maysync(o: ta.Any) -> bool:
|
534
|
+
return isinstance(o, AnyMaysyncFn) or getattr(o, _MAYSYNC_MARK_ATTR, False)
|
730
535
|
|
731
|
-
async def a(self) -> None:
|
732
|
-
if self.done:
|
733
|
-
return
|
734
536
|
|
735
|
-
|
736
|
-
self.result = await self._x.a()
|
737
|
-
except BaseException as ex: # noqa
|
738
|
-
self.error = ex
|
739
|
-
self.done = True
|
537
|
+
mark_maysync(AnyMaysyncFn)
|