aiocontextvars
aiocontextvars provides asyncio support for the PEP-567 contextvars backport, effectively offering 'task local' storage similar to 'threading.local' but scoped to asyncio tasks. It is primarily relevant for Python versions 3.5 and 3.6, as Python 3.7 and later include native `contextvars` support. The project is currently at version 0.2.2 and is explicitly marked for deprecation once native asyncio contextvars support is fully stable in older backports.
Common errors
-
LookupError: 'ContextVar' has no value in this context
cause Attempting to retrieve the value of a `ContextVar` using `var.get()` before a value has been set in the current execution context.fixEnsure `var.set(value)` is called at least once in the current context (or a parent context if inherited) before calling `var.get()`. You can also provide a default value when initializing the `ContextVar`: `my_variable = ContextVar('my_variable', default='initial_value')`. -
AttributeError: 'ContextVar' object has no attribute 'delete'
cause Attempting to use the `delete()` method on a `ContextVar` object, which was removed in version 0.2.0.fixReplace `var.delete()` with `var.reset(token)` where `token` was the result of the `var.set()` call you wish to revert. If no token is available, you cannot 'delete' a value, only reset to a prior state. -
Context not propagating correctly with uvloop or custom task factories.
cause `aiocontextvars` patches `asyncio.get_event_loop` and `loop.create_task`. Custom event loop implementations (like `uvloop`) or directly creating `asyncio.Task` instances via private APIs might bypass these patches, leading to incorrect context propagation.fixAlways use public `asyncio` APIs (e.g., `asyncio.run()`, `asyncio.create_task()`, `asyncio.get_event_loop()`) and ensure `aiocontextvars` is imported at startup. If using `uvloop`, consider if `aiocontextvars` is the right solution, as `uvloop` might need specific integration not provided by this library.
Warnings
- deprecated This package is explicitly marked for deprecation. For Python 3.7+ environments, `contextvars` is built into the standard library and should be used directly. This library's primary purpose is a backport for Python 3.5/3.6.
- breaking Version 0.2.0 introduced significant breaking changes. `ContextVar.delete()` was removed, `enable_inherit()` and `disable_inherit()` functions were removed (inheritance is always enabled), and `ContextVar.set()` now returns a `Token` object which must be used with the newly added `ContextVar.reset(token)` to restore the previous value.
- gotcha For correct behavior (especially patching `asyncio.get_event_loop` and `loop.create_task`), `aiocontextvars` MUST be imported at the very beginning of your application, before any asyncio event loops are created. Loops created before the import will not be patched.
- gotcha Contexts are copied when a new task is created (e.g., via `asyncio.create_task` or `asyncio.gather`). Changes made to `ContextVar` values in a parent task *after* a child task has been spawned will NOT be visible to that already-running child task, as it operates on a snapshot of the context at its creation time.
- gotcha When using `loop.call_soon()` (or similar low-level scheduling functions) in Python 3.5/3.6 and expecting context inheritance, you must explicitly pass the context. The standard `call_soon` in these versions does not automatically copy the context.
Install
-
pip install aiocontextvars
Imports
- ContextVar
from aiocontextvars import ContextVar
- copy_context
from aiocontextvars import copy_context
Quickstart
import asyncio
from aiocontextvars import ContextVar
# Define a ContextVar
my_variable = ContextVar('my_variable', default='default_value')
async def child_task(task_id):
current_val = my_variable.get()
print(f"Task {task_id}: Initial value in child: {current_val}")
my_variable.set(f"value_from_task_{task_id}")
print(f"Task {task_id}: New value set in child: {my_variable.get()}")
await asyncio.sleep(0.1)
async def main():
print(f"Main: Initial value: {my_variable.get()}")
token = my_variable.set("main_context_value")
print(f"Main: Value after set: {my_variable.get()}")
# Spawning child tasks - they inherit the context at creation
tasks = [
asyncio.create_task(child_task(1)),
asyncio.create_task(child_task(2))
]
await asyncio.gather(*tasks)
print(f"Main: Value after children complete: {my_variable.get()}")
my_variable.reset(token)
print(f"Main: Value after reset: {my_variable.get()}")
if __name__ == '__main__':
# IMPORTANT: aiocontextvars must be imported BEFORE creating event loops.
# For Python 3.5/3.6, ensure aiocontextvars is imported early in your application.
asyncio.run(main())