Commits

Andriy Kornatskyy committed 7661bc4

Added get_or_set_multi cache pattern plus decorator.

  • Participants
  • Parent commits ef25578

Comments (0)

Files changed (2)

File src/wheezy/caching/patterns.py

         else:
             return decorate(wrapped)
 
+    def get_or_set_multi(self, make_key, create_factory, args):
+        """ Cache Pattern: `get_milti` items by *make_key* over
+            *args* from *cache* and if there are any missing use
+            *create_factory* to aquire them. If result is not
+            `None` use cache `set_multi` operation to store results.
+        """
+        key_map = dict((make_key(a), a) for a in args)
+        cache_result = self.get_multi(key_map.keys())
+        if not cache_result:
+            data_result = create_factory(args)
+        elif len(cache_result) != len(key_map):
+            data_result = create_factory(
+                [key_map[key] for key in key_map
+                 if key not in cache_result])
+        else:
+            return dict([(key_map[key], cache_result[key])
+                         for key in cache_result])
+
+        if data_result:
+            self.set_multi(dict([
+                (key, data_result[k])
+                for key, k in key_map.items()
+                if k in data_result
+            ]))
+            data_result.update([(key_map[key], cache_result[key])
+                                for key in cache_result])
+        return data_result
+
+    def wraps_get_or_set_multi(self, make_key):
+        """ Returns specialized decorator for `get_or_set_multi` cache
+            pattern.
+
+            Example::
+
+                cached = Cached(cache, kb, time=60)
+
+                @cached.wraps_get_or_set_multi(
+                    make_key=lambda i: 'key:%r' % i)
+                def get_multi_account(account_ids):
+                    pass
+        """
+        assert make_key
+
+        def decorate(func):
+            argnames = getargspec(func)[0]
+            if argnames and argnames[0] in ('self', 'cls', 'klass'):
+                assert len(argnames) == 2
+
+                def get_or_set_multi_wrapper_with_ctx(ctx, args):
+                    return self.get_or_set_multi(
+                        make_key,
+                        lambda fargs: func(ctx, fargs),
+                        args)
+                return get_or_set_multi_wrapper_with_ctx
+            else:
+                assert len(argnames) == 1
+
+                def get_or_set_multi_wrapper(args):
+                    return self.get_or_set_multi(make_key, func, args)
+                return get_or_set_multi_wrapper
+        return decorate
+
     def one_pass_create(self, key, create_factory,
                         dependency_key_factory=None):
         """ Cache Pattern: try enter one pass: (1) if entered

File src/wheezy/caching/tests/test_patterns.py

         return create_factory()
 
 
+class GetOrSetMultiTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.mock_cache = Mock()
+        self.mock_create_factory = Mock()
+
+    def get_or_set_multi(self):
+        from wheezy.caching.patterns import Cached
+        mk = lambda i: 'k%d' % i
+        cached = Cached(self.mock_cache, time=10, namespace='ns')
+        r = cached.get_or_set_multi(
+            mk, self.mock_create_factory, [1, 2])
+        assert [(1, 'a'), (2, 'b')] == sorted(r.items())
+
+    def test_all_cache_hit(self):
+        """ All items are taken from cache.
+        """
+        def get_multi(keys, key_prefix, namespace):
+            assert ['k1', 'k2'] == sorted(keys)
+        self.mock_cache.get_multi.return_value = {'k1': 'a', 'k2': 'b'}
+        self.get_or_set_multi()
+        self.mock_cache.get_multi.side_effect = get_multi
+        assert not self.mock_create_factory.called
+
+    def test_all_cache_miss(self):
+        """ All items are missed in cache.
+        """
+        def set_multi(keys, time, key_prefix, namespace):
+            assert [('k1', 'a'), ('k2', 'b')] == sorted(keys.items())
+        self.mock_cache.get_multi.return_value = {}
+        self.mock_create_factory.return_value = {1: 'a', 2: 'b'}
+        self.mock_cache.set_multi.side_effect = set_multi
+        self.get_or_set_multi()
+        self.mock_create_factory.assert_called_once_with([1, 2])
+        assert self.mock_cache.set_multi.called
+
+    def test_some_cache_miss(self):
+        """ Some items are missed in cache.
+        """
+        self.mock_cache.get_multi.return_value = {'k2': 'b'}
+        self.mock_create_factory.return_value = {1: 'a'}
+        self.get_or_set_multi()
+        self.mock_create_factory.assert_called_once_with([1])
+        self.mock_cache.set_multi.assert_called_once_with(
+            {'k1': 'a'}, 10, '', 'ns')
+
+
+class WrapsGetOrSetMultiTestCase(GetOrSetMultiTestCase):
+
+    def get_or_set_multi(self):
+        from wheezy.caching.patterns import Cached
+        mk = lambda i: 'k%d' % i
+        cached = Cached(self.mock_cache, time=10, namespace='ns')
+
+        @cached.wraps_get_or_set_multi(make_key=mk)
+        def create_factory(ids):
+            return self.mock_create_factory(ids)
+        r = create_factory([1, 2])
+        assert [(1, 'a'), (2, 'b')] == sorted(r.items())
+
+
+class WrapsGetOrSetMultiCtxTestCase(GetOrSetMultiTestCase):
+
+    def get_or_set_multi(self):
+        from wheezy.caching.patterns import Cached
+        mk = lambda i: 'k%d' % i
+        cached = Cached(self.mock_cache, time=10, namespace='ns')
+
+        @cached.wraps_get_or_set_multi(make_key=mk)
+        def create_factory(cls, ids):
+            return self.mock_create_factory(ids)
+        r = create_factory('cls', [1, 2])
+        assert [(1, 'a'), (2, 'b')] == sorted(r.items())
+
+
 class KeyBuilderTestCase(unittest.TestCase):
 
     def setUp(self):