quattro: task control for asyncio
quattro is an Apache 2 licensed library, written in Python, for task control in asyncio applications. quattro is influenced by structured concurrency concepts from the Trio framework.
quattro supports Python versions 3.8 - 3.10, and the 3.8 PyPy beta.
Installation
To install quattro, simply:
$ pip install quattro
Task Groups
quattro contains a TaskGroup implementation. TaskGroups are inspired by Trio nurseries.
from quattro import TaskGroup
async def my_handler():
# We want to spawn some tasks, and ensure they are all handled before we return.
async def task_1():
...
async def task_2():
...
async with TaskGroup() as tg:
tg.start_soon(task_1)
tg.start_soon(task_2)
# The end of the `async with` block awaits the tasks, ensuring they are handled.
The implementation has been borrowed from the EdgeDB project.
Cancel Scopes
Cancel scopes are not supported on Python 3.8, since the necessary underlying asyncio machinery is not present on that version.
quattro contains an asyncio implementation of Trio CancelScopes. Due to fundamental differences between asyncio and Trio the actual runtime behavior isn't exactly the same, but close.
from quattro import move_on_after
async def my_handler():
with move_on_after(1.0) as cancel_scope:
await long_query()
# 1 second later, the function continues running
quattro contains the following helpers:
move_on_after
move_on_at
fail_after
fail_at
All helpers produce instances of quattro.CancelScope
, which is largely similar to the Trio variant.
CancelScopes
have the following attributes:
cancel()
- a method through which the scope can be cancelled manuallydeadline
- read/write, an optional deadline for the scope, at which the scope will be cancelledcancelled_caught
- a readonly bool property, whether the scope finished via cancellation
asyncio and Trio differences
fail_after
and fail_at
raise asyncio.Timeout
instead of trio.Cancelled
exceptions when they fail.
asyncio has edge-triggered cancellation semantics, while Trio has level-triggered cancellation semantics. The following example will behave differently in quattro and Trio:
with trio.move_on_after(TIMEOUT):
conn = make_connection()
try:
await conn.send_hello_msg()
finally:
await conn.send_goodbye_msg()
In Trio, if the TIMEOUT
expires while awaiting send_hello_msg()
, send_goodbye_msg()
will also be cancelled. In quattro, send_goodbye_msg()
will run (and potentially block) anyway. This is a limitation of the underlying framework.
In quattro, cancellation scopes cannot be shielded.
Changelog
0.1.0 (UNRELEASED)
- Initial release, containing task groups and cancellation scopes.
Credits
The initial TaskGroup implementation has been taken from the EdgeDB project. The CancelScope implementation was heavily influenced by Trio, and inspired by the async_timeout package.