API reference¶
Creating a portal¶
In order to use greenback
in a particular async task, you must first create
a greenback portal for that task to use. You may choose between:
ensure_portal()
: Create a portal to be used by the current task, which lasts for the lifetime of that task. Use case: minimally invasive code change to allowgreenback.await_()
in a particular task.bestow_portal()
: Create a portal to be used by some other specified task, which lasts for the lifetime of that task. Use case: enabling greenback in a task without that task’s cooperation, which may be useful in some debugging and instrumentation situations. (with_portal_run_tree()
is implemented using a Trio instrument that callsbestow_portal()
on certain newly spawned tasks.)with_portal_run()
: Run an async function (in the current task) that might eventually make calls toawait_()
, with a portal available for at least the duration of that call. Use case: less “magical” thanensure_portal()
; keeps the portal (and its perforamnce impact) scoped to just the portion of a task that needs it.with_portal_run_sync()
: Run a synchronous function (in the current task) that might eventually make calls toawait_()
, with a portal available for at least the duration of that call. Use case: same aswith_portal_run()
, but the implementation is simpler and will be a bit faster (probably only noticeable if the function you’re running is very short).with_portal_run_tree()
: Run an async function (in the current task) that can make calls toawait_()
both itself and in all of its child tasks, recursively. Available on Trio only, since asyncio lacks a clear task tree and also lacks the instrumentation features required to implement this. Use case: minimally invasive code change to allowgreenback.await_()
in an entire subsystem of your Trio program.
You can use has_portal()
to determine whether a portal has already
been set up.
- await greenback.ensure_portal()¶
Ensure that the current async task is able to use
greenback.await_()
.If the current task has called
ensure_portal()
previously, calling it again is a no-op. Otherwise,ensure_portal()
interposes a “coroutine shim” provided bygreenback
in between the event loop and the coroutine being used to run the task. For example, when running under Trio,trio.lowlevel.Task.coro
is replaced with a wrapper around the coroutine it previously referred to. (The same thing happens under asyncio, but asyncio doesn’t expose the coroutine field publicly, so some additional trickery is required in that case.)After installation of the coroutine shim, each task step passes through
greenback
on its way into and out of your code. At some performance cost, this effectively provides a portal that allows later calls togreenback.await_()
in the same task to access an async environment, even if the function that callsawait_()
is a synchronous function.This function is a cancellation point and a schedule point (a checkpoint, in Trio terms) even if the calling task already had a portal set up.
- greenback.bestow_portal(task)¶
Ensure that the given async task is able to use
greenback.await_()
.This works like calling
ensure_portal()
from within task, with one exception: if you pass the currently running task, then the portal will not become usable until after the task yields control to the event loop.
- await greenback.with_portal_run(async_fn, *args, **kwds)¶
Execute
await async_fn(*args, **kwds)
in a context that is able to usegreenback.await_()
.If the current task already has a greenback portal set up via a call to one of the other
greenback.*_portal()
functions, thenwith_portal_run()
simply calls async_fn. If async_fn usesgreenback.await_()
, the existing portal will take care of it.Otherwise (if there is no portal already available to the current task),
with_portal_run()
creates a new portal which lasts only for the duration of the call to async_fn. If async_fn then callsensure_portal()
, an additional portal will not be created: the task will still have just the portal installed bywith_portal_run()
, which will be removed when async_fn returns.This function does not add any cancellation point or schedule point beyond those that already exist inside async_fn.
- await greenback.with_portal_run_sync(sync_fn, *args, **kwds)¶
Execute
sync_fn(*args, **kwds)
in a context that is able to usegreenback.await_()
.If the current task already has a greenback portal set up via a call to one of the other
greenback.*_portal()
functions, thenwith_portal_run()
simply calls sync_fn. If sync_fn usesgreenback.await_()
, the existing portal will take care of it.Otherwise (if there is no portal already available to the current task),
with_portal_run_sync()
creates a new portal which lasts only for the duration of the call to sync_fn.This function does not add any cancellation point or schedule point beyond those that already exist due to any
await_()
s inside sync_fn.
- await greenback.with_portal_run_tree(async_fn, *args, **kwds)¶
Execute
await async_fn(*args, **kwds)
in a context that allows use ofgreenback.await_()
both in async_fn itself and in any tasks that are spawned into child nurseries of async_fn, recursively.You can use this to create an entire Trio run (except system tasks) that runs with
greenback.await_()
available: saytrio.run(with_portal_run_tree, main)
.This function does not add any cancellation point or schedule point beyond those that already exist inside async_fn.
Availability: Trio only.
Note
The automatic “portalization” of child tasks is implemented using a Trio
instrument
, which has a small performance impact on task spawning for the entire Trio run. To minimize this impact, a single instrument is used even if you have multiplewith_portal_run_tree()
calls running simultaneously, and the instrument will be removed as soon as all such calls have completed.
- greenback.has_portal(task=None)¶
Return true if the given task is currently able to use
greenback.await_()
, false otherwise. If no task is specified, query the currently executing task.
Using the portal¶
Once you’ve set up a portal using any of the above functions, you can use it
to run async functions by making calls to greenback.await_()
:
- greenback.await_(awaitable)¶
Run an async function or await an awaitable from a synchronous function, using the portal set up for the current async task by
ensure_portal()
,bestow_portal()
,with_portal_run()
, orwith_portal_run_sync()
.greenback.await_(foo())
is equivalent toawait foo()
, except that thegreenback
version can be written in a synchronous function while the native version cannot.
Additional utilities¶
greenback
comes with a few tools (built atop await_()
) which may
be helpful when adapting async code to work with synchronous interfaces.
- @greenback.autoawait¶
Decorator for an async function which allows (and requires) it to be called from synchronous contexts without
await
.For example, this can be used for magic methods, property setters, and so on.
- @greenback.decorate_as_sync(decorator: Callable[[F], F]) Callable[[AF], AF] ¶
- @greenback.decorate_as_sync(decorator: Callable[[...], Any]) Callable[[Callable[[...], Awaitable[Any]]], Callable[[...], Awaitable[Any]]]
Wrap the synchronous function decorator decorator so that it can be used to decorate an async function.
This can be used, for example, to apply an async-naive decorator such as
@functools.lru_cache()
to an async function:@greenback.decorate_as_sync(functools.lru_cache(maxsize=128)) async def some_fn(...): ...
Without the wrapping in
decorate_as_sync()
, the LRU cache would treat the inner function as a synchronous function, and would therefore unhelpfully cache the coroutine object that is returned when an async function is called withoutawait
.Internally, the “inner” async function is wrapped in a synchronous function that invokes that async function using
greenback.await_()
. This synchronous function is then decorated with the decorator.decorate_as_sync()
returns an “outer” async function which invokes the internal decorated synchronous function usinggreenback.with_portal_run_sync()
.In other words, the following two calls behave identically:
result = await greenback.decorate_as_sync(decorator)(async_fn)(*args, **kwds) result = await greenback.with_portal_run_sync( decorator(greenback.autoawait(async_fn)), *args, **kwds, )
- with greenback.async_context(async_cm)¶
Wraps an async context manager so it is usable in a synchronous
with
statement. That is,with async_context(foo) as bar:
behaves equivantly toasync with foo as bar:
as long as a portal has been created somewhere up the callstack.
- for ... in greenback.async_iter(async_iterable)¶
Wraps an async iterable so it is usable in a synchronous
for
loop,yield from
statement, or similar synchronous iteration context. That is,for elem in async_iter(foo):
behaves equivantly toasync for elem in foo:
as long as a portal has been created somewhere up the callstack.If the obtained async iterator implements the full async generator protocol (
asend()
,athrow()
, andaclose()
methods), then the returned synchronous iterator implements the corresponding methodssend()
,throw()
, andclose()
. This allows for better interoperation withyield from
, for example.