Aiorun
Aiorun is a Python library that simplifies the creation of `asyncio` applications by providing a `run()` function to manage common boilerplate for startup and graceful shutdown. It automatically handles event loop creation, task scheduling, and signal handling for `SIGINT` and `SIGTERM` (or CTRL-C/CTRL-BREAK on Windows). Aiorun is actively maintained, with a versioning scheme that often reflects the year of release.
Common errors
-
RuntimeError: Event loop is closed
cause This often occurs when attempting to interact with the asyncio event loop or schedule tasks after `aiorun.run()` has completed its execution and closed the loop, or if an event loop created independently is being used after `aiorun` has started its own.fixEnsure all `asyncio` operations, including task creation, are initiated from within the primary coroutine passed to `aiorun.run()` or its descendants. Avoid manually closing the loop or interacting with a loop that `aiorun` has already managed to completion. -
Task exception was never retrieved
cause An exception occurred in an `asyncio` task, but no `try...except` block caught it within the task, and no custom `loop.set_exception_handler()` was configured to process it. `aiorun` logs these by default but does not terminate.fixAdd `try...except` blocks within your `async` functions to handle expected exceptions gracefully. For unhandled exceptions across all tasks, implement a custom `loop.set_exception_handler()` to log, alert, or shut down the application as appropriate. -
My tasks don't stop when I press Ctrl+C (or send SIGINT)
cause While `aiorun` installs signal handlers to initiate graceful shutdown, tasks that do not handle `asyncio.CancelledError` or are protected by `aiorun.shutdown_waits_for()` will not stop immediately upon cancellation requests. Also, on Windows, signal handling is different.fixModify your `async` tasks to include `try...except asyncio.CancelledError` blocks to perform any necessary cleanup when a cancellation is requested. If a task *must* complete before shutdown, wrap its awaitable in `aiorun.shutdown_waits_for()`.
Warnings
- gotcha Unlike `asyncio.run()`, `aiorun.run()` (especially when called without a coroutine or with a coroutine that eventually finishes) will run the event loop indefinitely (`loop.run_forever()`) unless `loop.stop()` is explicitly called or a shutdown signal is received. This can be surprising if you expect the program to exit when the initial coroutine completes.
- gotcha Unhandled exceptions within `asyncio` tasks do not automatically cause `aiorun` to terminate the program by default. The event loop will continue running, and the exception will be logged but not propagated out of `aiorun.run()`, potentially masking critical failures.
- breaking The standard `asyncio.shield()` function does not protect coroutines from cancellation during `aiorun`'s graceful shutdown sequence because `shield()`'s internal task is also subject to cancellation. This can lead to tasks being unexpectedly interrupted during shutdown.
- gotcha On Windows, `aiorun` cannot intercept `SIGINT` or `SIGTERM` signals directly like on Unix-like systems. Instead, it relies on `CTRL-C` and `CTRL-BREAK` events generated when run in a console window. This limits its signal handling capabilities in certain deployment environments.
- gotcha `aiorun.run(coro)` creates a new event loop instance each time it's invoked. This can cause confusing errors if your application code directly interacts with or expects a global 'default' event loop instance provided by the standard library's `asyncio` that is different from the one `aiorun` is managing.
Install
-
pip install aiorun
Imports
- run
from aiorun import run
Quickstart
import asyncio
from aiorun import run
async def my_long_running_task(name, duration):
try:
print(f"Task {name}: Starting for {duration} seconds...")
for i in range(duration):
await asyncio.sleep(1)
print(f"Task {name}: {i + 1}/{duration} seconds passed.")
print(f"Task {name}: Completed normally.")
except asyncio.CancelledError:
print(f"Task {name}: Was cancelled, performing cleanup...")
finally:
print(f"Task {name}: Exiting.")
async def main():
print("Main application starting...")
# Schedule multiple tasks
asyncio.create_task(my_long_running_task("Alpha", 5))
asyncio.create_task(my_long_running_task("Beta", 10))
# Keep main running until a shutdown signal is received
# In aiorun, you don't typically need loop.run_forever() directly
# The 'run()' function itself manages the loop until a signal.
print("Main application tasks scheduled. Waiting for shutdown signal (e.g., Ctrl+C)...")
if __name__ == '__main__':
# aiorun.run() starts the event loop and handles graceful shutdown
# It expects an awaitable (coroutine or task) to start the application logic.
try:
run(main())
except KeyboardInterrupt:
print("\nApplication terminated by KeyboardInterrupt (Ctrl+C).")
print("Application shutdown complete.")