Aiotools: Idiomatic Asyncio Utilities
aiotools is a collection of idiomatic utilities designed to reduce boilerplate code when working with `asyncio`. It provides robust solutions for safe cancellation, structured concurrency through `TaskScope`, asynchronous context managers, multi-process server daemons, and other high-level coroutine utilities. The library is actively maintained and currently at version 2.2.3, with a release cadence that includes regular bug fixes and feature enhancements, targeting Python 3.11 and newer.
Warnings
- breaking The `TaskGroup` class has been deprecated since `aiotools` v2.0 in favor of `TaskScope`. While `TaskGroup` still exists, `TaskScope` is the recommended high-level API for structured concurrency and provides behavior consistent with `asyncio.TaskGroup` introduced in Python 3.11.
- deprecated The `aiotools.func.apartial` utility was deprecated in `aiotools` v2.0. Python's built-in `functools.partial()` now works seamlessly with asynchronous functions as of Python 3.8 and should be used instead.
- gotcha The `aiotools.timer.VirtualClock` feature, used for deterministic testing of `asyncio.sleep()` calls, relies on patching event loop internals and is only functional on UNIX-like operating systems. It will not work on Windows.
- gotcha Understanding the difference in error handling between `TaskScope` and `asyncio.TaskGroup` (which `aiotools.TaskGroup` wrapped prior to Python 3.11, and `TaskScope` now extends). `TaskScope` is designed for server-oriented tasks where the failure of one child task does NOT automatically cancel all other sibling tasks, allowing for more resilient services. `asyncio.TaskGroup` (and older `aiotools.TaskGroup` behavior) will cancel all siblings if one task raises an unhandled exception.
- gotcha When manually cancelling `asyncio` tasks, ensure to use `aiotools.cancel.cancel_and_wait()` for consistent behavior. Without it, managing `CancelledError` propagation (re-raising vs. absorbing) can be tricky and lead to inconsistencies across your codebase, especially prior to Python 3.11's structured concurrency improvements.
Install
-
pip install aiotools
Imports
- TaskScope
from aiotools import TaskScope
- cancel_and_wait
from aiotools.cancel import cancel_and_wait
- actxmgr
from aiotools.context import actxmgr
- start_server
from aiotools.server import start_server
- Supervisor
from aiotools.supervisor import Supervisor
Quickstart
import asyncio
from aiotools import TaskScope
async def worker(name, delay):
try:
print(f"Worker {name}: Starting...")
await asyncio.sleep(delay)
print(f"Worker {name}: Finished after {delay}s.")
return f"Result from {name}"
except asyncio.CancelledError:
print(f"Worker {name}: Was cancelled!")
raise
except Exception as e:
print(f"Worker {name}: Encountered error: {e}")
raise
async def main():
print("Main: Starting TaskScope example")
async with TaskScope() as scope:
task1 = scope.create_task(worker("Alpha", 2))
task2 = scope.create_task(worker("Beta", 1))
task3 = scope.create_task(worker("Gamma", 3))
# You can await individual tasks within the scope
print(f"Main: Awaiting Task Beta...")
result2 = await task2
print(f"Main: Task Beta finished with: {result2}")
print("Main: All tasks in TaskScope are complete or cancelled.")
# After exiting the TaskScope, all tasks are guaranteed to be done.
# Results and exceptions from other tasks can be retrieved if needed.
print(f"Main: Task Alpha result: {task1.result()}")
print(f"Main: Task Gamma result: {task3.result()}")
if __name__ == "__main__":
asyncio.run(main())