run_sync
def run_sync(
coro
):Run coroutine coro to completion from sync code and return its result
run_sync, iter_sync, ctx_sync, athreaded, maybe_await, and then, plus Debounce for coalescing bursts of calls
Use these helpers to call async libraries without making your own code async:
run_sync runs a coroutine and returns its result.iter_sync iterates an async generator.ctx_sync uses an async context manager in a with block.They share an event loop on a background daemon thread, started on first use.
Run coroutine coro to completion from sync code and return its result
Exceptions from the coroutine propagate to the caller. Ctrl-C cancels the submitted coroutine. Calling run_sync on its own background loop raises RuntimeError. Waiting there for a coroutine would prevent that same loop from running it.
async def _double(x):
await asyncio.sleep(0.01)
return x*2
test_eq(run_sync(_double(3)), 6)
async def _boom(): raise ValueError('boom')
with expect_fail(Exception, 'boom'): run_sync(_boom())
async def _nested(): return run_sync(_double(1))
with expect_fail(Exception, 'its own event loop'): run_sync(_nested())You can call run_sync from a thread with a running event loop, such as a Jupyter cell. The coroutine runs on the shared background loop. Calling asyncio.run in that situation would raise RuntimeError. This async function can call run_sync:
iter_sync pulls each item by running __anext__ on the shared loop, and closes agen when iteration ends, including when the consumer stops early:
__aenter__ and __aexit__ run on the shared loop, and exception suppression behaves as it would under async with:
athreaded turns a blocking function into an async function that runs its body in a worker thread. This lets async code use blocking libraries without stalling the event loop.
Run f in a worker thread, awaitably; use as @athreaded or @athreaded(executor=...)
The wrapper is a coroutine function and works as a method. It uses the awaiting loop’s default executor and copies contextvars into the worker thread. Exceptions propagate to the awaiting task with their tracebacks.
Calls can run concurrently under asyncio.gather:
Pass executor= for a dedicated pool, so long-blocking calls don’t queue behind whatever else the loop’s shared default pool is running:
Pipe x through each of fs, awaiting values as needed; result is awaitable only if x or a step result is
await maybe_await(o) awaits o if it is awaitable and returns it unchanged otherwise.
then(x, *fs) applies each function to the preceding result. It returns a plain value when all steps are synchronous, or an awaitable if x or any step is awaitable.
This is useful for clients with both sync and async modes. A method can use return then(self.gists.get(gid), ~Self.files.values(), first) in either mode, without duplicating its body.
then awaits every awaitable returned by a step. Steps cannot pass awaitables through as data.
async def _f(): return dict(a=1,b=2)
test_eq(then(dict(a=1,b=2), ~Self.values(), first), 1) # all sync: plain result
test_eq(await then(_f(), ~Self.values(), first), 1) # awaitable start
async def _double(x): return x*2
test_eq(await then(3, _double, mul(10)), 60) # awaitable appears mid-chain
test_eq(then(3), 3)
test_eq(await then(_f()), dict(a=1,b=2)) # no steps: pure passthroughThis is like functools.cache but for async functions, e.g:
Wraps the result of an asynchronous function into an object which can be awaited more than once
CachedCoro and reawaitable are partly based on python issue tracker code from Serhiy Storchaka. They allow an awaitable to be called multiple times.
data
data
Check if obj is an async callable, handling partial wrappers and callable instances
is_async_callable detects whether an object can be called asynchronously. It goes beyond asyncio.iscoroutinefunction by also handling functools.partial-wrapped async functions (unwrapping through any number of layers) and callable objects whose __call__ method is a coroutine. The implementation is thanks to Starlette.
Async map; apply f (sync or async) to items (sync or async iter) concurrently via gather
async def _double(x): return x*2
test_eq(await mapa(mul(2), [1,2,3]), [2,4,6]) # sync f, sync items
test_eq(await mapa(_double, [1,2,3]), [2,4,6]) # async f, sync items
test_eq(await mapa(mul(2), _agen()), [2,4,6]) # sync f, async items
test_eq(await mapa(_double, _agen()), [2,4,6]) # async f, async itemswait_until repeatedly calls a sync or async probe until it returns a truthy result or the timeout expires:
Call probe until truthy, returning its first truthy result
On timeout it raises TimeoutError naming what, or the probe itself when what is omitted:
Debounce delays a function call until wait seconds after calls stop arriving. For example, use it to autosave after editing pauses. Set max_wait to limit how long repeated calls can postpone it.
Calls take no arguments. Keep pending edits or queued items in your own state, and consume them in f.
Coalesce a burst of calls into one run of f, wait secs after calls stop (or max_wait secs after they start)
Calling a Debounce returns immediately and schedules f on its event loop. The instance keeps the loop it was constructed on, or the first loop it is called from.
f can be sync or async. Wrap slow blocking work with athreaded to avoid stalling the loop.
Each call resets the wait timer:
Calls every 0.06 seconds keep resetting the 0.1-second timer. f runs once after the calls stop:
max_wait sets a deadline relative to the start of the burst. Repeated calls reset the wait timer without extending this deadline. Setting max_wait=wait limits f to one run per wait seconds. The following calls never pause for wait seconds. The deadline lets f run before the stream stops:
Calls carry no data. The caller decides whether f uses the latest state or every queued item. Update a saved value for the first case, or accumulate items for the second. This example keeps all queued items and drains them in f:
By default, an exception in f ends the worker task and discards pending calls. Asyncio reports the unhandled exception.
With on_error, the handler receives the exception and the worker continues. Pending calls still run, and f can schedule a retry by calling the debounce again:
flush runs pending work immediately, for example at shutdown. It does nothing when no work is pending. Exceptions propagate to its caller, bypassing on_error.
Call flush and cancel only from the owner loop:
cancel discards the pending fire:
After the instance has an owner loop, calls from other threads are forwarded to it, even if those threads have their own loops:
Calls to the debounce while an async f is running return immediately and request one further run. That run waits for the current run to finish; the two never overlap:
Releasing the stalled fire lets the one coalesced follow-up run:
enable_async_magics(ip) lets an IPython shell await async line and cell magics. Sync magics continue to work normally. Only unindented magic calls are awaited; calls inside functions are unchanged.
Enabling it twice has no additional effect, and %reset does not disable it.
The transformation runs after IPython converts magic syntax to function calls. This requires wrapping transform_cell, since line_transforms run before line magic conversion.
Undo enable_async_magics on ip
Let line and cell magics on ip be async: coroutine results are awaited. Idempotent per shell.
ip = InteractiveShell()
async def _amag(line): return f'async:{line}'
async def _acell(line, cell): return f'{line}:{cell.strip()}'
ip.register_magic_function(_amag, 'line', 'amag')
ip.register_magic_function(lambda line: f'sync:{line}', 'line', 'smag')
ip.register_magic_function(_acell, 'cell', 'acell')
enable_async_magics(ip)
test_eq((await run_cell(ip, '%amag hi')).result, 'async:hi')
test_eq((await run_cell(ip, '%%acell top\nbody')).result, 'top:body')Out[0]: 'async:hi'
Out[0]: 'sync:hi'
Out[0]: 'top:body'
Synchronous magics return their usual values. An assignment such as x = %amag val stores the awaited result in x:
Enabling twice wraps exactly once, and function bodies stay sync:
fmt post-processes each awaited magic result, and disable_async_magics unwraps the shell:
Out[0]: 'ASYNC:HI'