Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

TaggedCache: High-Performance Tagged Caching for Python

A Python library providing a powerful, memory-efficient, and high-performance caching solution with tag-based invalidation. It's designed for demanding applications, such as web services built with FastAPI or Django, where quick cache access and precise invalidation are crucial. TaggedCache supports both synchronous and asynchronous functions.

Features

  • Blazing-Fast GETs: Highly optimized cache lookups using integer hash keys and minimal overhead on the hot path.
  • Tag-Based Invalidation: Associate cached items with multiple string tags and invalidate groups of items by tag (e.g., "user:123", "product_category:electronics").
  • Async Support: Seamlessly works with both synchronous (def) and asynchronous (async def) functions.
  • Memory Efficient:
    • Uses cachetools.TTLCache for underlying storage with configurable maxsize and TTL (Time-To-Live).
    • Internal cache keys and tag identifiers are stored as compact integer hashes (Python's hash() for keys, 64-bit xxhash for tags).
  • Thread-Safe: Designed for concurrent use with an internal threading.RLock to protect shared state.
  • Automatic Tag Cleanup: Tags associated with items are automatically cleaned up when items are evicted due to TTL expiry or cache maxsize limits, preventing memory leaks from stale tag references.
  • Robust Tag Context: On cache misses, it intelligently resolves all function arguments (positional, keyword, defaults) using inspect.signature to provide a comprehensive context for dynamic tag string generation.
  • Handles self/cls: Correctly manages self or cls arguments for instance/class methods in cache key generation and tag context.
  • Simple Decorator API: Easy to integrate using a @cache_instance.tag(...) decorator.

Installation

pip install tagged-cache

Quick Start

fromtagged_cacheimportTaggedCache# 1. Create cache instances with custom settingsuser_profile_cache=TaggedCache(ttl=3600, maxsize=10000) # Cache user profiles for 1 hourproduct_cache=TaggedCache(ttl=7200, maxsize=5000) # Cache products for 2 hours# 2. Define your functions and decorate themclassUserService:
@user_profile_cache.tag("user:{user_id}", "user_profile", "org:{org_id}")defget_user_profile(self, user_id: int, org_id: int):
print(f"DB HIT: Fetching profile for user {user_id} in org {org_id}")
# Simulate fetching data from a database or an external servicereturn {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com", "org_id": org_id}
classProductService:
@product_cache.tag("product:{product_id}", "product_details", "category:{category_name}")defget_product_details(self, product_id: str, category_name: str):
print(f"DB HIT: Fetching details for product {product_id} in category {category_name}")
# Simulate fetching datareturn {"id": product_id, "name": f"Product {product_id}", "price": 19.99, "category": category_name}
# 3. Use your servicesuser_service=UserService()
product_service=ProductService()
# First call - will hit the database and cache the resultprofile1=user_service.get_user_profile(user_id=101, org_id=1)
print(profile1)
product1=product_service.get_product_details(product_id="abc", category_name="electronics")
print(product1)
# Second call - will fetch from cacheprofile2=user_service.get_user_profile(user_id=101, org_id=1) # Cache HITprint(profile2)
# 4. Invalidate cache entries by tag# Imagine user 101's profile was updatedprint("\nInvalidating user:101...")
invalidated_count=user_profile_cache.invalidate_tag("user:101")
print(f"Invalidated {invalidated_count} cache entries for user:101.")
# This call will now miss the cache and re-fetchprofile_after_invalidation=user_service.get_user_profile(user_id=101, org_id=1)
print(profile_after_invalidation)
### Async Support Example```pythonimportasynciofromtagged_cacheimportTaggedCache# Create a cache instanceasync_cache=TaggedCache(ttl=3600, maxsize=1000)
# Define an async function and decorate it@async_cache.tag("user:{user_id}", "user_data")asyncdeffetch_user_data(user_id: int):
print(f"Fetching data for user {user_id}...")
# Simulate async database or API callawaitasyncio.sleep(1.0)
return {"id": user_id, "name": f"User {user_id}"}
asyncdefmain():
# First call (cache miss)user1=awaitfetch_user_data(123)
print(f"User data: {user1}")
# Second call (cache hit)user2=awaitfetch_user_data(123)
print(f"User data (cached): {user2}")
# Invalidate the taginvalidated=async_cache.invalidate_tag("user:123")
print(f"Invalidated {invalidated} entries")
# This should miss the cacheuser3=awaitfetch_user_data(123)
print(f"User data (after invalidation): {user3}")
# Run the async exampleasyncio.run(main())

API Reference

TaggedCache(ttl: int = 3600, maxsize: int = 1000)

Constructor for creating a new cache instance.

  • ttl (int): Default Time-To-Live for cache entries in seconds.
  • maxsize (int): Maximum number of entries the cache can hold. Oldest items (by TTL or LRU within TTL) are evicted when this limit is reached.

@cache_instance.tag(*tag_patterns: str)

Decorator to apply to functions whose results you want to cache. Works with both sync and async functions.

  • tag_patterns (str): One or more f-string like patterns. The placeholders in the patterns will be filled using the decorated function's arguments (including resolved defaults) at call time.
    • Example: @user_cache.tag("user:{user_id}", "role:{role_name}")

When decorating an async def function, the decorator will return an async function that must be awaited. The original function will be awaited on cache miss, and the result will be cached.

cache_instance.invalidate_tag(tag_string: str) -> int

Invalidates all cache entries associated with the exact tag_string.

  • tag_string (str): The specific tag to invalidate (e.g., "user:123").
  • Returns: The number of items actually removed from the cache.

cache_instance.clear_all() -> None

Removes all items from this specific cache instance and clears all its tag associations.

cache_instance.get_stats() -> Dict[str, int]

Returns statistics about the cache.

  • Returns: A dictionary with:
    • cache_size: Number of items in the cache
    • unique_tags: Number of unique tags in the cache
    • tag_mappings: Total number of tag-to-key mappings

len(cache_instance) -> int

Returns the current number of items in the cache.

cache_key_tuple in cache_instance -> bool

Checks if a key is in the cache. Can accept either an integer hash or a descriptive tuple.

How It Works Internally

  • Cache Key Generation: For each function call, a unique key is generated based on the function's module, name, and the values of its arguments (excluding self/cls). This descriptive key is then hashed to an integer for efficient dictionary lookups.
  • Tag Hashing: Tag strings (e.g., "user:123") are hashed into 64-bit integers using xxhash for compact storage and efficient lookup in tag-to-key mappings.
  • Tag-to-Key Mapping: A dictionary mapping TagHash -> Set[CacheKeyHash].
  • Key-to-Tag Mapping: A dictionary mapping CacheKeyHash -> Set[TagHash]. This is crucial for cleaning up all of a key's tags when it's evicted.
  • Eviction Notification: A custom TTLCache subclass calls back into TaggedCache when an item is removed, allowing TaggedCache to clean up the associated tag mappings.
  • Async Function Detection: At decoration time, inspect.iscoroutinefunction() is used to determine if a function is async, and the appropriate wrapper type (sync or async) is returned.
  • Conditional Await: Async functions are properly awaited when executed on cache miss, while sync functions are called directly.

Running Tests

# From the package root directory
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

High-performance Python cache with tag-based invalidation. Sync + async, thread-safe, decorator API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages