asyncio-atexit
asyncio-atexit is a Python library that provides `atexit`-like functionality for `asyncio` event loops. It allows users to register coroutines or synchronous functions to be executed when the current `asyncio` event loop closes, rather than when the Python interpreter exits. The current version is 1.0.1, and it is a small utility that is updated infrequently but remains active and functional for its specific purpose.
Warnings
- gotcha Callbacks registered with `asyncio-atexit` are called without arguments. To pass arguments to your cleanup functions, use `functools.partial` to wrap your function with its arguments.
- gotcha Callbacks are only invoked when the associated `asyncio` event loop is explicitly closed. If your application's `asyncio` event loop is never closed (e.g., in long-running services or certain environments like `nest_asyncio` where loops are reused), registered cleanup functions may not execute.
- gotcha `asyncio-atexit.register()` must be called from within a running `asyncio` event loop. Attempting to register a callback without an active loop will likely result in an error or unexpected behavior.
- breaking Using `asyncio-atexit` with `nest-asyncio` can be problematic, particularly on Python 3.14+. `nest_asyncio` alters the `asyncio` loop's closing behavior, often leaving the loop open and reused rather than truly closing it. This prevents `asyncio-atexit`'s hooks from firing, as they depend on the loop's `close()` method being called. Python 3.14+ further breaks `nest_asyncio` workarounds for nested loops.
Install
-
pip install asyncio-atexit
Imports
- register
from asyncio_atexit import register
- unregister
from asyncio_atexit import unregister
Quickstart
import asyncio
import asyncio_atexit
import functools
async def async_cleanup_task(resource_name):
print(f"Async cleanup for {resource_name} started...")
await asyncio.sleep(0.1) # Simulate async work
print(f"Async cleanup for {resource_name} finished.")
def sync_cleanup_task(message):
print(f"Sync cleanup: {message}")
async def main():
print("Main application started.")
# Register an async cleanup task
asyncio_atexit.register(functools.partial(async_cleanup_task, "Database Connection"))
# Register a synchronous cleanup task
asyncio_atexit.register(functools.partial(sync_cleanup_task, "Closing log file."))
print("Cleanup tasks registered.")
await asyncio.sleep(0.5) # Simulate main application work
print("Main application finishing.")
if __name__ == '__main__':
try:
asyncio.run(main())
finally:
# In some scenarios, especially with older asyncio.run or specific policies,
# you might explicitly close the loop if it wasn't handled by asyncio.run.
# asyncio-atexit hooks only run if the event loop is actually closed.
# This ensures the example triggers the atexit hooks.
loop = asyncio.get_event_loop()
if not loop.is_closed():
loop.close()