{"id":31793503,"url":"https://github.com/aimerpro/relaycache","last_synced_at":"2026-04-21T10:04:12.785Z","repository":{"id":318498336,"uuid":"1071551697","full_name":"AIMERPRO/relaycache","owner":"AIMERPRO","description":"Universal caching library for Python with sync/async support, tagging, singleflight pattern and distributed locks.","archived":false,"fork":false,"pushed_at":"2025-10-07T14:19:20.000Z","size":28,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-07T15:38:11.310Z","etag":null,"topics":["async","asyncio","cache","python","redis","redis-cache","singleflight"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/relaycache/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AIMERPRO.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-07T13:55:37.000Z","updated_at":"2025-10-07T14:19:24.000Z","dependencies_parsed_at":"2025-10-07T15:38:13.402Z","dependency_job_id":null,"html_url":"https://github.com/AIMERPRO/relaycache","commit_stats":null,"previous_names":["aimerpro/relaycache"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/AIMERPRO/relaycache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIMERPRO%2Frelaycache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIMERPRO%2Frelaycache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIMERPRO%2Frelaycache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIMERPRO%2Frelaycache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AIMERPRO","download_url":"https://codeload.github.com/AIMERPRO/relaycache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIMERPRO%2Frelaycache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004913,"owners_count":26083802,"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","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["async","asyncio","cache","python","redis","redis-cache","singleflight"],"created_at":"2025-10-10T18:19:06.057Z","updated_at":"2025-10-10T18:19:06.973Z","avatar_url":"https://github.com/AIMERPRO.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \n# 🚀 RelayCache\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003e⚡ Universal caching library for Python with sync/async support, tagging, singleflight pattern and distributed locks\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pypi.org/project/relaycache/\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/v/relaycache.svg\" alt=\"PyPI version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://pypi.org/project/relaycache/\"\u003e\n    \u003cimg src=\"https://img.shields.io/pypi/pyversions/relaycache.svg\" alt=\"Python versions\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/AIMERPRO/relaycache\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"MIT License\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/AIMERPRO/relaycache\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/stars/AIMERPRO/relaycache?style=social\" alt=\"GitHub stars\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#features\"\u003e✨ Features\u003c/a\u003e •\n  \u003ca href=\"#installation\"\u003e📦 Installation\u003c/a\u003e •\n  \u003ca href=\"#quick-start\"\u003e🚀 Quick Start\u003c/a\u003e •\n  \u003ca href=\"docs/README.md\"\u003e📖 Docs\u003c/a\u003e •\n  \u003ca href=\"examples.py\"\u003e💡 Examples\u003c/a\u003e\n\u003c/p\u003e\n\n\u003c/div\u003e\n\n---\n\n## ✨ Features\n\n- 🔄 **Universal API**: Works with both sync and async code\n- 🗄️ **Multiple backends**: In-memory, Redis, async Redis\n- 🏷️ **Cache tagging**: Group and invalidate related cache entries\n- ⚡ **Singleflight**: Prevent thundering herd with automatic deduplication\n- 🔒 **Distributed locks**: Cross-process coordination via Redis\n- ⏰ **TTL support**: Automatic expiration of cache entries\n- 🎯 **Type hints**: Full typing support\n\n## Installation\n\n```bash\npip install relaycache\n```\n\nFor development:\n```bash\npip install relaycache[dev]\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nfrom custom_cache import cache, InMemoryCache\n\n# Use with default in-memory backend\n@cache(ttl=300)  # Cache for 5 minutes\ndef expensive_function(x, y):\n    # Some expensive computation\n    return x * y + 42\n\nresult = expensive_function(10, 20)  # Computed\nresult = expensive_function(10, 20)  # From cache\n```\n\n### Redis Backend\n\n```python\nimport redis\nfrom custom_cache import cache, RedisCache\n\n# Setup Redis backend\nredis_client = redis.Redis(host='localhost', port=6379, db=0)\nredis_backend = RedisCache(redis_client, default_ttl=3600)\n\n@cache(ttl=1800, backend=redis_backend, tags=[\"users\"])\ndef get_user_profile(user_id):\n    # Fetch from database\n    return {\"id\": user_id, \"name\": \"John\", \"email\": \"john@example.com\"}\n\n# Usage\nprofile = get_user_profile(123)\n```\n\n### Async Support\n\n```python\nimport asyncio\nfrom redis.asyncio import Redis\nfrom custom_cache import cache, AioredisCache\n\n# Setup async Redis backend\nasync def main():\n    redis_client = Redis(host='localhost', port=6379, db=0)\n    async_backend = AioredisCache(redis_client, default_ttl=3600)\n\n    @cache(ttl=1800, backend=async_backend, tags=[\"posts\"])\n    async def get_post(post_id):\n        # Async database call\n        await asyncio.sleep(0.1)\n        return {\"id\": post_id, \"title\": \"Sample Post\"}\n\n    post = await get_post(456)  # Computed\n    post = await get_post(456)  # From cache\n\nasyncio.run(main())\n```\n\n## Advanced Features\n\n### Cache Tagging and Invalidation\n\n```python\nfrom custom_cache import cache, invalidate\n\n@cache(ttl=3600, tags=lambda user_id: [f\"user:{user_id}\", \"users\"])\ndef get_user_data(user_id):\n    return fetch_user_from_db(user_id)\n\n# Invalidate specific user\ninvalidate(tags=[f\"user:{user_id}\"])\n\n# Invalidate all users  \ninvalidate(tags=[\"users\"])\n```\n\n### Distributed Singleflight\n\nPrevent multiple processes from computing the same value simultaneously:\n\n```python\n@cache(\n    ttl=1800, \n    backend=redis_backend,\n    distributed_singleflight=True,  # Enable distributed coordination\n    dist_lock_ttl=5.0,             # Lock TTL in seconds\n    dist_lock_timeout=2.0          # Lock acquisition timeout\n)\ndef expensive_computation(key):\n    # Only one process will execute this at a time per key\n    time.sleep(10)  # Simulate expensive work\n    return f\"result_for_{key}\"\n```\n\n### Custom Key Building\n\n```python\nfrom custom_cache import cache, KeyBuilder\n\n# Custom key builder\nkb = KeyBuilder(prefix=\"myapp\", namespace=\"v1\")\n\n@cache(ttl=3600, key_builder=kb)\ndef my_function(arg1, arg2):\n    return arg1 + arg2\n\n# Or custom key function\n@cache(ttl=3600, key=lambda x, y: f\"sum:{x}:{y}\")\ndef sum_function(x, y):\n    return x + y\n```\n\n### Manual Cache Management\n\n```python\nfrom custom_cache import InMemoryCache\n\ncache_backend = InMemoryCache(default_ttl=3600)\n\n# Manual operations\ncache_backend.set(\"key1\", \"value1\", ttl=1800, tags=[\"group1\"])\nhit, value = cache_backend.get(\"key1\")\n\nif hit:\n    print(f\"Found: {value}\")\n\n# Delete specific key\ncache_backend.delete(\"key1\")\n\n# Clear all cache\ncache_backend.clear()\n\n# Invalidate by tags\ncache_backend.invalidate_tags([\"group1\"])\n```\n\n## Backends\n\n### InMemoryCache (Sync/Async)\n\nFast in-process cache with thread safety:\n\n```python\nfrom custom_cache import InMemoryCache\n\nbackend = InMemoryCache(default_ttl=3600)\n\n# Features:\n# - Thread-safe operations\n# - Automatic TTL expiration\n# - Tag support\n# - Memory efficient\n```\n\n### RedisCache (Sync)\n\nRedis-based cache for distributed applications:\n\n```python\nimport redis\nfrom custom_cache import RedisCache\n\nredis_client = redis.Redis(host='localhost', port=6379, db=0)\nbackend = RedisCache(\n    redis_client,\n    default_ttl=3600,\n    value_prefix=\"myapp:\",\n    meta_prefix=\"myapp:meta\"\n)\n\n# Features:\n# - Distributed caching\n# - Persistent storage\n# - Tag-based invalidation\n# - Distributed locks\n```\n\n### AioredisCache (Async)\n\nAsync Redis cache for high-performance async applications:\n\n```python\nfrom redis.asyncio import Redis\nfrom custom_cache import AioredisCache\n\nredis_client = Redis(host='localhost', port=6379, db=0)\nbackend = AioredisCache(\n    redis_client,\n    default_ttl=3600,\n    value_prefix=\"myapp:\",\n    meta_prefix=\"myapp:meta\"\n)\n\n# Features:\n# - Non-blocking operations\n# - High concurrency\n# - Async/await support\n# - All Redis features\n```\n\n## Error Handling\n\n```python\nfrom custom_cache import cache\nfrom redis.exceptions import RedisError\n\n@cache(ttl=1800, backend=redis_backend)\ndef robust_function(x):\n    # Cache failures won't break your app\n    return expensive_computation(x)\n\ntry:\n    result = robust_function(42)\nexcept RedisError:\n    # Redis is down, function still works\n    result = expensive_computation(42)\n```\n\n## Django Integration\n\nFor detailed Django integration guide with complete examples, see [Django Integration Guide](docs/DJANGO_INTEGRATION.md).\n\n```python\n# settings.py\nimport redis\nfrom custom_cache import RedisCache\n\nREDIS_CLIENT = redis.Redis(host='localhost', port=6379, db=0)\nCACHE_BACKEND = RedisCache(REDIS_CLIENT, default_ttl=3600)\n\n# views.py\nfrom django.conf import settings\nfrom custom_cache import cache\n\n@cache(backend=settings.CACHE_BACKEND, ttl=1800, tags=[\"articles\"])\ndef get_article_list():\n    return list(Article.objects.all().values())\n\n# Invalidate on model changes\nfrom django.db.models.signals import post_save\nfrom custom_cache import invalidate\n\n@receiver(post_save, sender=Article)\ndef invalidate_articles(sender, **kwargs):\n    invalidate(tags=[\"articles\"], backend=settings.CACHE_BACKEND)\n```\n\n📖 **[View Full Django Integration Guide →](docs/DJANGO_INTEGRATION.md)**\n\n## Performance Tips\n\n1. **Choose the right backend**: InMemory for single-process, Redis for distributed\n2. **Use appropriate TTL**: Balance between freshness and performance\n3. **Tag strategically**: Group related data for efficient invalidation\n4. **Enable singleflight**: For expensive computations with high concurrency\n5. **Monitor cache hit rates**: Use backend statistics methods\n\n## API Reference\n\n### @cache decorator\n\n```python\n@cache(\n    ttl: float,                                    # Cache TTL in seconds\n    key: Optional[Callable] = None,                # Custom key function\n    namespace: Optional[str] = None,               # Key namespace\n    backend: Optional[Backend] = None,             # Cache backend\n    key_builder: Optional[KeyBuilder] = None,      # Custom key builder\n    tags: Optional[Union[List, Callable]] = None,  # Cache tags\n    distributed_singleflight: bool = False,       # Enable distributed locks\n    dist_lock_ttl: float = 5.0,                   # Lock TTL\n    dist_lock_timeout: float = 2.0                # Lock timeout\n)\n```\n\n### Backend Methods\n\nAll backends implement:\n- `get(key) -\u003e (hit: bool, value: Any)`\n- `set(key, value, ttl, *, tags=None)`\n- `delete(key)`\n- `clear()`\n- `invalidate_tags(tags)`\n\nAsync backends also provide:\n- `aget(key)`, `aset(...)`, `adelete(key)`, `aclear()`, `ainvalidate_tags(...)`\n\n## Requirements\n\n- Python 3.8+\n- redis-py 4.0+\n\n## License\n\nMIT License. See LICENSE file for details.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Run the test suite: `pytest`\n5. Submit a pull request\n\n## Changelog\n\n### 0.1.1\n- Added `default_ttl` parameter to `AioredisCache` for API consistency\n- All backends now have unified constructor interface\n- Added `__setitem__` method to `AioredisCache` for `cache[key] = value` syntax\n\n### 0.1.0\n- Initial release\n- Sync/async cache support\n- Redis and in-memory backends\n- Cache tagging\n- Singleflight pattern\n- Distributed locks\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faimerpro%2Frelaycache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faimerpro%2Frelaycache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faimerpro%2Frelaycache/lists"}