Added cache_utils to the distribution because it includes a patch and is not packaged...
[plcapi.git] / cache_utils / decorators.py
diff --git a/cache_utils/decorators.py b/cache_utils/decorators.py
new file mode 100644 (file)
index 0000000..4dd1d4d
--- /dev/null
@@ -0,0 +1,54 @@
+#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
+
+def cached(timeout, group=None):
+    """ Caching decorator. Can be applied to function, method or classmethod.
+    Supports bulk cache invalidation and invalidation for exact parameter
+    set. Cache keys are human-readable because they are constructed from
+    callable's full name and arguments and then sanitized to make
+    memcached happy.
+
+    It can be used with or without group_backend. Without group_backend
+    bulk invalidation is not supported.
+
+    Wrapped callable gets `invalidate` methods. Call `invalidate` with
+    same arguments as function and the result for these arguments will be
+    invalidated.
+    """
+
+    backend_kwargs = {'group': group} if group else {}
+
+    def _cached(func):
+
+        func_type = _func_type(func)
+
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+
+            # full name is stored as attribute on first call
+            if not hasattr(wrapper, '_full_name'):
+                name, _args = _func_info(func, args)
+                wrapper._full_name = name
+
+            # try to get the value from cache
+            key = _cache_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
+            if value is None:
+                value = func(*args, **kwargs)
+                cache.set(key, value, timeout, **backend_kwargs)
+            return value
+
+        def invalidate(*args, **kwargs):
+            ''' 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)
+            cache.delete(key, **backend_kwargs)
+
+        wrapper.invalidate = invalidate
+        return wrapper
+    return _cached