Add support for deferreds to memoize_method decorator
authorRuben Pollan <meskio@sindominio.net>
Wed, 3 Dec 2014 16:40:38 +0000 (10:40 -0600)
committerRuben Pollan <meskio@sindominio.net>
Wed, 3 Dec 2014 17:26:45 +0000 (11:26 -0600)
changes/feature-memoize_deferred [new file with mode: 0644]
src/leap/common/decorators.py

diff --git a/changes/feature-memoize_deferred b/changes/feature-memoize_deferred
new file mode 100644 (file)
index 0000000..dd2fdee
--- /dev/null
@@ -0,0 +1 @@
+- Add support for deferreds to memoize_method decorator
index 99c3653..4b07ea9 100644 (file)
@@ -22,6 +22,13 @@ import datetime
 import functools
 import logging
 
+try:
+    from twisted.internet import defer
+except ImportError:
+    class defer:
+        class Deferred:
+            pass
+
 logger = logging.getLogger(__name__)
 
 
@@ -59,6 +66,7 @@ class _memoized(object):
         # consume a huge amount of memory.
         self.cache = {}
         self.cache_ts = {}
+        self.is_deferred = {}
 
     def __call__(self, *args, **kwargs):
         """
@@ -67,28 +75,7 @@ class _memoized(object):
         :tyoe args: tuple
         :type kwargs: dict
         """
-        def ret_or_raise(value):
-            """
-            Returns the value except if it is an exception,
-            in which case it's raised.
-            """
-            if isinstance(value, Exception):
-                raise value
-            return value
-
-        if self.is_method:
-            # forget about `self` as key
-            key_args = args[1:]
-        else:
-            key_args = args
-
-        if self.ignore_kwargs is True:
-            key = key_args
-        else:
-            key = (key_args, frozenset(
-                [(k, v) for k, v in kwargs.items()
-                 if k not in self.ignore_kwargs]))
-
+        key = self._build_key(*args, **kwargs)
         if not isinstance(key, collections.Hashable):
             # uncacheable. a list, for instance.
             # better to not cache than blow up.
@@ -97,9 +84,9 @@ class _memoized(object):
 
         if key in self.cache:
             if self._is_cache_still_valid(key):
-                value = self.cache[key]
                 logger.debug("Got value from cache...")
-                return ret_or_raise(value)
+                value = self._get_value(key)
+                return self._ret_or_raise(value)
             else:
                 logger.debug("Cache is invalid, evaluating again...")
 
@@ -109,9 +96,52 @@ class _memoized(object):
         except Exception as exc:
             logger.error("Exception while calling function: %r" % (exc,))
             value = exc
-        self.cache[key] = value
+
+        if isinstance(value, defer.Deferred):
+            value.addBoth(self._store_deferred_value(key))
+        else:
+            self.cache[key] = value
+            self.is_deferred[key] = False
         self.cache_ts[key] = datetime.datetime.now()
-        return ret_or_raise(value)
+        return self._ret_or_raise(value)
+
+    def _build_key(self, *args, **kwargs):
+        """
+        Build key from the function arguments
+        """
+        if self.is_method:
+            # forget about `self` as key
+            key_args = args[1:]
+        else:
+            key_args = args
+
+        if self.ignore_kwargs is True:
+            key = key_args
+        else:
+            key = (key_args, frozenset(
+                [(k, v) for k, v in kwargs.items()
+                 if k not in self.ignore_kwargs]))
+        return key
+
+    def _get_value(self, key):
+        """
+        Get a value form cache for a key
+        """
+        if self.is_deferred[key]:
+            value = self.cache[key]
+            # it produces an errback if value is Failure
+            return defer.succeed(value)
+        else:
+            return self.cache[key]
+
+    def _ret_or_raise(self, value):
+        """
+        Returns the value except if it is an exception,
+        in which case it's raised.
+        """
+        if isinstance(value, Exception):
+            raise value
+        return value
 
     def _is_cache_still_valid(self, key, now=datetime.datetime.now):
         """
@@ -131,6 +161,16 @@ class _memoized(object):
         delta = datetime.timedelta(seconds=self.CACHE_INVALIDATION_DELTA)
         return (now() - cached_ts) < delta
 
+    def _store_deferred_value(self, key):
+        """
+        Returns a callback to store values from deferreds
+        """
+        def callback(value):
+            self.cache[key] = value
+            self.is_deferred[key] = True
+            return value
+        return callback
+
     def __repr__(self):
         """
         Return the function's docstring.