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/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,323 +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
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
- @abc.abstractmethod
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 _SyncMaysyncContext(_MaysyncContext):
172
- mode: ta.ClassVar[ta.Literal['s']] = 's'
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
- try:
185
- return fn(*args, **kwargs)
130
+ @ta.final
131
+ class Maywaitable(AnyMaywaitable[MaysyncFn[T]], ta.Awaitable[T]):
132
+ _d: bool = False
186
133
 
187
- finally:
188
- if ph is not None:
189
- sys.set_asyncgen_hooks(*ph)
134
+ def __run_maysync__(self) -> T:
135
+ if self._d or self._aw is not None:
136
+ raise MaywaitableAlreadyConsumedError(self)
190
137
 
191
- _MaysyncThreadLocal.context = prev
138
+ self._d = True
139
+ return self._x._s(*self._args, **self._kwargs) # noqa
192
140
 
141
+ #
193
142
 
194
- @ta.final
195
- class _AsyncMaysyncContext(_MaysyncContext):
196
- mode: ta.ClassVar[ta.Literal['a']] = 'a'
143
+ _aw: ta.Optional[ta.Awaitable[T]] = None
197
144
 
198
- def run(self, fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
199
- prev = _MaysyncThreadLocal.context
200
- _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)
201
149
 
202
- try:
203
- 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)
204
154
 
205
- finally:
206
- _MaysyncThreadLocal.context = prev
155
+ return aw.__await__()
207
156
 
208
157
 
209
158
  ##
210
159
 
211
160
 
