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/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 is an object with two
8
- nullary methods:
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
- - `assert m_inc_int(5).s() == 6` in sync contexts
16
- - `assert await m_inc_int(5).a() == 6` in async and maysync contexts
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 using the `@maysync` decorator to wrap a 'maysync flavored' async function. 'Maysync flavored' async
20
- functions are ones which only call other maysync functions through their 'maysync context' - that is, they use the 'a'
21
- methods on maywaitables - for example: `await m_foo().a()` - and the maysync machinery will ultimately call the
22
- appropriate 'leaf' sync or async functions. Being regular python functions they are free to call whatever they like -
23
- for example doing sync IO - but the point is to, ideally, route all IO through maysync functions such that the maysync
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 Maywaitable(ta.Protocol[T_co]):
68
- """
69
- The maysync version of `Awaitable[T]`. Non-generator maysync functions return a `Maywaitable`, with the following
70
- nullary methods:
71
-
72
- - `def s()` - to be called in sync contexts
73
- - `async def a()` - to be called in async and maysync contexts
74
-
75
- Only the proper method should be called in each context.
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
- The concrete implementations are module-level implementation detail, and in general users should make a point to
115
- only interact with the protocols defined above, but introspection can be necessary at times.
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
- """If this maysync object is backed by a (sync, async) pair of functions, returns the pair."""
131
-
132
- raise NotImplementedError
87
+ return AnyMaysyncFn.FnPair(
88
+ self._s,
89
+ self._a,
90
+ )
133
91
 
134
- @abc.abstractmethod
135
- def cast(self):
136
- pass
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 MaysyncFn_(Maysync_, abc.ABC, ta.Generic[T]): # noqa
144
- @ta.final
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 MaysyncGeneratorFn_(Maysync_, abc.ABC, ta.Generic[O, I]): # noqa
107
+ class AnyMaywaitable(abc.ABC, ta.Generic[_MaysyncX]):
150
108
  @ta.final
151
- def cast(self) -> MaysyncGenerator[O, I]:
152
- return ta.cast('MaysyncGenerator[O, I]', self)
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
- class _MaysyncContext(abc.ABC):
163
- mode: ta.ClassVar[ta.Literal['s', 'a']]
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
- @abc.abstractmethod
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 _SyncMaysyncContext(_MaysyncContext):
176
- mode: ta.ClassVar[ta.Literal['s']] = 's'
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
- ph: ta.Any = sys.get_asyncgen_hooks()
183
- if ph.firstiter is not None or ph.finalizer is not None:
184
- sys.set_asyncgen_hooks(firstiter=None, finalizer=None)
185
- else:
186
- ph = None
130
+ @ta.final
131
+ class Maywaitable(AnyMaywaitable[MaysyncFn[T]], ta.Awaitable[T]):
132
+ _d: bool = False
187
133
 
188
- try:
189
- return fn(*args, **kwargs)
134
+ def __run_maysync__(self) -> T:
135
+ if self._d or self._aw is not None:
136
+ raise MaywaitableAlreadyConsumedError(self)
190
137
 
191
- finally:
192
- if ph is not None:
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
- @ta.final
199
- class _AsyncMaysyncContext(_MaysyncContext):
200
- mode: ta.ClassVar[ta.Literal['a']] = 'a'
143
+ _aw: ta.Optional[ta.Awaitable[T]] = None
201
144
 
202
- def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
203
- prev = _MaysyncThreadLocal.context
204
- _MaysyncThreadLocal.context = self
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
- try:
207
- return fn(*args, **kwargs)
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
- finally:
210
- _MaysyncThreadLocal.context = prev
155
+ return aw.__await__()
211
156
 
212
157
 
213
158
  ##
214
159
 
215
160
 
216
- class _MaywaitableLike(
217
- abc.ABC,
218
- ta.Generic[_MaysyncX],
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
- def __repr__(self) -> str:
234
- return f'{self.__class__.__name__}@{id(self):x}({self._x!r})'
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
- class _Maywaitable(
238
- _MaywaitableLike[_MaysyncX],
239
- abc.ABC,
240
- ta.Generic[_MaysyncX, T],
241
- ):
242
- pass
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
- class _MaysyncGenerator(
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
- class _FpMaysyncFnLike(
257
- abc.ABC,
258
- ta.Generic[_MaysyncRS, _MaysyncRA],
259
- ):
260
- """A maysync object backed by an underlying (sync, async) function pair."""
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
- def __init__(
263
- self,
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
- def __repr__(self) -> str:
275
- return f'{self.__class__.__name__}@{id(self):x}({self._s!r}, {self._a!r})'
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
- def fn_pair(self) -> ta.Optional[Maysync_.FnPair]:
278
- return Maysync_.FnPair(
279
- self._s,
280
- self._a,
281
- )
219
+ return inner()
282
220
 
283
- def __get__(self, instance, owner=None):
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
- @ta.final
291
- class _FpMaysyncFn(
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
- @ta.final
301
- class _FpMaywaitable(
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 a(self) -> ta.Awaitable[T]:
308
- if _MaysyncContext.current() is not None:
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
- return self._x._a(*self._args, **self._kwargs) # noqa
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
- ) -> MaysyncFn[T]:
249
+ ) -> ta.Callable[..., ta.Awaitable[T]]:
318
250
  """Constructs a MaysyncFn from a (sync, async) function pair."""
319
251
 
320
- return _FpMaysyncFn(s, a)
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
- ) -> MaysyncGeneratorFn[O, I]:
258
+ ) -> ta.Callable[..., ta.AsyncGenerator[O, I]]:
380
259
  """Constructs a MaysyncGeneratorFn from a (sync, async) generator function pair."""
381
260
 
382
- return _FpMaysyncGeneratorFn(s, a)
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
- ) -> MaysyncFn[T]:
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
- ) -> MaysyncGeneratorFn[O, I]:
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 make_maysync_generator_fn(s, a)
287
+ return MaysyncGeneratorFn(s, a)
409
288
  else:
410
- return make_maysync_fn(s, a)
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
- class _MgMaysyncFnLike(
417
- abc.ABC,
418
- ta.Generic[T],
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
- mg: ta.Callable[..., T],
343
+ x: Maywaitable[T],
423
344
  ) -> None:
424
- self._mg = mg
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._mg!r})'
348
+ return f'{self.__class__.__name__}@{id(self):x}({self._x!r}, done={self.done!r})'
430
349
 
431
- def fn_pair(self) -> ta.Optional[Maysync_.FnPair]:
432
- return None
350
+ done: bool = False
351
+ result: T
352
+ error: ta.Optional[BaseException] = None
433
353
 
434
- def __get__(self, instance, owner=None):
435
- return self.__class__(
436
- self._mg.__get__(instance, owner), # noqa
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
- @abc.abstractmethod # noqa
440
- def __call__(self, *args, **kwargs):
441
- raise NotImplementedError
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
- @ta.final
445
- class _MgMaysyncFn(
446
- _MgMaysyncFnLike[ta.Awaitable[T]],
447
- MaysyncFn_[T],
448
- ta.Generic[T],
449
- ):
450
- def __call__(self, *args, **kwargs):
451
- return _MgMaywaitable(self, args, kwargs)
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 _MgMaysyncDriver:
456
- def __init__(self, ctx: _MaysyncContext, mg: ta.Any) -> None:
457
- self.ctx = ctx
458
- self.mg = mg
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.mg.__await__()
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.mg.close)
426
+ self.ctx.run(self.coro.close)
490
427
 
491
428
 
492
- @ta.final
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
- return drv.value
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
- def a(self) -> ta.Awaitable[T]:
507
- if (ctx := _MaysyncContext.current()) is None or ctx.mode == 'a':
508
- return self._x._mg(*self._args, **self._kwargs) # noqa
438
+ for f in (drv := _MaysyncDriver(_MaysyncRunContext(m), m)):
439
+ f.s()
440
+ del f
509
441
 
510
- async def inner():
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
- return inner()
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
- ai = self.ag.__aiter__()
549
- try:
550
- i: ta.Any = None
551
- e: ta.Any = None
455
+ i: ta.Any = None
456
+ e: ta.Any = None
552
457
 
553
- while True:
554
- if e is not None:
555
- coro = ai.athrow(e)
556
- else:
557
- coro = ai.asend(i)
458
+ while True:
459
+ if e is not None:
460
+ coro = m.athrow(e)
461
+ else:
462
+ coro = m.asend(i)
558
463
 
559
- i = None
560
- e = None
464
+ i = None
465
+ e = None
561
466
 
562
- for f in (drv := _MgMaysyncDriver(self.ctx, coro)):
563
- if (x := (yield ('f', f))) is not None:
564
- raise RuntimeError(x)
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
- i, e = yield ('o', drv.value) # type: ignore[misc]
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
- elif t == 'o':
484
+ o = drv.value
606
485
  try:
607
- ie = ((yield x), None) # type: ignore[misc]
486
+ i = yield o
608
487
  except BaseException as ex: # noqa
609
- ie = (None, ex)
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
- async def inner():
621
- di = iter(self._driver(_AsyncMaysyncContext()))
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 _MgMaysyncGeneratorFn(m)
494
+ return inner()
666
495
 
667
496
 
668
497
  @ta.overload
669
- def maysync(
670
- m: ta.Callable[..., ta.Awaitable[T]],
671
- ) -> MaysyncFn[T]:
498
+ def run_maysync(
499
+ m: ta.Awaitable[T],
500
+ ) -> T:
672
501
  ...
673
502
 
674
503
 
675
504
  @ta.overload
676
- def maysync(
677
- m: ta.Callable[..., ta.AsyncGenerator[O, I]],
678
- ) -> MaysyncGeneratorFn[O, I]:
505
+ def run_maysync(
506
+ m: ta.AsyncGenerator[O, I],
507
+ ) -> ta.Generator[O, I, None]:
679
508
  ...
680
509
 
681
510
 
682
- def maysync(m):
683
- """
684
- Constructs a MaysyncFn or MaysyncGeneratorFn from 'maysync flavored' async function or async generator function,
685
- using `inspect.isasyncgenfunction` to determine the type. Usable as a decorator.
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
- return maysync_fn(m)
517
+ raise TypeError(m)
692
518
 
693
519
 
694
520
  ##
695
521
 
696
522
 
697
- @ta.final
698
- class _MaysyncFutureNotAwaitedError(RuntimeError):
699
- pass
523
+ _MAYSYNC_MARK_ATTR = '__maysync__'
700
524
 
701
525
 
702
- @ta.final
703
- class _MaysyncFuture(ta.Generic[T]):
704
- def __init__(
705
- self,
706
- x: ta.Callable[[], Maywaitable[T]],
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
- done: bool = False
714
- result: T
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
- try:
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)