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 allow greenback.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 calls bestow_portal() on certain newly spawned tasks.)

  • with_portal_run(): Run an async function (in the current task) that might eventually make calls to await_(), with a portal available for at least the duration of that call. Use case: less “magical” than ensure_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 to await_(), with a portal available for at least the duration of that call. Use case: same as with_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 to await_() 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 allow greenback.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 by greenback 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 to greenback.await_() in the same task to access an async environment, even if the function that calls await_() 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 use greenback.await_().

If the current task already has a greenback portal set up via a call to one of the other greenback.*_portal() functions, then with_portal_run() simply calls async_fn. If async_fn uses greenback.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 calls ensure_portal(), an additional portal will not be created: the task will still have just the portal installed by with_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 use greenback.await_().

If the current task already has a greenback portal set up via a call to one of the other greenback.*_portal() functions, then with_portal_run() simply calls sync_fn. If sync_fn uses greenback.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 of greenback.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: say trio.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 multiple with_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(), or with_portal_run_sync().

greenback.await_(foo()) is equivalent to await foo(), except that the greenback 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 without await.

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 using greenback.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 to async 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 to async 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(), and aclose() methods), then the returned synchronous iterator implements the corresponding methods send(), throw(), and close(). This allows for better interoperation with yield from, for example.