{"id":6311,"library":"asyncio-atexit","title":"asyncio-atexit","description":"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.","status":"active","version":"1.0.1","language":"en","source_language":"en","source_url":"https://github.com/minrk/asyncio-atexit","tags":["asyncio","cleanup","atexit","event-loop"],"install":[{"cmd":"pip install asyncio-atexit","lang":"bash","label":"Install stable version"}],"dependencies":[],"imports":[{"symbol":"register","correct":"from asyncio_atexit import register"},{"symbol":"unregister","correct":"from asyncio_atexit import unregister"}],"quickstart":{"code":"import asyncio\nimport asyncio_atexit\nimport functools\n\nasync def async_cleanup_task(resource_name):\n    print(f\"Async cleanup for {resource_name} started...\")\n    await asyncio.sleep(0.1) # Simulate async work\n    print(f\"Async cleanup for {resource_name} finished.\")\n\ndef sync_cleanup_task(message):\n    print(f\"Sync cleanup: {message}\")\n\nasync def main():\n    print(\"Main application started.\")\n    # Register an async cleanup task\n    asyncio_atexit.register(functools.partial(async_cleanup_task, \"Database Connection\"))\n    # Register a synchronous cleanup task\n    asyncio_atexit.register(functools.partial(sync_cleanup_task, \"Closing log file.\"))\n    print(\"Cleanup tasks registered.\")\n    await asyncio.sleep(0.5) # Simulate main application work\n    print(\"Main application finishing.\")\n\nif __name__ == '__main__':\n    try:\n        asyncio.run(main())\n    finally:\n        # In some scenarios, especially with older asyncio.run or specific policies,\n        # you might explicitly close the loop if it wasn't handled by asyncio.run.\n        # asyncio-atexit hooks only run if the event loop is actually closed.\n        # This ensures the example triggers the atexit hooks.\n        loop = asyncio.get_event_loop()\n        if not loop.is_closed():\n            loop.close()\n","lang":"python","description":"Registers an asynchronous function and a synchronous function to be called when the asyncio event loop shuts down. This example demonstrates using `functools.partial` to pass arguments to the cleanup functions, as `asyncio-atexit` callbacks are invoked without arguments by default. The `asyncio.run` function typically manages loop closure, but an explicit `loop.close()` is added in the `finally` block to guarantee cleanup function execution in all environments for demonstration purposes."},"warnings":[{"fix":"Wrap your target function with `functools.partial` before registering it, e.g., `asyncio_atexit.register(functools.partial(my_cleanup_func, arg1, kwarg='value'))`.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure that `loop.close()` is called on the event loop where callbacks were registered. If using `asyncio.run()`, ensure it completes normally, as it typically handles loop closure. In complex scenarios or with libraries like `nest_asyncio`, you might need to manage loop lifecycle more explicitly.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure that `asyncio_atexit.register()` calls are made after the event loop has started, typically within an `async` function executed by `asyncio.run()` or `loop.run_until_complete()`.","message":"`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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Avoid using `asyncio-atexit` with `nest-asyncio` if loop-closing guarantees are critical. If `nest-asyncio` is indispensable, ensure manual `loop.close()` calls are made when the outermost logical 'event loop' concludes, or reconsider cleanup strategies that don't rely on `asyncio-atexit`'s specific hook.","message":"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.","severity":"breaking","affected_versions":"asyncio-atexit all versions, especially with nest-asyncio on Python 3.14+"}],"env_vars":null,"last_verified":"2026-04-15T00:00:00.000Z","next_check":"2026-07-14T00:00:00.000Z"}