The Decorator
This module provides the @cache_results decorator, for persistent,.
argument-based caching of function results in Django’s cache framework.
Key Features:
Deterministic cache keys built from the function’s module, qualified name, and arguments bound to its signature (see
cache_results.keys).Persistent caching in a Django cache, e.g. Redis, that survives application restarts and serves multi-instance deployments.
Arguments that can’t be part of a safe key, such as model instances, run the function uncached and log an error, rather than be cached under
str().Special handling for functions returning
None, using a sentinel value to distinguish between a cachedNoneand a cache miss.Decorates both regular and
asyncfunctions, and classmethods.Within one process, concurrent callers that miss the same key wait for the first one to compute it, rather than all computing it.
Optional logging for cache hits, misses, and invalidations.
An
invalidatemethod on decorated functions to clear the entry of specific arguments.Configured by the
CACHE_RESULTSDjango setting (seecache_results.conf).
Usage Example:
from 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.
See the
cache_resultsdecorator docstring for more details on behavior and limitations.
- cache_results.decorators.cache_results(timeout: float | None = None, cache_key: str | None = None, logging_enabled: bool = 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. Every call serializes and hashes its arguments, and makes a round trip to the cache, so 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. Results are stored in the Django cache named by
CACHE_RESULTS["CACHE_ALIAS"]("default"unless set). A shared cache such as Redis survives application restarts and deployments, and natively serves multiple application server instances.Note
Decorators are imported and applied at module load time, which often happens while Django is still starting. Initializing Django’s cache framework then can leave it on an alternative ‘default’ memory-based backend, unexpectedly and silently, so that cached results endure less than specified. For this reason, a decorated function that is called before Django’s apps are ready runs uncached.
How It works:
A cache key is created by building a string of the module name + the function’s qualified name (e.g.
Library.get_book.<locals>._get), and then appending a 32-character hash of its arguments, bound to its signature and serialized (seecache_results.keys). This ensures that each unique set of arguments maps to a unique but repeatable cache key.CACHE_RESULTS["KEY_PREFIX"]and["VERSION"]start every key.Arguments must be plain values: str, int, float, bool, None, and lists, tuples, dicts and sets of them, plus datetimes, Decimals, UUIDs, classes and dataclasses.
CACHE_RESULTS["KEY_ENCODER"]can add types. Any other argument, such as a model instance, runs the function uncached and logs an error that names its type, once per process. For the same reason, instance methods can’t be decorated. Classmethods can.A qualified name records where a function is defined, not who calls it. A function defined inside a classmethod that subclasses inherit has the same qualified name for every subclass, so give it a
class_name: str = cls.__name__parameter, or subclasses will share its entries.Within one process, concurrent callers that miss the same key wait for the first one to compute and cache it. Callers in other processes don’t wait.
asyncfunctions are cached too, without this wait, and read and write the cache without blocking the event loop.- Parameters:
timeout (Optional[float]) – The cache timeout in seconds.
None(the default) meansCACHE_RESULTS["TIMEOUT"], or the cache alias’s own timeout when that isn’t set. 0 caches nothing.cache_key (Optional[str]) – An optional fixed cache key to use instead of generating one from the function arguments. Every call then shares one entry, whatever its arguments, so use it only for functions whose result doesn’t depend on them. Defaults to
None.logging_enabled (bool) – Whether to log cache hits, misses and invalidations, as when
CACHE_RESULTS["LOGGING_ENABLED"]is true. Results are never logged, only their type. Defaults toFalse.
- Returns:
The decorated function with caching applied.
- Return type:
Callable
- Raises:
TypeError – If the decorated function is an instance method.
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