Commits

Mike Bayer  committed 4d28426

- add asdict=True argument #35

  • Participants
  • Parent commits 630abea

Comments (0)

Files changed (3)

File docs/build/changelog.rst

 
     .. change::
         :tags: feature
-        :tickets: 33
+        :tickets: 33, 35
 
       Added new methods :meth:`.CacheRegion.get_or_create_multi`
       and :meth:`.CacheRegion.cache_multi_on_arguments`, which

File dogpile/cache/region.py

         @my_region.cache_on_arguments(namespace=('x', 'y'))
         def my_function(a, b, **kw):
             return my_data()
+
     :param function_multi_key_generator: Optional.
      Similar to ``function_key_generator`` parameter, but it's used in
      :meth:`.CacheRegion.cache_multi_on_arguments`. Generated function
         return decorator
 
     def cache_multi_on_arguments(self, namespace=None, expiration_time=None,
-                                        should_cache_fn=None):
+                                        should_cache_fn=None,
+                                        asdict=False):
         """A function decorator that will cache multiple return
         values from the function using a sequence of keys derived from the
         function itself and the arguments passed to it.
          callable.
 
         :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create_multi`.
+         This function is given a value as returned by the creator, and only
+         if it returns True will that value be placed in the cache.
+
+        :param asdict: if ``True``, the decorated function should return
+         its result as a dictionary of keys->values, and the final result
+         of calling the decorated function will also be a dictionary.
+         If left at its default value of ``False``, the decorated function
+         should return its result as a list of values, and the final
+         result of calling the decorated function will also be a list.
+
+         When ``asdict==True`` if the dictionary returned by the decorated
+         function is missing keys, those keys will not be cached.
 
         .. versionadded:: 0.5.0
 
 
                 timeout = expiration_time() if expiration_time_is_callable \
                             else expiration_time
-                return self.get_or_create_multi(keys, creator, timeout,
+
+                if asdict:
+                    def dict_create(*keys):
+                        d_values = creator(*keys)
+                        return [d_values.get(key_lookup[k], NO_VALUE) for k in keys]
+
+                    def wrap_cache_fn(value):
+                        if value is NO_VALUE:
+                            return False
+                        elif not should_cache_fn:
+                            return True
+                        else:
+                            return should_cache_fn(value)
+
+                    result = self.get_or_create_multi(keys, dict_create, timeout,
+                                         wrap_cache_fn)
+                    result = dict((k, v) for k, v in zip(cache_keys, result)
+                                        if v is not NO_VALUE)
+                else:
+                    result = self.get_or_create_multi(keys, creator, timeout,
                                           should_cache_fn)
 
+                return result
+
             def invalidate(*arg):
                 keys = key_generator(*arg)
                 self.delete_multi(keys)

File tests/cache/test_region.py

         generate.set({7: 18, 10: 15})
         eq_(generate(2, 7, 10), ['2 5', 18, 15])
 
+    def test_multi_asdict(self):
+        reg = self._region()
+
+        counter = itertools.count(1)
+
+        @reg.cache_multi_on_arguments(asdict=True)
+        def generate(*args):
+            return dict(
+                    [(arg, "%d %d" % (arg, next(counter))) for arg in args]
+                    )
+
+        eq_(generate(2, 8, 10), {2: '2 2', 8: '8 3', 10: '10 1'})
+        eq_(generate(2, 9, 10), {2: '2 2', 9: '9 4', 10: '10 1'})
+
+        generate.invalidate(2)
+        eq_(generate(2, 7, 10), {2: '2 5', 7: '7 6', 10: '10 1'})
+
+        generate.set({7: 18, 10: 15})
+        eq_(generate(2, 7, 10), {2: '2 5', 7: 18, 10: 15})
+
+    def test_multi_asdict_keys_missing(self):
+        reg = self._region()
+
+        counter = itertools.count(1)
+
+        @reg.cache_multi_on_arguments(asdict=True)
+        def generate(*args):
+            return dict(
+                    [(arg, "%d %d" % (arg, next(counter)))
+                        for arg in args if arg != 10]
+                    )
+
+        eq_(generate(2, 8, 10), {2: '2 1', 8: '8 2'})
+        eq_(generate(2, 9, 10), {2: '2 1', 9: '9 3'})
+
+        assert reg.get(10) is NO_VALUE
+
+        generate.invalidate(2)
+        eq_(generate(2, 7, 10), {2: '2 4', 7: '7 5'})
+
+        generate.set({7: 18, 10: 15})
+        eq_(generate(2, 7, 10), {2: '2 4', 7: 18, 10: 15})
+
+    def test_multi_asdict_keys_missing_existing_cache_fn(self):
+        reg = self._region()
+
+        counter = itertools.count(1)
+
+        @reg.cache_multi_on_arguments(asdict=True,
+                            should_cache_fn=lambda v: not v.startswith('8 '))
+        def generate(*args):
+            return dict(
+                    [(arg, "%d %d" % (arg, next(counter)))
+                        for arg in args if arg != 10]
+                    )
+
+        eq_(generate(2, 8, 10), {2: '2 1', 8: '8 2'})
+        eq_(generate(2, 8, 10), {2: '2 1', 8: '8 3'})
+        eq_(generate(2, 8, 10), {2: '2 1', 8: '8 4'})
+        eq_(generate(2, 9, 10), {2: '2 1', 9: '9 5'})
+
+        assert reg.get(10) is NO_VALUE
+
+        generate.invalidate(2)
+        eq_(generate(2, 7, 10), {2: '2 6', 7: '7 7'})
+
+        generate.set({7: 18, 10: 15})
+        eq_(generate(2, 7, 10), {2: '2 6', 7: 18, 10: 15})
+
     def test_multi_namespace(self):
         reg = self._region()