Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion redis_cache/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from rediscache import *
from .rediscache import *
85 changes: 65 additions & 20 deletions redis_cache/rediscache.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,23 @@ 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
self.prefix = namespace
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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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
24 changes: 12 additions & 12 deletions redis_cache/test_rediscache.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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")
Expand Down
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="[email protected]",
version="0.1.6",
author="Vivek Narayanan, Flávio Juvenal, Sam Zaydel, Yash Sinha",
author_email="[email protected], [email protected]",
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()],
Expand Down