Ticket #13795: cache_key_prefix_14190.diff

File cache_key_prefix_14190.diff, 34.2 KB (added by bruth, 5 years ago)
  • django/conf/global_settings.py

     
    428428# The cache backend to use.  See the docstring in django.core.cache for the
    429429# possible values.
    430430CACHE_BACKEND = 'locmem://'
     431CACHE_VERSION = 1
     432CACHE_KEY_PREFIX = ''
     433CACHE_KEY_MODULE = None
    431434CACHE_MIDDLEWARE_KEY_PREFIX = ''
    432435CACHE_MIDDLEWARE_SECONDS = 600
    433436
  • django/core/cache/__init__.py

     
    2424from django.core import signals
    2525from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
    2626from django.utils import importlib
     27from django.utils.encoding import smart_str
     28from django.utils.hashcompat import md5_constructor
    2729
    2830# Name for use in settings file --> name of module in "backends" directory.
    2931# Any backend scheme that is not in this dictionary is treated as a Python
     
    6062
    6163    return scheme, host, params
    6264
    63 def get_cache(backend_uri):
     65def 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
    6476    scheme, host, params = parse_backend_uri(backend_uri)
    6577    if scheme in BACKENDS:
    6678        name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
    6779    else:
    6880        name = scheme
    6981    module = importlib.import_module(name)
    70     return module.CacheClass(host, params)
     82    return module.CacheClass(host, params, key_prefix, version, key_func)
    7183
    7284cache = get_cache(settings.CACHE_BACKEND)
    7385
    74 # Some caches -- pythont-memcached in particular -- need to do a cleanup at the
     86# Some caches -- python-memcached in particular -- need to do a cleanup at the
    7587# end of a request cycle. If the cache provides a close() method, wire it up
    7688# here.
    7789if hasattr(cache, 'close'):
  • django/core/cache/backends/base.py

     
    22
    33import warnings
    44
     5from django.utils.hashcompat import md5_constructor
    56from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
     7from django.utils.encoding import smart_str
    68
    79class InvalidCacheBackendError(ImproperlyConfigured):
    810    pass
     
    1315# Memcached does not accept keys longer than this.
    1416MEMCACHE_MAX_KEY_LENGTH = 250
    1517
     18def 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
    1629class BaseCache(object):
    17     def __init__(self, params):
     30    def __init__(self, params, key_prefix='', version=1, key_func=None):
    1831        timeout = params.get('timeout', 300)
    1932        try:
    2033            timeout = int(timeout)
    2134        except (ValueError, TypeError):
    2235            timeout = 300
    2336        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
    2440
    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.
    2645        """
     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        """
    2755        Set a value in the cache if the key does not already exist. If
    2856        timeout is given, that timeout will be used for the key; otherwise
    2957        the default cache timeout will be used.
     
    3260        """
    3361        raise NotImplementedError
    3462
    35     def get(self, key, default=None):
     63    def get(self, key, default=None, version=None):
    3664        """
    3765        Fetch a given key from the cache. If the key does not exist, return
    3866        default, which itself defaults to None.
    3967        """
    4068        raise NotImplementedError
    4169
    42     def set(self, key, value, timeout=None):
     70    def set(self, key, value, timeout=None, version=None):
    4371        """
    4472        Set a value in the cache. If timeout is given, that timeout will be
    4573        used for the key; otherwise the default cache timeout will be used.
    4674        """
    4775        raise NotImplementedError
    4876
    49     def delete(self, key):
     77    def delete(self, key, version=None):
    5078        """
    5179        Delete a key from the cache, failing silently.
    5280        """
    5381        raise NotImplementedError
    5482
    55     def get_many(self, keys):
     83    def get_many(self, keys, version=None):
    5684        """
    5785        Fetch a bunch of keys from the cache. For certain backends (memcached,
    5886        pgsql) this can be *much* faster when fetching multiple values.
     
    6290        """
    6391        d = {}
    6492        for k in keys:
    65             val = self.get(k)
     93            val = self.get(k, version=version)
    6694            if val is not None:
    6795                d[k] = val
    6896        return d
    6997
    70     def has_key(self, key):
     98    def has_key(self, key, version=None):
    7199        """
    72100        Returns True if the key is in the cache and has not expired.
    73101        """
    74         return self.get(key) is not None
     102        return self.get(key, version=version) is not None
    75103
    76     def incr(self, key, delta=1):
     104    def incr(self, key, delta=1, version=None):
    77105        """
    78106        Add delta to value in the cache. If the key does not exist, raise a
    79107        ValueError exception.
    80108        """
    81         if key not in self:
     109        value = self.get(key, version=version)
     110        if value is None:
    82111            raise ValueError("Key '%s' not found" % key)
    83         new_value = self.get(key) + delta
    84         self.set(key, new_value)
     112        new_value = value + delta
     113        self.set(key, new_value, version=version)
    85114        return new_value
    86115
    87     def decr(self, key, delta=1):
     116    def decr(self, key, delta=1, version=None):
    88117        """
    89118        Subtract delta from value in the cache. If the key does not exist, raise
    90119        a ValueError exception.
    91120        """
    92         return self.incr(key, -delta)
     121        return self.incr(key, -delta, version=version)
    93122
    94123    def __contains__(self, key):
    95124        """
     
    100129        # if a subclass overrides it.
    101130        return self.has_key(key)
    102131
    103     def set_many(self, data, timeout=None):
     132    def set_many(self, data, timeout=None, version=None):
    104133        """
    105134        Set a bunch of values in the cache at once from a dict of key/value
    106135        pairs.  For certain backends (memcached), this is much more efficient
     
    110139        the default cache timeout will be used.
    111140        """
    112141        for key, value in data.items():
    113             self.set(key, value, timeout)
     142            self.set(key, value, timeout=timeout, version=version)
    114143
    115     def delete_many(self, keys):
     144    def delete_many(self, keys, version=None):
    116145        """
    117146        Set a bunch of values in the cache at once.  For certain backends
    118147        (memcached), this is much more efficient than calling delete() multiple
    119148        times.
    120149        """
    121150        for key in keys:
    122             self.delete(key)
     151            self.delete(key, version=version)
    123152
    124153    def clear(self):
    125154        """Remove *all* values from the cache at once."""
     
    142171                        'errors if used with memcached: %r' % key,
    143172                              CacheKeyWarning)
    144173
     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

     
    33from django.core.cache.backends.base import BaseCache
    44
    55class CacheClass(BaseCache):
    6     def __init__(self, *args, **kwargs):
    7         pass
     6    def __init__(self, host, *args, **kwargs):
     7        BaseCache.__init__(self, *args, **kwargs)
    88
    99    def add(self, key, *args, **kwargs):
    10         self.validate_key(key)
     10        self.make_key(key)
    1111        return True
    1212
    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)
    1515        return default
    1616
    1717    def set(self, key, *args, **kwargs):
    18         self.validate_key(key)
     18        self.make_key(key)
    1919
    2020    def delete(self, key, *args, **kwargs):
    21         self.validate_key(key)
     21        self.make_key(key)
    2222
    2323    def get_many(self, *args, **kwargs):
    2424        return {}
    2525
    2626    def has_key(self, key, *args, **kwargs):
    27         self.validate_key(key)
     27        self.make_key(key)
    2828        return False
    2929
    3030    def set_many(self, *args, **kwargs):
  • django/core/cache/backends/locmem.py

     
    1010from django.utils.synch import RWLock
    1111
    1212class 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)
    1515        self._cache = {}
    1616        self._expire_info = {}
    1717
     
    2929
    3030        self._lock = RWLock()
    3131
    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)
    3434        self._lock.writer_enters()
    3535        try:
    3636            exp = self._expire_info.get(key)
     
    4444        finally:
    4545            self._lock.writer_leaves()
    4646
    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)
    4949        self._lock.reader_enters()
    5050        try:
    5151            exp = self._expire_info.get(key)
     
    7777        self._cache[key] = value
    7878        self._expire_info[key] = time.time() + timeout
    7979
    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)
    8282        self._lock.writer_enters()
    8383        # Python 2.4 doesn't allow combined try-except-finally blocks.
    8484        try:
     
    8989        finally:
    9090            self._lock.writer_leaves()
    9191
    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)
    9494        self._lock.reader_enters()
    9595        try:
    9696            exp = self._expire_info.get(key)
     
    130130        except KeyError:
    131131            pass
    132132
    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)
    135135        self._lock.writer_enters()
    136136        try:
    137137            self._delete(key)
  • django/core/cache/backends/filebased.py

     
    1212from django.utils.hashcompat import md5_constructor
    1313
    1414class 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)
    1717
    1818        max_entries = params.get('max_entries', 300)
    1919        try:
     
    3131        if not os.path.exists(self._dir):
    3232            self._createdir()
    3333
    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):
    3736            return False
    3837
    39         self.set(key, value, timeout)
     38        self.set(key, value, timeout, version=version)
    4039        return True
    4140
    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
    4444        fname = self._key_to_file(key)
    4545        try:
    4646            f = open(fname, 'rb')
     
    5757            pass
    5858        return default
    5959
    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
    6263        fname = self._key_to_file(key)
    6364        dirname = os.path.dirname(fname)
    6465
     
    8182        except (IOError, OSError):
    8283            pass
    8384
    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)
    8687        try:
    8788            self._delete(self._key_to_file(key))
    8889        except (IOError, OSError):
     
    9899        except (IOError, OSError):
    99100            pass
    100101
    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)
    103104        fname = self._key_to_file(key)
    104105        try:
    105106            f = open(fname, 'rb')
     
    153154        Thus, a cache key of "foo" gets turnned into a file named
    154155        ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
    155156        """
    156         path = md5_constructor(key.encode('utf-8')).hexdigest()
     157        path = md5_constructor(key).hexdigest()
    157158        path = os.path.join(path[:2], path[2:4], path[4:])
    158159        return os.path.join(self._dir, path)
    159160
  • django/core/cache/backends/db.py

     
    2626        self.proxy = False
    2727
    2828class 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)
    3131        self._table = table
    3232
    3333        class CacheEntry(object):
     
    4545        except (ValueError, TypeError):
    4646            self._cull_frequency = 3
    4747
    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
    5051        db = router.db_for_read(self.cache_model_class)
    5152        table = connections[db].ops.quote_name(self._table)
    5253        cursor = connections[db].cursor()
     
    6566        value = connections[db].ops.process_clob(row[1])
    6667        return pickle.loads(base64.decodestring(value))
    6768
    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)
    7071        self._base_set('set', key, value, timeout)
    7172
    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)
    7475        return self._base_set('add', key, value, timeout)
    7576
    7677    def _base_set(self, mode, key, value, timeout=None):
     
    105106            transaction.commit_unless_managed(using=db)
    106107            return True
    107108
    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
    110112        db = router.db_for_write(self.cache_model_class)
    111113        table = connections[db].ops.quote_name(self._table)
    112114        cursor = connections[db].cursor()
     
    114116        cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
    115117        transaction.commit_unless_managed(using=db)
    116118
    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
    119122        db = router.db_for_read(self.cache_model_class)
    120123        table = connections[db].ops.quote_name(self._table)
    121124        cursor = connections[db].cursor()
  • django/core/cache/backends/memcached.py

     
    33import time
    44
    55from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
    6 from django.utils.encoding import smart_unicode, smart_str
    76
    87try:
    98    import cmemcache as memcache
     
    1918        raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcache' library")
    2019
    2120class 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)
    2423        self._cache = memcache.Client(server.split(';'))
    2524
    2625    def _get_memcache_timeout(self, timeout):
     
    3938            timeout += int(time.time())
    4039        return timeout
    4140
    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)
    4343        if isinstance(value, unicode):
    4444            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))
    4646
    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)
    4950        if val is None:
    5051            return default
    5152        return val
    5253
    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))
    5557
    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)
    5861
    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
    6172
    6273    def close(self, **kwargs):
    6374        self._cache.disconnect_all()
    6475
    65     def incr(self, key, delta=1):
     76    def incr(self, key, delta=1, version=None):
     77        key = self.make_key(key, version=version)
    6678        try:
    6779            val = self._cache.incr(key, delta)
    6880
     
    7688
    7789        return val
    7890
    79     def decr(self, key, delta=1):
     91    def decr(self, key, delta=1, version=None):
     92        key = self.make_key(key, version=version)
    8093        try:
    8194            val = self._cache.decr(key, delta)
    8295
     
    89102            raise ValueError("Key '%s' not found" % key)
    90103        return val
    91104
    92     def set_many(self, data, timeout=0):
     105    def set_many(self, data, timeout=0, version=None):
    93106        safe_data = {}
    94107        for key, value in data.items():
     108            key = self.make_key(key, version=version)
    95109            if isinstance(value, unicode):
    96110                value = value.encode('utf-8')
    97             safe_data[smart_str(key)] = value
     111            safe_data[key] = value
    98112        self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
    99113
    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))
    102117
    103118    def clear(self):
    104119        self._cache.flush_all()
  • tests/regressiontests/cache/tests.py

     
    144144        "clear does nothing for the dummy cache backend"
    145145        self.cache.clear()
    146146
     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')
    147152
     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
    148160class BaseCacheTests(object):
    149161    # A common set of tests to apply to all cache backends
    150162    def tearDown(self):
     
    162174        self.assertEqual(result, False)
    163175        self.assertEqual(self.cache.get("addkey1"), "value")
    164176
     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
    165189    def test_non_existent(self):
    166190        # Non-existent cache keys return as None/default
    167191        # get with non-existent keys
     
    375399        with more liberal key rules. Refs #6447.
    376400
    377401        """
     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
    378409        # On Python 2.6+ we could use the catch_warnings context
    379410        # manager to test this warning nicely. Since we can't do that
    380411        # yet, the cleanest option is to temporarily ask for
     
    392423        # it. The effect will be the same, as long as the Django test
    393424        # runner doesn't add any global warning filters (it currently
    394425        # does not).
     426        self.cache.key_func = old_func
    395427        warnings.resetwarnings()
    396428        warnings.simplefilter("ignore", PendingDeprecationWarning)
    397429
     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
    398444class DBCacheTests(unittest.TestCase, BaseCacheTests):
    399445    def setUp(self):
    400446        # Spaces are used in the table name to ensure quoting/escaping is working
    401447        self._table_name = 'test cache table'
    402448        management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
    403449        self.cache = get_cache('db://%s?max_entries=30' % self._table_name)
     450        self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix')
    404451
    405452    def tearDown(self):
    406453        from django.db import connection
     
    413460class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
    414461    def setUp(self):
    415462        self.cache = get_cache('locmem://?max_entries=30')
     463        self.pfx_cache = get_cache('locmem://', 'cacheprefix')
    416464
    417465    def test_cull(self):
    418466        self.perform_cull_test(50, 29)
     
    425473    class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
    426474        def setUp(self):
    427475            self.cache = get_cache(settings.CACHE_BACKEND)
     476            self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix')
    428477
    429478        def test_invalid_keys(self):
    430479            """
     
    436485            that a generic exception of some kind is raised.
    437486
    438487            """
     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
    439496            # memcached does not allow whitespace or control characters in keys
    440497            self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value')
    441498            # memcached limits key length to 250
    442499            self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value')
    443500
     501            self.cache.key_func = old_func
    444502
    445503class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
    446504    """
     
    449507    def setUp(self):
    450508        self.dirname = tempfile.mkdtemp()
    451509        self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
     510        self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix')
    452511
    453512    def test_hashing(self):
    454513        """Test that keys are hashed into subdirectories correctly"""
    455514        self.cache.set("foo", "bar")
    456         keyhash = md5_constructor("foo").hexdigest()
     515        key = self.cache.make_key("foo")
     516        keyhash = md5_constructor(key).hexdigest()
    457517        keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
    458518        self.assert_(os.path.exists(keypath))
    459519
     
    462522        Make sure that the created subdirectories are correctly removed when empty.
    463523        """
    464524        self.cache.set("foo", "bar")
    465         keyhash = md5_constructor("foo").hexdigest()
     525        key = self.cache.make_key("foo")
     526        keyhash = md5_constructor(key).hexdigest()
    466527        keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
    467528        self.assert_(os.path.exists(keypath))
    468529
     
    472533        self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
    473534
    474535    def test_cull(self):
    475         self.perform_cull_test(50, 28)
     536        self.perform_cull_test(50, 29)
    476537
    477538class CustomCacheKeyValidationTests(unittest.TestCase):
    478539    """
     
    495556
    496557    def setUp(self):
    497558        self.path = '/cache/test/'
    498         self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    499         self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
     559        self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
     560        self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
    500561        self.orig_use_i18n = settings.USE_I18N
    501562        settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
    502563        settings.CACHE_MIDDLEWARE_SECONDS = 1
    503564        settings.USE_I18N = False
    504565
    505566    def tearDown(self):
    506         settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
    507         settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
     567        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix
     568        settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds
    508569        settings.USE_I18N = self.orig_use_i18n
    509570
    510571    def _get_request(self, path):
     
    557618        learn_cache_key(request, response)
    558619        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    559620
     621class 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
    560631class CacheI18nTest(unittest.TestCase):
    561632
    562633    def setUp(self):
     
    652723        get_cache_data = FetchFromCacheMiddleware().process_request(request)
    653724        self.assertEqual(get_cache_data.content, es_message)
    654725
     726class 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
    655736if __name__ == '__main__':
    656737    unittest.main()
  • docs/topics/cache.txt

     
    680680...and use the dotted Python path to this module as the scheme portion of your
    681681:setting:`CACHE_BACKEND`.
    682682
     683Cache Prefix
     684------------
     685
     686.. versionadded:: 1.3
     687
     688It is a common occurence to have a shared cache instance running on your development
     689or production server for use across multiple projects, i.e. all sites are pointing
     690to the memcached instance running on port 11211. As a result, cache key conflicts
     691may arise which could result in data from one site being used by another.
     692
     693To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys
     694that 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
     702Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for
     703both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty
     704string ``''``.
     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
     714Cache Versioning
     715----------------
     716
     717.. versionadded:: 1.3
     718
     719Cache versioning can be a simple solution to dealing with invalid cache. The
     720setting ``CACHE_VERSION`` provides a means of defining which version of cache
     721your project will use. This is useful primarily for project upgrades or
     722downgrades. For example, if a project is in production and new features are
     723being 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
     725will 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
     734For more complex cache versioning strategies, the cache version can specified
     735at 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
     741If the version is not supplied, it defaults to ``CACHE_VERSION``. This flexibility
     742allows for interesting caching strategies such as cache dependency checking. For
     743example, if an object's cache version has been incremented, any dependent objects
     744can be checked (and rebuilt) in cache with the incremented version.
     745
     746Two 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
     760Each method returns the new version number associated with the key.
     761
     762Cache Key Module
     763----------------
     764
     765.. versionadded:: 1.3
     766
     767The setting ``CACHE_KEY_MODULE`` can be specified which must contain a function
     768named ``make_key`` that handles constructing the key that is ultimately used
     769when 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``.
     771The default function simply concatenates the three arguments and takes the md5
     772hexdigest of the result. In most cases the default will suffice and a custom
     773will not have to be defined.
     774
    683775Upstream caches
    684776===============
    685777
Back to Top