{"id":15527270,"url":"https://github.com/turall/cache-house","last_synced_at":"2025-12-26T08:54:33.533Z","repository":{"id":45179700,"uuid":"443565633","full_name":"Turall/cache-house","owner":"Turall","description":null,"archived":false,"fork":false,"pushed_at":"2022-02-05T17:25:58.000Z","size":70,"stargazers_count":15,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-19T09:58:18.472Z","etag":null,"topics":["caching","fastapi","python","redis","redis-cache","redis-cluster"],"latest_commit_sha":null,"homepage":"https://github.com/Turall/cache-house","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Turall.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-01T15:24:33.000Z","updated_at":"2023-06-10T17:29:10.000Z","dependencies_parsed_at":"2022-09-24T19:51:08.117Z","dependency_job_id":null,"html_url":"https://github.com/Turall/cache-house","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Turall%2Fcache-house","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Turall%2Fcache-house/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Turall%2Fcache-house/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Turall%2Fcache-house/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Turall","download_url":"https://codeload.github.com/Turall/cache-house/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250431458,"owners_count":21429480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","fastapi","python","redis","redis-cache","redis-cluster"],"created_at":"2024-10-02T11:05:20.313Z","updated_at":"2025-12-26T08:54:33.527Z","avatar_url":"https://github.com/Turall.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cdiv align=\"center\"\u003e \u003ch2\u003e Caching tool for python, working with Redis single instance and Redis cluster mode \u003ch1\u003e \u003c/div\u003e\n\n\n[PyPi link](https://pypi.org/project/cache-house/)\n\n### Features ###\n\n- ✅ **Automatic reconnection**: Redis client handles reconnection automatically\n- ✅ **Graceful degradation**: Falls back to in-memory cache when Redis is unavailable\n- ✅ **No crashes**: All operations handle errors gracefully\n- ✅ **Async \u0026 Sync support**: Works with both async and sync functions\n- ✅ **Redis Cluster support**: Works with single Redis instance and Redis Cluster\n- ✅ **Custom encoders/decoders**: Support for custom serialization\n\n### Installation ###\n\n```sh\n $ pip install cache-house \n```\nor with poetry\n```sh\npoetry add cache-house\n```\n\n\n*****\n### ***Quick Start*** ###\n*****\n\nCache decorator works with both async and sync functions. The library automatically handles Redis reconnection and falls back to in-memory cache when Redis is unavailable.\n\n```python\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\nimport asyncio\n\n# Initialize Redis with fallback enabled (recommended for production)\nRedisFactory.init(fallback_to_memory=True)\n\n@cache()  # default expire time is 180 seconds\nasync def test_cache(a: int, b: int):\n    print(\"async cached - this only prints on cache miss\")\n    return [a, b]\n\n@cache()\ndef test_cache_1(a: int, b: int):\n    print(\"cached - this only prints on cache miss\")\n    return [a, b]\n\n\nif __name__ == \"__main__\":\n    print(\"First call (cache miss):\")\n    print(test_cache_1(3, 4))\n    print(\"\\nSecond call (cache hit):\")\n    print(test_cache_1(3, 4))  # This will use cache, print won't execute\n    \n    print(\"\\nAsync function:\")\n    print(asyncio.run(test_cache(1, 2)))\n    print(asyncio.run(test_cache(1, 2)))  # Cached\n```\n\n**Output:**\n```\nFirst call (cache miss):\ncached - this only prints on cache miss\n[3, 4]\n\nSecond call (cache hit):\n[3, 4]  # No print - served from cache\n\nAsync function:\nasync cached - this only prints on cache miss\n[1, 2]\n[1, 2]  # Cached\n```\n\nCheck stored cache keys:\n```sh\n➜ $ rdcli KEYS \"*\"\n1) cachehouse:main:8f65aed1010f0062a783c83eb430aca0\n2) cachehouse:main:f665833ea64e4fc32653df794257ca06\n```\n\n*****\n### ***Setup Redis cache instance***\n*****\n\nYou can pass all [redis-py](https://github.com/redis/redis-py) arguments to `RedisFactory.init` method and additional arguments: \n\n```python\ndef RedisFactory.init(\n        host: str = \"localhost\",\n        port: int = 6379,\n        encoder: Callable[..., Any] = ...,\n        decoder: Callable[..., Any] = ...,\n        namespace: str = ...,\n        key_prefix: str = ...,\n        key_builder: Callable[..., Any] = ...,\n        password: str = ...,\n        db: int = ...,\n        cluster_mode: bool = False,        # Force cluster mode (skip auto-detection)\n        autodetect_cluster: bool = True,   # Auto-detect if Redis is running in cluster mode\n        fallback_to_memory: bool = True,   # Enable in-memory fallback when Redis is unavailable\n        **redis_kwargs\n    )\n```\n\n#### ***Cluster auto-detection***\n\nBy default (`autodetect_cluster=True`), `RedisFactory.init` will:\n\n- Try to send `CLUSTER INFO` to the target Redis node\n- If the command succeeds → **cluster mode is detected**, and `RedisClusterCache` is used internally\n- If the command fails with a Redis error → **standalone mode is assumed**, and `RedisCache` is used\n\nThis means you can usually just call:\n\n```python\nfrom cache_house.backends import RedisFactory\n\nRedisFactory.init(\n    host=\"localhost\",\n    port=6379,\n    fallback_to_memory=True,\n    # autodetect_cluster=True by default\n)\n```\n\nand cache-house will automatically choose the correct backend (standalone or cluster) based on the Redis server configuration.\n\n#### ***Explicit modes (optional)***\n\n- **Force standalone Redis (no detection)**:\n\n```python\nRedisFactory.init(\n    host=\"localhost\",\n    port=6379,\n    cluster_mode=False,\n    autodetect_cluster=False,  # Always use standalone RedisCache\n)\n```\n\n- **Force Redis Cluster (no detection)**:\n\n```python\nRedisFactory.init(\n    host=\"localhost\",\n    port=6379,\n    cluster_mode=True,         # Always use RedisClusterCache\n    autodetect_cluster=False,  # Optional, explicit\n)\n```\n\n#### ***Best Practice: Initialize Redis with fallback enabled***\n\n```python\nfrom cache_house.backends import RedisFactory\n\n# Initialize with fallback to memory cache (default: True)\n# Your application will continue working even if Redis is temporarily unavailable\nRedisFactory.init(\n    host=\"localhost\",\n    port=6379,\n    password=\"your_password\",  # Optional\n    db=0,\n    fallback_to_memory=True  # Falls back to in-memory cache when Redis is down\n)\n```\n\n#### ***Custom encoder and decoder***\n\n```python\nfrom cache_house.backends import RedisFactory\nimport json\n\ndef custom_encoder(data):\n    return json.dumps(data)\n\ndef custom_decoder(data):\n    return json.loads(data)\n\nRedisFactory.init(\n    encoder=custom_encoder, \n    decoder=custom_decoder,\n    fallback_to_memory=True\n)\n```\n\n#### ***Default encoder and decoder is pickle module.***\n\n*****\n### ***Setup Redis Cluster cache instance***\n*****\n\nAll manipulation with `RedisCache` is the same with `RedisClusterCache`\n\n```python\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\n# Initialize Redis Cluster with fallback enabled\nRedisFactory.init(\n    cluster_mode=True,\n    startup_nodes=[\n        {\"host\": \"127.0.0.1\", \"port\": \"7000\"},\n        {\"host\": \"127.0.0.1\", \"port\": \"7001\"},\n    ],\n    fallback_to_memory=True  # Falls back to in-memory cache when cluster is unavailable\n)\n\n@cache()\nasync def test_cache(a: int, b: int):\n    print(\"cached\")\n    return [a, b]\n```\n\n**Redis Cluster parameters** (all [redis-py cluster](https://redis-py.readthedocs.io/en/stable/cluster.html) arguments are supported):\n\n```python\nRedisFactory.init(\n    cluster_mode=True,\n    startup_nodes=[{\"host\": \"127.0.0.1\", \"port\": \"7000\"}],\n    cluster_error_retry_attempts: int = 3,\n    require_full_coverage: bool = True,\n    skip_full_coverage_check: bool = False,\n    reinitialize_steps: int = 10,\n    read_from_replicas: bool = False,\n    fallback_to_memory: bool = True,\n    **redis_kwargs\n)\n```\n\n*****\n### ***Setup cache instance with FastAPI***\n*****\n\n**Best Practice**: Initialize Redis in startup event with fallback enabled. Your application will continue working even if Redis is temporarily unavailable.\n\n```python\nimport logging\nimport uvicorn\nfrom fastapi.applications import FastAPI\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\napp = FastAPI()\n\n\n@app.on_event(\"startup\")\nasync def startup():\n    # Initialize with fallback - app won't crash if Redis is unavailable\n    RedisFactory.init(\n        host=\"localhost\",\n        port=6379,\n        fallback_to_memory=True  # Enable in-memory fallback\n    )\n    print(\"App started - Redis cache initialized\")\n\n\n@app.on_event(\"shutdown\")\nasync def shutdown():\n    # Gracefully close connections\n    RedisFactory.close_connections()\n    print(\"App shutdown - Redis connections closed\")\n\n\n@app.get(\"/notcached\")\nasync def test_route():\n    print(\"notcached\")\n    return {\"hello\": \"world\"}\n\n\n@app.get(\"/cached\")\n@cache(expire=60)  # Cache for 60 seconds\nasync def test_route():\n    print(\"cached\")  # This print only runs on cache miss\n    return {\"hello\": \"world\"}\n\n\n@app.get(\"/cached-with-custom-expire\")\n@cache(expire=300, namespace=\"api\")  # Cache for 5 minutes with custom namespace\nasync def expensive_operation():\n    # Simulate expensive operation\n    import time\n    time.sleep(1)\n    return {\"result\": \"expensive computation\"}\n\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, port=8033)\n```\n\n\n*****\n### ***Cache decorator options***\n*****\n\nYou can set expire time (seconds or timedelta), namespace, and key prefix in the cache decorator:\n\n```python\nfrom datetime import timedelta\nfrom cache_house.cache import cache\n\n# Using seconds\n@cache(expire=30, namespace=\"app\", key_prefix=\"test\") \nasync def test_cache(a: int, b: int):\n    print(\"cached\")\n    return [a, b]\n\n# Using timedelta\n@cache(expire=timedelta(minutes=5), namespace=\"app\", key_prefix=\"test\")\ndef test_cache_sync(a: int, b: int):\n    print(\"cached\")\n    return [a, b]\n\nif __name__ == \"__main__\":\n    print(asyncio.run(test_cache(1, 2)))\n    print(test_cache_sync(3, 4))\n```\n\nCheck stored cache:\n```sh\nrdcli KEYS \"*\"\n1) test:app:f665833ea64e4fc32653df794257ca06\n```\n\n*****\n### ***Understanding Namespaces and Key Builders***\n*****\n\n#### **Namespaces**\n\nNamespaces help organize your cache keys and make it easier to manage different parts of your application. The default namespace is `\"main\"`.\n\n**Key Format**: `{prefix}:{namespace}:{hash}`\n\n**Example with different namespaces:**\n\n```python\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\nRedisFactory.init(fallback_to_memory=True)\n\n# API endpoints namespace\n@cache(expire=60, namespace=\"api\")\ndef get_user(user_id: int):\n    return {\"id\": user_id, \"name\": f\"User {user_id}\"}\n\n# Database queries namespace\n@cache(expire=300, namespace=\"database\")\ndef get_user_posts(user_id: int):\n    return [{\"id\": 1, \"title\": \"Post 1\"}]\n\n# Configuration namespace\n@cache(expire=3600, namespace=\"config\")\ndef get_app_config():\n    return {\"setting\": \"value\"}\n\n# Default namespace (if not specified)\n@cache(expire=180)\ndef default_function():\n    return \"default\"\n```\n\n**Cache keys will be:**\n```\ncachehouse:api:abc123...\ncachehouse:database:def456...\ncachehouse:config:ghi789...\ncachehouse:main:jkl012...  # default namespace\n```\n\n**Benefits of using namespaces:**\n- **Organization**: Group related cache entries together\n- **Easy cleanup**: Clear all keys in a specific namespace\n- **Multi-tenancy**: Separate cache for different applications/services\n- **Debugging**: Easier to identify cache keys in Redis\n\n#### **Global Namespace Configuration**\n\nYou can set a default namespace for all cache operations:\n\n```python\nfrom cache_house.backends import RedisFactory\n\n# Set default namespace for all cache operations\nRedisFactory.init(\n    namespace=\"myapp\",  # All cache keys will use \"myapp\" namespace by default\n    key_prefix=\"app\",  # Change default prefix from \"cachehouse\" to \"app\"\n    fallback_to_memory=True\n)\n\n# This will use \"myapp\" namespace\n@cache(expire=60)\ndef my_function():\n    return \"data\"\n\n# Override namespace for specific function\n@cache(expire=60, namespace=\"special\")\ndef special_function():\n    return \"special data\"\n```\n\n**Resulting keys:**\n```\napp:myapp:abc123...  # default namespace\napp:special:def456...  # overridden namespace\n```\n\n#### **Key Prefix**\n\nThe key prefix is the first part of every cache key. Default is `\"cachehouse\"`.\n\n```python\n# Global prefix\nRedisFactory.init(key_prefix=\"myapp\", namespace=\"v1\")\n\n# Per-decorator prefix (overrides global)\n@cache(expire=60, key_prefix=\"api\", namespace=\"users\")\ndef get_user(id: int):\n    return {\"id\": id}\n```\n\n**Key format**: `{key_prefix}:{namespace}:{hash}`\n\n#### **Custom Key Builder**\n\nYou can create your own key builder function for complete control over cache key generation:\n\n```python\nimport hashlib\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\ndef custom_key_builder(module, name, args, kwargs, prefix=\"cachehouse\", namespace=\"main\"):\n    \"\"\"\n    Custom key builder function\n    \n    Args:\n        module: Function's module name\n        name: Function name\n        args: Function positional arguments\n        kwargs: Function keyword arguments\n        prefix: Key prefix\n        namespace: Namespace\n    \"\"\"\n    # Example: Create a more readable key\n    # Format: prefix:namespace:module.function:arg1:arg2:kwarg1=value1\n    key_parts = [prefix, namespace, f\"{module}.{name}\"]\n    \n    # Add positional arguments\n    for arg in args:\n        key_parts.append(str(arg))\n    \n    # Add keyword arguments\n    for k, v in sorted(kwargs.items()):\n        key_parts.append(f\"{k}={v}\")\n    \n    # Join and create hash for long keys\n    key_string = \":\".join(key_parts)\n    if len(key_string) \u003e 200:  # Redis key length limit\n        key_hash = hashlib.md5(key_string.encode()).hexdigest()\n        return f\"{prefix}:{namespace}:{key_hash}\"\n    \n    return key_string\n\n# Use custom key builder globally\nRedisFactory.init(\n    key_builder=custom_key_builder,\n    namespace=\"custom\",\n    fallback_to_memory=True\n)\n\n@cache(expire=60)\ndef my_function(a: int, b: int, name: str = \"test\"):\n    return {\"result\": a + b, \"name\": name}\n```\n\n**Or use custom key builder per decorator:**\n\n```python\ndef simple_key_builder(module, name, args, kwargs, prefix=\"cache\", namespace=\"app\"):\n    # Simple key: just use function name and first argument\n    first_arg = args[0] if args else \"default\"\n    return f\"{prefix}:{namespace}:{name}:{first_arg}\"\n\n@cache(expire=60, key_builder=simple_key_builder, namespace=\"simple\")\ndef get_item(item_id: int):\n    return {\"id\": item_id}\n```\n\n**Key builder function signature:**\n```python\ndef key_builder(\n    module: str,      # Function's module (e.g., \"__main__\" or \"myapp.services\")\n    name: str,       # Function name\n    args: tuple,     # Positional arguments\n    kwargs: dict,     # Keyword arguments\n    prefix: str,      # Key prefix\n    namespace: str    # Namespace\n) -\u003e str:\n    # Return the cache key as a string\n    return \"your:custom:key:format\"\n```\n\n#### **Clearing Cache by Namespace**\n\nYou can clear all cache keys in a specific namespace:\n\n```python\nfrom cache_house.backends import RedisCache\n\n# Clear all keys in a namespace\nRedisCache.clear_keys(\"cachehouse:api\")  # Clears all keys starting with \"cachehouse:api\"\n\n# Or with custom prefix\nRedisCache.clear_keys(\"myapp:database\")  # Clears all keys in \"database\" namespace\n```\n\n**Example: Clear cache for a specific namespace**\n\n```python\nfrom cache_house.backends import RedisFactory, RedisCache\n\nRedisFactory.init(namespace=\"myapp\", fallback_to_memory=True)\n\n# Cache some data\n@cache(expire=300, namespace=\"users\")\ndef get_user(id: int):\n    return {\"id\": id}\n\n@cache(expire=300, namespace=\"posts\")\ndef get_post(id: int):\n    return {\"id\": id}\n\n# Later, clear only \"users\" namespace\nRedisCache.clear_keys(\"cachehouse:users\")  # Only clears users cache\n# Posts cache remains intact\n```\n\n#### **Best Practices for Namespaces**\n\n1. **Use descriptive namespaces**:\n   ```python\n   @cache(namespace=\"api.users\")  # Good\n   @cache(namespace=\"x\")          # Bad - not descriptive\n   ```\n\n2. **Organize by feature or service**:\n   ```python\n   namespace=\"api.users\"\n   namespace=\"api.products\"\n   namespace=\"database.queries\"\n   namespace=\"external.api\"\n   ```\n\n3. **Use consistent naming**:\n   ```python\n   # Good - consistent pattern\n   namespace=\"v1.api\"\n   namespace=\"v1.database\"\n   namespace=\"v2.api\"\n   ```\n\n4. **Set global namespace for multi-tenant apps**:\n   ```python\n   # Different namespace per tenant\n   tenant_id = get_current_tenant()\n   RedisFactory.init(namespace=f\"tenant_{tenant_id}\")\n   ```\n\n5. **Use namespaces for cache invalidation**:\n   ```python\n   # When user data changes, clear user namespace\n   def update_user(user_id):\n       # ... update logic ...\n       RedisCache.clear_keys(\"cachehouse:users\")  # Clear all user cache\n   ```\n\n*****\n### ***Custom encoder and decoder in decorator***\n*****\n\nIf your function works with non-standard data types, you can pass custom encoder and decoder functions to the cache decorator:\n\n```python\nimport asyncio\nimport json\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\nRedisFactory.init(fallback_to_memory=True)\n\ndef custom_encoder(data):\n    return json.dumps(data)\n\ndef custom_decoder(data):\n    return json.loads(data)\n\n@cache(expire=30, encoder=custom_encoder, decoder=custom_decoder, namespace=\"custom\")\nasync def test_cache(a: int, b: int):\n    print(\"async cached\")\n    return {\"a\": a, \"b\": b}\n\n\n@cache(expire=30)\ndef test_cache_1(a: int, b: int):\n    print(\"cached\")\n    return [a, b]\n\n\nif __name__ == \"__main__\":\n    print(asyncio.run(test_cache(1, 2)))\n    print(test_cache_1(3, 4))\n```\n\nCheck stored cache:\n```sh\nrdcli KEYS \"*\"\n1) cachehouse:main:8f65aed1010f0062a783c83eb430aca0\n2) cachehouse:custom:f665833ea64e4fc32653df794257ca06\n```\n\n*****\n### ***Error Handling and Resilience***\n*****\n\n**cache-house** is designed to be resilient and won't crash your application:\n\n#### **Automatic Reconnection**\nRedis client handles reconnection automatically. You don't need to manage connections manually.\n\n#### **In-Memory Fallback**\nWhen Redis is unavailable, cache operations automatically fall back to in-memory cache:\n\n```python\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\n# Initialize with fallback enabled (default: True)\nRedisFactory.init(\n    host=\"localhost\",\n    port=6379,\n    fallback_to_memory=True  # Falls back to in-memory cache when Redis is down\n)\n\n@cache(expire=60)\ndef expensive_operation(data):\n    # This function will work even if Redis is unavailable\n    # Results will be cached in memory temporarily\n    return process_data(data)\n```\n\n#### **Graceful Error Handling**\nAll cache operations handle errors gracefully:\n\n```python\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\n# Even if Redis is not initialized, your code won't crash\ncache_instance = RedisFactory.get_instance()\nif cache_instance is None:\n    print(\"Cache not available, but app continues running\")\n\n@cache(expire=60)\ndef my_function():\n    # If Redis fails, this function still executes normally\n    # Cache errors are logged but don't crash the app\n    return expensive_computation()\n```\n\n#### **Best Practices**\n\n1. **Always enable fallback for production**:\n   ```python\n   RedisFactory.init(fallback_to_memory=True)\n   ```\n\n2. **Handle cache as optional**:\n   ```python\n   @cache(expire=60)\n   def my_function():\n       # Function works with or without cache\n       return compute_result()\n   ```\n\n3. **Use appropriate expiration times**:\n   ```python\n   @cache(expire=300)  # 5 minutes for stable data\n   def get_stable_data():\n       return fetch_data()\n   \n   @cache(expire=30)  # 30 seconds for frequently changing data\n   def get_dynamic_data():\n       return fetch_data()\n   ```\n\n4. **Close connections on shutdown** (e.g., in FastAPI):\n   ```python\n   @app.on_event(\"shutdown\")\n   async def shutdown():\n       RedisFactory.close_connections()\n   ```\n\n*****\n### ***Complete Example: Production-Ready Setup***\n*****\n\nHere's a complete example showing best practices for using cache-house in production:\n\n```python\nimport asyncio\nimport logging\nfrom datetime import timedelta\nfrom cache_house.backends import RedisFactory\nfrom cache_house.cache import cache\n\n# Configure logging to see cache operations\nlogging.basicConfig(level=logging.INFO)\n\n# Initialize Redis with fallback enabled\n# Your app will work even if Redis is temporarily unavailable\nRedisFactory.init(\n    host=\"localhost\",\n    port=6379,\n    password=None,  # Set if your Redis requires authentication\n    db=0,\n    fallback_to_memory=True,  # Enable in-memory fallback\n    # You can pass any redis-py connection arguments here\n    socket_connect_timeout=5,\n    socket_timeout=5,\n)\n\n# Example 1: Cache expensive computation\n@cache(expire=300)  # Cache for 5 minutes\ndef expensive_computation(n: int):\n    \"\"\"This expensive operation will be cached\"\"\"\n    result = sum(i * i for i in range(n))\n    print(f\"Computed result for {n}: {result}\")\n    return result\n\n# Example 2: Cache API response\n@cache(expire=60, namespace=\"api\")  # Cache for 1 minute with namespace\nasync def fetch_user_data(user_id: int):\n    \"\"\"Simulate API call - will be cached\"\"\"\n    print(f\"Fetching user {user_id} from API...\")\n    await asyncio.sleep(0.1)  # Simulate network delay\n    return {\"user_id\": user_id, \"name\": f\"User {user_id}\"}\n\n# Example 3: Cache with custom expiration\n@cache(expire=timedelta(hours=1), namespace=\"long_term\")\ndef get_configuration():\n    \"\"\"Configuration that changes rarely\"\"\"\n    print(\"Loading configuration...\")\n    return {\"setting1\": \"value1\", \"setting2\": \"value2\"}\n\n# Example 4: Cache database query result\n@cache(expire=180, namespace=\"database\")\nasync def get_user_posts(user_id: int):\n    \"\"\"Simulate database query\"\"\"\n    print(f\"Querying database for user {user_id} posts...\")\n    await asyncio.sleep(0.05)\n    return [{\"id\": 1, \"title\": \"Post 1\"}, {\"id\": 2, \"title\": \"Post 2\"}]\n\nasync def main():\n    print(\"=== Example 1: Expensive computation ===\")\n    print(expensive_computation(1000000))  # First call - computes\n    print(expensive_computation(1000000))  # Second call - from cache\n    \n    print(\"\\n=== Example 2: API response caching ===\")\n    print(await fetch_user_data(1))  # First call - fetches\n    print(await fetch_user_data(1))  # Second call - from cache\n    \n    print(\"\\n=== Example 3: Configuration caching ===\")\n    print(get_configuration())  # First call - loads\n    print(get_configuration())  # Second call - from cache\n    \n    print(\"\\n=== Example 4: Database query caching ===\")\n    print(await get_user_posts(1))  # First call - queries\n    print(await get_user_posts(1))  # Second call - from cache\n    \n    # Clean up\n    RedisFactory.close_connections()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n**Output:**\n```\nINFO:cache_house.backends.redis_backend:redis initialized (Redis will handle reconnections automatically)\n=== Example 1: Expensive computation ===\nComputed result for 1000000: 333333333333500000\nComputed result for 1000000: 333333333333500000\n=== Example 2: API response caching ===\nFetching user 1 from API...\n{'user_id': 1, 'name': 'User 1'}\n{'user_id': 1, 'name': 'User 1'}\n...\n```\n\n**Note**: If Redis is unavailable, all operations will still work using the in-memory fallback cache. Your application won't crash!\n\n*****\n### ***All examples work with both Redis Cluster and single Redis instance.***\n*****\n\n# Contributing #\n\n#### Free to open issue and send PR ####\n\n### cache-house  supports Python \u003e= 3.10\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fturall%2Fcache-house","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fturall%2Fcache-house","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fturall%2Fcache-house/lists"}