Ticket #13795: t13795-rc1.diff
File t13795-rc1.diff, 48.1 KB (added by , 14 years ago) |
---|
-
django/conf/global_settings.py
diff -r 9aed8047e568 django/conf/global_settings.py
a b 433 433 # The cache backend to use. See the docstring in django.core.cache for the 434 434 # possible values. 435 435 CACHE_BACKEND = 'locmem://' 436 CACHE_VERSION = 1 437 CACHE_KEY_PREFIX = '' 438 CACHE_KEY_FUNCTION = None 436 439 CACHE_MIDDLEWARE_KEY_PREFIX = '' 437 440 CACHE_MIDDLEWARE_SECONDS = 600 438 441 -
django/core/cache/__init__.py
diff -r 9aed8047e568 django/core/cache/__init__.py
a b 67 67 68 68 return scheme, host, params 69 69 70 def get_cache(backend_uri): 70 def get_cache(backend_uri, key_prefix=None, version=None, key_func=None): 71 if key_prefix is None: 72 key_prefix = settings.CACHE_KEY_PREFIX 73 if version is None: 74 version = settings.CACHE_VERSION 75 if key_func is None: 76 key_func = settings.CACHE_KEY_FUNCTION 77 78 if key_func is not None and not callable(key_func): 79 key_func_module_path, key_func_name = settings.CACHE_KEY_FUNCTION.rsplit('.', 1) 80 key_func_module = importlib.import_module(key_func_module_path) 81 key_func = getattr(key_func_module, key_func_name) 82 71 83 scheme, host, params = parse_backend_uri(backend_uri) 72 84 if scheme in BACKENDS: 73 85 name = 'django.core.cache.backends.%s' % BACKENDS[scheme] 74 86 else: 75 87 name = scheme 76 88 module = importlib.import_module(name) 77 return module.CacheClass(host, params )89 return module.CacheClass(host, params, key_prefix, version, key_func) 78 90 79 91 cache = get_cache(settings.CACHE_BACKEND) 80 92 81 # Some caches -- python t-memcached in particular -- need to do a cleanup at the93 # Some caches -- python-memcached in particular -- need to do a cleanup at the 82 94 # end of a request cycle. If the cache provides a close() method, wire it up 83 95 # here. 84 96 if hasattr(cache, 'close'): -
django/core/cache/backends/base.py
diff -r 9aed8047e568 django/core/cache/backends/base.py
a b 3 3 import warnings 4 4 5 5 from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning 6 from django.utils.encoding import smart_str 6 7 7 8 class InvalidCacheBackendError(ImproperlyConfigured): 8 9 pass … … 13 14 # Memcached does not accept keys longer than this. 14 15 MEMCACHE_MAX_KEY_LENGTH = 250 15 16 17 def make_key(key, key_prefix, version): 18 """Default function to generate keys. 19 20 Constructs the key used by all other methods. By default it prepends 21 the `key_prefix'. CACHE_KEY_FUNCTION can be used to specify an alternate 22 function with custom key making behavior. 23 """ 24 return ':'.join([key_prefix, str(version), smart_str(key)]) 25 16 26 class BaseCache(object): 17 def __init__(self, params ):27 def __init__(self, params, key_prefix='', version=1, key_func=None): 18 28 timeout = params.get('timeout', 300) 19 29 try: 20 30 timeout = int(timeout) … … 34 44 except (ValueError, TypeError): 35 45 self._cull_frequency = 3 36 46 37 def add(self, key, value, timeout=None): 47 self.key_prefix = smart_str(key_prefix) 48 self.version = version 49 self.key_func = key_func or make_key 50 51 def _make_key(self, key, version=None): 52 """Constructs the key used by all other methods. By default it prepends 53 the `key_prefix'. In cache backend subclasses this can be overriden to 54 provide custom key making behavior. 55 """ 56 if version is None: 57 version = self.version 58 59 new_key = self.key_func(key, self.key_prefix, version) 60 self.validate_key(new_key) 61 return new_key 62 63 def add(self, key, value, timeout=None, version=None): 38 64 """ 39 65 Set a value in the cache if the key does not already exist. If 40 66 timeout is given, that timeout will be used for the key; otherwise … … 44 70 """ 45 71 raise NotImplementedError 46 72 47 def get(self, key, default=None ):73 def get(self, key, default=None, version=None): 48 74 """ 49 75 Fetch a given key from the cache. If the key does not exist, return 50 76 default, which itself defaults to None. 51 77 """ 52 78 raise NotImplementedError 53 79 54 def set(self, key, value, timeout=None ):80 def set(self, key, value, timeout=None, version=None): 55 81 """ 56 82 Set a value in the cache. If timeout is given, that timeout will be 57 83 used for the key; otherwise the default cache timeout will be used. 58 84 """ 59 85 raise NotImplementedError 60 86 61 def delete(self, key ):87 def delete(self, key, version=None): 62 88 """ 63 89 Delete a key from the cache, failing silently. 64 90 """ 65 91 raise NotImplementedError 66 92 67 def get_many(self, keys ):93 def get_many(self, keys, version=None): 68 94 """ 69 95 Fetch a bunch of keys from the cache. For certain backends (memcached, 70 96 pgsql) this can be *much* faster when fetching multiple values. … … 74 100 """ 75 101 d = {} 76 102 for k in keys: 77 val = self.get(k )103 val = self.get(k, version=version) 78 104 if val is not None: 79 105 d[k] = val 80 106 return d 81 107 82 def has_key(self, key ):108 def has_key(self, key, version=None): 83 109 """ 84 110 Returns True if the key is in the cache and has not expired. 85 111 """ 86 return self.get(key ) is not None112 return self.get(key, version=version) is not None 87 113 88 def incr(self, key, delta=1 ):114 def incr(self, key, delta=1, version=None): 89 115 """ 90 116 Add delta to value in the cache. If the key does not exist, raise a 91 117 ValueError exception. 92 118 """ 93 if key not in self: 119 value = self.get(key, version=version) 120 if value is None: 94 121 raise ValueError("Key '%s' not found" % key) 95 new_value = self.get(key)+ delta96 self.set(key, new_value )122 new_value = value + delta 123 self.set(key, new_value, version=version) 97 124 return new_value 98 125 99 def decr(self, key, delta=1 ):126 def decr(self, key, delta=1, version=None): 100 127 """ 101 128 Subtract delta from value in the cache. If the key does not exist, raise 102 129 a ValueError exception. 103 130 """ 104 return self.incr(key, -delta )131 return self.incr(key, -delta, version=version) 105 132 106 133 def __contains__(self, key): 107 134 """ … … 112 139 # if a subclass overrides it. 113 140 return self.has_key(key) 114 141 115 def set_many(self, data, timeout=None ):142 def set_many(self, data, timeout=None, version=None): 116 143 """ 117 144 Set a bunch of values in the cache at once from a dict of key/value 118 145 pairs. For certain backends (memcached), this is much more efficient … … 122 149 the default cache timeout will be used. 123 150 """ 124 151 for key, value in data.items(): 125 self.set(key, value, timeout )152 self.set(key, value, timeout=timeout, version=version) 126 153 127 def delete_many(self, keys ):154 def delete_many(self, keys, version=None): 128 155 """ 129 156 Set a bunch of values in the cache at once. For certain backends 130 157 (memcached), this is much more efficient than calling delete() multiple 131 158 times. 132 159 """ 133 160 for key in keys: 134 self.delete(key )161 self.delete(key, version=version) 135 162 136 163 def clear(self): 137 164 """Remove *all* values from the cache at once.""" … … 154 181 'errors if used with memcached: %r' % key, 155 182 CacheKeyWarning) 156 183 184 def incr_version(self, key, delta=1, version=None): 185 """Adds delta to the cache version for the supplied key. Returns the 186 new version. 187 """ 188 if version is None: 189 version = self.version 190 191 value = self.get(key, version=version) 192 if value is None: 193 raise ValueError("Key '%s' not found" % key) 194 195 self.set(key, value, version=version+delta) 196 self.delete(key, version=version) 197 return version+delta 198 199 def decr_version(self, key, delta=1, version=None): 200 """Substracts delta from the cache version for the supplied key. Returns 201 the new version. 202 """ 203 return self.incr_version(key, -delta, version) -
django/core/cache/backends/db.py
diff -r 9aed8047e568 django/core/cache/backends/db.py
a b 26 26 self.proxy = False 27 27 28 28 class BaseDatabaseCacheClass(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): … … 35 35 self.cache_model_class = CacheEntry 36 36 37 37 class CacheClass(BaseDatabaseCacheClass): 38 def get(self, key, default=None ):39 self.validate_key(key)38 def get(self, key, default=None, version=None): 39 key = self._make_key(key, version=version) 40 40 db = router.db_for_read(self.cache_model_class) 41 41 table = connections[db].ops.quote_name(self._table) 42 42 cursor = connections[db].cursor() … … 55 55 value = connections[db].ops.process_clob(row[1]) 56 56 return pickle.loads(base64.decodestring(value)) 57 57 58 def set(self, key, value, timeout=None ):59 self.validate_key(key)58 def set(self, key, value, timeout=None, version=None): 59 key = self._make_key(key, version=version) 60 60 self._base_set('set', key, value, timeout) 61 61 62 def add(self, key, value, timeout=None ):63 self.validate_key(key)62 def add(self, key, value, timeout=None, version=None): 63 key = self._make_key(key, version=version) 64 64 return self._base_set('add', key, value, timeout) 65 65 66 66 def _base_set(self, mode, key, value, timeout=None): … … 95 95 transaction.commit_unless_managed(using=db) 96 96 return True 97 97 98 def delete(self, key): 99 self.validate_key(key) 98 def delete(self, key, version=None): 99 key = self._make_key(key, version=version) 100 100 101 db = router.db_for_write(self.cache_model_class) 101 102 table = connections[db].ops.quote_name(self._table) 102 103 cursor = connections[db].cursor() … … 104 105 cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key]) 105 106 transaction.commit_unless_managed(using=db) 106 107 107 def has_key(self, key): 108 self.validate_key(key) 108 def has_key(self, key, version=None): 109 key = self._make_key(key, version=version) 110 109 111 db = router.db_for_read(self.cache_model_class) 110 112 table = connections[db].ops.quote_name(self._table) 111 113 cursor = connections[db].cursor() -
django/core/cache/backends/dummy.py
diff -r 9aed8047e568 django/core/cache/backends/dummy.py
a b 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/filebased.py
diff -r 9aed8047e568 django/core/cache/backends/filebased.py
a b 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 self._dir = dir 18 18 if not os.path.exists(self._dir): 19 19 self._createdir() 20 20 21 def add(self, key, value, timeout=None): 22 self.validate_key(key) 23 if self.has_key(key): 21 def add(self, key, value, timeout=None, version=None): 22 if self.has_key(key, version=version): 24 23 return False 25 24 26 self.set(key, value, timeout )25 self.set(key, value, timeout, version=version) 27 26 return True 28 27 29 def get(self, key, default=None): 30 self.validate_key(key) 28 def get(self, key, default=None, version=None): 29 key = self._make_key(key, version=version) 30 31 31 fname = self._key_to_file(key) 32 32 try: 33 33 f = open(fname, 'rb') … … 44 44 pass 45 45 return default 46 46 47 def set(self, key, value, timeout=None): 48 self.validate_key(key) 47 def set(self, key, value, timeout=None, version=None): 48 key = self._make_key(key, version=version) 49 49 50 fname = self._key_to_file(key) 50 51 dirname = os.path.dirname(fname) 51 52 … … 68 69 except (IOError, OSError): 69 70 pass 70 71 71 def delete(self, key ):72 self.validate_key(key)72 def delete(self, key, version=None): 73 key = self._make_key(key, version=version) 73 74 try: 74 75 self._delete(self._key_to_file(key)) 75 76 except (IOError, OSError): … … 85 86 except (IOError, OSError): 86 87 pass 87 88 88 def has_key(self, key ):89 self.validate_key(key)89 def has_key(self, key, version=None): 90 key = self._make_key(key, version=version) 90 91 fname = self._key_to_file(key) 91 92 try: 92 93 f = open(fname, 'rb') … … 140 141 Thus, a cache key of "foo" gets turnned into a file named 141 142 ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``. 142 143 """ 143 path = md5_constructor(key .encode('utf-8')).hexdigest()144 path = md5_constructor(key).hexdigest() 144 145 path = os.path.join(path[:2], path[2:4], path[4:]) 145 146 return os.path.join(self._dir, path) 146 147 -
django/core/cache/backends/locmem.py
diff -r 9aed8047e568 django/core/cache/backends/locmem.py
a b 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 self._lock = RWLock() 18 18 19 def add(self, key, value, timeout=None ):20 self.validate_key(key)19 def add(self, key, value, timeout=None, version=None): 20 key = self._make_key(key, version=version) 21 21 self._lock.writer_enters() 22 22 try: 23 23 exp = self._expire_info.get(key) … … 31 31 finally: 32 32 self._lock.writer_leaves() 33 33 34 def get(self, key, default=None ):35 self.validate_key(key)34 def get(self, key, default=None, version=None): 35 key = self._make_key(key, version=version) 36 36 self._lock.reader_enters() 37 37 try: 38 38 exp = self._expire_info.get(key) … … 64 64 self._cache[key] = value 65 65 self._expire_info[key] = time.time() + timeout 66 66 67 def set(self, key, value, timeout=None ):68 self.validate_key(key)67 def set(self, key, value, timeout=None, version=None): 68 key = self._make_key(key, version=version) 69 69 self._lock.writer_enters() 70 70 # Python 2.4 doesn't allow combined try-except-finally blocks. 71 71 try: … … 76 76 finally: 77 77 self._lock.writer_leaves() 78 78 79 def has_key(self, key ):80 self.validate_key(key)79 def has_key(self, key, version=None): 80 key = self._make_key(key, version=version) 81 81 self._lock.reader_enters() 82 82 try: 83 83 exp = self._expire_info.get(key) … … 117 117 except KeyError: 118 118 pass 119 119 120 def delete(self, key ):121 self.validate_key(key)120 def delete(self, key, version=None): 121 key = self._make_key(key, version=version) 122 122 self._lock.writer_enters() 123 123 try: 124 124 self._delete(key) -
django/core/cache/backends/memcached.py
diff -r 9aed8047e568 django/core/cache/backends/memcached.py
a b 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() -
docs/ref/settings.txt
diff -r 9aed8047e568 docs/ref/settings.txt
a b 136 136 137 137 The cache backend to use. See :doc:`/topics/cache`. 138 138 139 .. setting:: CACHE_KEY_FUNCTION 140 141 CACHE_KEY_FUNCTION 142 ------------------ 143 144 Default: ``None`` 145 146 A string containing a dotted path to a function that defines how to 147 compose a prefix, version and key into a final cache key. The default 148 implementation is equivalent to the function:: 149 150 def make_key(key, key_prefix, version): 151 return ':'.join([key_prefix, str(version), smart_str(key)]) 152 153 You may use any key function you want, as long as it adheres to the same 154 prototype. 155 156 See the :ref:`cache documentation <cache_key_transformation>` for more information. 157 139 158 .. setting:: CACHE_MIDDLEWARE_ANONYMOUS_ONLY 140 159 141 160 CACHE_MIDDLEWARE_ANONYMOUS_ONLY … … 172 191 The default number of seconds to cache a page when the caching middleware or 173 192 ``cache_page()`` decorator is used. 174 193 194 .. setting:: CACHE_PREFIX 195 196 CACHE_PREFIX 197 ------------ 198 199 Default: ``''`` (Empty string) 200 201 A string that will be automatically included (prepended by default) to 202 all cache keys used by the Django server. 203 204 See the :ref:`cache documentation <cache_key_prefixing>` for more information. 205 206 .. setting:: CACHE_VERSION 207 208 CACHE_VERSION 209 ------------- 210 211 Default: ``1`` 212 213 The default version number for cache keys generated by the Django server. 214 215 See the :ref:`cache documentation <cache_versioning>` for more information. 216 217 175 218 .. setting:: CSRF_COOKIE_DOMAIN 176 219 177 220 CSRF_COOKIE_DOMAIN -
docs/releases/1.3.txt
diff -r 9aed8047e568 docs/releases/1.3.txt
a b 155 155 :meth:`~django.test.client.Client.assertNumQueries` -- making it 156 156 easier to test the database activity associated with a view. 157 157 158 * :ref:`Versioning <cache_versioning>`, :ref:`site-wide prefixing 159 <cache_key_prefixing>` and :ref:`transformation 160 <cache_key_transformation>` has been added to the cache API. 158 161 159 162 .. _backwards-incompatible-changes-1.3: 160 163 -
docs/topics/cache.txt
diff -r 9aed8047e568 docs/topics/cache.txt
a b 643 643 However, if the backend doesn't natively provide an increment/decrement 644 644 operation, it will be implemented using a two-step retrieve/update. 645 645 646 .. _cache_key_prefixing: 647 648 Cache key prefixing 649 ------------------- 650 651 .. versionadded:: 1.3 652 653 If you are sharing a cache instance between servers, or between your 654 production and development environments, it's possible for data cached 655 by one server to be used by another server. If the format of cached 656 data is different between servers, this can lead to some very hard to 657 diagnose problems. 658 659 To prevent this, Django provides the ability to prefix all cache keys 660 used by a server. When a particular cache key is saved or retrieved, 661 Django will automatically prefix the cache key with the value of the 662 :setting:`CACHE_KEY_PREFIX` setting. 663 664 By ensuring each Django instance has a different 665 :setting:`CACHE_KEY_PREFIX`, you can ensure that there will be no 666 collisions in cache values. 667 668 .. _cache_versioning: 669 670 Cache versioning 671 ---------------- 672 673 .. versionadded:: 1.3 674 675 When you change running code that uses cached values, you may need to 676 purge any existing cached values. The easiest way to do this is to 677 flush the entire cache, but this may result in the loss of cache 678 values that are still valid and useful, but are lost because the 679 entire cache is purged. 680 681 Django provides a better way to target individual cache values. 682 Django's cache framework has a system-wide version identifier, 683 specified using the :setting:`CACHE_VERSION` setting. The value of 684 this setting is automatically combined with the cache prefix and the 685 user-provided cache key to obtain the final cache key. 686 687 By default, any key request will automatically include the site 688 default cache key version. However, the primitive cache functions all 689 include a ``version`` argument, so you can specify a particular cache 690 key version to set or get. For example:: 691 692 # Set version 2 of a cache key 693 >>> cache.set('my_key', 'hello world!', version=2) 694 # Get the default version (assuming version=1) 695 >>> cache.get('my_key') 696 None 697 # Get version 2 of the same key 698 >>> cache.get('my_key', version=2) 699 'hello world!' 700 701 The value of a specific key can be incremented and decremented using 702 the :func:`incr_version()` and :func:`decr_version()` methods. This 703 enables specific keys to be bumped to a new version, leaving other 704 keys unaffected. Continuing our previous example:: 705 706 # Increment the version of 'my_key' 707 >>> cache.incr_version('my_key') 708 # The default version still isn't available 709 >>> cache.get('my_key') 710 None 711 # Version 2 isn't available, either 712 >>> cache.get('my_key', version=2) 713 None 714 # But version 3 *is* availble 715 >>> cache.get('my_key', version=3) 716 'hello world!' 717 718 .. _cache_key_transformation: 719 720 Cache key transformation 721 ------------------------ 722 723 .. versionadded:: 1.3 724 725 As described in the previous two sections, the cache key provided by a 726 user is not used verbatim -- it is combined with the cache prefix and 727 key version to provide a final cache key. By default, the three parts 728 are joined using colons to produce a final string:: 729 730 def make_key(key, key_prefix, version): 731 return ':'.join([key_prefix, str(version), smart_str(key)]) 732 733 If you want to combine the parts in different ways, or apply other 734 processing to the final key (e.g., taking a hash digest of the key 735 parts), you can provide a custom key function. 736 737 The setting :setting:`CACHE_KEY_FUNCTION` specifies a dotted-path to 738 a function matching the prototype of :func:`make_key()` above. If 739 provided, this custom key function will be used instead of the default 740 key combining function. 741 646 742 Cache key warnings 647 743 ------------------ 648 744 -
tests/regressiontests/cache/tests.py
diff -r 9aed8047e568 tests/regressiontests/cache/tests.py
a b 145 145 "clear does nothing for the dummy cache backend" 146 146 self.cache.clear() 147 147 148 def test_incr_version(self): 149 "Dummy cache versions can't be incremented" 150 self.cache.set('answer', 42) 151 self.assertRaises(ValueError, self.cache.incr_version, 'answer') 152 self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist') 153 154 def test_decr_version(self): 155 "Dummy cache versions can't be decremented" 156 self.cache.set('answer', 42) 157 self.assertRaises(ValueError, self.cache.decr_version, 'answer') 158 self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist') 159 148 160 149 161 class BaseCacheTests(object): 150 162 # A common set of tests to apply to all cache backends 151 def tearDown(self):152 self.cache.clear()153 163 154 164 def test_simple(self): 155 165 # Simple cache set/get works … … 163 173 self.assertEqual(result, False) 164 174 self.assertEqual(self.cache.get("addkey1"), "value") 165 175 176 def test_prefix(self): 177 # Test for same cache key conflicts between shared backend 178 self.cache.set('somekey', 'value') 179 180 # should not be set in the prefixed cache 181 self.assertFalse(self.prefix_cache.has_key('somekey')) 182 183 self.prefix_cache.set('somekey', 'value2') 184 185 self.assertEqual(self.cache.get('somekey'), 'value') 186 self.assertEqual(self.prefix_cache.get('somekey'), 'value2') 187 166 188 def test_non_existent(self): 167 189 # Non-existent cache keys return as None/default 168 190 # get with non-existent keys … … 376 398 with more liberal key rules. Refs #6447. 377 399 378 400 """ 401 # mimic custom ``make_key`` method being defined since the default will 402 # never show the below warnings 403 def func(key, *args): 404 return key 405 406 old_func = self.cache.key_func 407 self.cache.key_func = func 379 408 # On Python 2.6+ we could use the catch_warnings context 380 409 # manager to test this warning nicely. Since we can't do that 381 410 # yet, the cleanest option is to temporarily ask for … … 390 419 self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value') 391 420 finally: 392 421 restore_warnings_state(_warnings_state) 422 self.cache.key_func = old_func 423 424 def test_cache_versioning_get_set(self): 425 # set, using default version = 1 426 self.cache.set('answer1', 42) 427 self.assertEqual(self.cache.get('answer1'), 42) 428 self.assertEqual(self.cache.get('answer1', version=1), 42) 429 self.assertEqual(self.cache.get('answer1', version=2), None) 430 431 self.assertEqual(self.v2_cache.get('answer1'), None) 432 # print '---' 433 # print 'c1',self.cache._cache 434 # print 'v2',self.v2_cache._cache 435 self.assertEqual(self.v2_cache.get('answer1', version=1), 42) 436 self.assertEqual(self.v2_cache.get('answer1', version=2), None) 437 438 # set, default version = 1, but manually override version = 2 439 self.cache.set('answer2', 42, version=2) 440 self.assertEqual(self.cache.get('answer2'), None) 441 self.assertEqual(self.cache.get('answer2', version=1), None) 442 self.assertEqual(self.cache.get('answer2', version=2), 42) 443 444 self.assertEqual(self.v2_cache.get('answer2'), 42) 445 self.assertEqual(self.v2_cache.get('answer2', version=1), None) 446 self.assertEqual(self.v2_cache.get('answer2', version=2), 42) 447 448 # v2 set, using default version = 2 449 self.v2_cache.set('answer3', 42) 450 self.assertEqual(self.cache.get('answer3'), None) 451 self.assertEqual(self.cache.get('answer3', version=1), None) 452 self.assertEqual(self.cache.get('answer3', version=2), 42) 453 454 self.assertEqual(self.v2_cache.get('answer3'), 42) 455 self.assertEqual(self.v2_cache.get('answer3', version=1), None) 456 self.assertEqual(self.v2_cache.get('answer3', version=2), 42) 457 458 # v2 set, default version = 2, but manually override version = 1 459 self.v2_cache.set('answer4', 42, version=1) 460 self.assertEqual(self.cache.get('answer4'), 42) 461 self.assertEqual(self.cache.get('answer4', version=1), 42) 462 self.assertEqual(self.cache.get('answer4', version=2), None) 463 464 self.assertEqual(self.v2_cache.get('answer4'), None) 465 self.assertEqual(self.v2_cache.get('answer4', version=1), 42) 466 self.assertEqual(self.v2_cache.get('answer4', version=2), None) 467 468 def test_cache_versioning_has_key(self): 469 self.cache.set('answer1', 42) 470 471 # has_key 472 self.assertTrue(self.cache.has_key('answer1')) 473 self.assertTrue(self.cache.has_key('answer1', version=1)) 474 self.assertFalse(self.cache.has_key('answer1', version=2)) 475 476 self.assertFalse(self.v2_cache.has_key('answer1')) 477 self.assertTrue(self.v2_cache.has_key('answer1', version=1)) 478 self.assertFalse(self.v2_cache.has_key('answer1', version=2)) 479 480 def test_cache_versioning_delete(self): 481 self.cache.set('answer1', 37, version=1) 482 self.cache.set('answer1', 42, version=2) 483 self.cache.delete('answer1') 484 self.assertEqual(self.cache.get('answer1', version=1), None) 485 self.assertEqual(self.cache.get('answer1', version=2), 42) 486 487 self.cache.set('answer2', 37, version=1) 488 self.cache.set('answer2', 42, version=2) 489 self.cache.delete('answer2', version=2) 490 self.assertEqual(self.cache.get('answer2', version=1), 37) 491 self.assertEqual(self.cache.get('answer2', version=2), None) 492 493 self.cache.set('answer3', 37, version=1) 494 self.cache.set('answer3', 42, version=2) 495 self.v2_cache.delete('answer3') 496 self.assertEqual(self.cache.get('answer3', version=1), 37) 497 self.assertEqual(self.cache.get('answer3', version=2), None) 498 499 self.cache.set('answer4', 37, version=1) 500 self.cache.set('answer4', 42, version=2) 501 self.v2_cache.delete('answer4', version=1) 502 self.assertEqual(self.cache.get('answer4', version=1), None) 503 self.assertEqual(self.cache.get('answer4', version=2), 42) 504 505 def test_cache_versioning_incr_decr(self): 506 self.cache.set('answer1', 37, version=1) 507 self.cache.set('answer1', 42, version=2) 508 self.cache.incr('answer1') 509 self.assertEqual(self.cache.get('answer1', version=1), 38) 510 self.assertEqual(self.cache.get('answer1', version=2), 42) 511 self.cache.decr('answer1') 512 self.assertEqual(self.cache.get('answer1', version=1), 37) 513 self.assertEqual(self.cache.get('answer1', version=2), 42) 514 515 self.cache.set('answer2', 37, version=1) 516 self.cache.set('answer2', 42, version=2) 517 self.cache.incr('answer2', version=2) 518 self.assertEqual(self.cache.get('answer2', version=1), 37) 519 self.assertEqual(self.cache.get('answer2', version=2), 43) 520 self.cache.decr('answer2', version=2) 521 self.assertEqual(self.cache.get('answer2', version=1), 37) 522 self.assertEqual(self.cache.get('answer2', version=2), 42) 523 524 self.cache.set('answer3', 37, version=1) 525 self.cache.set('answer3', 42, version=2) 526 self.v2_cache.incr('answer3') 527 self.assertEqual(self.cache.get('answer3', version=1), 37) 528 self.assertEqual(self.cache.get('answer3', version=2), 43) 529 self.v2_cache.decr('answer3') 530 self.assertEqual(self.cache.get('answer3', version=1), 37) 531 self.assertEqual(self.cache.get('answer3', version=2), 42) 532 533 self.cache.set('answer4', 37, version=1) 534 self.cache.set('answer4', 42, version=2) 535 self.v2_cache.incr('answer4', version=1) 536 self.assertEqual(self.cache.get('answer4', version=1), 38) 537 self.assertEqual(self.cache.get('answer4', version=2), 42) 538 self.v2_cache.decr('answer4', version=1) 539 self.assertEqual(self.cache.get('answer4', version=1), 37) 540 self.assertEqual(self.cache.get('answer4', version=2), 42) 541 542 def test_cache_versioning_get_set_many(self): 543 # set, using default version = 1 544 self.cache.set_many({'ford1': 37, 'arthur1': 42}) 545 self.assertEqual(self.cache.get_many(['ford1','arthur1']), 546 {'ford1': 37, 'arthur1': 42}) 547 self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=1), 548 {'ford1': 37, 'arthur1': 42}) 549 self.assertEqual(self.cache.get_many(['ford1','arthur1'], version=2), {}) 550 551 self.assertEqual(self.v2_cache.get_many(['ford1','arthur1']), {}) 552 self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=1), 553 {'ford1': 37, 'arthur1': 42}) 554 self.assertEqual(self.v2_cache.get_many(['ford1','arthur1'], version=2), {}) 555 556 # set, default version = 1, but manually override version = 2 557 self.cache.set_many({'ford2': 37, 'arthur2': 42}, version=2) 558 self.assertEqual(self.cache.get_many(['ford2','arthur2']), {}) 559 self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=1), {}) 560 self.assertEqual(self.cache.get_many(['ford2','arthur2'], version=2), 561 {'ford2': 37, 'arthur2': 42}) 562 563 self.assertEqual(self.v2_cache.get_many(['ford2','arthur2']), 564 {'ford2': 37, 'arthur2': 42}) 565 self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=1), {}) 566 self.assertEqual(self.v2_cache.get_many(['ford2','arthur2'], version=2), 567 {'ford2': 37, 'arthur2': 42}) 568 569 # v2 set, using default version = 2 570 self.v2_cache.set_many({'ford3': 37, 'arthur3': 42}) 571 self.assertEqual(self.cache.get_many(['ford3','arthur3']), {}) 572 self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=1), {}) 573 self.assertEqual(self.cache.get_many(['ford3','arthur3'], version=2), 574 {'ford3': 37, 'arthur3': 42}) 575 576 self.assertEqual(self.v2_cache.get_many(['ford3','arthur3']), 577 {'ford3': 37, 'arthur3': 42}) 578 self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=1), {}) 579 self.assertEqual(self.v2_cache.get_many(['ford3','arthur3'], version=2), 580 {'ford3': 37, 'arthur3': 42}) 581 582 # v2 set, default version = 2, but manually override version = 1 583 self.v2_cache.set_many({'ford4': 37, 'arthur4': 42}, version=1) 584 self.assertEqual(self.cache.get_many(['ford4','arthur4']), 585 {'ford4': 37, 'arthur4': 42}) 586 self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=1), 587 {'ford4': 37, 'arthur4': 42}) 588 self.assertEqual(self.cache.get_many(['ford4','arthur4'], version=2), {}) 589 590 self.assertEqual(self.v2_cache.get_many(['ford4','arthur4']), {}) 591 self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=1), 592 {'ford4': 37, 'arthur4': 42}) 593 self.assertEqual(self.v2_cache.get_many(['ford4','arthur4'], version=2), {}) 594 595 def test_incr_version(self): 596 self.cache.set('answer', 42, version=2) 597 self.assertEqual(self.cache.get('answer'), None) 598 self.assertEqual(self.cache.get('answer', version=1), None) 599 self.assertEqual(self.cache.get('answer', version=2), 42) 600 self.assertEqual(self.cache.get('answer', version=3), None) 601 602 self.assertEqual(self.cache.incr_version('answer', version=2), 3) 603 self.assertEqual(self.cache.get('answer'), None) 604 self.assertEqual(self.cache.get('answer', version=1), None) 605 self.assertEqual(self.cache.get('answer', version=2), None) 606 self.assertEqual(self.cache.get('answer', version=3), 42) 607 608 self.v2_cache.set('answer2', 42) 609 self.assertEqual(self.v2_cache.get('answer2'), 42) 610 self.assertEqual(self.v2_cache.get('answer2', version=1), None) 611 self.assertEqual(self.v2_cache.get('answer2', version=2), 42) 612 self.assertEqual(self.v2_cache.get('answer2', version=3), None) 613 614 self.assertEqual(self.v2_cache.incr_version('answer2'), 3) 615 self.assertEqual(self.v2_cache.get('answer2'), None) 616 self.assertEqual(self.v2_cache.get('answer2', version=1), None) 617 self.assertEqual(self.v2_cache.get('answer2', version=2), None) 618 self.assertEqual(self.v2_cache.get('answer2', version=3), 42) 619 620 self.assertRaises(ValueError, self.cache.incr_version, 'does_not_exist') 621 622 def test_decr_version(self): 623 self.cache.set('answer', 42, version=2) 624 self.assertEqual(self.cache.get('answer'), None) 625 self.assertEqual(self.cache.get('answer', version=1), None) 626 self.assertEqual(self.cache.get('answer', version=2), 42) 627 628 self.assertEqual(self.cache.decr_version('answer', version=2), 1) 629 self.assertEqual(self.cache.get('answer'), 42) 630 self.assertEqual(self.cache.get('answer', version=1), 42) 631 self.assertEqual(self.cache.get('answer', version=2), None) 632 633 self.v2_cache.set('answer2', 42) 634 self.assertEqual(self.v2_cache.get('answer2'), 42) 635 self.assertEqual(self.v2_cache.get('answer2', version=1), None) 636 self.assertEqual(self.v2_cache.get('answer2', version=2), 42) 637 638 self.assertEqual(self.v2_cache.decr_version('answer2'), 1) 639 self.assertEqual(self.v2_cache.get('answer2'), None) 640 self.assertEqual(self.v2_cache.get('answer2', version=1), 42) 641 self.assertEqual(self.v2_cache.get('answer2', version=2), None) 642 643 self.assertRaises(ValueError, self.cache.decr_version, 'does_not_exist', version=2) 644 645 def test_custom_key_func(self): 646 # Two caches with different key functions aren't visible to each other 647 self.cache.set('answer1', 42) 648 self.assertEqual(self.cache.get('answer1'), 42) 649 self.assertEqual(self.custom_key_cache.get('answer1'), None) 650 651 self.custom_key_cache.set('answer2', 42) 652 self.assertEqual(self.cache.get('answer2'), None) 653 self.assertEqual(self.custom_key_cache.get('answer2'), 42) 654 655 def custom_key_func(key, key_prefix, version): 656 "A customized cache key function" 657 return 'CUSTOM-' + '-'.join([key_prefix, str(version), key]) 393 658 394 659 class DBCacheTests(unittest.TestCase, BaseCacheTests): 395 660 def setUp(self): … … 397 662 self._table_name = 'test cache table' 398 663 management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False) 399 664 self.cache = get_cache('db://%s?max_entries=30' % self._table_name) 665 self.prefix_cache = get_cache('db://%s' % self._table_name, key_prefix='cacheprefix') 666 self.v2_cache = get_cache('db://%s' % self._table_name, version=2) 667 self.custom_key_cache = get_cache('db://%s' % self._table_name, key_func=custom_key_func) 400 668 401 669 def tearDown(self): 402 670 from django.db import connection … … 413 681 class LocMemCacheTests(unittest.TestCase, BaseCacheTests): 414 682 def setUp(self): 415 683 self.cache = get_cache('locmem://?max_entries=30') 684 self.prefix_cache = get_cache('locmem://', key_prefix='cacheprefix') 685 self.v2_cache = get_cache('locmem://', version=2) 686 self.custom_key_cache = get_cache('locmem://?max_entries=30', key_func=custom_key_func) 687 688 # LocMem requires a hack to make the other caches 689 # share a data store with the 'normal' cache. 690 self.prefix_cache._cache = self.cache._cache 691 self.prefix_cache._expire_info = self.cache._expire_info 692 693 self.v2_cache._cache = self.cache._cache 694 self.v2_cache._expire_info = self.cache._expire_info 695 696 self.custom_key_cache._cache = self.cache._cache 697 self.custom_key_cache._expire_info = self.cache._expire_info 416 698 417 699 def test_cull(self): 418 700 self.perform_cull_test(50, 29) … … 428 710 class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): 429 711 def setUp(self): 430 712 self.cache = get_cache(settings.CACHE_BACKEND) 713 self.prefix_cache = get_cache(settings.CACHE_BACKEND, key_prefix='cacheprefix') 714 self.v2_cache = get_cache(settings.CACHE_BACKEND, version=2) 715 self.custom_key_cache = get_cache(settings.CACHE_BACKEND, key_func=custom_key_func) 716 717 def tearDown(self): 718 self.cache.clear() 719 self.prefix_cache.clear() 720 self.v2_cache.clear() 721 self.custom_key_cache.clear() 431 722 432 723 def test_invalid_keys(self): 433 724 """ … … 439 730 that a generic exception of some kind is raised. 440 731 441 732 """ 733 _warnings_state = get_warnings_state() 734 warnings.simplefilter("ignore", CacheKeyWarning) 735 442 736 # memcached does not allow whitespace or control characters in keys 443 737 self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value') 444 738 # memcached limits key length to 250 445 739 self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value') 740 741 restore_warnings_state(_warnings_state) 742 446 743 MemcachedCacheTests = unittest.skipUnless(settings.CACHE_BACKEND.startswith('memcached://'), "memcached not available")(MemcachedCacheTests) 447 744 448 745 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): … … 452 749 def setUp(self): 453 750 self.dirname = tempfile.mkdtemp() 454 751 self.cache = get_cache('file://%s?max_entries=30' % self.dirname) 752 self.prefix_cache = get_cache('file://%s' % self.dirname, key_prefix='cacheprefix') 753 self.v2_cache = get_cache('file://%s' % self.dirname, version=2) 754 self.custom_key_cache = get_cache('file://%s' % self.dirname, key_func=custom_key_func) 755 756 def tearDown(self): 757 self.cache.clear() 758 self.prefix_cache.clear() 759 self.v2_cache.clear() 760 self.custom_key_cache.clear() 455 761 456 762 def test_hashing(self): 457 763 """Test that keys are hashed into subdirectories correctly""" 458 764 self.cache.set("foo", "bar") 459 keyhash = md5_constructor("foo").hexdigest() 765 key = self.cache._make_key("foo") 766 keyhash = md5_constructor(key).hexdigest() 460 767 keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) 461 768 self.assert_(os.path.exists(keypath)) 462 769 … … 465 772 Make sure that the created subdirectories are correctly removed when empty. 466 773 """ 467 774 self.cache.set("foo", "bar") 468 keyhash = md5_constructor("foo").hexdigest() 775 key = self.cache._make_key("foo") 776 keyhash = md5_constructor(key).hexdigest() 469 777 keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) 470 778 self.assert_(os.path.exists(keypath)) 471 779 … … 475 783 self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath)))) 476 784 477 785 def test_cull(self): 478 self.perform_cull_test(50, 2 8)786 self.perform_cull_test(50, 29) 479 787 480 788 class CustomCacheKeyValidationTests(unittest.TestCase): 481 789 """ … … 498 806 499 807 def setUp(self): 500 808 self.path = '/cache/test/' 501 self.old_ settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX502 self.old_ middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS809 self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX 810 self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS 503 811 self.orig_use_i18n = settings.USE_I18N 504 812 settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix' 505 813 settings.CACHE_MIDDLEWARE_SECONDS = 1 506 814 settings.USE_I18N = False 507 815 508 816 def tearDown(self): 509 settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_ settings_key_prefix510 settings.CACHE_MIDDLEWARE_SECONDS = self.old_ middleware_seconds817 settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix 818 settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds 511 819 settings.USE_I18N = self.orig_use_i18n 512 820 513 821 def _get_request(self, path, method='GET'): … … 561 869 learn_cache_key(request, response) 562 870 self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') 563 871 872 class PrefixedCacheUtils(CacheUtils): 873 def setUp(self): 874 super(PrefixedCacheUtils, self).setUp() 875 self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX 876 settings.CACHE_KEY_PREFIX = 'cacheprefix' 877 878 def tearDown(self): 879 super(PrefixedCacheUtils, self).tearDown() 880 settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix 881 564 882 class CacheHEADTest(unittest.TestCase): 565 883 566 884 def setUp(self): … … 714 1032 get_cache_data = FetchFromCacheMiddleware().process_request(request) 715 1033 self.assertEqual(get_cache_data.content, es_message) 716 1034 1035 class PrefixedCacheI18nTest(CacheI18nTest): 1036 def setUp(self): 1037 super(PrefixedCacheI18nTest, self).setUp() 1038 self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX 1039 settings.CACHE_KEY_PREFIX = 'cacheprefix' 1040 1041 def tearDown(self): 1042 super(PrefixedCacheI18nTest, self).tearDown() 1043 settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix 1044 717 1045 if __name__ == '__main__': 718 1046 unittest.main()