Source code for asyncutils.util

  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