diff --git a/README.md b/README.md index 068c9e4..ecf9f9b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ redis-simple-cache is a pythonic interface for creating a cache over redis. It provides simple decorators that can be added to any function to cache its return values. +Changes in this fork: +------------- +Adds python3 support + Requirements: ------------- redis 2.6.2 @@ -10,7 +14,7 @@ redis-py 2.7.1 (see requirements.txt file) Installation: ------------- - pip install redis-simple-cache + pip/pip3 install git+https://github.com/YashSinha1996/redis-simple-cache.git or to get the latest version diff --git a/redis_cache/__init__.py b/redis_cache/__init__.py index a46e94e..c186c2c 100644 --- a/redis_cache/__init__.py +++ b/redis_cache/__init__.py @@ -1 +1 @@ -from rediscache import * \ No newline at end of file +from .rediscache import * \ No newline at end of file diff --git a/redis_cache/rediscache.py b/redis_cache/rediscache.py index f951bd8..bab172e 100644 --- a/redis_cache/rediscache.py +++ b/redis_cache/rediscache.py @@ -76,7 +76,8 @@ def __init__(self, port=None, db=None, password=None, - namespace="SimpleCache"): + namespace="SimpleCache", + quiet_nohash=False): self.limit = limit # No of json encoded strings to cache self.expire = expire # Time to keys to expire in seconds @@ -84,13 +85,14 @@ def __init__(self, self.host = host self.port = port self.db = db + self.quiet = quiet_nohash try: self.connection = RedisConnect(host=self.host, port=self.port, db=self.db, password=password).connect() - except RedisNoConnException, e: + except RedisNoConnException as e: self.connection = None pass @@ -146,7 +148,7 @@ def expire_all_in_set(self): keys successfully expired. :return: int, int """ - all_members = self.keys() + all_members = list(self.keys()) keys = [self.make_key(k) for k in all_members] with self.connection.pipeline() as pipe: @@ -208,7 +210,7 @@ def get(self, key): self.connection.srem(self.get_set_name(), key) raise ExpiredKeyException else: - return value + return to_str(value) def mget(self, keys): """ @@ -244,7 +246,7 @@ def mget_json(self, keys): """ d = self.mget(keys) if d: - for key in d.keys(): + for key in list(d.keys()): d[key] = json.loads(d[key]) if d[key] else None return d @@ -293,16 +295,25 @@ def flush_namespace(self, space): pipe.srem(setname, *space) pipe.execute() - def get_hash(self, args): - if self.hashkeys: - key = hashlib.md5(args).hexdigest() - else: - key = pickle.dumps(args) - return key + def get_hash(self, args, serializer): + try: + args = serializer.dumps(args) + + if self.hashkeys: + args = hashlib.md5(to_unicode(args)).hexdigest() + + return args + + except TypeError as e: + + if self.quiet: + return "" + else: + raise e def cache_it(limit=10000, expire=DEFAULT_EXPIRY, cache=None, - use_json=False, namespace=None): + use_json=False, namespace=None, quiet=False, host=None, port=None): """ Arguments and function result must be pickleable. :param limit: maximum number of keys to maintain in the set @@ -312,10 +323,23 @@ def cache_it(limit=10000, expire=DEFAULT_EXPIRY, cache=None, """ cache_ = cache ## Since python 2.x doesn't have the nonlocal keyword, we need to do this expire_ = expire ## Same here. + namespace_ = namespace + quiet_ = quiet + host_, port_ = host, port + def decorator(function): - cache, expire = cache_, expire_ + cache, expire, namespace, quiet = cache_, expire_, namespace_, quiet_ + + host, port = host_, port_ + + if namespace and isinstance(namespace, str): + namespace = str(function.__module__) + ':' + namespace + else: + namespace = str(function.__module__) + if cache is None: - cache = SimpleCache(limit, expire, hashkeys=True, namespace=function.__module__) + cache = SimpleCache(limit, expire, hashkeys=True, namespace=namespace, + quiet_nohash=quiet, host=host, port=port) elif expire == DEFAULT_EXPIRY: # If the expire arg value is the default, set it to None so we store # the expire value of the passed cache object @@ -334,7 +358,17 @@ def func(*args, **kwargs): ## Key will be either a md5 hash or just pickle object, ## in the form of `function name`:`key` - key = cache.get_hash(serializer.dumps([args, kwargs])) + + key = [] + + for arg in args: + key.append(cache.get_hash(arg, serializer=serializer)) + + for arg in kwargs.items(): + key.append(cache.get_hash(arg, serializer=serializer)) + + key = '--'.join(key) + cache_key = '{func_name}:{key}'.format(func_name=function.__name__, key=key) @@ -366,7 +400,8 @@ def func(*args, **kwargs): -def cache_it_json(limit=10000, expire=DEFAULT_EXPIRY, cache=None, namespace=None): +def cache_it_json(limit=10000, expire=DEFAULT_EXPIRY, cache=None, namespace=None, + quiet=False, host=None, port=None): """ Arguments and function result must be able to convert to JSON. :param limit: maximum number of keys to maintain in the set @@ -375,11 +410,21 @@ def cache_it_json(limit=10000, expire=DEFAULT_EXPIRY, cache=None, namespace=None :return: decorated function """ return cache_it(limit=limit, expire=expire, use_json=True, - cache=cache, namespace=None) + cache=cache, namespace=namespace, quiet=quiet, host=host, port=port) def to_unicode(obj, encoding='utf-8'): - if isinstance(obj, basestring): - if not isinstance(obj, unicode): - obj = unicode(obj, encoding) + if isinstance(obj, str): + obj = obj.encode(encoding) + return obj + + +def to_str(obj, encoding='utf-8'): + + try: + if isinstance(obj, bytes): + obj = obj.decode(encoding) + except UnicodeDecodeError as e: + pass + return obj diff --git a/redis_cache/test_rediscache.py b/redis_cache/test_rediscache.py index f961d8c..b85a6ed 100644 --- a/redis_cache/test_rediscache.py +++ b/redis_cache/test_rediscache.py @@ -1,7 +1,7 @@ #SimpleCache Tests #~~~~~~~~~~~~~~~~~~~ from datetime import timedelta -from rediscache import SimpleCache, RedisConnect, cache_it, cache_it_json, CacheMissException, ExpiredKeyException, DoNotCache +from .rediscache import SimpleCache, RedisConnect, cache_it, cache_it_json, CacheMissException, ExpiredKeyException, DoNotCache from unittest import TestCase, main import time @@ -83,7 +83,7 @@ def test_no_cache(n): result = n * 10 raise DoNotCache(result) - keys_before = len(self.c.keys()) + keys_before = len(list(self.c.keys())) r1 = test_no_cache(20) r2 = test_no_cache(10) r3 = test_no_cache(30) @@ -94,7 +94,7 @@ def test_no_cache(n): self.assertEqual(r3, (10 * 30)) self.assertEqual(r4, (10 * 20)) - keys_after = len(self.c.keys()) + keys_after = len(list(self.c.keys())) self.assertEqual(keys_before, keys_after) @@ -109,7 +109,7 @@ def test_no_cache(n): except Exception: pass - keys_before = len(self.c.keys()) + keys_before = len(list(self.c.keys())) r1 = test_no_cache(20) r2 = test_no_cache(10) r3 = test_no_cache(30) @@ -120,7 +120,7 @@ def test_no_cache(n): self.assertEqual(r2, (10 * 10)) self.assertEqual(r3, (10 * 30)) - keys_after = len(self.c.keys()) + keys_after = len(list(self.c.keys())) self.assertEqual(keys_before, keys_after) @@ -132,10 +132,10 @@ def test_no_cache(n): except ZeroDivisionError as e: raise DoNotCache(e) - keys_before = len(self.c.keys()) + keys_before = len(list(self.c.keys())) r1 = test_no_cache(20) self.assertTrue(isinstance(r1, ZeroDivisionError)) - keys_after = len(self.c.keys()) + keys_after = len(list(self.c.keys())) self.assertEqual(keys_before, keys_after) def test_decorator_json(self): @@ -169,8 +169,8 @@ def add(x, y): def test_cache_limit(self): for i in range(100): self.c.store("foo%d" % i, "foobar") - self.failUnless(len(self.c) <= 10) - self.failUnless(len(self.c.keys()) <= 10) + self.assertTrue(len(self.c) <= 10) + self.assertTrue(len(list(self.c.keys())) <= 10) def test_flush(self): connection = self.c.connection @@ -189,13 +189,13 @@ def test_flush(self): connection.delete("will_not_be_deleted") def test_flush_namespace(self): - self.redis.flushall() + self.redis.flushall() self.c.store("foo:one", "bir") self.c.store("foo:two", "bor") self.c.store("fii", "bur") - len_keys_before = len(self.c.keys()) + len_keys_before = len(list(self.c.keys())) self.c.flush_namespace('foo') - len_keys_after = len(self.c.keys()) + len_keys_after = len(list(self.c.keys())) self.assertEqual((len_keys_before - len_keys_after), 2) self.assertEqual(self.c.get('fii'), 'bur') self.assertRaises(CacheMissException, self.c.get, "foo:one") diff --git a/setup.py b/setup.py index ac2a2ea..294fd50 100644 --- a/setup.py +++ b/setup.py @@ -7,14 +7,14 @@ def openf(fname): setup( name="redis-simple-cache", - version="0.0.6", - author="Vivek Narayanan, Flávio Juvenal, Sam Zaydel", - author_email="flaviojuvenal@gmail.com", + version="0.1.6", + author="Vivek Narayanan, Flávio Juvenal, Sam Zaydel, Yash Sinha", + author_email="flaviojuvenal@gmail.com, yashpratyushsinha@gmail.com", description="redis-simple-cache is a pythonic interface for creating a cache over redis. " "It provides simple decorators that can be added to any function to cache its return values. ", license="3-clause BSD", keywords="decorator decorators redis cache", - url="https://github.com/vivekn/redis-simple-cache", + url="https://github.com/YashSinha1996/redis-simple-cache", packages=['redis_cache'], long_description=openf("README.md").read(), install_requires=[line.strip() for line in openf("requirements.txt") if line.strip()],