212
- class _MaywaitableLike(
213
- abc.ABC,
214
- ta.Generic[_MaysyncX],
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
- def __repr__(self) -> str:
228
- 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
229
171
 
172
+ _sg: ta.Optional[ta.Generator[O, I, None]] = None
230
173
 
231
- class _Maywaitable(
232
- _MaywaitableLike[_MaysyncX],
233
- abc.ABC,
234
- ta.Generic[_MaysyncX, T],
235
- ):
236
- 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
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
- class _MaysyncGenerator(
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
- class _FpMaysyncFnLike(
251
- abc.ABC,
252
- ta.Generic[_MaysyncRS, _MaysyncRA],
253
- ):
254
- """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
255
206
 
256
- def __init__(
257
- self,
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
- def __repr__(self) -> str:
269
- 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
270
218
 
271
- def fn_pair(self) -> ta.Optional[Maysync_.FnPair]:
272
- return Maysync_.FnPair(
273
- self._s,
274
- self._a,
275
- )
219
+ return inner()
276
220
 
277
- def __get__(self, instance, owner=None):
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
- @ta.final
285
- class _FpMaysyncFn(
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
- @ta.final
295
- class _FpMaywaitable(
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 a(self) -> ta.Awaitable[T]:
302
- if _MaysyncThreadLocal.context is not None:
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
- return self._x._a(*self._args, **self._kwargs) # noqa
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
- ) -> MaysyncFn[T]:
249
+ ) -> ta.Callable[..., ta.Awaitable[T]]:
312
250
  """Constructs a MaysyncFn from a (sync, async) function pair."""
313
251
 
314
- return _FpMaysyncFn(s, a)
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
- ) -> MaysyncGeneratorFn[O, I]:
258
+ ) -> ta.Callable[..., ta.AsyncGenerator[O, I]]:
374
259
  """Constructs a MaysyncGeneratorFn from a (sync, async) generator function pair."""
375
260
 
376
- return _FpMaysyncGeneratorFn(s, a)
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
- ) -> MaysyncFn[T]:
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
- ) -> MaysyncGeneratorFn[O, I]:
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 make_maysync_generator_fn(s, a)
287
+ return MaysyncGeneratorFn(s, a)
403
288
  else:
404
- 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
405
329
 
406
330
 
407
331
  ##
408
332
 
409
333
 
410
- class _MgMaysyncFnLike(
411
- abc.ABC,
412
- ta.Generic[T],
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
- mg: ta.Callable[..., T],
343
+ x: Maywaitable[T],
417
344
  ) -> None:
418
- self._mg = mg
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._mg!r})'
348
+ return f'{self.__class__.__name__}@{id(self):x}({self._x!r}, done={self.done!r})'
424
349
 
425
- def fn_pair(self) -> ta.Optional[Maysync_.FnPair]:
426
- return None
350
+ done: bool = False
351
+ result: T
352
+ error: ta.Optional[BaseException] = None
427
353
 
428
- def __get__(self, instance, owner=None):
429
- return self.__class__(
430
- self._mg.__get__(instance, owner), # noqa
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
- @abc.abstractmethod # noqa
434
- def __call__(self, *args, **kwargs):
435
- raise NotImplementedError
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
- @ta.final
439
- class _MgMaysyncFn(
440
- _MgMaysyncFnLike[ta.Awaitable[T]],
441
- MaysyncFn_[T],
442
- ta.Generic[T],
443
- ):
444
- def __call__(self, *args, **kwargs):
445
- 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
+ ##
446
386
 
447
387
 
448
388
  @ta.final
449
- class _MgMaysyncDriver:
450
- def __init__(self, ctx: _MaysyncContext, mg: ta.Any) -> None:
451
- self.ctx = ctx
452
- 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
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.mg.__await__()
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.mg.close)
426
+ self.ctx.run(self.coro.close)
484
427
 
485
428
 
486
- @ta.final
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
- 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__()
499
437
 
500
- def a(self) -> ta.Awaitable[T]:
501
- if (ctx := _MaysyncThreadLocal.context) is None or ctx.mode == 'a':
502
- return self._x._mg(*self._args, **self._kwargs) # noqa
438
+ for f in (drv := _MaysyncDriver(_MaysyncRunContext(m), m)):
439
+ f.s()
440
+ del f
503
441
 
504
- async def inner():
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
- 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__()
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
- ai = self.ag.__aiter__()
543
- try:
544
- i: ta.Any = None
545
- e: ta.Any = None
455
+ i: ta.Any = None
456
+ e: ta.Any = None
546
457
 
547
- while True:
548
- if e is not None:
549
- coro = ai.athrow(e)
550
- else:
551
- 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)
552
463
 
553
- i = None
554
- e = None
464
+ i = None
465
+ e = None
555
466
 
556
- for f in (drv := _MgMaysyncDriver(self.ctx, coro)):
557
- if (x := (yield ('f', f))) is not None:
558
- 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
559
477
 
478
+ f.s()
560
479
  del f
561
480
 
562
- i, e = yield ('o', drv.value) # type: ignore[misc]
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
- elif t == 'o':
484
+ o = drv.value
600
485
  try:
601
- ie = ((yield x), None) # type: ignore[misc]
486
+ i = yield o
602
487
  except BaseException as ex: # noqa
603
- ie = (None, ex)
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
- async def inner():
615
- di = iter(self._driver(_AsyncMaysyncContext()))
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 _MgMaysyncGeneratorFn(m)
494
+ return inner()
660
495
 
661
496
 
662
497
  @ta.overload
663
- def maysync(
664
- m: ta.Callable[..., ta.Awaitable[T]],
665
- ) -> MaysyncFn[T]:
498
+ def run_maysync(
499
+ m: ta.Awaitable[T],
500
+ ) -> T:
666
501
  ...
667
502
 
668
503
 
669
504
  @ta.overload
670
- def maysync(
671
- m: ta.Callable[..., ta.AsyncGenerator[O, I]],
672
- ) -> MaysyncGeneratorFn[O, I]:
505
+ def run_maysync(
506
+ m: ta.AsyncGenerator[O, I],
507
+ ) -> ta.Generator[O, I, None]:
673
508
  ...
674
509
 
675
510
 
676
- def maysync(m):
677
- """
678
- Constructs a MaysyncFn or MaysyncGeneratorFn from 'maysync flavored' async function or async generator function,
679
- using `inspect.isasyncgenfunction` to determine the type. Usable as a decorator.
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
- return maysync_fn(m)
517
+ raise TypeError(m)
686
518
 
687
519
 
688
520
  ##
689
521
 
690
522
 
691
- @ta.final
692
- class _MaysyncFutureNotAwaitedError(RuntimeError):
693
- pass
523
+ _MAYSYNC_MARK_ATTR = '__maysync__'
694
524
 
695
525
 
696
- @ta.final
697
- class _MaysyncFuture(ta.Generic[T]):
698
- def __init__(
699
- self,
700
- x: Maywaitable[T],
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
- try:
726
- self.result = self._x.s()
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
- try:
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)