Ticket #13795: cache_key_prefix_13448.diff

File cache_key_prefix_13448.diff, 17.2 KB (added by Byron Ruth, 14 years ago)
  • django/conf/global_settings.py

     
    427427# The cache backend to use.  See the docstring in django.core.cache for the
    428428# possible values.
    429429CACHE_BACKEND = 'locmem://'
     430CACHE_KEY_PREFIX = ''
    430431CACHE_MIDDLEWARE_KEY_PREFIX = ''
    431432CACHE_MIDDLEWARE_SECONDS = 600
    432433
  • 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=settings.CACHE_KEY_PREFIX):
    6060    scheme, host, params = parse_backend_uri(backend_uri)
    6161    if scheme in BACKENDS:
    6262        name = 'django.core.cache.backends.%s' % BACKENDS[scheme]
    6363    else:
    6464        name = scheme
    6565    module = importlib.import_module(name)
    66     return getattr(module, 'CacheClass')(host, params)
     66    return getattr(module, 'CacheClass')(host, params, key_prefix)
    6767
    6868cache = get_cache(settings.CACHE_BACKEND)
    6969
    70 # Some caches -- pythont-memcached in particular -- need to do a cleanup at the
     70# Some caches -- python-memcached in particular -- need to do a cleanup at the
    7171# end of a request cycle. If the cache provides a close() method, wire it up
    7272# here.
    7373if hasattr(cache, 'close'):
  • django/core/cache/backends/base.py

     
    11"Base Cache class."
    22
    33from django.core.exceptions import ImproperlyConfigured
     4from django.utils.encoding import smart_str
    45
    56class InvalidCacheBackendError(ImproperlyConfigured):
    67    pass
    78
    89class BaseCache(object):
    9     def __init__(self, params):
     10    def __init__(self, params, key_prefix=''):
    1011        timeout = params.get('timeout', 300)
    1112        try:
    1213            timeout = int(timeout)
    1314        except (ValueError, TypeError):
    1415            timeout = 300
    1516        self.default_timeout = timeout
     17        self.key_prefix = smart_str(key_prefix)
    1618
     19    def make_key(self, key):
     20        """Constructs the key used by all other methods. By default it prepends
     21        the `key_prefix'. In cache backend subclasses this can be overriden to
     22        provide custom key making behavior.
     23        """
     24        return self.key_prefix + smart_str(key)
     25
    1726    def add(self, key, value, timeout=None):
    1827        """
    1928        Set a value in the cache if the key does not already exist. If
  • 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._lock.writer_enters()
    3435        try:
    3536            exp = self._expire_info.get(key)
     
    4445            self._lock.writer_leaves()
    4546
    4647    def get(self, key, default=None):
     48        key = self.make_key(key)
    4749        self._lock.reader_enters()
    4850        try:
    4951            exp = self._expire_info.get(key)
     
    7678        self._expire_info[key] = time.time() + timeout
    7779
    7880    def set(self, key, value, timeout=None):
     81        key = self.make_key(key)
    7982        self._lock.writer_enters()
    8083        # Python 2.4 doesn't allow combined try-except-finally blocks.
    8184        try:
     
    8790            self._lock.writer_leaves()
    8891
    8992    def has_key(self, key):
     93        key = self.make_key(key)
    9094        self._lock.reader_enters()
    9195        try:
    9296            exp = self._expire_info.get(key)
     
    127131            pass
    128132
    129133    def delete(self, key):
     134        key = self.make_key(key)
    130135        self._lock.writer_enters()
    131136        try:
    132137            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=''):
     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        key = self.make_key(key)
    3536        if self.has_key(key):
    3637            return False
    3738
     
    3940        return True
    4041
    4142    def get(self, key, default=None):
     43        key = self.make_key(key)
    4244        fname = self._key_to_file(key)
    4345        try:
    4446            f = open(fname, 'rb')
     
    5658        return default
    5759
    5860    def set(self, key, value, timeout=None):
     61        key = self.make_key(key)
    5962        fname = self._key_to_file(key)
    6063        dirname = os.path.dirname(fname)
    6164
     
    7982            pass
    8083
    8184    def delete(self, key):
     85        key = self.make_key(key)
    8286        try:
    8387            self._delete(self._key_to_file(key))
    8488        except (IOError, OSError):
     
    9599            pass
    96100
    97101    def has_key(self, key):
     102        key = self.make_key(key)
    98103        fname = self._key_to_file(key)
    99104        try:
    100105            f = open(fname, 'rb')
     
    148153        Thus, a cache key of "foo" gets turnned into a file named
    149154        ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
    150155        """
    151         path = md5_constructor(key.encode('utf-8')).hexdigest()
     156        key = self.make_key(key)
     157        path = md5_constructor(key).hexdigest()
    152158        path = os.path.join(path[:2], path[2:4], path[4:])
    153159        return os.path.join(self._dir, path)
    154160
  • django/core/cache/backends/db.py

     
    1010    import pickle
    1111
    1212class CacheClass(BaseCache):
    13     def __init__(self, table, params):
    14         BaseCache.__init__(self, params)
     13    def __init__(self, table, params, key_prefix=''):
     14        BaseCache.__init__(self, params, key_prefix)
    1515        self._table = connection.ops.quote_name(table)
    1616        max_entries = params.get('max_entries', 300)
    1717        try:
     
    2525            self._cull_frequency = 3
    2626
    2727    def get(self, key, default=None):
     28        key = self.make_key(key)
    2829        cursor = connection.cursor()
    2930        cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
    3031        row = cursor.fetchone()
     
    4546        return self._base_set('add', key, value, timeout)
    4647
    4748    def _base_set(self, mode, key, value, timeout=None):
     49        key = self.make_key(key)
    4850        if timeout is None:
    4951            timeout = self.default_timeout
    5052        cursor = connection.cursor()
     
    7476            return True
    7577
    7678    def delete(self, key):
     79        key = self.make_key(key)
    7780        cursor = connection.cursor()
    7881        cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
    7982        transaction.commit_unless_managed()
    8083
    8184    def has_key(self, key):
     85        key = self.make_key(key)
    8286        now = datetime.now().replace(microsecond=0)
    8387        cursor = connection.cursor()
    8488        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s and expires > %%s" % self._table,
  • 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        keys = map(self.make_key, keys)
     64        return self._cache.get_multi(keys)
    6165
    6266    def close(self, **kwargs):
    6367        self._cache.disconnect_all()
    6468
    6569    def incr(self, key, delta=1):
     70        key = self.make_key(key)
    6671        try:
    6772            val = self._cache.incr(key, delta)
    6873
     
    7782        return val
    7883
    7984    def decr(self, key, delta=1):
     85        key = self.make_key(key)
    8086        try:
    8187            val = self._cache.decr(key, delta)
    8288
     
    9298    def set_many(self, data, timeout=0):
    9399        safe_data = {}
    94100        for key, value in data.items():
     101            key = self.make_key(key)
    95102            if isinstance(value, unicode):
    96103                value = value.encode('utf-8')
    97             safe_data[smart_str(key)] = value
     104            safe_data[key] = value
    98105        self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))
    99106
    100107    def delete_many(self, keys):
    101         self._cache.delete_multi(map(smart_str, keys))
     108        keys = map(self.make_key, keys)
     109        self._cache.delete_multi(keys)
    102110
    103111    def clear(self):
    104112        self._cache.flush_all()
  • tests/regressiontests/cache/tests.py

     
    143143        "clear does nothing for the dummy cache backend"
    144144        self.cache.clear()
    145145
    146 
    147146class BaseCacheTests(object):
    148147    # A common set of tests to apply to all cache backends
    149148    def tearDown(self):
     
    161160        self.assertEqual(result, False)
    162161        self.assertEqual(self.cache.get("addkey1"), "value")
    163162
     163    def test_prefix(self):
     164        # Test for same cache key conflicts between shared backend
     165        self.cache.set('somekey', 'value')
     166
     167        # should not be set in the prefixed cache
     168        self.assertFalse(self.pfx_cache.has_key('somekey'))
     169
     170        self.pfx_cache.set('somekey', 'value2')
     171
     172        self.assertEqual(self.cache.get('somekey'), 'value')
     173        self.assertEqual(self.pfx_cache.get('somekey'), 'value2')
     174
    164175    def test_non_existent(self):
    165176        # Non-existent cache keys return as None/default
    166177        # get with non-existent keys
     
    358369        self._table_name = 'test cache table'
    359370        management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False)
    360371        self.cache = get_cache('db://%s' % self._table_name)
     372        self.pfx_cache = get_cache('db://%s' % self._table_name, 'cacheprefix')
    361373
    362374    def tearDown(self):
    363375        from django.db import connection
     
    367379class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
    368380    def setUp(self):
    369381        self.cache = get_cache('locmem://')
     382        self.pfx_cache = get_cache('locmem://', 'cacheprefix')
    370383
    371384# memcached backend isn't guaranteed to be available.
    372385# To check the memcached backend, the test settings file will
     
    376389    class MemcachedCacheTests(unittest.TestCase, BaseCacheTests):
    377390        def setUp(self):
    378391            self.cache = get_cache(settings.CACHE_BACKEND)
     392            self.pfx_cache = get_cache(settings.CACHE_BACKEND, 'cacheprefix')
    379393
    380394class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
    381395    """
     
    384398    def setUp(self):
    385399        self.dirname = tempfile.mkdtemp()
    386400        self.cache = get_cache('file://%s' % self.dirname)
     401        self.pfx_cache = get_cache('file://%s' % self.dirname, 'cacheprefix')
    387402
    388403    def test_hashing(self):
    389404        """Test that keys are hashed into subdirectories correctly"""
     
    411426
    412427    def setUp(self):
    413428        self.path = '/cache/test/'
    414         self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    415         self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
     429        self.old_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
     430        self.old_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
    416431        self.orig_use_i18n = settings.USE_I18N
    417432        settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix'
    418433        settings.CACHE_MIDDLEWARE_SECONDS = 1
    419434        settings.USE_I18N = False
    420435
    421436    def tearDown(self):
    422         settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix
    423         settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
     437        settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_cache_middleware_key_prefix
     438        settings.CACHE_MIDDLEWARE_SECONDS = self.old_cache_middleware_seconds
    424439        settings.USE_I18N = self.orig_use_i18n
    425440
    426441    def _get_request(self, path):
     
    473488        learn_cache_key(request, response)
    474489        self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    475490
     491class PrefixedCacheUtils(CacheUtils):
     492    def setUp(self):
     493        super(PrefixedCacheUtils, self).setUp()
     494        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
     495        settings.CACHE_KEY_PREFIX = 'cacheprefix'
     496
     497    def tearDown(self):
     498        super(PrefixedCacheUtils, self).tearDown()
     499        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
     500
    476501class CacheI18nTest(unittest.TestCase):
    477502
    478503    def setUp(self):
     
    568593        get_cache_data = FetchFromCacheMiddleware().process_request(request)
    569594        self.assertEqual(get_cache_data.content, es_message)
    570595
     596class PrefixedCacheI18nTest(CacheI18nTest):
     597    def setUp(self):
     598        super(PrefixedCacheI18nTest, self).setUp()
     599        self.old_cache_key_prefix = settings.CACHE_KEY_PREFIX
     600        settings.CACHE_KEY_PREFIX = 'cacheprefix'
     601
     602    def tearDown(self):
     603        super(PrefixedCacheI18nTest, self).tearDown()
     604        settings.CACHE_KEY_PREFIX = self.old_cache_key_prefix
     605
    571606if __name__ == '__main__':
    572607    unittest.main()
  • docs/topics/cache.txt

     
    600600    However, if the backend doesn't natively provide an increment/decrement
    601601    operation, it will be implemented using a two-step retrieve/update.
    602602
     603CACHE_KEY_PREFIX
     604----------------
     605
     606It is a common occurence to have a shared cache instance running on your development
     607or production server for use across multiple projects, i.e. all sites are pointing
     608to the memcached instance running on port 11211. As a result, cache key conflicts
     609may arise which could result in data from one site being used by another.
     610
     611To alleviate this, CACHE_KEY_PREFIX can be set. This will be prepended to all keys
     612that are used with the cache backend, transparently::
     613
     614    # in settings, CACHE_KEY_PREFIX = 'myproject_'
     615
     616    >>> cache.set('my_key', 'hello world!') # set with key 'myproject_my_key'
     617    >>> cache.get('my_key') # retrieved with key 'myproject_my_key'
     618    'hello world!'
     619
     620Of course, for sites that _do_ share content, simply set the CACHE_KEY_PREFIX for
     621both sites to the same value. The default value for CACHE_KEY_PREFIX is the empty
     622string ``''``.
     623
     624.. note::
     625   
     626   This does *not* conflict with the CACHE_MIDDLEWARE_KEY_PREFIX and can be used
     627   in conjunction with it. CACHE_KEY_PREFIX acts as a global prefix for a
     628   particular cache instance, therefore it will be prepended to the
     629   CACHE_MIDDLEWARE_KEY_PREFIX transparently.
     630
    603631Upstream caches
    604632===============
    605633
Back to Top