Ticket #13795: cache_key_prefix_14190.diff
File cache_key_prefix_14190.diff, 34.2 KB (added by , 14 years ago) |
---|
-
django/conf/global_settings.py
428 428 # The cache backend to use. See the docstring in django.core.cache for the 429 429 # possible values. 430 430 CACHE_BACKEND = 'locmem://' 431 CACHE_VERSION = 1 432 CACHE_KEY_PREFIX = '' 433 CACHE_KEY_MODULE = None 431 434 CACHE_MIDDLEWARE_KEY_PREFIX = '' 432 435 CACHE_MIDDLEWARE_SECONDS = 600 433 436 -
django/core/cache/__init__.py
24 24 from django.core import signals 25 25 from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning 26 26 from django.utils import importlib 27 from django.utils.encoding import smart_str 28 from django.utils.hashcompat import md5_constructor 27 29 28 30 # Name for use in settings file --> name of module in "backends" directory. 29 31 # Any backend scheme that is not in this dictionary is treated as a Python … … 60 62 61 63 return scheme, host, params 62 64 63 def get_cache(backend_uri): 65 def get_cache(backend_uri, key_prefix=None, version=None): 66 if key_prefix is None: 67 key_prefix = settings.CACHE_KEY_PREFIX 68 if version is None: 69 version = settings.CACHE_VERSION 70 71 if settings.CACHE_KEY_MODULE is None: 72 key_func = None 73 else: 74 key_func = importlib.import_module(settings.CACHE_KEY_MODULE).make_key 75 64 76 scheme, host, params = parse_backend_uri(backend_uri) 65 77 if scheme in BACKENDS: 66 78 name = 'django.core.cache.backends.%s' % BACKENDS[scheme] 67 79 else: 68 80 name = scheme 69 81 module = importlib.import_module(name) 70 return module.CacheClass(host, params )82 return module.CacheClass(host, params, key_prefix, version, key_func) 71 83 72 84 cache = get_cache(settings.CACHE_BACKEND) 73 85 74 # Some caches -- python t-memcached in particular -- need to do a cleanup at the86 # Some caches -- python-memcached in particular -- need to do a cleanup at the 75 87 # end of a request cycle. If the cache provides a close() method, wire it up 76 88 # here. 77 89 if hasattr(cache, 'close'): -
django/core/cache/backends/base.py
2 2 3 3 import warnings 4 4 5 from django.utils.hashcompat import md5_constructor 5 6 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning 7 from django.utils.encoding import smart_str 6 8 7 9 class InvalidCacheBackendError(ImproperlyConfigured): 8 10 pass … … 13 15 # Memcached does not accept keys longer than this. 14 16 MEMCACHE_MAX_KEY_LENGTH = 250 15 17 18 def make_key(key, key_prefix, version): 19 """Default function to generate keys. 20 21 Constructs the key used by all other methods. By default it prepends 22 the `key_prefix'. In cache backend subclasses this can be overriden to 23 provide custom key making behavior. 24 """ 25 s = key_prefix + smart_str(key) + str(version) 26 hashed = md5_constructor(s).hexdigest() 27 return hashed 28 16 29 class BaseCache(object): 17 def __init__(self, params ):30 def __init__(self, params, key_prefix='', version=1, key_func=None): 18 31 timeout = params.get('timeout', 300) 19 32 try: 20 33 timeout = int(timeout) 21 34 except (ValueError, TypeError): 22 35 timeout = 300 23 36 self.default_timeout = timeout 37 self.key_prefix = smart_str(key_prefix) 38 self.version = version 39 self.key_func = key_func or make_key 24 40 25 def add(self, key, value, timeout=None): 41 def make_key(self, key, version=None): 42 """Constructs the key used by all other methods. By default it prepends 43 the `key_prefix'. In cache backend subclasses this can be overriden to 44 provide custom key making behavior. 26 45 """ 46 if version is None: 47 version = self.version 48 49 new_key = self.key_func(key, self.key_prefix, version) 50 self.validate_key(new_key) 51 return new_key 52 53 def add(self, key, value, timeout=None, version=None): 54 """ 27 55 Set a value in the cache if the key does not already exist. If 28 56 timeout is given, that timeout will be used for the key; otherwise 29 57 the default cache timeout will be used. … … 32 60 """ 33 61 raise NotImplementedError 34 62 35 def get(self, key, default=None ):63 def get(self, key, default=None, version=None): 36 64 """ 37 65 Fetch a given key from the cache. If the key does not exist, return 38 66 default, which itself defaults to None. 39 67 """ 40 68 raise NotImplementedError 41 69 42 def set(self, key, value, timeout=None ):70 def set(self, key, value, timeout=None, version=None): 43 71 """ 44 72 Set a value in the cache. If timeout is given, that timeout will be 45 73 used for the key; otherwise the default cache timeout will be used. 46 74 """ 47 75 raise NotImplementedError 48 76 49 def delete(self, key ):77 def delete(self, key, version=None): 50 78 """ 51 79 Delete a key from the cache, failing silently. 52 80 """ 53 81 raise NotImplementedError 54 82 55 def get_many(self, keys ):83 def get_many(self, keys, version=None): 56 84 """ 57 85 Fetch a bunch of keys from the cache. For certain backends (memcached, 58 86 pgsql) this can be *much* faster when fetching multiple values. … … 62 90 """ 63 91 d = {} 64 92 for k in keys: 65 val = self.get(k )93 val = self.get(k, version=version) 66 94 if val is not None: 67 95 d[k] = val 68 96 return d 69 97 70 def has_key(self, key ):98 def has_key(self, key, version=None): 71 99 """ 72 100 Returns True if the key is in the cache and has not expired. 73 101 """ 74 return self.get(key ) is not None102 return self.get(key, version=version) is not None 75 103 76 def incr(self, key, delta=1 ):104 def incr(self, key, delta=1, version=None): 77 105 """ 78 106 Add delta to value in the cache. If the key does not exist, raise a 79 107 ValueError exception. 80 108 """ 81 if key not in self: 109 value = self.get(key, version=version) 110 if value is None: 82 111 raise ValueError("Key '%s' not found" % key) 83 new_value = self.get(key)+ delta84 self.set(key, new_value )112 new_value = value + delta 113 self.set(key, new_value, version=version) 85 114 return new_value 86 115 87 def decr(self, key, delta=1 ):116 def decr(self, key, delta=1, version=None): 88 117 """ 89 118 Subtract delta from value in the cache. If the key does not exist, raise 90 119 a ValueError exception. 91 120 """ 92 return self.incr(key, -delta )121 return self.incr(key, -delta, version=version) 93 122 94 123 def __contains__(self, key): 95 124 """ … … 100 129 # if a subclass overrides it. 101 130 return self.has_key(key) 102 131 103 def set_many(self, data, timeout=None ):132 def set_many(self, data, timeout=None, version=None): 104 133 """ 105 134 Set a bunch of values in the cache at once from a dict of key/value 106 135 pairs. For certain backends (memcached), this is much more efficient … … 110 139 the default cache timeout will be used. 111 140 """ 112 141 for key, value in data.items(): 113 self.set(key, value, timeout )142 self.set(key, value, timeout=timeout, version=version) 114 143 115 def delete_many(self, keys ):144 def delete_many(self, keys, version=None): 116 145 """ 117 146 Set a bunch of values in the cache at once. For certain backends 118 147 (memcached), this is much more efficient than calling delete() multiple 119 148 times. 120 149 """ 121 150 for key in keys: 122 self.delete(key )151 self.delete(key, version=version) 123 152 124 153 def clear(self): 125 154 """Remove *all* values from the cache at once.""" … … 142 171 'errors if used with memcached: %r' % key, 143 172 CacheKeyWarning) 144 173 174 def incr_version(self, key, delta=1, version=None): 175 """Adds delta to the cache version for the supplied key. Returns the 176 new version. 177 """ 178 if version is None: 179 version = self.version 180 181 value = self.get(key, version=version) 182 if value is None: 183 raise ValueError("Key '%s' not found" % key) 184 185 self.set(key, value, version=version+delta) 186 self.delete(key, version=version) 187 return version+delta 188 189 def decr_version(self, key, delta=1, version=None): 190 """Substracts delta from the cache version for the supplied key. Returns 191 the new version. 192 """ 193 return self.incr_version(key, -delta, version) 194 -
django/core/cache/backends/dummy.py
3 3 from django.core.cache.backends.base import BaseCache 4 4 5 5 class CacheClass(BaseCache): 6 def __init__(self, *args, **kwargs):7 pass6 def __init__(self, host, *args, **kwargs): 7 BaseCache.__init__(self, *args, **kwargs) 8 8 9 9 def add(self, key, *args, **kwargs): 10 self. validate_key(key)10 self.make_key(key) 11 11 return True 12 12 13 def get(self, key, default=None ):14 self. validate_key(key)13 def get(self, key, default=None, version=None): 14 self.make_key(key) 15 15 return default 16 16 17 17 def set(self, key, *args, **kwargs): 18 self. validate_key(key)18 self.make_key(key) 19 19 20 20 def delete(self, key, *args, **kwargs): 21 self. validate_key(key)21 self.make_key(key) 22 22 23 23 def get_many(self, *args, **kwargs): 24 24 return {} 25 25 26 26 def has_key(self, key, *args, **kwargs): 27 self. validate_key(key)27 self.make_key(key) 28 28 return False 29 29 30 30 def set_many(self, *args, **kwargs): -
django/core/cache/backends/locmem.py
10 10 from django.utils.synch import RWLock 11 11 12 12 class CacheClass(BaseCache): 13 def __init__(self, _, params ):14 BaseCache.__init__(self, params )13 def __init__(self, _, params, key_prefix='', version=1, key_func=None): 14 BaseCache.__init__(self, params, key_prefix, version, key_func) 15 15 self._cache = {} 16 16 self._expire_info = {} 17 17 … … 29 29 30 30 self._lock = RWLock() 31 31 32 def add(self, key, value, timeout=None ):33 self.validate_key(key)32 def add(self, key, value, timeout=None, version=None): 33 key = self.make_key(key, version=version) 34 34 self._lock.writer_enters() 35 35 try: 36 36 exp = self._expire_info.get(key) … … 44 44 finally: 45 45 self._lock.writer_leaves() 46 46 47 def get(self, key, default=None ):48 self.validate_key(key)47 def get(self, key, default=None, version=None): 48 key = self.make_key(key, version=version) 49 49 self._lock.reader_enters() 50 50 try: 51 51 exp = self._expire_info.get(key) … … 77 77 self._cache[key] = value 78 78 self._expire_info[key] = time.time() + timeout 79 79 80 def set(self, key, value, timeout=None ):81 self.validate_key(key)80 def set(self, key, value, timeout=None, version=None): 81 key = self.make_key(key, version=version) 82 82 self._lock.writer_enters() 83 83 # Python 2.4 doesn't allow combined try-except-finally blocks. 84 84 try: … … 89 89 finally: 90 90 self._lock.writer_leaves() 91 91 92 def has_key(self, key ):93 self.validate_key(key)92 def has_key(self, key, version=None): 93 key = self.make_key(key, version=version) 94 94 self._lock.reader_enters() 95 95 try: 96 96 exp = self._expire_info.get(key) … … 130 130 except KeyError: 131 131 pass 132 132 133 def delete(self, key ):134 self.validate_key(key)133 def delete(self, key, version=None): 134 key = self.make_key(key, version=version) 135 135 self._lock.writer_enters() 136 136 try: 137 137 self._delete(key) -
django/core/cache/backends/filebased.py
12 12 from django.utils.hashcompat import md5_constructor 13 13 14 14 class CacheClass(BaseCache): 15 def __init__(self, dir, params ):16 BaseCache.__init__(self, params )15 def __init__(self, dir, params, key_prefix='', version=1, key_func=None): 16 BaseCache.__init__(self, params, key_prefix, version, key_func) 17 17 18 18 max_entries = params.get('max_entries', 300) 19 19 try: … … 31 31 if not os.path.exists(self._dir): 32 32 self._createdir() 33 33 34 def add(self, key, value, timeout=None): 35 self.validate_key(key) 36 if self.has_key(key): 34 def add(self, key, value, timeout=None, version=None): 35 if self.has_key(key, version=version): 37 36 return False 38 37 39 self.set(key, value, timeout )38 self.set(key, value, timeout, version=version) 40 39 return True 41 40 42 def get(self, key, default=None): 43 self.validate_key(key) 41 def get(self, key, default=None, version=None): 42 key = self.make_key(key, version=version) 43 44 44 fname = self._key_to_file(key) 45 45 try: 46 46 f = open(fname, 'rb') … … 57 57 pass 58 58 return default 59 59 60 def set(self, key, value, timeout=None): 61 self.validate_key(key) 60 def set(self, key, value, timeout=None, version=None): 61 key = self.make_key(key, version=version) 62 62 63 fname = self._key_to_file(key) 63 64 dirname = os.path.dirname(fname) 64 65 … … 81 82 except (IOError, OSError): 82 83 pass 83 84 84 def delete(self, key ):85 self.validate_key(key)85 def delete(self, key, version=None): 86 key = self.make_key(key, version=version) 86 87 try: 87 88 self._delete(self._key_to_file(key)) 88 89 except (IOError, OSError): … … 98 99 except (IOError, OSError): 99 100 pass 100 101 101 def has_key(self, key ):102 self.validate_key(key)102 def has_key(self, key, version=None): 103 key = self.make_key(key, version=version) 103 104 fname = self._key_to_file(key) 104 105 try: 105 106 f = open(fname, 'rb') … … 153 154 Thus, a cache key of "foo" gets turnned into a file named 154 155 ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. 155 156 """ 156 path = md5_constructor(key .encode('utf-8')).hexdigest()157 path = md5_constructor(key).hexdigest() 157 158 path = os.path.join(path[:2], path[2:4], path[4:]) 158 159 return os.path.join(self._dir, path) 159 160 -
django/core/cache/backends/db.py
26 26 self.proxy = False 27 27 28 28 class CacheClass(BaseCache): 29 def __init__(self, table, params ):30 BaseCache.__init__(self, params )29 def __init__(self, table, params, key_prefix='', version=1, key_func=None): 30 BaseCache.__init__(self, params, key_prefix, version, key_func) 31 31 self._table = table 32 32 33 33 class CacheEntry(object): … … 45 45 except (ValueError, TypeError): 46 46 self._cull_frequency = 3 47 47 48 def get(self, key, default=None): 49 self.validate_key(key) 48 def get(self, key, default=None, version=None): 49 key = self.make_key(key, version=version) 50 50 51 db = router.db_for_read(self.cache_model_class) 51 52 table = connections[db].ops.quote_name(self._table) 52 53 cursor = connections[db].cursor() … … 65 66 value = connections[db].ops.process_clob(row[1]) 66 67 return pickle.loads(base64.decodestring(value)) 67 68 68 def set(self, key, value, timeout=None ):69 self.validate_key(key)69 def set(self, key, value, timeout=None, version=None): 70 key = self.make_key(key, version=version) 70 71 self._base_set('set', key, value, timeout) 71 72 72 def add(self, key, value, timeout=None ):73 self.validate_key(key)73 def add(self, key, value, timeout=None, version=None): 74 key = self.make_key(key, version=version) 74 75 return self._base_set('add', key, value, timeout) 75 76 76 77 def _base_set(self, mode, key, value, timeout=None): … … 105 106 transaction.commit_unless_managed(using=db) 106 107 return True 107 108 108 def delete(self, key): 109 self.validate_key(key) 109 def delete(self, key, version=None): 110 key = self.make_key(key, version=version) 111 110 112 db = router.db_for_write(self.cache_model_class) 111 113 table = connections[db].ops.quote_name(self._table) 112 114 cursor = connections[db].cursor() … … 114 116 cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key]) 115 117 transaction.commit_unless_managed(using=db) 116 118 117 def has_key(self, key): 118 self.validate_key(key) 119 def has_key(self, key, version=None): 120 key = self.make_key(key, version=version) 121 119 122 db = router.db_for_read(self.cache_model_class) 120 123 table = connections[db].ops.quote_name(self._table) 121 124 cursor = connections[db].cursor() -
django/core/cache/backends/memcached.py
3 3 import time 4 4 5 5 from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError 6 from django.utils.encoding import smart_unicode, smart_str7 6 8 7 try: 9 8 import cmemcache as memcache … … 19 18 raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library") 20 19 21 20 class CacheClass(BaseCache): 22 def __init__(self, server, params ):23 BaseCache.__init__(self, params )21 def __init__(self, server, params, key_prefix='', version=1, key_func=None): 22 BaseCache.__init__(self, params, key_prefix, version, key_func) 24 23 self._cache = memcache.Client(server.split(';')) 25 24 26 25 def _get_memcache_timeout(self, timeout): … … 39 38 timeout += int(time.time()) 40 39 return timeout 41 40 42 def add(self, key, value, timeout=0): 41 def add(self, key, value, timeout=0, version=None): 42 key = self.make_key(key, version=version) 43 43 if isinstance(value, unicode): 44 44 value = value.encode('utf-8') 45 return self._cache.add( smart_str(key), value, self._get_memcache_timeout(timeout))45 return self._cache.add(key, value, self._get_memcache_timeout(timeout)) 46 46 47 def get(self, key, default=None): 48 val = self._cache.get(smart_str(key)) 47 def get(self, key, default=None, version=None): 48 key = self.make_key(key, version=version) 49 val = self._cache.get(key) 49 50 if val is None: 50 51 return default 51 52 return val 52 53 53 def set(self, key, value, timeout=0): 54 self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout)) 54 def set(self, key, value, timeout=0, version=None): 55 key = self.make_key(key, version=version) 56 self._cache.set(key, value, self._get_memcache_timeout(timeout)) 55 57 56 def delete(self, key): 57 self._cache.delete(smart_str(key)) 58 def delete(self, key, version=None): 59 key = self.make_key(key, version=version) 60 self._cache.delete(key) 58 61 59 def get_many(self, keys): 60 return self._cache.get_multi(map(smart_str,keys)) 62 def get_many(self, keys, version=None): 63 new_keys = map(lambda x: self.make_key(x, version=version), keys) 64 ret = self._cache.get_multi(new_keys) 65 if ret: 66 _ = {} 67 m = dict(zip(new_keys, keys)) 68 for k, v in ret.items(): 69 _[m[k]] = v 70 ret = _ 71 return ret 61 72 62 73 def close(self, **kwargs): 63 74 self._cache.disconnect_all() 64 75 65 def incr(self, key, delta=1): 76 def incr(self, key, delta=1, version=None): 77 key = self.make_key(key, version=version) 66 78 try: 67 79 val = self._cache.incr(key, delta) 68 80 … … 76 88 77 89 return val 78 90 79 def decr(self, key, delta=1): 91 def decr(self, key, delta=1, version=None): 92 key = self.make_key(key, version=version) 80 93 try: 81 94 val = self._cache.decr(key, delta) 82 95 … … 89 102 raise ValueError("Key '%s' not found" % key) 90 103 return val 91 104 92 def set_many(self, data, timeout=0 ):105 def set_many(self, data, timeout=0, version=None): 93 106 safe_data = {} 94 107 for key, value in data.items(): 108 key = self.make_key(key, version=version) 95 109 if isinstance(value, unicode): 96 110 value = value.encode('utf-8') 97 safe_data[ smart_str(key)] = value111 safe_data[key] = value 98 112 self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout)) 99 113 100 def delete_many(self, keys): 101 self._cache.delete_multi(map(smart_str, keys)) 114 def delete_many(self, keys, version=None): 115 l = lambda x: self.make_key(x, version=version) 116 self._cache.delete_multi(map(l, keys)) 102 117 103 118 def clear(self): 104 119 self._cache.flush_all() -
tests/regressiontests/cache/tests.py
144 144 "clear does nothing for the dummy cache backend" 145 145 self.cache.clear() 146 146 147 def test_incr_version(self): 148 "Dummy cache versions can't be incremented" 149 self.cache.set('answer', 42) 150 self.assertRaises(ValueError, self.cache.incr_version, 'answer') 151 self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist') 147 152 153 def test_decr_version(self): 154 "Dummy cache versions can't be decremented" 155 self.cache.set('answer', 42) 156 self.assertRaises(ValueError, self.cache.decr_version, 'answer') 157 self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist') 158 159 148 160 class BaseCacheTests(object): 149 161 # A common set of tests to apply to all cache backends 150 162 def tearDown(self): … … 162 174 self.assertEqual(result, False) 163 175 self.assertEqual(self.cache.get("addkey1"), "value") 164 176 177 def test_prefix(self): 178 # Test for same cache key conflicts between shared backend 179 self.cache.set('somekey', 'value') 180 181 # should not be set in the prefixed cache 182 self.assertFalse(self.pfx_cache.has_key('somekey')) 183 184 self.pfx_cache.set('somekey', 'value2') 185 186 self.assertEqual(self.cache.get('somekey'), 'value') 187 self.assertEqual(self.pfx_cache.get('somekey'), 'value2') 188 165 189 def test_non_existent(self): 166 190 # Non-existent cache keys return as None/default 167 191 # get with non-existent keys … … 375 399 with more liberal key rules. Refs #6447. 376 400 377 401 """ 402 # mimic custom ``make_key`` method being defined since the default will 403 # never show the below warnings 404 def func(key, *args): 405 return key 406 407 old_func = self.cache.key_func 408 self.cache.key_func = func 378 409 # On Python 2.6+ we could use the catch_warnings context 379 410 # manager to test this warning nicely. Since we can't do that 380 411 # yet, the cleanest option is to temporarily ask for … … 392 423 # it. The effect will be the same, as long as the Django test 393 424 # runner doesn't add any global warning filters (it currently 394 425 # does not). 426 self.cache.key_func = old_func 395 427 warnings.resetwarnings() 396 428 warnings.simplefilter("ignore", PendingDeprecationWarning) 397 429 430 def test_incr_version(self): 431 self.cache.set('answer', 42) 432 self.assertEqual(self.cache.incr_version('answer'), 2) 433 self.assertEqual(self.cache.get('answer', version=2), 42) 434 self.assertEqual(self.cache.get('answer'), None) 435 self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist') 436 437 def test_decr_version(self): 438 self.cache.set('answer', 42, version=2) 439 self.assertEqual(self.cache.decr_version('answer', version=2), 1) 440 self.assertEqual(self.cache.get('answer', version=2), None) 441 self.assertEqual(self.cache.get('answer'), 42) 442 self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist', version=2) 443 398 444 class DBCacheTests(unittest.TestCase, BaseCacheTests): 399 445 def setUp(self): 400 446 # Spaces are used in the table name to ensure quoting/escaping is working 401 447 self._table_name = 'test cache table' 402 448 management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False) 403 449 self.cache = get_cache('db://%s?max_entries=30' % self._table_name) 450 self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix') 404 451 405 452 def tearDown(self): 406 453 from django.db import connection … … 413 460 class LocMemCacheTests(unittest.TestCase, BaseCacheTests): 414 461 def setUp(self): 415 462 self.cache = get_cache('locmem://?max_entries=30') 463 self.pfx_cache = get_cache('locmem://', 'cacheprefix') 416 464 417 465 def test_cull(self): 418 466 self.perform_cull_test(50, 29) … … 425 473 class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): 426 474 def setUp(self): 427 475 self.cache = get_cache(settings.CACHE_BACKEND) 476 self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix') 428 477 429 478 def test_invalid_keys(self): 430 479 """ … … 436 485 that a generic exception of some kind is raised. 437 486 438 487 """ 488 # mimic custom ``make_key`` method being defined since the default will 489 # never show the below warnings 490 def func(key, *args): 491 return key 492 493 old_func = self.cache.key_func 494 self.cache.key_func = func 495 439 496 # memcached does not allow whitespace or control characters in keys 440 497 self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value') 441 498 # memcached limits key length to 250 442 499 self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value') 443 500 501 self.cache.key_func = old_func 444 502 445 503 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): 446 504 """ … … 449 507 def setUp(self): 450 508 self.dirname = tempfile.mkdtemp() 451 509 self.cache = get_cache('file://%s?max_entries=30' % self.dirname) 510 self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix') 452 511 453 512 def test_hashing(self): 454 513 """Test that keys are hashed into subdirectories correctly""" 455 514 self.cache.set("foo", "bar") 456 keyhash = md5_constructor("foo").hexdigest() 515 key = self.cache.make_key("foo") 516 keyhash = md5_constructor(key).hexdigest() 457 517 keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) 458 518 self.assert_(os.path.exists(keypath)) 459 519 … … 462 522 Make sure that the created subdirectories are correctly removed when empty. 463 523 """ 464 524 self.cache.set("foo", "bar") 465 keyhash = md5_constructor("foo").hexdigest() 525 key = self.cache.make_key("foo") 526 keyhash = md5_constructor(key).hexdigest() 466 527 keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) 467 528 self.assert_(os.path.exists(keypath)) 468 529 … … 472 533 self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath)))) 473 534 474 535 def test_cull(self): 475 self.perform_cull_test(50, 2 8)536 self.perform_cull_test(50, 29) 476 537 477 538 class CustomCacheKeyValidationTests(unittest.TestCase): 478 539 """ … … 495 556 496 557 def setUp(self): 497 558 self.path = '/cache/test/' 498 self.old_ settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX499 self.old_ middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS559 self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX 560 self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS 500 561 self.orig_use_i18n = settings.USE_I18N 501 562 settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix' 502 563 settings.CACHE_MIDDLEWARE_SECONDS = 1 503 564 settings.USE_I18N = False 504 565 505 566 def tearDown(self): 506 settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_ settings_key_prefix507 settings.CACHE_MIDDLEWARE_SECONDS = self.old_ middleware_seconds567 settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix 568 settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds 508 569 settings.USE_I18N = self.orig_use_i18n 509 570 510 571 def _get_request(self, path): … … 557 618 learn_cache_key(request, response) 558 619 self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') 559 620 621 class PrefixedCacheUtils(CacheUtils): 622 def setUp(self): 623 super(PrefixedCacheUtils, self).setUp() 624 self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX 625 settings.CACHE_KEY_PREFIX = 'cacheprefix' 626 627 def tearDown(self): 628 super(PrefixedCacheUtils, self).tearDown() 629 settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix 630 560 631 class CacheI18nTest(unittest.TestCase): 561 632 562 633 def setUp(self): … … 652 723 get_cache_data = FetchFromCacheMiddleware().process_request(request) 653 724 self.assertEqual(get_cache_data.content, es_message) 654 725 726 class PrefixedCacheI18nTest(CacheI18nTest): 727 def setUp(self): 728 super(PrefixedCacheI18nTest, self).setUp() 729 self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX 730 settings.CACHE_KEY_PREFIX = 'cacheprefix' 731 732 def tearDown(self): 733 super(PrefixedCacheI18nTest, self).tearDown() 734 settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix 735 655 736 if __name__ == '__main__': 656 737 unittest.main() -
docs/topics/cache.txt
680 680 ...and use the dotted Python path to this module as the scheme portion of your 681 681 :setting:`CACHE_BACKEND`. 682 682 683 Cache Prefix 684 ------------ 685 686 .. versionadded:: 1.3 687 688 It is a common occurence to have a shared cache instance running on your development 689 or production server for use across multiple projects, i.e. all sites are pointing 690 to the memcached instance running on port 11211. As a result, cache key conflicts 691 may arise which could result in data from one site being used by another. 692 693 To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys 694 that are used with the cache backend, transparently:: 695 696 # in settings, CACHE_KEY_PREFIX = 'myproject_' 697 698 >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key' 699 >>> cache.get('my_key') # retrieved with key 'myproject_my_key' 700 'hello world!' 701 702 Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for 703 both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty 704 string ``''``. 705 706 .. note:: 707 708 This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used 709 in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a 710 particular cache instance, therefore it will be prepended to the 711 CACHE_MIDDLEWARE_KEY_PREFIX transparently. 712 713 714 Cache Versioning 715 ---------------- 716 717 .. versionadded:: 1.3 718 719 Cache versioning can be a simple solution to dealing with invalid cache. The 720 setting ``CACHE_VERSION`` provides a means of defining which version of cache 721 your project will use. This is useful primarily for project upgrades or 722 downgrades. For example, if a project is in production and new features are 723 being added to it, the cache being used may not be useful any longer. The 724 ``CACHE_VERSION`` can be incremented and all previous vesions of the cache 725 will be ignored. 726 727 .. note:: 728 729 This technique simulates "invalidating" your cache, but does not 730 actually free up the space used by the other cache versions. Assuming 731 a reasonable ``max_entries`` and `cull_frequency`` has been defined, 732 this should not be a problem since the old cache will be culled as needed. 733 734 For more complex cache versioning strategies, the cache version can specified 735 at runtime as such:: 736 737 >>> cache.set('my_key', 'hello world!', version=2) 738 >>> cache.get('my_key', version=2) # hello world! 739 >>> cache.get('my_key', version=3) # None 740 741 If the version is not supplied, it defaults to ``CACHE_VERSION``. This flexibility 742 allows for interesting caching strategies such as cache dependency checking. For 743 example, if an object's cache version has been incremented, any dependent objects 744 can be checked (and rebuilt) in cache with the incremented version. 745 746 Two helper functions for reusing other versions of cache exist:: 747 748 # CACHE_VERSION = 1 749 750 >>> cache.set('my_key', 'hello world!') 751 752 >>> v2 = cache.incr_version('my_key') # 2 753 >>> cache.get('my_key') # None 754 >>> cache.get('my_key', version=v) # hello world! 755 756 >>> v1 = cache.decr_version('my_key', version=v2) # 1 757 >>> cache.get('my_key', version=v2) # None 758 >>> cache.get('my_key') # hello world! 759 760 Each method returns the new version number associated with the key. 761 762 Cache Key Module 763 ---------------- 764 765 .. versionadded:: 1.3 766 767 The setting ``CACHE_KEY_MODULE`` can be specified which must contain a function 768 named ``make_key`` that handles constructing the key that is ultimately used 769 when interacting with the cache backend. The function is passed the raw key 770 (what the user specifies), the ``CACHE_KEY_PREFIX``, and the ``CACHE_VERSION``. 771 The default function simply concatenates the three arguments and takes the md5 772 hexdigest of the result. In most cases the default will suffice and a custom 773 will not have to be defined. 774 683 775 Upstream caches 684 776 =============== 685 777