Introcution

Prerequistes

AioContext works with Python 3.4 and greater. It is compatible with both asyncio and uvloop event loops and does not depend on external libraries.

Installation

The recommended way to install packages is to use pip inside a virtual environment:

$ pip install aiocontext

To install a development version:

$ git clone https://github.com/sqreen/AioContext.git
$ cd aiocontext
$ pip install -r requirements.txt
$ pip install -e .

Basic API usage

This section is a brief introduction to AioContext API.

AioContext allows to store context information inside the asyncio.Task object. A typical use case for it is to pass information between coroutine calls without the need to do it explicitly using the called coroutine args.

To create a new context store, instanciate a aiocontext.Context instance:

from aiocontext import Context
context = Context()

A context object is a dict so you can store any value you want inside. For example, in a web application, you can share a request_id between asynchronous calls with the following code:

async def print_request():
    print("Request ID:", context.get('request_id', 'unknown'))

async def handle_request():
    context['request_id'] = 42
    await print_request()

To enable context propagation between tasks (i.e. between calls like asyncio.ensure_future(), asyncio.wait_for(), asyncio.gather(), etc.), the task factory of the event loop must be changed to be made context-aware. This is done by calling aiocontext.wrap_task_factory():

from aiocontext import wrap_task_factory
loop = asyncio.get_event_loop()
wrap_task_factory(loop)

If a custom task factory is already set, this function will “wrap” it with context management code, so it must be called after asyncio.Loop.set_task_factory().

Finally, the context must be attached to the event loop:

context.attach(loop)

The full code looks like:

import asyncio
import aiocontext

context = aiocontext.Context()

async def print_request():
    print("Request ID:", context.get('request_id', 'unknown'))

async def handle_request():
    context['request_id'] = 42
    await print_request()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    aiocontext.wrap_task_factory(loop)
    context.attach(loop)
    loop.run_until_complete(handle_request())

Comparison with other solutions

aiotask-context was an important source of inspiration and is a more battle-tested library. It provides a simpler API with a global, unique context. It does not support overloading custom task factories at the moment.

aiolocals is another library to track task-local states. It comes with aiohttp integration to track HTTP requests. New tasks must be explicitly spawned with a wrap_async function to share contexts, which may be problematic when using libraries.

tasklocals strives to provide an interface similar to threading.local(). It provides no mechanism of context sharing when a child task is spawned. The project looks abandoned.

In the future, asynchronous context storage could be supported natively in the Python language. This is discussed in PEP 550 and PEP 567.