{"id":7014,"library":"async-cache","title":"async-cache","description":"async-cache is an asyncio application layer cache and dataloader for Python-based microservices and applications. It provides features like thundering herd protection, cache warmup, invalidation, and metrics. The current version is 2.0.0, and releases appear to be infrequent, driven by new feature additions.","status":"active","version":"2.0.0","language":"en","source_language":"en","source_url":"https://github.com/iamsinghrajat/async-cache","tags":["asyncio","cache","dataloader","microservices","performance","thundering-herd"],"install":[{"cmd":"pip install async-cache","lang":"bash","label":"Install stable version"}],"dependencies":[],"imports":[{"symbol":"AsyncCache","correct":"from async_cache import AsyncCache"},{"symbol":"InMemoryCacheBackend","correct":"from async_cache import InMemoryCacheBackend"},{"symbol":"DataLoader","correct":"from async_cache import DataLoader"}],"quickstart":{"code":"import asyncio\nimport time\nfrom async_cache import AsyncCache, InMemoryCacheBackend, DataLoader\n\n# --- Basic Caching Example ---\nasync def run_basic_cache_example():\n    print(\"--- Basic Caching Example ---\")\n    # Initialize an in-memory cache backend\n    cache_backend = InMemoryCacheBackend()\n    # Set a default TTL of 60 seconds for cache entries\n    cache = AsyncCache(cache_backend=cache_backend, default_ttl=60)\n\n    @cache.cache(key=\"my_expensive_function:{arg1}\")\n    async def expensive_function(arg1: int, arg2: str) -> str:\n        print(f\"Executing expensive_function with {arg1}, {arg2}...\")\n        await asyncio.sleep(1)  # Simulate network call or heavy computation\n        return f\"Result for {arg1}, {arg2} at {time.time()}\"\n\n    print(\"First call (should execute function):\")\n    result1 = await expensive_function(1, \"hello\")\n    print(f\"Result 1: {result1}\")\n\n    print(\"\\nSecond call (should be cached, no function execution):\")\n    result2 = await expensive_function(1, \"hello\")\n    print(f\"Result 2: {result2}\")\n\n    print(\"\\nThird call (different args, not cached, executes function):\")\n    result3 = await expensive_function(2, \"world\")\n    print(f\"Result 3: {result3}\")\n\n# --- DataLoader Example (v2 feature) ---\nasync def run_dataloader_example():\n    print(\"\\n--- Dataloader Example ---\")\n\n    # A batch function that fetches multiple items efficiently\n    async def fetch_users_batch(user_ids: list[int]) -> list[str]:\n        print(f\"Fetching users for IDs: {user_ids}\")\n        await asyncio.sleep(0.5) # Simulate batch API call\n        return [f\"User_{uid}_data\" for uid in user_ids]\n\n    # Initialize a dataloader with the batch function\n    # The dataloader will collect individual load calls and batch them\n    user_loader = DataLoader(batch_function=fetch_users_batch)\n\n    async def get_user_data(user_id: int) -> str:\n        return await user_loader.load(user_id)\n\n    print(\"Calling get_user_data for multiple IDs (some duplicated):\")\n    # The dataloader will ensure fetch_users_batch is called only once for [1, 2, 3]\n    results = await asyncio.gather(\n        get_user_data(1),\n        get_user_data(2),\n        get_user_data(1), # This will be deduplicated by the dataloader\n        get_user_data(3)\n    )\n    print(f\"Dataloader results: {results}\")\n\nasync def main():\n    await run_basic_cache_example()\n    await run_dataloader_example()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n","lang":"python","description":"This quickstart demonstrates basic asynchronous caching with `AsyncCache` and its `InMemoryCacheBackend`, showcasing how to decorate an async function to cache its results. It also includes an example of `DataLoader` from version 2.0.0, illustrating how to batch requests for multiple items into a single backend call, effectively preventing N+1 problems in async applications."},"warnings":[{"fix":"Use f-strings or explicit argument values in cache key definitions (e.g., `key=\"my_func:{arg1}:{arg2}\"`) to ensure keys are sufficiently granular.","message":"Cache Key Specificity: Using overly broad or static cache keys can lead to incorrect cache hits or prevent dynamic data from being refreshed. Ensure keys are unique per unique set of arguments that determine the cached data.","severity":"gotcha","affected_versions":"All"},{"fix":"Always `await` calls to functions decorated with `@cache.cache` or accessed via `DataLoader`, e.g., `result = await my_cached_function(...)`.","message":"Awaiting Cached Functions: Functions decorated with `@cache.cache` or accessed via `DataLoader.load()` must always be `await`ed, even if the result is a cache hit. Forgetting `await` will result in a coroutine object being returned, not the actual data.","severity":"gotcha","affected_versions":"All"},{"fix":"Understand `InMemoryCacheBackend`'s limitations; implement a custom persistent backend that integrates with `async-cache` for production use cases requiring data survival across restarts.","message":"Backend Persistency: The `InMemoryCacheBackend` (used in examples) is not persistent across application restarts. For production environments requiring data to survive restarts, consider implementing or integrating a persistent cache backend (e.g., Redis).","severity":"gotcha","affected_versions":"All"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Always `await` calls to your cached functions, e.g., `result = await my_cached_function(...)` or `data = await my_dataloader.load(...)`.","cause":"An `async` function decorated with `@cache.cache` or accessed via `DataLoader.load()` was called without `await`. The result is a coroutine object, not the actual cached value.","error":"TypeError: object async_generator is not awaitable"},{"fix":"Install the library using `pip install async-cache`.","cause":"The `async-cache` library is not installed in the current Python environment.","error":"ModuleNotFoundError: No module named 'async_cache'"},{"fix":"Ensure all calls to `async` functions are `await`ed within an `async` context (e.g., inside another `async def` function), or scheduled explicitly using `asyncio.create_task()`.","cause":"An `async` function (possibly decorated with `@cache.cache`) was called, but its returned coroutine object was not `await`ed or scheduled to run within an `asyncio` event loop.","error":"RuntimeWarning: coroutine 'my_function' was never awaited"},{"fix":"Review your `@cache.cache(key=...)` definition. Ensure the key includes all relevant arguments that define uniqueness for the cached data. Use `cache.invalidate_key()` to force refresh if needed, and adjust `default_ttl` or specific `ttl` arguments.","cause":"Incorrect or overly broad cache key, or the Time-To-Live (TTL) for the cache entry is too long, leading to unintended cache misses or stale data.","error":"(No explicit error, but unexpected behavior) My cached data is not updating, or my function is always executing despite caching."}]}