Django Cache Memoize
django-cache-memoize is a Django utility that provides a memoization decorator, `cache_memoize`, which leverages the Django cache framework. It's designed to cache function call results, supports invalidation, and works with complex arguments and keyword arguments. The current version is 0.2.1, and it maintains an active release cadence.
Common errors
-
TypeError: cannot pickle '_thread.RLock' object
cause The return value of your memoized function (or one of its components) is an unpickleable object, such as an active Django QuerySet, database connection, or a complex class instance not designed for pickling.fixModify the decorated function to return only basic Python types (numbers, strings, lists, dictionaries) or custom objects that are explicitly designed to be pickleable. For QuerySets, convert them to lists of model instances or dictionaries/values before returning. -
My memoized function keeps executing on every call, it's not caching.
cause Django's cache framework is either not configured, misconfigured, or the cache backend (e.g., Memcached, Redis) is not running or accessible.fixVerify your `CACHES` setting in `settings.py` is correctly configured and points to an available cache backend. Ensure any external cache servers (like Redis or Memcached) are running and accessible from your Django application. Check Django's `CACHE_MIDDLEWARE_SECONDS` if applicable, although this decorator uses low-level cache directly. -
Cache is not clearing when I call .invalidate()
cause The arguments passed to `.invalidate()` do not exactly match the arguments originally used when the function's result was cached. `django-cache-memoize` generates cache keys based on the exact arguments.fixWhen calling `function.invalidate()`, provide the identical positional and keyword arguments as were passed during the original function call that created the cache entry. For example, `expensive_calculation.invalidate(10, 20)` matches `expensive_calculation(10, 20)`. -
Stale data is returned even after I update the database.
cause The cache entry was not explicitly invalidated after the corresponding data in the database was modified. `django-cache-memoize` does not automatically detect database changes.fixAfter performing a database write operation that affects data potentially used by a memoized function, manually call `function.invalidate(*args, **kwargs)` for the relevant cache entry (or entries) to ensure fresh data is retrieved on subsequent calls.
Warnings
- gotcha The decorated function's return value must be pickleable by Python's `pickle` module. If the return value is not pickleable (e.g., certain unpicklable object instances, database connections), caching will fail.
- gotcha To effectively use `django-cache-memoize`, Django's caching framework must be properly configured in your project's `settings.py` (e.g., using `LocMemCache`, `RedisCache`, or `MemcachedCache`). If no cache backend is configured, the decorator will not store or retrieve values.
- gotcha Cache invalidation using `.invalidate(*args, **kwargs)` requires providing the exact same arguments (positional and keyword) that were used when the function was originally called. There is no built-in mechanism to invalidate cache entries based on partial argument matches or patterns.
- gotcha Using mutable objects (like lists or custom class instances without a stable `__str__` method) as arguments to a memoized function can lead to unexpected cache key generation or misses, as the default key generation relies on `str()` representation of arguments.
Install
-
pip install django-cache-memoize
Imports
- cache_memoize
from cache_memoize import cache_memoize
Quickstart
import random
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse
from cache_memoize import cache_memoize
# Minimal Django settings for cache configuration
if not settings.configured:
settings.configure(
CACHES={
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
)
# Example of a function that would be expensive
@cache_memoize(timeout=60 * 5) # Cache for 5 minutes
def expensive_calculation(a, b):
print(f"Calculating for {a}, {b}...") # This will only print on cache miss
return a + b + random.randint(0, 100)
# Example Django view
def my_view(request):
result1 = expensive_calculation(10, 20)
result2 = expensive_calculation(10, 20) # This should be a cache hit
result3 = expensive_calculation(1, 2)
# Invalidate cache for specific arguments
# expensive_calculation.invalidate(10, 20)
# print("Invalidated (10, 20)")
# result_after_invalidation = expensive_calculation(10, 20)
return HttpResponse(
f"Result 1 (10, 20): {result1}<br>" +
f"Result 2 (10, 20) (cached): {result2}<br>" +
f"Result 3 (1, 2): {result3}<br>" +
f"Cache key for (10, 20): {expensive_calculation.get_memoize_key(10, 20)}"
)
# To run this, you would integrate it into a Django project's urls.py
# e.g., path('my_cached_view/', my_view),
# and ensure Django's cache settings are configured (as above or in settings.py)