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.