Commits

Nicolas Perriault committed c96839a Merge

fixed issues with cache key generation

Comments (0)

Files changed (7)

+Authors
+-------
+
+* Mikhail Korobov
+* Sapan Bhatia
+* Tymofiy Babych

cache_utils/decorators.py

 #coding: utf-8
 from django.core.cache import cache
 from django.utils.functional import wraps
-from cache_utils.utils import _cache_key, _func_info, _func_type
+from cache_utils.utils import _cache_key, _func_info, _func_type, sanitize_memcached_key
 
 def cached(timeout, group=None):
     """ Caching decorator. Can be applied to function, method or classmethod.
     invalidated.
     """
 
-    backend_kwargs = {'group': group} if group else {}
+    if group:
+        backend_kwargs = {'group': group}
+        get_key = _cache_key
+    else:
+        backend_kwargs = {}
+        def get_key(*args, **kwargs):
+            return sanitize_memcached_key(_cache_key(*args, **kwargs))
 
     def _cached(func):
 
                 wrapper._full_name = name
 
             # try to get the value from cache
-            key = _cache_key(wrapper._full_name, func_type, args, kwargs)
+            key = get_key(wrapper._full_name, func_type, args, kwargs)
             value = cache.get(key, **backend_kwargs)
 
             # in case of cache miss recalculate the value and put it to the cache
             ''' invalidates cache result for function called with passed arguments '''
             if not hasattr(wrapper, '_full_name'):
                 return
-            key = _cache_key(wrapper._full_name, 'function', args, kwargs)
+            key = get_key(wrapper._full_name, 'function', args, kwargs)
             cache.delete(key, **backend_kwargs)
 
         wrapper.invalidate = invalidate

cache_utils/group_backend.py

 from django.core.cache.backends.memcached import CacheClass as MemcachedCacheClass
 from django.conf import settings
 
+from django.utils.encoding import smart_str
+
+
 # This prefix is appended to the group name to prevent cache key clashes.
 _VERSION_PREFIX = getattr(settings, 'VERSION', "")
 _KEY_PREFIX = "_group::"
 
+
 # MINT_DELAY is an upper bound on how long any value should take to
 # be generated (in seconds)
 MINT_DELAY = 30
             key = "%s:%s-%s" % (group, key, hashkey)
         return key
 
+    def make_key(self, key, *args, **kwargs):
+        """
+        A hack to make backend work correctly with django 1.3.
+
+        Key prefixes and cache versions are now supported out of box but
+        this class uses its own settings and in order to provide
+        backward compatibility django's new features are not used.
+        """
+        return smart_str(key)
+
     def _get_hashkey(self, group):
         """ This can be useful sometimes if you're doing a very large number
             of operations and you want to avoid all of the extra cache hits.

cache_utils/tests.py

     def bar(cls, x):
         pass
 
+class Store(object):
+    """ Class for encoding error test """
+    def __unicode__(self):
+        return u'Вася'
+    def __repr__(self):
+        return u'Вася'.encode('utf8')
+
+
 class FuncTypeTest(TestCase):
     def assertFuncType(self, func, tp):
         self.assertEqual(_func_type(func), tp)
         self.assertEqual(info[1], args_out)
 
     def test_func(self):
-        self.assertFuncInfo(foo, [1,2], 'cache_utils.tests.foo', [1,2])
+        self.assertFuncInfo(foo, [1,2], 'cache_utils.tests.foo:9', [1,2])
 
     def test_method(self):
         foo_obj = Foo()
         self.assertFuncInfo(Foo.foo, [foo_obj, 1, 2],
-                            'cache_utils.tests.Foo.foo', [1,2])
+                            'cache_utils.tests.Foo.foo:13', [1,2])
 
     def test_classmethod(self):
         self.assertFuncInfo(Foo.bar, [Foo, 1],
-                            'cache_utils.tests.Foo.bar', [1])
+                            'cache_utils.tests.Foo.bar:15', [1])
 
 
 class ClearMemcachedTest(TestCase):
 
         self.assertEqual(my_func(u"Ы"*500), u"5"+u"Ы"*500)
         self.assertEqual(my_func(u"Ы"*500), u"5"+u"Ы"*500)
+
+    def test_utf8_args(self):
+        @cached(60)
+        def func(utf8_array, *args):
+            return utf8_array
+        func([u'Василий'.encode('utf8')], u'Петрович'.encode('utf8'))
+
+    def test_utf8_repr(self):
+        @cached(60)
+        def func(param):
+            return param
+
+        func(Store())
+

cache_utils/utils.py

 
 
 def _func_info(func, args):
-    """ introspect function's or method's full name.
+    ''' introspect function's or method's full name.
     Returns a tuple (name, normalized_args,) with
-    'cls' and 'self' removed from normalized_args 
-    """
+    'cls' and 'self' removed from normalized_args '''
+
     func_type = _func_type(func)
+    lineno = ":%s" % func.func_code.co_firstlineno
+
     if func_type == 'function':
-        return ".".join([func.__module__, func.__name__]), args
+        name = ".".join([func.__module__, func.__name__]) + lineno
+        return name, args
+
     class_name = args[0].__class__.__name__
     if func_type == 'classmethod':
         class_name = args[0].__name__
 
-    return ".".join([func.__module__, class_name, func.__name__]), args[1:]
+    name = ".".join([func.__module__, class_name, func.__name__]) + lineno
+    return name, args[1:]
 
 
 def _cache_key(func_name, func_type, args, kwargs):
 #!/usr/bin/env python
 from distutils.core import setup
 
-version='0.6.2'
+version='0.7.2'
 
 setup(
     name='django-cache-utils',

test_project/settings.py

 PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
 sys.path.insert(0, os.path.dirname(PROJECT_ROOT))
 
+# django < 1.3
 CACHE_BACKEND = 'cache_utils.group_backend://localhost:11211/' #?timeout=60
 
+# django 1.3+
+CACHES = {
+    'default': {
+        'BACKEND': 'cache_utils.group_backend.CacheClass',
+        'LOCATION': '127.0.0.1:11211',
+    },
+}
+
 DATABASE_ENGINE = 'sqlite3'
 DATABASE_NAME = ':memory:'