{"id":464,"library":"async-lru","title":"async-lru: Asynchronous LRU Cache for asyncio","description":"async-lru is a simple LRU (Least Recently Used) cache implementation designed specifically for asynchronous Python functions within the asyncio ecosystem. It serves as a 100% port of Python's built-in `functools.lru_cache` for `async def` functions, ensuring that multiple concurrent calls to a cached coroutine result in only one execution of the wrapped function. The library is actively maintained, with regular releases addressing bug fixes, performance improvements, and new features, with the latest major version being 2.3.0.","status":"active","version":"2.3.0","language":"python","source_language":"en","source_url":"https://github.com/aio-libs/async-lru","tags":["asyncio","cache","lru","decorator"],"install":[{"cmd":"pip install async-lru","lang":"bash","label":"Install latest version"}],"dependencies":[],"imports":[{"note":"Using `functools.lru_cache` on an `async def` function will cache the coroutine object itself, not its awaited result, leading to `RuntimeError: cannot reuse already awaited coroutine` on subsequent awaits. `async-lru` provides `alru_cache` for correct async function caching.","wrong":"from functools import lru_cache","symbol":"alru_cache","correct":"from async_lru import alru_cache"}],"quickstart":{"code":"import asyncio\nimport aiohttp\nfrom async_lru import alru_cache\n\n@alru_cache(maxsize=32, ttl=10, jitter=2)\nasync def get_pep(num):\n    \"\"\"Fetches a PEP from python.org, caches the result.\"\"\"\n    resource = f'http://www.python.org/dev/peps/pep-{num:04d}/'\n    print(f\"Fetching PEP {num}...\")\n    async with aiohttp.ClientSession() as session:\n        try:\n            async with session.get(resource) as s:\n                if s.status == 200:\n                    return await s.text()\n                return f'Not Found (Status: {s.status})'\n        except aiohttp.ClientError as e:\n            return f'Network Error: {e}'\n\nasync def main():\n    print(\"\\n--- First round (misses) ---\")\n    for n in 8, 290, 308, 320:\n        pep = await get_pep(n)\n        print(f\"PEP {n}: {len(pep) if pep else 'Error'} characters\")\n\n    print(\"\\n--- Second round (hits) ---\")\n    for n in 8, 218, 320:\n        pep = await get_pep(n)\n        print(f\"PEP {n}: {len(pep) if pep else 'Error'} characters\")\n\n    print(\"\\n--- Cache Info ---\")\n    print(get_pep.cache_info())\n\n    print(\"\\n--- Checking cache_contains ---\")\n    print(f\"Cache contains PEP 8: {get_pep.cache_contains(8)}\")\n    print(f\"Cache contains PEP 9991: {get_pep.cache_contains(9991)}\")\n\n    # Simulate passage of time for TTL\n    print(\"\\n--- Waiting for TTL expiration (10 seconds) ---\")\n    await asyncio.sleep(10) # Wait for TTL\n\n    print(\"\\n--- After TTL: PEP 8 (should re-fetch) ---\")\n    pep = await get_pep(8)\n    print(f\"PEP 8: {len(pep) if pep else 'Error'} characters\")\n    print(get_pep.cache_info())\n\n    # Closing is optional but highly recommended to release resources\n    await get_pep.cache_close()\n\nif __name__ == '__main__':\n    # This example requires aiohttp for network requests\n    # If aiohttp is not installed, the example will still run but 'get_pep' will fail.\n    # pip install aiohttp\n    try:\n        asyncio.run(main())\n    except RuntimeError as e:\n        print(f\"Caught a runtime error: {e}. This might happen if the event loop is already running.\")","lang":"python","description":"This quickstart demonstrates basic usage of the `alru_cache` decorator with `maxsize`, `ttl` (time-to-live), and `jitter` parameters. It shows how to inspect cache statistics using `cache_info()`, check for cache presence with `cache_contains()`, and explicitly close the cache with `cache_close()` to release resources. Note that `aiohttp` is used for demonstration purposes of an actual async network call."},"warnings":[{"fix":"For versions before 2.3.0, ensure a cache instance is strictly used with a single event loop, or create separate cache instances per loop. If you need cross-loop usage, upgrade to v2.3.0+ and be aware of the new auto-reset behavior.","message":"Cross-event loop cache access behavior changed significantly between v2.2.0 and v2.3.0. Prior to v2.3.0 (from v2.2.0 onwards), attempting to use an `alru_cache` instance with a different event loop than where it was first called would raise a `RuntimeError` ('alru_cache is not safe to use across event loops').","severity":"breaking","affected_versions":">=2.2.0, <2.3.0"},{"fix":"If multi-event loop usage is intentional, be mindful that the cache will reset. For persistent caching across different loops or threads, consider explicit cache management (e.g., using `threading.local` for per-thread/loop caches) or a shared, thread-safe external caching mechanism.","message":"As of v2.3.0, cross-event loop cache access no longer raises a `RuntimeError` but instead triggers an auto-reset and rebind to the current event loop, emitting an `AlruCacheLoopResetWarning`. While this prevents hard crashes, it means the cache effectively clears and reinitializes when the event loop changes, potentially losing cached data.","severity":"gotcha","affected_versions":">=2.3.0"},{"fix":"Always call `await func.cache_close()` on your cached functions when they are no longer needed, typically during application shutdown or when disposing of objects that hold cached methods.","message":"It is highly recommended to explicitly close `alru_cache` instances using `cache_close()`, especially when using `ttl` (time-to-live). Failing to close the cache can lead to resource leaks (e.g., lingering asyncio tasks or timers) that might prevent your application from shutting down cleanly or lead to unexpected behavior.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Utilize the `jitter` parameter (introduced in v2.2.0) with `ttl` to randomize expiration times. For example, `@alru_cache(ttl=3600, jitter=1800)` will spread expirations over a 1.5-hour window around the 1-hour TTL.","message":"When using `ttl` (time-to-live) for cache entries, many entries expiring simultaneously can lead to a 'thundering herd' problem, where many clients try to recompute the same value at once. This can negate the benefits of caching and strain backend resources.","severity":"gotcha","affected_versions":"All versions with `ttl`"},{"fix":"Ensure that 'aiohttp' is installed in the environment where the script is being executed (e.g., by adding `pip install aiohttp` to the setup steps).","message":"The script failed because a required dependency, 'aiohttp', was not found. This indicates an incomplete or incorrect environment setup for running the script.","severity":"breaking","affected_versions":"All versions where 'aiohttp' is required by the tested script but is not installed."},{"fix":"Ensure 'aiohttp' is listed as a dependency and properly installed in the environment where the application is run (e.g., via `pip install aiohttp`). If using a `requirements.txt` file, make sure 'aiohttp' is present there.","message":"The application failed due to a missing 'aiohttp' dependency, resulting in a ModuleNotFoundError. This typically means the package was not included in the environment setup or installation steps.","severity":"breaking","affected_versions":"All versions (where 'aiohttp' is a dependency)"}],"env_vars":null,"last_verified":"2026-05-12T13:58:44.494Z","next_check":"2026-06-26T00:00:00.000Z","problems":[{"fix":"Install the module using pip: 'pip install async-lru'.","cause":"The 'async_lru' module is not installed in the Python environment.","error":"ModuleNotFoundError: No module named 'async_lru'"},{"fix":"Ensure the correct import statement: 'from async_lru import alru_cache'.","cause":"The 'alru_cache' function is not found in the 'async_lru' module, possibly due to an incorrect import statement.","error":"ImportError: cannot import name 'alru_cache' from 'async_lru'"},{"fix":"Create separate cache instances for each event loop to avoid cross-event loop usage.","cause":"The 'alru_cache' instance is being accessed from a different event loop than the one it was first used with.","error":"RuntimeError: alru_cache is not safe to use across event loops: this cache instance was first used with a different event loop. Use separate cache instances per event loop."},{"fix":"Update 'async-lru' to the latest version using pip: 'pip install --upgrade async-lru'.","cause":"The 'ttl' parameter is not recognized, possibly due to using an outdated version of 'async-lru'.","error":"TypeError: alru_cache() got an unexpected keyword argument 'ttl'"},{"fix":"Ensure the function is decorated with '@alru_cache' before calling 'cache_invalidate'.","cause":"The 'cache_invalidate' method is being called on a function that is not decorated with 'alru_cache'.","error":"AttributeError: 'function' object has no attribute 'cache_invalidate'"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":null,"install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.1,"mem_mb":4.5,"disk_size":"18.1M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":4.5,"disk_size":"19M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.17,"mem_mb":5.2,"disk_size":"19.7M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.14,"mem_mb":5.2,"disk_size":"20M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.37,"mem_mb":8.2,"disk_size":"11.5M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.33,"mem_mb":8.2,"disk_size":"12M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.38,"mem_mb":8.7,"disk_size":"11.2M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.34,"mem_mb":8.7,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.09,"mem_mb":4.4,"disk_size":"17.6M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.08,"mem_mb":4.4,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-23","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]}}