Commit ff32fd8f authored by Jannis Leidel's avatar Jannis Leidel

Separated the 1.3 compatible cache class from the rest of the code and reverted…

Separated the 1.3 compatible cache class from the rest of the code and reverted a few unneeded changes.

This also handles the key better internally to prevent multiple "making" of the key.
parent ccb0eaee
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Redis Django Cache Backend Redis Django Cache Backend
========================== ==========================
A simple Redis cache backend for Django >= 1.3 A simple Redis cache backend for Django
Notes Notes
----- -----
...@@ -18,19 +18,23 @@ Usage ...@@ -18,19 +18,23 @@ Usage
1. Run ``python setup.py install`` to install, 1. Run ``python setup.py install`` to install,
or place ``redis_cache`` on your Python path. or place ``redis_cache`` on your Python path.
2. Modify your Django settings to use ``redis_cache`` :: 2. Modify your Django settings to use ``redis_cache`` :
On Django < 1.3::
CACHE_BACKEND = 'redis_cache.cache://<host>:<port>'
On Django >= 1.3::
CACHES = { CACHES = {
'default': { 'default': {
'BACKEND': 'redis_cache.cache.CacheClass', 'BACKEND': 'redis_cache.RedisCache',
'LOCATION': '<host>:<port>', 'LOCATION': '<host>:<port>',
'OPTIONS': { # optional 'OPTIONS': { # optional
'DB': 1, 'DB': 1,
'PASSWORD': 'yadayada',
}, },
}, },
} }
.. _redis-py: http://github.com/andymccurdy/redis-py/ .. _redis-py: http://github.com/andymccurdy/redis-py/
from redis_cache.cache import RedisCache
...@@ -14,18 +14,25 @@ except ImportError: ...@@ -14,18 +14,25 @@ except ImportError:
"Redis cache backend requires the 'redis-py' library") "Redis cache backend requires the 'redis-py' library")
class CacheKey(basestring):
"""
A stub string class that we can use to check if a key was created already.
"""
pass
class CacheClass(BaseCache): class CacheClass(BaseCache):
def __init__(self, server, params): def __init__(self, server, params):
""" """
Connect to Redis, and set up cache backend. Connect to Redis, and set up cache backend.
""" """
BaseCache.__init__(self, params) super(CacheClass, self).__init__(params)
db = params.get('OPTIONS', {}).get('db', 1) options = params.get('OPTIONS', {})
password = params.get('password', options.get('PASSWORD', None))
db = params.get('db', options.get('DB', 1))
try: try:
db = int(db) db = int(db)
except (ValueError, TypeError): except (ValueError, TypeError):
db = 1 db = 1
password = params.get('password', None)
if ':' in server: if ':' in server:
host, port = server.split(':') host, port = server.split(':')
try: try:
...@@ -39,9 +46,12 @@ class CacheClass(BaseCache): ...@@ -39,9 +46,12 @@ class CacheClass(BaseCache):
def make_key(self, key, version=None): def make_key(self, key, version=None):
""" """
Returns the utf-8 encoded bytestring of the given key. Returns the utf-8 encoded bytestring of the given key as a CacheKey
instance to be able to check if it was "made" before.
""" """
return smart_str(super(CacheClass, self).make_key(key, version)) if isinstance(key, CacheKey):
key = CacheKey(smart_str(key))
return key
def add(self, key, value, timeout=None, version=None): def add(self, key, value, timeout=None, version=None):
""" """
...@@ -49,37 +59,32 @@ class CacheClass(BaseCache): ...@@ -49,37 +59,32 @@ class CacheClass(BaseCache):
Returns ``True`` if the object was added, ``False`` if not. Returns ``True`` if the object was added, ``False`` if not.
""" """
_key = key
key = self.make_key(key, version=version) key = self.make_key(key, version=version)
if self._cache.exists(key): if self._cache.exists(key):
return False return False
return self.set(_key, value, timeout) return self.set(key, value, timeout)
def get(self, key, default=None, version=None): def get(self, key, default=None, version=None):
""" """
Retrieve a value from the cache. Retrieve a value from the cache.
Returns unpicked value if key is found, ``None`` if not. Returns unpickled value if key is found, the default if not.
""" """
# get the value from the cache key = self.make_key(key, version=version)
value = self._cache.get(self.make_key(key, version=version)) value = self._cache.get(key)
if value is None: if value is None:
return default return default
# pickle doesn't want a unicode! return self.unpickle(value)
value = smart_str(value)
# hydrate that pickle
return pickle.loads(value)
def set(self, key, value, timeout=None, version=None): def set(self, key, value, timeout=None, version=None):
""" """
Persist a value to the cache, and set an optional expiration time. Persist a value to the cache, and set an optional expiration time.
""" """
_key = key
key = self.make_key(key, version=version) key = self.make_key(key, version=version)
# store the pickled value # store the pickled value
result = self._cache.set(key, pickle.dumps(value)) result = self._cache.set(key, pickle.dumps(value))
# set expiration if needed # set expiration if needed
self.expire(_key, timeout, version=version) self.expire(key, timeout, version=version)
# result is a boolean # result is a boolean
return result return result
...@@ -87,7 +92,6 @@ class CacheClass(BaseCache): ...@@ -87,7 +92,6 @@ class CacheClass(BaseCache):
""" """
Set content expiration, if necessary Set content expiration, if necessary
""" """
_key = key
key = self.make_key(key, version=version) key = self.make_key(key, version=version)
if timeout == 0: if timeout == 0:
# force the key to be non-volatile # force the key to be non-volatile
...@@ -98,22 +102,22 @@ class CacheClass(BaseCache): ...@@ -98,22 +102,22 @@ class CacheClass(BaseCache):
# If the expiration command returns false, we need to reset the key # If the expiration command returns false, we need to reset the key
# with the new expiration # with the new expiration
if not self._cache.expire(key, timeout): if not self._cache.expire(key, timeout):
value = self.get(_key, version=version) value = self.get(key, version=version)
self.set(_key, value, timeout, version=version) self.set(key, value, timeout, version=version)
def delete(self, key, version=None): def delete(self, key, version=None):
""" """
Remove a key from the cache. Remove a key from the cache.
""" """
self._cache.delete(self.make_key(key, version)) self._cache.delete(self.make_key(key, version=version))
def delete_many(self, keys, version=None): def delete_many(self, keys, version=None):
""" """
Remove multiple keys at once. Remove multiple keys at once.
""" """
if keys: if keys:
l = lambda x: self.make_key(x, version=version) keys = map(lambda key: self.make_key(key, version=version), keys)
self._cache.delete(*map(l, keys)) self._cache.delete(*keys)
def clear(self): def clear(self):
""" """
...@@ -122,21 +126,27 @@ class CacheClass(BaseCache): ...@@ -122,21 +126,27 @@ class CacheClass(BaseCache):
# TODO : potential data loss here, should we only delete keys based on the correct version ? # TODO : potential data loss here, should we only delete keys based on the correct version ?
self._cache.flushdb() self._cache.flushdb()
def unpickle(self, value):
"""
Unpickles the given value.
"""
# pickle doesn't want a unicode!
value = smart_str(value)
# hydrate that pickle
return pickle.loads(value)
def get_many(self, keys, version=None): def get_many(self, keys, version=None):
""" """
Retrieve many keys. Retrieve many keys.
""" """
recovered_data = SortedDict() recovered_data = SortedDict()
new_keys = map(lambda x: self.make_key(x, version=version), keys) new_keys = map(lambda key: self.make_key(key, version=version), keys)
results = self._cache.mget(new_keys)
map_keys = dict(zip(new_keys, keys)) map_keys = dict(zip(new_keys, keys))
results = self._cache.mget(new_keys)
for key, value in zip(new_keys, results): for key, value in zip(new_keys, results):
if value is None: if value is None:
continue continue
# pickle doesn't want a unicode! value = self.unpickle(value)
value = smart_str(value)
# hydrate that pickle
value = pickle.loads(value)
if isinstance(value, basestring): if isinstance(value, basestring):
value = smart_unicode(value) value = smart_unicode(value)
recovered_data[map_keys[key]] = value recovered_data[map_keys[key]] = value
...@@ -164,17 +174,26 @@ class CacheClass(BaseCache): ...@@ -164,17 +174,26 @@ class CacheClass(BaseCache):
""" """
self._cache.connection.disconnect() self._cache.connection.disconnect()
class RedisCache(CacheClass):
"""
A subclass that is supposed to be used on Django >= 1.3.
"""
def make_key(self, key, version=None):
if not isinstance(key, CacheKey):
key = CacheKey(smart_str(super(CacheClass, self).make_key(key, version)))
return key
def incr_version(self, key, delta=1, version=None): def incr_version(self, key, delta=1, version=None):
"""Adds delta to the cache version for the supplied key. Returns the """
Adds delta to the cache version for the supplied key. Returns the
new version. new version.
""" """
if version is None: if version is None:
version = self.version version = self.version
key = self.make_key(key, version)
value = self.get(key, version=version) value = self.get(key, version=version)
if value is None: if value is None:
raise ValueError("Key '%s' not found" % key) raise ValueError("Key '%s' not found" % key)
incr_key = self.make_key(key, version=version+delta)
self._cache.rename(self.make_key(key, version), self.make_key(key, self._cache.rename(key, incr_key)
version=version+delta)) return version + delta
return version+delta
\ No newline at end of file
...@@ -168,17 +168,15 @@ class RedisCacheTests(unittest.TestCase): ...@@ -168,17 +168,15 @@ class RedisCacheTests(unittest.TestCase):
def test_set_expiration_timeout_None(self): def test_set_expiration_timeout_None(self):
key, value = 'key', 'value' key, value = 'key', 'value'
self.cache.set(key, value); self.cache.set(key, value);
self.assertEqual(self.cache._cache.ttl(key), None) self.assertTrue(self.cache._cache.ttl(key) > 0)
def test_set_expiration_timeout_0(self): def test_set_expiration_timeout_0(self):
key, value = 'key', 'value' key, value = 'key', 'value'
_key = self.cache.make_key(key) self.cache.set(key, value)
self.cache.set(key, value); self.assertTrue(self.cache._cache.ttl(key) > 0)
self.assertTrue(self.cache._cache.ttl(_key) > 0)
self.cache.expire(key, 0) self.cache.expire(key, 0)
self.assertEqual(self.cache.get(key), value) self.assertEqual(self.cache.get(key), value)
self.assertEqual(self.cache._cache.ttl(_key), None) self.assertTrue(self.cache._cache.ttl(key) < 0)
def test_set_expiration_first_expire_call(self): def test_set_expiration_first_expire_call(self):
key, value = self.cache.make_key('key'), 'value' key, value = self.cache.make_key('key'), 'value'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment