Smarter cache_results Decorator
Smarter.lib.cache.cache_results
This module provides the @cache_results decorator and related utilities
for persistent, argument-based function result caching in the Smarter framework.
Key Features:
Deterministic cache key generation based on function name, arguments, and sorted keyword arguments.
Persistent caching using Smarter’s Redis-backed cache infrastructure (via
lazy_cache), supporting application restarts and multi-instance deployments.Handles serialization of arguments for robust cache key creation, with fallback for non-serializable data.
Special handling for functions returning
Noneusing a sentinel value to distinguish between cachedNoneand cache misses.Optional logging for cache hits, misses, and invalidations for debugging and transparency.
Provides an
invalidatemethod on decorated functions to manually clear cache entries for specific arguments.
Usage Example:
from smarter.lib.cache.cache_results import cache_results
@cache_results(timeout=600)
def expensive_function(x, y, *args, **kwargs):
# Perform expensive computation ...
return x + y
# Invalidate cache for specific arguments
expensive_function.invalidate(1, 2)
Notes:
This decorator is intended for expensive, long-lived, persistable caching scenarios (e.g., database reads), not for short-lived in-memory caching.
The cache key generation is robust but not immune to hash collisions (statistically extremely rare).
See the
cache_resultsdecorator docstring for more details on behavior and limitations.
- smarter.lib.cache.decorators.cache_results(timeout=60, cache_key=None, logging_enabled=False)[source]
A decorator that caches the result of a function based on the arguments passed to it.
Important
This decorator is intended for expensive and long-lasting, persistable caching scenarios such as Django ORM reads, where the results of the decorated function should endure application restarts and deployments. This caching decorator itself is using Python function caching in order to generate cache keys, which is to say that using this decorator could be counter-productive for caching scenarios that are better served by in-memory caching of short-lived results.
When the decorated function is called, the decorator first checks if a cached result exists for the given arguments. If a cached result is found, it is returned immediately. If not, the original function is called, its result is cached, and then returned. Smarter’s cache infrastructure is based on Redis and runs as a remote service that services application restarts, deployments, and, it natively services multiple application server instances.
Note
One of the challenges with implementing a caching decorator based on Django cache regards working around Django’s application startup sequence. Decorators are imported and applied at module load time, which often results in Django’s cache framework being prematurely imported and initialized while Django itself is still running its own application startup process.
This often leads to situations where Django falls back to an alternative ‘default’ memory-based cache backend unexpectedly (and silently). When this happens, the fallback cache most likely will not persist data as expected, leading to buggy cache misses such as users’ browser session values not being stored, and cached results of this decorator enduring less than specified.
How It works:
A cache key is created by building a string of the module name + the function name, and then appending a 32-character hash of its serialized positional arguments and sorted keyword pairs. This ensures that each unique set of arguments maps to a unique but repeatable cache key. Technically speaking, there is a statistical non-zero probability of hash collisions, but, the risk of this happening is EXTREMELY low.
- Parameters:
timeout (int) – The cache timeout in seconds. Defaults to
smarter_settings.cache_expiration.cache_key (
str|None) – An optional pre-computed cache key to use instead of generating one from the function arguments. This marginally increases performance by skipping the cache key generation step, but should only be used if you are certain that the provided cache key is unique and correctly represents the function arguments. Defaults toNone.logging_enabled (bool) – Whether to enable logging for cache hits and misses. Defaults to
True.
- Returns:
The decorated function with caching applied.
- Return type:
Callable
Note
If the function returns
None, a sentinel value is cached to distinguish between a cachedNoneand a cache miss.Usage example:
@cache_results(timeout=600) def expensive_function(x, y, *args, **kwargs): # Perform expensive computation ... result = "some very expensive computational result" return result expensive_function.invalidate(1, 2) # Invalidate cache for specific arguments