Permalink
Browse files

Fixed #13795 -- Added a site-wide cache prefix and cache versioning. …

…Thanks to bruth for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14623 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 261aee2 commit 99d247f4cb0c22d19a4482a72a7a93584a5189da @freakboy3742 freakboy3742 committed Nov 19, 2010
@@ -433,6 +433,9 @@
# The cache backend to use. See the docstring in django.core.cache for the
# possible values.
CACHE_BACKEND = 'locmem://'
+CACHE_VERSION = 1
+CACHE_KEY_PREFIX = ''
+CACHE_KEY_FUNCTION = None
CACHE_MIDDLEWARE_KEY_PREFIX = ''
CACHE_MIDDLEWARE_SECONDS = 600
@@ -67,18 +67,30 @@ def parse_backend_uri(backend_uri):
return scheme, host, params
-def get_cache(backend_uri):
+def get_cache(backend_uri, key_prefix=None, version=None, key_func=None):
+ if key_prefix is None:
+ key_prefix = settings.CACHE_KEY_PREFIX
+ if version is None:
+ version = settings.CACHE_VERSION
+ if key_func is None:
+ key_func = settings.CACHE_KEY_FUNCTION
+
+ if key_func is not None and not callable(key_func):
+ key_func_module_path, key_func_name = key_func.rsplit('.', 1)
+ key_func_module = importlib.import_module(key_func_module_path)
+ key_func = getattr(key_func_module, key_func_name)
+
scheme, host, params = parse_backend_uri(backend_uri)
if scheme in BACKENDS:
name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
else:
name = scheme
module = importlib.import_module(name)
- return module.CacheClass(host, params)
+ return module.CacheClass(host, params, key_prefix=key_prefix, version=version, key_func=key_func)
cache = get_cache(settings.CACHE_BACKEND)
-# Some caches -- pythont-memcached in particular -- need to do a cleanup at the
+# Some caches -- python-memcached in particular -- need to do a cleanup at the
# end of a request cycle. If the cache provides a close() method, wire it up
# here.
if hasattr(cache, 'close'):
@@ -3,6 +3,7 @@
import warnings
from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
+from django.utils.encoding import smart_str
class InvalidCacheBackendError(ImproperlyConfigured):
pass
@@ -13,8 +14,17 @@ class CacheKeyWarning(DjangoRuntimeWarning):
# Memcached does not accept keys longer than this.
MEMCACHE_MAX_KEY_LENGTH = 250
+def default_key_func(key, key_prefix, version):
+ """Default function to generate keys.
+
+ Constructs the key used by all other methods. By default it prepends
+ the `key_prefix'. CACHE_KEY_FUNCTION can be used to specify an alternate
+ function with custom key making behavior.
+ """
+ return ':'.join([key_prefix, str(version), smart_str(key)])
+
class BaseCache(object):
- def __init__(self, params):
+ def __init__(self, params, key_prefix='', version=1, key_func=None):
timeout = params.get('timeout', 300)
try:
timeout = int(timeout)
@@ -34,7 +44,25 @@ def __init__(self, params):
except (ValueError, TypeError):
self._cull_frequency = 3
- def add(self, key, value, timeout=None):
+ self.key_prefix = smart_str(key_prefix)
+ self.version = version
+ self.key_func = key_func or default_key_func
+
+ def make_key(self, key, version=None):
+ """Constructs the key used by all other methods. By default it
+ uses the key_func to generate a key (which, by default,
+ prepends the `key_prefix' and 'version'). An different key
+ function can be provided at the time of cache construction;
+ alternatively, you can subclass the cache backend to provide
+ custom key making behavior.
+ """
+ if version is None:
+ version = self.version
+
+ new_key = self.key_func(key, self.key_prefix, version)
+ return new_key
+
+ def add(self, key, value, timeout=None, version=None):
"""
Set a value in the cache if the key does not already exist. If
timeout is given, that timeout will be used for the key; otherwise
@@ -44,27 +72,27 @@ def add(self, key, value, timeout=None):
"""
raise NotImplementedError
- def get(self, key, default=None):
+ def get(self, key, default=None, version=None):
"""
Fetch a given key from the cache. If the key does not exist, return
default, which itself defaults to None.
"""
raise NotImplementedError
- def set(self, key, value, timeout=None):
+ def set(self, key, value, timeout=None, version=None):
"""
Set a value in the cache. If timeout is given, that timeout will be
used for the key; otherwise the default cache timeout will be used.
"""
raise NotImplementedError
- def delete(self, key):
+ def delete(self, key, version=None):
"""
Delete a key from the cache, failing silently.
"""
raise NotImplementedError
- def get_many(self, keys):
+ def get_many(self, keys, version=None):
"""
Fetch a bunch of keys from the cache. For certain backends (memcached,
pgsql) this can be *much* faster when fetching multiple values.
@@ -74,34 +102,35 @@ def get_many(self, keys):
"""
d = {}
for k in keys:
- val = self.get(k)
+ val = self.get(k, version=version)
if val is not None:
d[k] = val
return d
- def has_key(self, key):
+ def has_key(self, key, version=None):
"""
Returns True if the key is in the cache and has not expired.
"""
- return self.get(key) is not None
+ return self.get(key, version=version) is not None
- def incr(self, key, delta=1):
+ def incr(self, key, delta=1, version=None):
"""
Add delta to value in the cache. If the key does not exist, raise a
ValueError exception.
"""
- if key not in self:
+ value = self.get(key, version=version)
+ if value is None:
raise ValueError("Key '%s' not found" % key)
- new_value = self.get(key) + delta
- self.set(key, new_value)
+ new_value = value + delta
+ self.set(key, new_value, version=version)
return new_value
- def decr(self, key, delta=1):
+ def decr(self, key, delta=1, version=None):
"""
Subtract delta from value in the cache. If the key does not exist, raise
a ValueError exception.
"""
- return self.incr(key, -delta)
+ return self.incr(key, -delta, version=version)
def __contains__(self, key):
"""
@@ -112,7 +141,7 @@ def __contains__(self, key):
# if a subclass overrides it.
return self.has_key(key)
- def set_many(self, data, timeout=None):
+ def set_many(self, data, timeout=None, version=None):
"""
Set a bunch of values in the cache at once from a dict of key/value
pairs. For certain backends (memcached), this is much more efficient
@@ -122,16 +151,16 @@ def set_many(self, data, timeout=None):
the default cache timeout will be used.
"""
for key, value in data.items():
- self.set(key, value, timeout)
+ self.set(key, value, timeout=timeout, version=version)
- def delete_many(self, keys):
+ def delete_many(self, keys, version=None):
"""
Set a bunch of values in the cache at once. For certain backends
(memcached), this is much more efficient than calling delete() multiple
times.
"""
for key in keys:
- self.delete(key)
+ self.delete(key, version=version)
def clear(self):
"""Remove *all* values from the cache at once."""
@@ -154,3 +183,23 @@ def validate_key(self, key):
'errors if used with memcached: %r' % key,
CacheKeyWarning)
+ def incr_version(self, key, delta=1, version=None):
+ """Adds delta to the cache version for the supplied key. Returns the
+ new version.
+ """
+ if version is None:
+ version = self.version
+
+ value = self.get(key, version=version)
+ if value is None:
+ raise ValueError("Key '%s' not found" % key)
+
+ self.set(key, value, version=version+delta)
+ self.delete(key, version=version)
+ return version+delta
+
+ def decr_version(self, key, delta=1, version=None):
+ """Substracts delta from the cache version for the supplied key. Returns
+ the new version.
+ """
+ return self.incr_version(key, -delta, version)
@@ -26,16 +26,17 @@ def __init__(self, table):
self.proxy = False
class BaseDatabaseCacheClass(BaseCache):
- def __init__(self, table, params):
- BaseCache.__init__(self, params)
+ def __init__(self, table, params, key_prefix='', version=1, key_func=None):
+ BaseCache.__init__(self, params, key_prefix, version, key_func)
self._table = table
class CacheEntry(object):
_meta = Options(table)
self.cache_model_class = CacheEntry
class CacheClass(BaseDatabaseCacheClass):
- def get(self, key, default=None):
+ def get(self, key, default=None, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
db = router.db_for_read(self.cache_model_class)
table = connections[db].ops.quote_name(self._table)
@@ -55,11 +56,13 @@ def get(self, key, default=None):
value = connections[db].ops.process_clob(row[1])
return pickle.loads(base64.decodestring(value))
- def set(self, key, value, timeout=None):
+ def set(self, key, value, timeout=None, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
self._base_set('set', key, value, timeout)
- def add(self, key, value, timeout=None):
+ def add(self, key, value, timeout=None, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
return self._base_set('add', key, value, timeout)
@@ -95,17 +98,21 @@ def _base_set(self, mode, key, value, timeout=None):
transaction.commit_unless_managed(using=db)
return True
- def delete(self, key):
+ def delete(self, key, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
+
db = router.db_for_write(self.cache_model_class)
table = connections[db].ops.quote_name(self._table)
cursor = connections[db].cursor()
cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
transaction.commit_unless_managed(using=db)
- def has_key(self, key):
+ def has_key(self, key, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
+
db = router.db_for_read(self.cache_model_class)
table = connections[db].ops.quote_name(self._table)
cursor = connections[db].cursor()
@@ -3,34 +3,39 @@
from django.core.cache.backends.base import BaseCache
class CacheClass(BaseCache):
- def __init__(self, *args, **kwargs):
- pass
+ def __init__(self, host, *args, **kwargs):
+ BaseCache.__init__(self, *args, **kwargs)
- def add(self, key, *args, **kwargs):
+ def add(self, key, value, timeout=None, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
return True
- def get(self, key, default=None):
+ def get(self, key, default=None, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
return default
- def set(self, key, *args, **kwargs):
+ def set(self, key, value, timeout=None, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
- def delete(self, key, *args, **kwargs):
+ def delete(self, key, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
- def get_many(self, *args, **kwargs):
+ def get_many(self, keys, version=None):
return {}
- def has_key(self, key, *args, **kwargs):
+ def has_key(self, key, version=None):
+ key = self.make_key(key, version=version)
self.validate_key(key)
return False
- def set_many(self, *args, **kwargs):
+ def set_many(self, data, version=None):
pass
- def delete_many(self, *args, **kwargs):
+ def delete_many(self, keys, version=None):
pass
def clear(self):
Oops, something went wrong.

0 comments on commit 99d247f

Please sign in to comment.