Ticket #13795: cache_key_prefix_13891.diff

File cache_key_prefix_13891.diff, 18.1 KB (added by Austin Gabel, 14 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_KEY_PREFIX = ''
    431432CACHE_MIDDLEWARE_KEY_PREFIX = ''
    432433CACHE_MIDDLEWARE_SECONDS = 600
    433434
  • django/core/cache/__init__.py

     
    5656
    5757    return scheme, host, params
    5858
    59 def get_cache(backend_uri):
     59def get_cache(backend_uri, key_prefix=''):
     60    if not key_prefix:
     61        key_prefix = settings.CACHE_KEY_PREFIX
    6062    scheme, host, params = parse_backend_uri(backend_uri)
    6163    if scheme in BACKENDS:
    6264        name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
    6365    else:
    6466        name = scheme
    6567    module = importlib.import_module(name)
    66     return module.CacheClass(host, params)
     68    return module.CacheClass(host, params, key_prefix)
    6769
    6870cache = get_cache(settings.CACHE_BACKEND)
    6971
    70 # Some caches -- pythont-memcached in particular -- need to do a cleanup at the
     72# Some caches -- python-memcached in particular -- need to do a cleanup at the
    7173# end of a request cycle. If the cache provides a close() method, wire it up
    7274# here.
    7375if hasattr(cache, 'close'):
  • django/core/cache/backends/base.py

     
    33import warnings
    44
    55from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
     6from django.utils.encoding import smart_str
    67
    78class InvalidCacheBackendError(ImproperlyConfigured):
    89    pass
     
    1415MEMCACHE_MAX_KEY_LENGTH = 250
    1516
    1617class BaseCache(object):
    17     def __init__(self, params):
     18    def __init__(self, params, key_prefix=''):
    1819        timeout = params.get('timeout', 300)
    1920        try:
    2021            timeout = int(timeout)
    2122        except (ValueError, TypeError):
    2223            timeout = 300
    2324        self.default_timeout = timeout
     25        self.key_prefix = smart_str(key_prefix)
    2426
     27    def make_key(self, key):
     28        """Constructs the key used by all other methods. By default it prepends
     29        the `key_prefix'. In cache backend subclasses this can be overriden to
     30        provide custom key making behavior.
     31        """
     32        return self.key_prefix + smart_str(key)
     33
    2534    def add(self, key, value, timeout=None):
    2635        """
    2736        Set a value in the cache if the key does not already exist. If
  • 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=''):
     30        BaseCache.__init__(self, params, key_prefix)
    3131        self._table = table
    3232
    3333        class CacheEntry(object):
     
    4646            self._cull_frequency = 3
    4747
    4848    def get(self, key, default=None):
     49        key = self.make_key(key)
    4950        self.validate_key(key)
    5051        db = router.db_for_read(self.cache_model_class)
    5152        table = connections[db].ops.quote_name(self._table)
     
    6667        return pickle.loads(base64.decodestring(value))
    6768
    6869    def set(self, key, value, timeout=None):
     70        key = self.make_key(key)
    6971        self.validate_key(key)
    7072        self._base_set('set', key, value, timeout)
    7173
    7274    def add(self, key, value, timeout=None):
     75        key = self.make_key(key)
    7376        self.validate_key(key)
    7477        return self._base_set('add', key, value, timeout)
    7578
     
    106109            return True
    107110
    108111    def delete(self, key):
     112        key = self.make_key(key)
    109113        self.validate_key(key)
    110114        db = router.db_for_write(self.cache_model_class)
    111115        table = connections[db].ops.quote_name(self._table)
     
    115119        transaction.commit_unless_managed(using=db)
    116120
    117121    def has_key(self, key):
     122        key = self.make_key(key)
    118123        self.validate_key(key)
    119124        db = router.db_for_read(self.cache_model_class)
    120125        table = connections[db].ops.quote_name(self._table)
  • 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=''):
     16        BaseCache.__init__(self, params, key_prefix)
    1717
    1818        max_entries = params.get('max_entries', 300)
    1919        try:
     
    3232            self._createdir()
    3333
    3434    def add(self, key, value, timeout=None):
    35         self.validate_key(key)
     35        pfx_key = self.make_key(key)
     36        self.validate_key(pfx_key)
    3637        if self.has_key(key):
    3738            return False
    3839
     
    4041        return True
    4142
    4243    def get(self, key, default=None):
     44        key = self.make_key(key)
    4345        self.validate_key(key)
    4446        fname = self._key_to_file(key)
    4547        try:
     
    5860        return default
    5961
    6062    def set(self, key, value, timeout=None):
     63        key = self.make_key(key)
    6164        self.validate_key(key)
    6265        fname = self._key_to_file(key)
    6366        dirname = os.path.dirname(fname)
     
    8285            pass
    8386
    8487    def delete(self, key):
     88        key = self.make_key(key)
    8589        self.validate_key(key)
    8690        try:
    8791            self._delete(self._key_to_file(key))
     
    99103            pass
    100104
    101105    def has_key(self, key):
     106        key = self.make_key(key)
    102107        self.validate_key(key)
    103108        fname = self._key_to_file(key)
    104109        try:
     
    153158        Thus, a cache key of "foo" gets turnned into a file named
    154159        ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
    155160        """
    156         path = md5_constructor(key.encode('utf-8')).hexdigest()
     161        path = md5_constructor(key).hexdigest()
    157162        path = os.path.join(path[:2], path[2:4], path[4:])
    158163        return os.path.join(self._dir, path)
    159164
  • 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=''):
     14        BaseCache.__init__(self, params, key_prefix)
    1515        self._cache = {}
    1616        self._expire_info = {}
    1717
     
    3030        self._lock = RWLock()
    3131
    3232    def add(self, key, value, timeout=None):
     33        key = self.make_key(key)
    3334        self.validate_key(key)
    3435        self._lock.writer_enters()
    3536        try:
     
    4546            self._lock.writer_leaves()
    4647
    4748    def get(self, key, default=None):
     49        key = self.make_key(key)
    4850        self.validate_key(key)
    4951        self._lock.reader_enters()
    5052        try:
     
    7880        self._expire_info[key] = time.time() + timeout
    7981
    8082    def set(self, key, value, timeout=None):
     83        key = self.make_key(key)
    8184        self.validate_key(key)
    8285        self._lock.writer_enters()
    8386        # Python 2.4 doesn't allow combined try-except-finally blocks.
     
    9093            self._lock.writer_leaves()
    9194
    9295    def has_key(self, key):
     96        key = self.make_key(key)
    9397        self.validate_key(key)
    9498        self._lock.reader_enters()
    9599        try:
     
    131135            pass
    132136
    133137    def delete(self, key):
     138        key = self.make_key(key)
    134139        self.validate_key(key)
    135140        self._lock.writer_enters()
    136141        try:
  • 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):
     22        BaseCache.__init__(self, params, key_prefix)
    2423        self._cache = memcache.Client(server.split(';'))
    2524
    2625    def _get_memcache_timeout(self, timeout):
     
    4039        return timeout
    4140
    4241    def add(self, key, value, timeout=0):
     42        key = self.make_key(key)
    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
    4747    def get(self, key, default=None):
    48         val = self._cache.get(smart_str(key))
     48        key = self.make_key(key)
     49        val = self._cache.get(key)
    4950        if val is None:
    5051            return default
    5152        return val
    5253
    5354    def set(self, key, value, timeout=0):
    54         self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout))
     55        key = self.make_key(key)
     56        self._cache.set(key, value, self._get_memcache_timeout(timeout))
    5557
    5658    def delete(self, key):
    57         self._cache.delete(smart_str(key))
     59        key = self.make_key(key)
     60        self._cache.delete(key)
    5861
    5962    def get_many(self, keys):
    60         return self._cache.get_multi(map(smart_str,keys))
     63        return self._cache.get_multi(map(self.make_key,keys))
    6164
    6265    def close(self, **kwargs):
    6366        self._cache.disconnect_all()
    6467
    6568    def incr(self, key, delta=1):
     69        key = self.make_key(key)
    6670        try:
    6771            val = self._cache.incr(key, delta)
    6872
     
    7781        return val
    7882
    7983    def decr(self, key, delta=1):
     84        key = self.make_key(key)
    8085        try:
    8186            val = self._cache.decr(key, delta)
    8287
     
    9297    def set_many(self, data, timeout=0):
    9398        safe_data = {}
    9499        for key, value in data.items():
     100            key = self.make_key(key)
    95101            if isinstance(value, unicode):
    96102                value = value.encode('utf-8')
    97             safe_data[smart_str(key)] = value
     103            safe_data[key] = value
    98104        self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
    99105
    100106    def delete_many(self, keys):
    101         self._cache.delete_multi(map(smart_str, keys))
     107        self._cache.delete_multi(map(self.make_key, keys))
    102108
    103109    def clear(self):
    104110        self._cache.flush_all()
  • 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_KEY_PREFIX
     684----------------
     685
     686It is a common occurence to have a shared cache instance running on your development
     687or production server for use across multiple projects, i.e. all sites are pointing
     688to the memcached instance running on port 11211. As a result, cache key conflicts
     689may arise which could result in data from one site being used by another.
     690
     691To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys
     692that are used with the cache backend, transparently::
     693
     694    # in settings, CACHE_KEY_PREFIX = 'myproject_'
     695
     696    >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key'
     697    >>> cache.get('my_key') # retrieved with key 'myproject_my_key'
     698    'hello world!'
     699
     700Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for
     701both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty
     702string ``''``.
     703
     704.. note::
     705   
     706   This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used
     707   in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a
     708   particular cache instance, therefore it will be prepended to the
     709   CACHE_MIDDLEWARE_KEY_PREFIX transparently.
     710
    683711Upstream caches
    684712===============
    685713
  • tests/regressiontests/cache/tests.py

     
    144144        "clear does nothing for the dummy cache backend"
    145145        self.cache.clear()
    146146
    147 
    148147class BaseCacheTests(object):
    149148    # A common set of tests to apply to all cache backends
    150149    def tearDown(self):
     
    162161        self.assertEqual(result, False)
    163162        self.assertEqual(self.cache.get("addkey1"), "value")
    164163
     164    def test_prefix(self):
     165        # Test for same cache key conflicts between shared backend
     166        self.cache.set('somekey', 'value')
     167
     168        # should not be set in the prefixed cache
     169        self.assertFalse(self.pfx_cache.has_key('somekey'))
     170
     171        self.pfx_cache.set('somekey', 'value2')
     172
     173        self.assertEqual(self.cache.get('somekey'), 'value')
     174        self.assertEqual(self.pfx_cache.get('somekey'), 'value2')
     175
    165176    def test_non_existent(self):
    166177        # Non-existent cache keys return as None/default
    167178        # get with non-existent keys
     
    400411        self._table_name = 'test cache table'
    401412        management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
    402413        self.cache = get_cache('db://%s?max_entries=30' % self._table_name)
     414        self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix')
    403415
    404416    def tearDown(self):
    405417        from django.db import connection
     
    412424class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
    413425    def setUp(self):
    414426        self.cache = get_cache('locmem://?max_entries=30')
     427        self.pfx_cache = get_cache('locmem://', 'cacheprefix')
    415428
    416429    def test_cull(self):
    417430        self.perform_cull_test(50, 29)
     
    424437    class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
    425438        def setUp(self):
    426439            self.cache = get_cache(settings.CACHE_BACKEND)
     440            self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix')
    427441
    428442        def test_invalid_keys(self):
    429443            """
     
    448462    def setUp(self):
    449463        self.dirname = tempfile.mkdtemp()
    450464        self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
     465        self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix')
    451466
    452467    def test_hashing(self):
    453468        """Test that keys are hashed into subdirectories correctly"""
    454469        self.cache.set("foo", "bar")
    455         keyhash = md5_constructor("foo").hexdigest()
     470        key = self.cache.make_key("foo")
     471        keyhash = md5_constructor(key).hexdigest()
    456472        keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
    457473        self.assert_(os.path.exists(keypath))
    458474
     
    461477        Make sure that the created subdirectories are correctly removed when empty.
    462478        """
    463479        self.cache.set("foo", "bar")
    464         keyhash = md5_constructor("foo").hexdigest()
     480        key = self.cache.make_key("foo")
     481        keyhash = md5_constructor(key).hexdigest()
    465482        keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
    466483        self.assert_(os.path.exists(keypath))
    467484
     
    494511
    495512    def setUp(self):
    496513        self.path = '/cache/test/'
    497         self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    498         self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
     514        self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
     515        self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
    499516        self.orig_use_i18n = settings.USE_I18N
    500517        settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
    501518        settings.CACHE_MIDDLEWARE_SECONDS = 1
    502519        settings.USE_I18N = False
    503520
    504521    def tearDown(self):
    505         settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
    506         settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
     522        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix
     523        settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds
    507524        settings.USE_I18N = self.orig_use_i18n
    508525
    509526    def _get_request(self, path):
     
    556573        learn_cache_key(request, response)
    557574        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    558575
     576class PrefixedCacheUtils(CacheUtils):
     577    def setUp(self):
     578        super(PrefixedCacheUtils, self).setUp()
     579        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
     580        settings.CACHE_KEY_PREFIX = 'cacheprefix'
     581
     582    def tearDown(self):
     583        super(PrefixedCacheUtils, self).tearDown()
     584        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
     585
    559586class CacheI18nTest(unittest.TestCase):
    560587
    561588    def setUp(self):
     
    651678        get_cache_data = FetchFromCacheMiddleware().process_request(request)
    652679        self.assertEqual(get_cache_data.content, es_message)
    653680
     681class PrefixedCacheI18nTest(CacheI18nTest):
     682    def setUp(self):
     683        super(PrefixedCacheI18nTest, self).setUp()
     684        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
     685        settings.CACHE_KEY_PREFIX = 'cacheprefix'
     686
     687    def tearDown(self):
     688        super(PrefixedCacheI18nTest, self).tearDown()
     689        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
     690
    654691if __name__ == '__main__':
    655692    unittest.main()
Back to Top