Ticket #13795: cache_key_prefix_14405.diff

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

     
    424424# The cache backend to use.  See the docstring in django.core.cache for the
    425425# possible values.
    426426CACHE_BACKEND = 'locmem://'
     427CACHE_VERSION = 1
     428CACHE_KEY_PREFIX = ''
     429CACHE_KEY_MODULE = None
    427430CACHE_MIDDLEWARE_KEY_PREFIX = ''
    428431CACHE_MIDDLEWARE_SECONDS = 600
    429432
  • django/core/cache/__init__.py

     
    3131from django.core import signals
    3232from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
    3333from django.utils import importlib
     34from django.utils.encoding import smart_str
     35from django.utils.hashcompat import md5_constructor
    3436
    3537# Name for use in settings file --> name of module in "backends" directory.
    3638# Any backend scheme that is not in this dictionary is treated as a Python
     
    6769
    6870    return scheme, host, params
    6971
    70 def get_cache(backend_uri):
     72def get_cache(backend_uri, key_prefix=None, version=None):
     73    if key_prefix is None:
     74        key_prefix = settings.CACHE_KEY_PREFIX
     75    if version is None:
     76        version = settings.CACHE_VERSION
     77
     78    if settings.CACHE_KEY_MODULE is None:
     79        key_func = None
     80    else:
     81        key_func = importlib.import_module(settings.CACHE_KEY_MODULE).make_key
     82
    7183    scheme, host, params = parse_backend_uri(backend_uri)
    7284    if scheme in BACKENDS:
    7385        name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
    7486    else:
    7587        name = scheme
    7688    module = importlib.import_module(name)
    77     return module.CacheClass(host, params)
     89    return module.CacheClass(host, params, key_prefix, version, key_func)
    7890
    7991cache = get_cache(settings.CACHE_BACKEND)
    8092
    81 # Some caches -- pythont-memcached in particular -- need to do a cleanup at the
     93# Some caches -- python-memcached in particular -- need to do a cleanup at the
    8294# end of a request cycle. If the cache provides a close() method, wire it up
    8395# here.
    8496if 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, method='GET'):
     
    558619        learn_cache_key(request, response)
    559620        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    560621
     622class PrefixedCacheUtils(CacheUtils):
     623    def setUp(self):
     624        super(PrefixedCacheUtils, self).setUp()
     625        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
     626        settings.CACHE_KEY_PREFIX = 'cacheprefix'
     627
     628    def tearDown(self):
     629        super(PrefixedCacheUtils, self).tearDown()
     630        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
     631
    561632class CacheHEADTest(unittest.TestCase):
    562633
    563634    def setUp(self):
     
    711782        get_cache_data = FetchFromCacheMiddleware().process_request(request)
    712783        self.assertEqual(get_cache_data.content, es_message)
    713784
     785class PrefixedCacheI18nTest(CacheI18nTest):
     786    def setUp(self):
     787        super(PrefixedCacheI18nTest, self).setUp()
     788        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
     789        settings.CACHE_KEY_PREFIX = 'cacheprefix'
     790
     791    def tearDown(self):
     792        super(PrefixedCacheI18nTest, self).tearDown()
     793        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
     794
    714795if __name__ == '__main__':
    715796    unittest.main()
  • docs/topics/cache.txt

     
    682682...and use the dotted Python path to this module as the scheme portion of your
    683683:setting:`CACHE_BACKEND`.
    684684
     685Cache Prefix
     686------------
     687
     688.. versionadded:: 1.3
     689
     690It is a common occurence to have a shared cache instance running on your development
     691or production server for use across multiple projects, i.e. all sites are pointing
     692to the memcached instance running on port 11211. As a result, cache key conflicts
     693may arise which could result in data from one site being used by another.
     694
     695To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys
     696that are used with the cache backend, transparently::
     697
     698    # in settings, CACHE_KEY_PREFIX = 'myproject_'
     699
     700    >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key'
     701    >>> cache.get('my_key') # retrieved with key 'myproject_my_key'
     702    'hello world!'
     703
     704Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for
     705both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty
     706string ``''``.
     707
     708.. note::
     709   
     710   This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used
     711   in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a
     712   particular cache instance, therefore it will be prepended to the
     713   CACHE_MIDDLEWARE_KEY_PREFIX transparently.
     714
     715
     716Cache Versioning
     717----------------
     718
     719.. versionadded:: 1.3
     720
     721Cache versioning can be a simple solution to dealing with invalid cache. The
     722setting ``CACHE_VERSION`` provides a means of defining which version of cache
     723your project will use. This is useful primarily for project upgrades or
     724downgrades. For example, if a project is in production and new features are
     725being added to it, the cache being used may not be useful any longer. The
     726``CACHE_VERSION`` can be incremented and all previous vesions of the cache
     727will be ignored.
     728
     729.. note::
     730
     731    This technique simulates "invalidating" your cache, but does not
     732    actually free up the space used by the other cache versions. Assuming
     733    a reasonable ``max_entries`` and `cull_frequency`` has been defined,
     734    this should not be a problem since the old cache will be culled as needed.
     735
     736For more complex cache versioning strategies, the cache version can specified
     737at runtime as such::
     738
     739    >>> cache.set('my_key', 'hello world!', version=2)
     740    >>> cache.get('my_key', version=2) # hello world!
     741    >>> cache.get('my_key', version=3) # None
     742
     743If the version is not supplied, it defaults to ``CACHE_VERSION``. This flexibility
     744allows for interesting caching strategies such as cache dependency checking. For
     745example, if an object's cache version has been incremented, any dependent objects
     746can be checked (and rebuilt) in cache with the incremented version.
     747
     748Two helper functions for reusing other versions of cache exist::
     749
     750    # CACHE_VERSION = 1
     751
     752    >>> cache.set('my_key', 'hello world!')
     753
     754    >>> v2 = cache.incr_version('my_key') # 2
     755    >>> cache.get('my_key') # None
     756    >>> cache.get('my_key', version=v) # hello world!
     757
     758    >>> v1 = cache.decr_version('my_key', version=v2) # 1
     759    >>> cache.get('my_key', version=v2) # None
     760    >>> cache.get('my_key') # hello world!   
     761
     762Each method returns the new version number associated with the key.
     763
     764Cache Key Module
     765----------------
     766
     767.. versionadded:: 1.3
     768
     769The setting ``CACHE_KEY_MODULE`` can be specified which must contain a function
     770named ``make_key`` that handles constructing the key that is ultimately used
     771when interacting with the cache backend. The function is passed the raw key
     772(what the user specifies), the ``CACHE_KEY_PREFIX``, and the ``CACHE_VERSION``.
     773The default function simply concatenates the three arguments and takes the md5
     774hexdigest of the result. In most cases the default will suffice and a custom
     775will not have to be defined.
     776
    685777Upstream caches
    686778===============
    687779
Back to Top