omlish 0.0.0.dev411__py3-none-any.whl → 0.0.0.dev413__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 -7
- omlish/lang/asyncs.py +7 -14
- omlish/lang/descriptors.py +14 -0
- omlish/lang/maysyncs.py +40 -43
- omlish/lite/maysyncs.py +302 -510
- omlish/subprocesses/maysyncs.py +11 -17
- {omlish-0.0.0.dev411.dist-info → omlish-0.0.0.dev413.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev411.dist-info → omlish-0.0.0.dev413.dist-info}/RECORD +14 -14
- {omlish-0.0.0.dev411.dist-info → omlish-0.0.0.dev413.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev411.dist-info → omlish-0.0.0.dev413.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev411.dist-info → omlish-0.0.0.dev413.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev411.dist-info → omlish-0.0.0.dev413.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,329 +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
|
160
|
-
|
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
|
161
116
|
|
162
|
-
|
163
|
-
|
117
|
+
def __repr__(self) -> str:
|
118
|
+
return f'{self.__class__.__name__}@{id(self):x}({self._x!r})'
|
164
119
|
|
165
|
-
@classmethod
|
166
|
-
def current(cls) -> ta.Optional['_MaysyncContext']:
|
167
|
-
return _MaysyncThreadLocal.context
|
168
120
|
|
169
|
-
|
170
|
-
def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
|
171
|
-
raise NotImplementedError
|
121
|
+
##
|
172
122
|
|
173
123
|
|
174
124
|
@ta.final
|
175
|
-
class
|
176
|
-
|
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
|
-
def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
|
179
|
-
prev = _MaysyncThreadLocal.context
|
180
|
-
_MaysyncThreadLocal.context = self
|
181
129
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
else:
|
186
|
-
ph = None
|
130
|
+
@ta.final
|
131
|
+
class Maywaitable(AnyMaywaitable[MaysyncFn[T]], ta.Awaitable[T]):
|
132
|
+
_d: bool = False
|
187
133
|
|
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
|
-
|
192
|
-
|
193
|
-
sys.set_asyncgen_hooks(*ph)
|
194
|
-
|
195
|
-
_MaysyncThreadLocal.context = prev
|
138
|
+
self._d = True
|
139
|
+
return self._x._s(*self._args, **self._kwargs) # noqa
|
196
140
|
|
141
|
+
#
|
197
142
|
|
198
|
-
|
199
|
-
class _AsyncMaysyncContext(_MaysyncContext):
|
200
|
-
mode: ta.ClassVar[ta.Literal['a']] = 'a'
|
143
|
+
_aw: ta.Optional[ta.Awaitable[T]] = None
|
201
144
|
|
202
|
-
def
|
203
|
-
|
204
|
-
|
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)
|
205
149
|
|
206
|
-
|
207
|
-
|
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)
|
208
154
|
|
209
|
-
|
210
|
-
_MaysyncThreadLocal.context = prev
|
155
|
+
return aw.__await__()
|
211
156
|
|
212
157
|
|
213
158
|
##
|
214
159
|
|
215
160
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
)
|
220
|
-
"""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)
|
221
165
|
|
222
|
-
@ta.final
|
223
|
-
def __init__(
|
224
|
-
self,
|
225
|
-
x: _MaysyncX,
|
226
|
-
args: ta.Tuple[ta.Any, ...],
|
227
|
-
kwargs: ta.Mapping[str, ta.Any],
|
228
|
-
) -> None:
|
229
|
-
self._x = x
|
230
|
-
self._args = args
|
231
|
-
self._kwargs = kwargs
|
232
166
|
|
233
|
-
|
234
|
-
|
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
|
235
171
|
|
172
|
+
_sg: ta.Optional[ta.Generator[O, I, None]] = None
|
236
173
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
)
|
242
|
-
|
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
|
243
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())
|
244
183
|
|
245
|
-
|
246
|
-
_MaywaitableLike[_MaysyncX],
|
247
|
-
abc.ABC,
|
248
|
-
ta.Generic[_MaysyncX, O, I],
|
249
|
-
):
|
250
|
-
pass
|
184
|
+
#
|
251
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
|
252
189
|
|
253
|
-
|
190
|
+
async def inner():
|
191
|
+
g = self._x._s(*self._args, **self._kwargs) # noqa
|
254
192
|
|
193
|
+
i: ta.Any = None
|
194
|
+
e: ta.Any = None
|
255
195
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
)
|
260
|
-
|
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
|
261
206
|
|
262
|
-
|
263
|
-
|
264
|
-
s: ta.Callable[..., _MaysyncRS],
|
265
|
-
a: ta.Callable[..., _MaysyncRA],
|
266
|
-
) -> None:
|
267
|
-
if s is None:
|
268
|
-
raise TypeError(s)
|
269
|
-
if a is None:
|
270
|
-
raise TypeError(a)
|
271
|
-
self._s = s
|
272
|
-
self._a = a
|
207
|
+
i = None
|
208
|
+
e = None
|
273
209
|
|
274
|
-
|
275
|
-
|
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
|
276
218
|
|
277
|
-
|
278
|
-
return Maysync_.FnPair(
|
279
|
-
self._s,
|
280
|
-
self._a,
|
281
|
-
)
|
219
|
+
return inner()
|
282
220
|
|
283
|
-
|
284
|
-
return self.__class__(
|
285
|
-
self._s.__get__(instance, owner), # noqa
|
286
|
-
self._a.__get__(instance, owner), # noqa
|
287
|
-
)
|
221
|
+
_ag: ta.Optional[ta.AsyncGenerator[O, I]] = None
|
288
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
|
289
229
|
|
290
|
-
|
291
|
-
|
292
|
-
_FpMaysyncFnLike[T, ta.Awaitable[T]],
|
293
|
-
MaysyncFn_[T],
|
294
|
-
ta.Generic[T],
|
295
|
-
):
|
296
|
-
def __call__(self, *args, **kwargs):
|
297
|
-
return _FpMaywaitable(self, args, kwargs)
|
230
|
+
def __anext__(self):
|
231
|
+
return (self._ag if self._ag is not None else self._get_ag()).__anext__()
|
298
232
|
|
233
|
+
def asend(self, value: I):
|
234
|
+
return (self._ag if self._ag is not None else self._get_ag()).asend(value)
|
299
235
|
|
300
|
-
|
301
|
-
|
302
|
-
_Maywaitable[_FpMaysyncFn[T], T],
|
303
|
-
):
|
304
|
-
def s(self) -> T:
|
305
|
-
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)
|
306
238
|
|
307
|
-
def
|
308
|
-
if
|
309
|
-
return _MaysyncFuture(functools.partial(self._x, *self._args, **self._kwargs)) # noqa
|
239
|
+
def aclose(self):
|
240
|
+
return (self._ag if self._ag is not None else self._get_ag()).aclose()
|
310
241
|
|
311
|
-
|
242
|
+
|
243
|
+
##
|
312
244
|
|
313
245
|
|
314
246
|
def make_maysync_fn(
|
315
247
|
s: ta.Callable[..., T],
|
316
248
|
a: ta.Callable[..., ta.Awaitable[T]],
|
317
|
-
) ->
|
249
|
+
) -> ta.Callable[..., ta.Awaitable[T]]:
|
318
250
|
"""Constructs a MaysyncFn from a (sync, async) function pair."""
|
319
251
|
|
320
|
-
return
|
321
|
-
|
322
|
-
|
323
|
-
@ta.final
|
324
|
-
class _FpMaysyncGeneratorFn(
|
325
|
-
_FpMaysyncFnLike[ta.Generator[O, I, None], ta.AsyncGenerator[O, I]],
|
326
|
-
MaysyncGeneratorFn_[O, I],
|
327
|
-
ta.Generic[O, I],
|
328
|
-
):
|
329
|
-
def __call__(self, *args, **kwargs):
|
330
|
-
return _FpMaysyncGenerator(self, args, kwargs)
|
331
|
-
|
332
|
-
|
333
|
-
@ta.final
|
334
|
-
class _FpMaysyncGenerator(
|
335
|
-
_MaysyncGenerator[_FpMaysyncGeneratorFn[O, I], O, I],
|
336
|
-
):
|
337
|
-
def s(self) -> ta.Generator[O, I, None]:
|
338
|
-
return self._x._s(*self._args, **self._kwargs) # noqa
|
339
|
-
|
340
|
-
def a(self) -> ta.AsyncGenerator[O, I]:
|
341
|
-
if (ctx := _MaysyncContext.current()) is not None and ctx.mode == 's':
|
342
|
-
async def inner():
|
343
|
-
g = self._x._s(*self._args, **self._kwargs) # noqa
|
344
|
-
|
345
|
-
i: ta.Any = None
|
346
|
-
e: ta.Any = None
|
347
|
-
|
348
|
-
while True:
|
349
|
-
try:
|
350
|
-
if e is not None:
|
351
|
-
o = g.throw(e)
|
352
|
-
else:
|
353
|
-
o = g.send(i)
|
354
|
-
except StopIteration as ex:
|
355
|
-
if ex.value is not None:
|
356
|
-
raise TypeError(ex) from None
|
357
|
-
return
|
358
|
-
|
359
|
-
i = None
|
360
|
-
e = None
|
361
|
-
|
362
|
-
try:
|
363
|
-
i = yield o
|
364
|
-
except StopIteration as ex: # noqa
|
365
|
-
raise NotImplementedError # noqa
|
366
|
-
except StopAsyncIteration as ex: # noqa
|
367
|
-
raise NotImplementedError # noqa
|
368
|
-
except BaseException as ex: # noqa
|
369
|
-
e = ex
|
370
|
-
|
371
|
-
return inner()
|
372
|
-
|
373
|
-
return self._x._a(*self._args, **self._kwargs) # noqa
|
252
|
+
return MaysyncFn(s, a)
|
374
253
|
|
375
254
|
|
376
255
|
def make_maysync_generator_fn(
|
377
256
|
s: ta.Callable[..., ta.Generator[O, I, None]],
|
378
257
|
a: ta.Callable[..., ta.AsyncGenerator[O, I]],
|
379
|
-
) ->
|
258
|
+
) -> ta.Callable[..., ta.AsyncGenerator[O, I]]:
|
380
259
|
"""Constructs a MaysyncGeneratorFn from a (sync, async) generator function pair."""
|
381
260
|
|
382
|
-
return
|
261
|
+
return MaysyncGeneratorFn(s, a)
|
383
262
|
|
384
263
|
|
385
264
|
@ta.overload
|
386
265
|
def make_maysync(
|
387
266
|
s: ta.Callable[..., T],
|
388
267
|
a: ta.Callable[..., ta.Awaitable[T]],
|
389
|
-
) ->
|
268
|
+
) -> ta.Callable[..., ta.Awaitable[T]]:
|
390
269
|
...
|
391
270
|
|
392
271
|
|
@@ -394,7 +273,7 @@ def make_maysync(
|
|
394
273
|
def make_maysync(
|
395
274
|
s: ta.Callable[..., ta.Generator[O, I, None]],
|
396
275
|
a: ta.Callable[..., ta.AsyncGenerator[O, I]],
|
397
|
-
) ->
|
276
|
+
) -> ta.Callable[..., ta.AsyncGenerator[O, I]]:
|
398
277
|
...
|
399
278
|
|
400
279
|
|
@@ -405,63 +284,121 @@ def make_maysync(s, a):
|
|
405
284
|
"""
|
406
285
|
|
407
286
|
if inspect.isasyncgenfunction(a):
|
408
|
-
return
|
287
|
+
return MaysyncGeneratorFn(s, a)
|
409
288
|
else:
|
410
|
-
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
|
411
329
|
|
412
330
|
|
413
331
|
##
|
414
332
|
|
415
333
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
334
|
+
@ta.final
|
335
|
+
class _MaysyncFutureNotAwaitedError(RuntimeError):
|
336
|
+
pass
|
337
|
+
|
338
|
+
|
339
|
+
@ta.final
|
340
|
+
class _MaysyncFuture(ta.Generic[T]):
|
420
341
|
def __init__(
|
421
342
|
self,
|
422
|
-
|
343
|
+
x: Maywaitable[T],
|
423
344
|
) -> None:
|
424
|
-
self.
|
425
|
-
|
426
|
-
functools.update_wrapper(self, mg, updated=())
|
345
|
+
self._x = x
|
427
346
|
|
428
347
|
def __repr__(self) -> str:
|
429
|
-
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})'
|
430
349
|
|
431
|
-
|
432
|
-
|
350
|
+
done: bool = False
|
351
|
+
result: T
|
352
|
+
error: ta.Optional[BaseException] = None
|
433
353
|
|
434
|
-
def
|
435
|
-
|
436
|
-
self
|
437
|
-
|
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
|
438
363
|
|
439
|
-
|
440
|
-
|
441
|
-
|
364
|
+
def s(self) -> None:
|
365
|
+
if self.done:
|
366
|
+
return
|
442
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
|
443
373
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
)
|
450
|
-
|
451
|
-
|
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
|
+
##
|
452
386
|
|
453
387
|
|
454
388
|
@ta.final
|
455
|
-
class
|
456
|
-
def __init__(
|
457
|
-
|
458
|
-
|
389
|
+
class _MaysyncDriver:
|
390
|
+
def __init__(
|
391
|
+
self,
|
392
|
+
ctx: _MaysyncRunContext,
|
393
|
+
coro: ta.Any,
|
394
|
+
) -> None:
|
395
|
+
self.ctx, self.coro = ctx, coro
|
459
396
|
|
460
397
|
value: ta.Any
|
461
398
|
|
462
399
|
def __iter__(self) -> ta.Generator['_MaysyncFuture', None, None]:
|
463
400
|
try:
|
464
|
-
a = self.
|
401
|
+
a = self.coro.__await__()
|
465
402
|
try:
|
466
403
|
g = iter(a)
|
467
404
|
try:
|
@@ -486,260 +423,115 @@ class _MgMaysyncDriver:
|
|
486
423
|
self.ctx.run(a.close)
|
487
424
|
|
488
425
|
finally:
|
489
|
-
self.ctx.run(self.
|
426
|
+
self.ctx.run(self.coro.close)
|
490
427
|
|
491
428
|
|
492
|
-
|
493
|
-
class _MgMaywaitable(
|
494
|
-
_Maywaitable[_MgMaysyncFn[T], T],
|
495
|
-
):
|
496
|
-
def _driver(self, ctx: _MaysyncContext) -> _MgMaysyncDriver:
|
497
|
-
return _MgMaysyncDriver(ctx, self._x._mg(*self._args, **self._kwargs)) # noqa
|
429
|
+
##
|
498
430
|
|
499
|
-
def s(self) -> T:
|
500
|
-
for f in (drv := self._driver(_SyncMaysyncContext())):
|
501
|
-
f.s()
|
502
|
-
del f
|
503
431
|
|
504
|
-
|
432
|
+
def run_maysync_fn(
|
433
|
+
m: ta.Awaitable[T],
|
434
|
+
) -> T:
|
435
|
+
if isinstance(m, Maywaitable):
|
436
|
+
return m.__run_maysync__()
|
505
437
|
|
506
|
-
|
507
|
-
|
508
|
-
|
438
|
+
for f in (drv := _MaysyncDriver(_MaysyncRunContext(m), m)):
|
439
|
+
f.s()
|
440
|
+
del f
|
509
441
|
|
510
|
-
|
511
|
-
for f in (drv := self._driver(_AsyncMaysyncContext())):
|
512
|
-
await f.a()
|
513
|
-
del f
|
442
|
+
return drv.value
|
514
443
|
|
515
|
-
return drv.value
|
516
444
|
|
517
|
-
|
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__()
|
518
450
|
|
451
|
+
def inner():
|
452
|
+
ctx = _MaysyncRunContext(m)
|
519
453
|
|
520
|
-
@ta.final
|
521
|
-
class _MgMaysyncGeneratorFn(
|
522
|
-
_MgMaysyncFnLike[ta.AsyncGenerator[O, I]],
|
523
|
-
MaysyncGeneratorFn_[O, I],
|
524
|
-
ta.Generic[O, I],
|
525
|
-
):
|
526
|
-
def __call__(self, *args, **kwargs):
|
527
|
-
return _MgMaysyncGenerator(self, args, kwargs)
|
528
|
-
|
529
|
-
|
530
|
-
@ta.final
|
531
|
-
class _MgMaysyncGeneratorDriver:
|
532
|
-
def __init__(self, ctx: _MaysyncContext, ag: ta.Any) -> None:
|
533
|
-
self.ctx = ctx
|
534
|
-
self.ag = ag
|
535
|
-
|
536
|
-
def __iter__(self) -> ta.Generator[
|
537
|
-
ta.Union[
|
538
|
-
ta.Tuple[ta.Literal['f'], '_MaysyncFuture'],
|
539
|
-
ta.Tuple[ta.Literal['o'], ta.Any],
|
540
|
-
],
|
541
|
-
ta.Union[
|
542
|
-
ta.Tuple[ta.Any, BaseException],
|
543
|
-
None,
|
544
|
-
],
|
545
|
-
None,
|
546
|
-
]:
|
547
454
|
try:
|
548
|
-
|
549
|
-
|
550
|
-
i: ta.Any = None
|
551
|
-
e: ta.Any = None
|
455
|
+
i: ta.Any = None
|
456
|
+
e: ta.Any = None
|
552
457
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
458
|
+
while True:
|
459
|
+
if e is not None:
|
460
|
+
coro = m.athrow(e)
|
461
|
+
else:
|
462
|
+
coro = m.asend(i)
|
558
463
|
|
559
|
-
|
560
|
-
|
464
|
+
i = None
|
465
|
+
e = None
|
561
466
|
|
562
|
-
|
563
|
-
|
564
|
-
|
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
|
565
477
|
|
478
|
+
f.s()
|
566
479
|
del f
|
567
480
|
|
568
|
-
|
569
|
-
|
570
|
-
finally:
|
571
|
-
if ai is not self.ag:
|
572
|
-
for f in _MgMaysyncDriver(self.ctx, ai.aclose()):
|
573
|
-
yield ('f', f)
|
574
|
-
|
575
|
-
finally:
|
576
|
-
for f in _MgMaysyncDriver(self.ctx, self.ag.aclose()):
|
577
|
-
yield ('f', f)
|
578
|
-
|
579
|
-
|
580
|
-
@ta.final
|
581
|
-
class _MgMaysyncGenerator(
|
582
|
-
_MaysyncGenerator[_MgMaysyncGeneratorFn[O, I], O, I],
|
583
|
-
):
|
584
|
-
def _driver(self, ctx: _MaysyncContext) -> _MgMaysyncGeneratorDriver:
|
585
|
-
return _MgMaysyncGeneratorDriver(ctx, self._x._mg(*self._args, **self._kwargs)) # noqa
|
586
|
-
|
587
|
-
def s(self) -> ta.Generator[O, I, None]:
|
588
|
-
di = iter(self._driver(_SyncMaysyncContext()))
|
589
|
-
|
590
|
-
ie: ta.Any = None
|
591
|
-
|
592
|
-
while True:
|
593
|
-
try:
|
594
|
-
t, x = di.send(ie)
|
595
|
-
except StopAsyncIteration:
|
596
|
-
return
|
597
|
-
except StopIteration:
|
598
|
-
raise RuntimeError from None
|
599
|
-
|
600
|
-
ie = None
|
601
|
-
|
602
|
-
if t == 'f':
|
603
|
-
x.s()
|
481
|
+
finally:
|
482
|
+
di.close()
|
604
483
|
|
605
|
-
|
484
|
+
o = drv.value
|
606
485
|
try:
|
607
|
-
|
486
|
+
i = yield o
|
608
487
|
except BaseException as ex: # noqa
|
609
|
-
|
610
|
-
|
611
|
-
else:
|
612
|
-
raise RuntimeError((t, x))
|
613
|
-
|
614
|
-
del x
|
615
|
-
|
616
|
-
def a(self) -> ta.AsyncGenerator[O, I]:
|
617
|
-
if _MaysyncContext.current() is not None:
|
618
|
-
return self._x._mg(*self._args, **self._kwargs) # noqa
|
488
|
+
e = ex
|
619
489
|
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
ie: ta.Any = None
|
624
|
-
|
625
|
-
while True:
|
626
|
-
try:
|
627
|
-
t, x = di.send(ie)
|
628
|
-
except StopAsyncIteration:
|
629
|
-
return
|
630
|
-
except StopIteration:
|
631
|
-
raise RuntimeError from None
|
632
|
-
|
633
|
-
ie = None
|
634
|
-
|
635
|
-
if t == 'f':
|
636
|
-
await x.a()
|
637
|
-
|
638
|
-
elif t == 'o':
|
639
|
-
try:
|
640
|
-
ie = ((yield x), None)
|
641
|
-
except BaseException as ex: # noqa
|
642
|
-
ie = (None, ex)
|
643
|
-
|
644
|
-
else:
|
645
|
-
raise RuntimeError((t, x))
|
646
|
-
|
647
|
-
del x
|
648
|
-
|
649
|
-
return inner()
|
650
|
-
|
651
|
-
|
652
|
-
def maysync_fn(
|
653
|
-
m: ta.Callable[..., ta.Awaitable[T]],
|
654
|
-
) -> MaysyncFn[T]:
|
655
|
-
"""Constructs a MaysyncFn from a 'maysync flavored' async function."""
|
656
|
-
|
657
|
-
return _MgMaysyncFn(m)
|
658
|
-
|
659
|
-
|
660
|
-
def maysync_generator_fn(
|
661
|
-
m: ta.Callable[..., ta.AsyncGenerator[O, I]],
|
662
|
-
) -> MaysyncGeneratorFn[O, I]:
|
663
|
-
"""Constructs a MaysyncGeneratorFn from a 'maysync flavored' async generator function."""
|
490
|
+
finally:
|
491
|
+
for f in _MaysyncDriver(ctx, m.aclose()):
|
492
|
+
f.s()
|
664
493
|
|
665
|
-
return
|
494
|
+
return inner()
|
666
495
|
|
667
496
|
|
668
497
|
@ta.overload
|
669
|
-
def
|
670
|
-
m: ta.
|
671
|
-
) ->
|
498
|
+
def run_maysync(
|
499
|
+
m: ta.Awaitable[T],
|
500
|
+
) -> T:
|
672
501
|
...
|
673
502
|
|
674
503
|
|
675
504
|
@ta.overload
|
676
|
-
def
|
677
|
-
m: ta.
|
678
|
-
) ->
|
505
|
+
def run_maysync(
|
506
|
+
m: ta.AsyncGenerator[O, I],
|
507
|
+
) -> ta.Generator[O, I, None]:
|
679
508
|
...
|
680
509
|
|
681
510
|
|
682
|
-
def
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
if inspect.isasyncgenfunction(m):
|
689
|
-
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)
|
690
516
|
else:
|
691
|
-
|
517
|
+
raise TypeError(m)
|
692
518
|
|
693
519
|
|
694
520
|
##
|
695
521
|
|
696
522
|
|
697
|
-
|
698
|
-
class _MaysyncFutureNotAwaitedError(RuntimeError):
|
699
|
-
pass
|
523
|
+
_MAYSYNC_MARK_ATTR = '__maysync__'
|
700
524
|
|
701
525
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
) -> None:
|
708
|
-
self._x = x
|
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
|
709
531
|
|
710
|
-
def __repr__(self) -> str:
|
711
|
-
return f'{self.__class__.__name__}@{id(self):x}({self._x!r}, done={self.done!r})'
|
712
532
|
|
713
|
-
|
714
|
-
|
715
|
-
error: ta.Optional[BaseException] = None
|
533
|
+
def is_maysync(o: ta.Any) -> bool:
|
534
|
+
return isinstance(o, AnyMaysyncFn) or getattr(o, _MAYSYNC_MARK_ATTR, False)
|
716
535
|
|
717
|
-
def __await__(self):
|
718
|
-
if not self.done:
|
719
|
-
yield self
|
720
|
-
if not self.done:
|
721
|
-
raise _MaysyncFutureNotAwaitedError
|
722
|
-
if self.error is not None:
|
723
|
-
raise self.error
|
724
|
-
else:
|
725
|
-
return self.result
|
726
|
-
|
727
|
-
def s(self) -> None:
|
728
|
-
if self.done:
|
729
|
-
return
|
730
|
-
|
731
|
-
try:
|
732
|
-
self.result = self._x().s()
|
733
|
-
except BaseException as ex: # noqa
|
734
|
-
self.error = ex
|
735
|
-
self.done = True
|
736
|
-
|
737
|
-
async def a(self) -> None:
|
738
|
-
if self.done:
|
739
|
-
return
|
740
536
|
|
741
|
-
|
742
|
-
self.result = await self._x().a()
|
743
|
-
except BaseException as ex: # noqa
|
744
|
-
self.error = ex
|
745
|
-
self.done = True
|
537
|
+
mark_maysync(AnyMaysyncFn)
|