Commit 5f6f3836 authored by Sean Bleier's avatar Sean Bleier

Added more tests to increase coverage and fixed a couple bugs.

We can't inherit a class from a basestring and instantiate it, so I changed
the base class to 'object' so we can still to the type checking.

I also discovered a bug with the Django 1.3 compatibility work.  In the
incr_version method, we were using the Redis's rename function to move the key,
but in Redis 2.0 you cannot rename a volitile key.

Added some moar authors
parent aaff2679
...@@ -2,5 +2,7 @@ Sean Bleier <http://github.com/sebleier> ...@@ -2,5 +2,7 @@ Sean Bleier <http://github.com/sebleier>
Matt Dennewitz <http://github.com/blackbrrr> Matt Dennewitz <http://github.com/blackbrrr>
Jannis Leidel <http://github.com/jezdez> Jannis Leidel <http://github.com/jezdez>
S. Angel / Twidi <http://github.com/twidi> S. Angel / Twidi <http://github.com/twidi>
Noah Kantrowitz / coderanger <http://github.com/coderanger>
Martin Mahner / bartTC <http://github.com/bartTC>
Forked from http://github.com/sebleier/django-redis-cache Forked from http://github.com/sebleier/django-redis-cache
...@@ -14,11 +14,22 @@ except ImportError: ...@@ -14,11 +14,22 @@ except ImportError:
"Redis cache backend requires the 'redis-py' library") "Redis cache backend requires the 'redis-py' library")
class CacheKey(basestring): class CacheKey(object):
""" """
A stub string class that we can use to check if a key was created already. A stub string class that we can use to check if a key was created already.
""" """
pass def __init__(self, key):
self._key = key
def __eq__(self, other):
return self._key == other
def __str__(self):
return self.__unicode__()
def __unicode__(self):
return smart_str(self._key)
class CacheClass(BaseCache): class CacheClass(BaseCache):
def __init__(self, server, params): def __init__(self, server, params):
...@@ -49,8 +60,8 @@ class CacheClass(BaseCache): ...@@ -49,8 +60,8 @@ class CacheClass(BaseCache):
Returns the utf-8 encoded bytestring of the given key as a CacheKey Returns the utf-8 encoded bytestring of the given key as a CacheKey
instance to be able to check if it was "made" before. instance to be able to check if it was "made" before.
""" """
if isinstance(key, CacheKey): if not isinstance(key, CacheKey):
key = CacheKey(smart_str(key)) key = CacheKey(key)
return key return key
def add(self, key, value, timeout=None, version=None): def add(self, key, value, timeout=None, version=None):
...@@ -93,12 +104,13 @@ class CacheClass(BaseCache): ...@@ -93,12 +104,13 @@ class CacheClass(BaseCache):
Set content expiration, if necessary Set content expiration, if necessary
""" """
key = self.make_key(key, version=version) key = self.make_key(key, version=version)
if timeout == 0: if timeout is None:
timeout = self.default_timeout
if timeout <= 0:
# force the key to be non-volatile # force the key to be non-volatile
result = self._cache.get(key) result = self._cache.get(key)
self._cache.set(key, result) self._cache.set(key, result)
else: else:
timeout = timeout or self.default_timeout
# 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):
...@@ -130,9 +142,7 @@ class CacheClass(BaseCache): ...@@ -130,9 +142,7 @@ class CacheClass(BaseCache):
""" """
Unpickles the given value. Unpickles the given value.
""" """
# pickle doesn't want a unicode!
value = smart_str(value) value = smart_str(value)
# hydrate that pickle
return pickle.loads(value) return pickle.loads(value)
def get_many(self, keys, version=None): def get_many(self, keys, version=None):
...@@ -178,22 +188,30 @@ class RedisCache(CacheClass): ...@@ -178,22 +188,30 @@ class RedisCache(CacheClass):
""" """
A subclass that is supposed to be used on Django >= 1.3. A subclass that is supposed to be used on Django >= 1.3.
""" """
def make_key(self, key, version=None): def make_key(self, key, version=None):
if not isinstance(key, CacheKey): if not isinstance(key, CacheKey):
key = CacheKey(smart_str(super(CacheClass, self).make_key(key, version))) key = CacheKey(super(CacheClass, self).make_key(key, version))
return key 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.
Note: In Redis 2.0 you cannot rename a volitle key, so we have to move
the value from the old key to the new key and maintain the ttl.
""" """
if version is None: if version is None:
version = self.version version = self.version
key = self.make_key(key, version) old_key = self.make_key(key, version)
value = self.get(key, version=version) value = self.get(old_key, version=version)
ttl = self._cache.ttl(old_key)
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) new_key = self.make_key(key, version=version+delta)
self._cache.rename(key, incr_key) # TODO: See if we can check the version of Redis, since 2.2 will be able
# to rename volitile keys.
self.set(new_key, value, timeout=ttl)
self.delete(old_key)
return version + delta return version + delta
...@@ -12,7 +12,17 @@ if not settings.configured: ...@@ -12,7 +12,17 @@ if not settings.configured:
}, },
INSTALLED_APPS = [ INSTALLED_APPS = [
'tests.testapp', 'tests.testapp',
] ],
CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
'LOCATION': '127.0.0.1:6379',
'OPTIONS': {
'DB': 15,
'PASSWORD': 'yadayada',
},
},
}
) )
from django.test.simple import DjangoTestSuiteRunner from django.test.simple import DjangoTestSuiteRunner
......
...@@ -9,3 +9,14 @@ DATABASES = { ...@@ -9,3 +9,14 @@ DATABASES = {
INSTALLED_APPS = [ INSTALLED_APPS = [
'tests.testapp', 'tests.testapp',
] ]
CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
'LOCATION': '127.0.0.1:6379',
'OPTIONS': { # optional
'DB': 15,
'PASSWORD': 'yadayada',
},
},
}
...@@ -7,9 +7,10 @@ try: ...@@ -7,9 +7,10 @@ try:
import cPickle as pickle import cPickle as pickle
except ImportError: except ImportError:
import pickle import pickle
from django import VERSION
from django.core.cache import get_cache from django.core.cache import get_cache
from models import Poll, expensive_calculation from models import Poll, expensive_calculation
from redis_cache.cache import RedisCache
# functions/classes for complex data type tests # functions/classes for complex data type tests
def f(): def f():
...@@ -25,10 +26,32 @@ class RedisCacheTests(unittest.TestCase): ...@@ -25,10 +26,32 @@ class RedisCacheTests(unittest.TestCase):
""" """
def setUp(self): def setUp(self):
# use DB 16 for testing and hope there isn't any important data :-> # use DB 16 for testing and hope there isn't any important data :->
self.cache = get_cache('redis_cache.cache://127.0.0.1:6379?db=15') self.cache = self.get_cache()
def tearDown(self): def tearDown(self):
self.cache.clear() self.cache.clear()
self.cache.close()
def get_cache(self, backend=None):
if VERSION[0] == 1 and VERSION[1] < 3:
cache = get_cache(backend or 'redis_cache.cache://127.0.0.1:6379?db=15')
elif VERSION[0] == 1 and VERSION[1] >= 3:
cache = get_cache(backend or 'default')
return cache
def test_bad_db_initialization(self):
self.cache = self.get_cache('redis_cache.cache://127.0.0.1:6379?db=not_a_number')
self.assertEqual(self.cache._cache.db, 1)
def test_bad_port_initialization(self):
self.cache = self.get_cache('redis_cache.cache://127.0.0.1:not_a_number?db=15')
self.assertEqual(self.cache._cache.port, 6379)
def test_default_initialization(self):
self.cache = self.get_cache('redis_cache.cache://127.0.0.1')
self.assertEqual(self.cache._cache.host, '127.0.0.1')
self.assertEqual(self.cache._cache.db, 1)
self.assertEqual(self.cache._cache.port, 6379)
def test_simple(self): def test_simple(self):
# Simple cache set/get works # Simple cache set/get works
...@@ -166,12 +189,12 @@ class RedisCacheTests(unittest.TestCase): ...@@ -166,12 +189,12 @@ class RedisCacheTests(unittest.TestCase):
self.assertEqual(self.cache.has_key("expire3"), False) self.assertEqual(self.cache.has_key("expire3"), False)
def test_set_expiration_timeout_None(self): def test_set_expiration_timeout_None(self):
key, value = 'key', 'value' key, value = self.cache.make_key('key'), 'value'
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)
def test_set_expiration_timeout_0(self): def test_set_expiration_timeout_0(self):
key, value = 'key', 'value' key, value = self.cache.make_key('key'), 'value'
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)
...@@ -268,5 +291,18 @@ class RedisCacheTests(unittest.TestCase): ...@@ -268,5 +291,18 @@ class RedisCacheTests(unittest.TestCase):
self.assertEqual(self.cache.get('key3'), 'sausage') self.assertEqual(self.cache.get('key3'), 'sausage')
self.assertEqual(self.cache.get('key4'), 'lobster bisque') self.assertEqual(self.cache.get('key4'), 'lobster bisque')
def test_incr_version(self):
if isinstance(self.cache, RedisCache):
old_key = "key1"
self.cache.set(old_key, "spam", version=1)
self.assertEqual(self.cache.make_key(old_key), ':1:key1')
new_version = self.cache.incr_version(old_key, 1)
self.assertEqual(new_version, 2)
new_key = self.cache.make_key(old_key, version=new_version)
self.assertEqual(new_key, ':2:key1')
self.assertEqual(self.cache.get(old_key), None)
self.assertEqual(self.cache.get(new_key), 'spam')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
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