1import asyncio as I, asyncutils as A
2from asyncutils.constants import _NO_DEFAULT
3from asyncutils._internal import helpers as H
4from asyncutils._internal.patch import patch_function_signatures
5from asyncutils._internal.submodules import util_all as __all__
6from functools import partial, wraps
7from sys import audit, exc_info
[docs]
8def avalify(v):
9 async def g(*_a, **_): return v # noqa: RUF029
10 return g
11afalsify, atruthify, anullify = map(avalify, (False, True, None))
12anullcontext = object.__new__(type('anullcontext', (), {'__new__': lambda _, /: anullcontext, '__aenter__': anullify, '__aexit__': anullify}))
[docs]
13async def wrap_in_coro(aw, /):
14 try: return await aw
15 except A.CRITICAL: raise A.Critical
[docs]
16def done_evt(*, evtcls=I.Event): (E := evtcls()).set(); return E
[docs]
17def done_fut(res=None, *, futcls=I.Future): F = futcls(); F.set_exception(A.unwrap_exc(res)) if A.exception_occurred(res) else F.set_result(res); return F
[docs]
18async def locked_lock(*, lcls=I.Lock): await (l := lcls()).acquire(); return l
[docs]
19def get_future(aw, loop=None): return (H.get_loop_and_set() if loop is None else loop).create_task(wrap_in_coro(aw))
[docs]
20def new_eager_tasks(*aws): (l := H.get_loop_and_set()).set_task_factory(I.eager_task_factory); yield from map(partial(get_future, loop=l), aws)
[docs]
21def afcopy(f, /): return wraps(f)(lambda *a, **k: wrap_in_coro(f(*a, **k)))
[docs]
22def discard_retval(f, /): return evaluate_and_return(f, None)
[docs]
23def evaluate_and_return(f, r, /):
24 async def g(*a, **k): await f(*a, **k); return r
25 return wraps(f)(g)
[docs]
26def to_sync(f, /, loop=None, *, timeout=None):
27 audit('asyncutils.util.to_sync', H.fullname(f))
28 if (f := getattr(f, '__sync__', f)) is not f: return f
29 (g := afcopy(f)).__sync__ = r = wraps(f)(lambda *a, **k: sync_await(f(*a, **k), timeout=timeout, loop=loop)); r.__async__ = g; return r # ty: ignore[unresolved-attribute]
[docs]
30def to_sync_from_loop(loop): return partial(to_sync, loop=loop)
31def _(f, c, /): f.set_result(c())
[docs]
32def transient_block(l, f, /, *a, _threadsafe_=False, **k): (l.call_soon_threadsafe if _threadsafe_ else l.call_soon)(_, F := l.create_future(), partial(f, *a, **k)); return F
[docs]
33def transient_block_from_loop(loop, *, threadsafe=False): return partial(transient_block, loop, _threadsafe_=threadsafe)
[docs]
34def sync_await(aw, loop=None, *, never_block=True, timeout=None):
35 audit('asyncutils.util.sync_await', H.fullname(aw))
36 if loop is None: loop = H.get_loop_and_set()
37 return (A.raise_exc(A.Deadlock, 'asyncutils.util.sync_await: cannot await in the current loop without blocking it') if loop is I._get_running_loop() else I.run_coroutine_threadsafe(wrap_in_coro(aw), loop).result(timeout)) if never_block or loop.is_running() else loop.run_until_complete(I.wait_for(I.ensure_future(aw, loop=loop), timeout))
[docs]
38def semaphore(bounded=False, workers=None):
39 if workers is None: workers = A.getcontext().SEMAPHORE_DEFAULT_VALUE
40 return (I.Lock() if workers == 1 else I.BoundedSemaphore(workers)) if bounded else I.Semaphore(workers)
[docs]
41def lockf(f, /, lf=I.Lock, _lc=__import__('weakref').WeakKeyDictionary()): # noqa: B008
42 if (l := _lc.get(f)) is None: _lc[f] = l = lf()
43 async def r(*a, **k):
44 async with l: return await f(*a, **k)
45 return wraps(f)(r)
[docs]
46def to_async(f, /):
47 audit('asyncutils.util.to_async', H.fullname(f))
48 if (f := getattr(f, '__async__', f)) is not f: return f
49 if (e := getattr(to_async, 'executor', None)) is None: e = H.create_executor(to_async)
50 r = partial(H.get_loop_and_set().run_in_executor, e)
51 async def h(*a, **k): return await r(partial(f, *a, **k))
52 g.__async__, h.__sync__ = wraps(f)(h), (g := wraps(f)(lambda *a, **k: f(*a, **k))); return h # noqa: PLW0108 # ty: ignore[unresolved-attribute]
[docs]
53async def aiter_from_f(f, s=_NO_DEFAULT, /, *, yield_sentinel=False):
54 while True:
55 if H.check(r := await f(), s):
56 if yield_sentinel: yield r
57 break
58 yield r
[docs]
59async def safe_cancel(t, /):
60 F = t.get_loop().create_future()
61 def f(_):
62 if not F.done(): F.set_result(None)
63 t.add_done_callback(f)
64 if not t.done(): t.cancel()
65 try: await F
66 finally: t.remove_done_callback(f)
67class DualContextManager:
68 __slots__ = '_aentered', '_ce', '_entered', '_gen', '_st', '_ue'
69 def __init__(self, /, *_): self._gen, self._ce, self._ue, self._st = _; self._entered = self._aentered = False
70 def __enter__(self):
71 if self._aentered: raise RuntimeError('asyncutils.util.dualcontextmanager: context manager already entered asynchronously')
72 if self._entered: raise RuntimeError('asyncutils.util.dualcontextmanager: context manager already entered')
73 try: self._gen = g = A.aiter_to_gen(self._gen, strict=self._st, use_futures=True); self._entered = True; return next(g)
74 except StopIteration: raise RuntimeError("asyncutils.util.dualcontextmanager: generator didn't yield") from None
75 def __exit__(self, t, v, b, /):
76 if self._aentered: raise RuntimeError('asyncutils.util.dualcontextmanager: cannot exit async context manager synchronously')
77 if not self._entered: raise RuntimeError('asyncutils.util.dualcontextmanager: context manager was never entered')
78 g = self._gen
79 if t is None:
80 try: next(g)
81 except StopIteration: return False
82 try: raise RuntimeError("asyncutils.util.dualcontextmanager: generator didn't stop")
83 finally: g.close()
84 if v is None: v = t()
85 try: g.throw(v)
86 except BaseException as e:
87 f = e is v
88 if isinstance(e, StopIteration): return not f
89 if f or (isinstance(e, RuntimeError) and isinstance(v, StopIteration) and e.__cause__ is (e := v)): e.__traceback__ = b; return False
90 raise
91 try: raise RuntimeError("asyncutils.util.dualcontextmanager: generator didn't stop after throw")
92 finally: g.close()
93 def __aenter__(self):
94 if self._aentered: raise RuntimeError('asyncutils.util.dualcontextmanager: async context manager already entered')
95 if self._entered: raise RuntimeError('asyncutils.util.dualcontextmanager: async context manager already entered synchronously')
96 try: self._gen = g = A.iter_to_agen(self._gen, strict=self._st, use_existing_executor=self._ue, create_executor=self._ce); self._aentered = True; return anext(g)
97 except StopAsyncIteration: raise RuntimeError("asyncutils.util.dualcontextmanager: async generator didn't yield") from None
98 async def __aexit__(self, t, v, b, /):
99 if self._entered: raise RuntimeError('asyncutils.util.dualcontextmanager: cannot exit sync context manager asynchronously')
100 if not self._aentered: raise RuntimeError('asyncutils.util.dualcontextmanager: async context manager was never entered')
101 g = self._gen
102 if t is None:
103 try: await anext(g)
104 except StopAsyncIteration: return False
105 try: raise RuntimeError("asyncutils.util.dualcontextmanager: async generator didn't stop")
106 finally: await g.aclose()
107 if v is None: v = t()
108 try: await g.athrow(v)
109 except BaseException as e:
110 f = e is v
111 if isinstance(e, StopAsyncIteration): return not f
112 if f or (isinstance(e, RuntimeError) and isinstance(v, StopAsyncIteration) and e.__cause__ is (e := v)): e.__traceback__ = b; return False
113 raise
114 try: raise RuntimeError("asyncutils.util.dualcontextmanager: async generator didn't stop after athrow")
115 finally: await g.aclose()
[docs]
116def dualcontextmanager(f=None, /, _=DualContextManager, *, use_existing_executor=None, create_executor=None, strict=None):
117 if f is None: return lambda f, /: dualcontextmanager(f, use_existing_executor=use_existing_executor, create_executor=create_executor, strict=strict)
118 return wraps(f)(lambda *a, **k: (c := A.getcontext()) and _(f(*a, **k), c.DUAL_CONTEXT_MANAGER_DEFAULT_USE_EXISTING_EXECUTOR if use_existing_executor is None else use_existing_executor, c.DUAL_CONTEXT_MANAGER_DEFAULT_MAY_CREATE_EXECUTOR if create_executor is None else create_executor, c.DUAL_CONTEXT_MANAGER_DEFAULT_STRICT if strict is None else strict))
[docs]
119def aawcmf2dcmff(**d):
120 def f(f, /, _=dualcontextmanager(**d)): # noqa: B008
121 async def g(*a, **k):
122 c = f(*a, **k)
123 with A.ignore_typeerrs: c = await c
124 if H.check_methods(c, '__aenter__', '__aexit__'):
125 async with c as r: yield r; return # noqa: ASYNC119
126 if (e := getattr(aawcmf2dcmff, 'executor', None)) is None: e = H.create_executor(aawcmf2dcmff)
127 r = await (h := partial(H.get_loop_and_set().run_in_executor, e))(c.__enter__)
128 try: yield r
129 finally: await h(c.__exit__, *exc_info())
130 return _(g)
131 f.__text_signature__ = '(f, /)'; return f # ty: ignore[unresolved-attribute]
[docs]
132def make_task_factory(tcls, eager=None):
133 if eager is None: eager = A.getcontext().MAKE_TASK_FACTORY_DEFAULT_EAGER
134 return lambda loop, coro, eager_start=eager, **k: tcls(coro, loop=loop, eager_start=eager_start, **k)
135dcm, ignore_cancellation = (aawcmf2dcmf := aawcmf2dcmff()).__defaults__[0], A.IgnoreErrors(I.CancelledError)
136patch_function_signatures((lockf, 'f, /, lf={}'), (dualcontextmanager, 'f=None, /, *, use_existing_executor=None, create_executor=None, strict=None'))
137del DualContextManager