From 75364a9d85d66d7e58857ba07737943c6e4313e5 Mon Sep 17 00:00:00 2001 From: Josep Cugat Date: Sat, 26 May 2018 19:22:07 +0200 Subject: [PATCH 1/2] Disable decorators cache per-call --- aiocache/decorators.py | 39 +++++++++++++++-------- tests/ut/test_decorators.py | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 13 deletions(-) diff --git a/aiocache/decorators.py b/aiocache/decorators.py index cd710924..9359e3c0 100644 --- a/aiocache/decorators.py +++ b/aiocache/decorators.py @@ -72,14 +72,19 @@ async def wrapper(*args, **kwargs): return wrapper async def decorator(self, f, *args, **kwargs): + disable_read = kwargs.pop('aiocache_disable_read', False) + disable_write = kwargs.pop('aiocache_disable_write', False) key = self.get_cache_key(f, args, kwargs) - value = await self.get_from_cache(key) - if value is not None: - return value + if not disable_read: + value = await self.get_from_cache(key) + if value is not None: + return value result = await f(*args, **kwargs) - await self.set_in_cache(key, result) + + if not disable_write: + await self.set_in_cache(key, result) return result @@ -241,18 +246,24 @@ async def wrapper(*args, **kwargs): return wrapper async def decorator(self, f, *args, **kwargs): + disable_read = kwargs.pop('aiocache_disable_read', False) + disable_write = kwargs.pop('aiocache_disable_write', False) + missing_keys = [] partial = {} keys, new_args, args_index = self.get_cache_keys(f, args, kwargs) - values = await self.get_from_cache(*keys) - for key, value in zip(keys, values): - if value is None: - missing_keys.append(key) - else: - partial[key] = value - if values and None not in values: - return partial + if not disable_read: + values = await self.get_from_cache(*keys) + for key, value in zip(keys, values): + if value is None: + missing_keys.append(key) + else: + partial[key] = value + if values and None not in values: + return partial + else: + missing_keys = list(keys) if args_index > -1: new_args[args_index] = missing_keys @@ -261,7 +272,9 @@ async def decorator(self, f, *args, **kwargs): result = await f(*new_args, **kwargs) result.update(partial) - await self.set_in_cache(result, args, kwargs) + + if not disable_write: + await self.set_in_cache(result, args, kwargs) return result diff --git a/tests/ut/test_decorators.py b/tests/ut/test_decorators.py index b7468bec..163e7e7d 100644 --- a/tests/ut/test_decorators.py +++ b/tests/ut/test_decorators.py @@ -94,6 +94,37 @@ async def test_calls_get_and_returns(self, decorator, decorator_call): assert decorator.cache.set.call_count == 0 assert stub.call_count == 0 + @pytest.mark.asyncio + async def test_disable_read(self, decorator, decorator_call): + decorator.cache.get = CoroutineMock(return_value=1) + + await decorator_call(aiocache_disable_read=True) + + assert decorator.cache.get.call_count == 0 + assert decorator.cache.set.call_count == 1 + assert stub.call_count == 1 + + @pytest.mark.asyncio + async def test_disable_write(self, decorator, decorator_call): + decorator.cache.get = CoroutineMock(return_value=None) + + await decorator_call(aiocache_disable_write=True) + + assert decorator.cache.get.call_count == 1 + assert decorator.cache.set.call_count == 0 + assert stub.call_count == 1 + + @pytest.mark.asyncio + async def test_disable_params_not_propagated(self, decorator, decorator_call): + decorator.cache.get = CoroutineMock(return_value=None) + + await decorator_call( + aiocache_disable_read=True, + aiocache_disable_write=True, + ) + + stub.assert_called_once_with() + @pytest.mark.asyncio async def test_get_from_cache_returns(self, decorator, decorator_call): decorator.cache.get = CoroutineMock(return_value=1) @@ -434,6 +465,38 @@ async def test_calls_fn_raises_exception(self, mocker, decorator, decorator_call with pytest.raises(Exception): assert await decorator_call(keys=[]) + @pytest.mark.asyncio + async def test_disable_read(self, decorator, decorator_call): + decorator.cache.multi_get = CoroutineMock(return_value=[1, 2]) + + await decorator_call(1, keys=['a', 'b'], aiocache_disable_read=True) + + assert decorator.cache.multi_get.call_count == 0 + assert decorator.cache.multi_set.call_count == 1 + assert stub_dict.call_count == 1 + + @pytest.mark.asyncio + async def test_disable_write(self, decorator, decorator_call): + decorator.cache.multi_get = CoroutineMock(return_value=[None, None]) + + await decorator_call(1, keys=['a', 'b'], aiocache_disable_write=True) + + assert decorator.cache.multi_get.call_count == 1 + assert decorator.cache.multi_set.call_count == 0 + assert stub_dict.call_count == 1 + + @pytest.mark.asyncio + async def test_disable_params_not_propagated(self, decorator, decorator_call): + decorator.cache.multi_get = CoroutineMock(return_value=[None, None]) + + await decorator_call( + 1, keys=['a', 'b'], + aiocache_disable_read=True, + aiocache_disable_write=True, + ) + + stub_dict.assert_called_once_with(1, keys=['a', 'b']) + @pytest.mark.asyncio async def test_set_in_cache(self, decorator, decorator_call): await decorator.set_in_cache({'a': 1, 'b': 2}, (), {}) From bef0dc74eeed72638cce392b6304e47b1ba30096 Mon Sep 17 00:00:00 2001 From: Josep Cugat Date: Sun, 27 May 2018 12:33:00 +0200 Subject: [PATCH 2/2] Address MR comments --- aiocache/decorators.py | 23 ++++++++++++----------- tests/ut/test_decorators.py | 31 ++++++++++--------------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/aiocache/decorators.py b/aiocache/decorators.py index 9359e3c0..15c3311c 100644 --- a/aiocache/decorators.py +++ b/aiocache/decorators.py @@ -21,6 +21,9 @@ class cached: Only one cache instance is created per decorated call. If you expect high concurrency of calls to the same function, you should adapt the pool size as needed. + When calling the decorated function, the reads and writes from/to the cache can be controlled + with the parameters ``cache_read`` and ``cache_write`` (both are enabled by default). + :param ttl: int seconds to store the function call. Default is None which means no expiration. :param key: str value to set as key for the function return. Takes precedence over key_builder param. If key and key_builder are not passed, it will use module_name @@ -71,19 +74,17 @@ async def wrapper(*args, **kwargs): wrapper.cache = self.cache return wrapper - async def decorator(self, f, *args, **kwargs): - disable_read = kwargs.pop('aiocache_disable_read', False) - disable_write = kwargs.pop('aiocache_disable_write', False) + async def decorator(self, f, *args, cache_read=True, cache_write=True, **kwargs): key = self.get_cache_key(f, args, kwargs) - if not disable_read: + if cache_read: value = await self.get_from_cache(key) if value is not None: return value result = await f(*args, **kwargs) - if not disable_write: + if cache_write: await self.set_in_cache(key, result) return result @@ -200,6 +201,9 @@ class multi_cached: Only one cache instance is created per decorated function. If you expect high concurrency of calls to the same function, you should adapt the pool size as needed. + When calling the decorated function, the reads and writes from/to the cache can be controlled + with the parameters ``cache_read`` and ``cache_write`` (both are enabled by default). + :param keys_from_attr: arg or kwarg name from the function containing an iterable to use as keys to index in the cache. :param key_builder: Callable that allows to change the format of the keys before storing. @@ -245,15 +249,12 @@ async def wrapper(*args, **kwargs): wrapper.cache = self.cache return wrapper - async def decorator(self, f, *args, **kwargs): - disable_read = kwargs.pop('aiocache_disable_read', False) - disable_write = kwargs.pop('aiocache_disable_write', False) - + async def decorator(self, f, *args, cache_read=True, cache_write=True, **kwargs): missing_keys = [] partial = {} keys, new_args, args_index = self.get_cache_keys(f, args, kwargs) - if not disable_read: + if cache_read: values = await self.get_from_cache(*keys) for key, value in zip(keys, values): if value is None: @@ -273,7 +274,7 @@ async def decorator(self, f, *args, **kwargs): result = await f(*new_args, **kwargs) result.update(partial) - if not disable_write: + if cache_write: await self.set_in_cache(result, args, kwargs) return result diff --git a/tests/ut/test_decorators.py b/tests/ut/test_decorators.py index 163e7e7d..0dfab56c 100644 --- a/tests/ut/test_decorators.py +++ b/tests/ut/test_decorators.py @@ -95,20 +95,18 @@ async def test_calls_get_and_returns(self, decorator, decorator_call): assert stub.call_count == 0 @pytest.mark.asyncio - async def test_disable_read(self, decorator, decorator_call): - decorator.cache.get = CoroutineMock(return_value=1) - - await decorator_call(aiocache_disable_read=True) + async def test_cache_read_disabled(self, decorator, decorator_call): + await decorator_call(cache_read=False) assert decorator.cache.get.call_count == 0 assert decorator.cache.set.call_count == 1 assert stub.call_count == 1 @pytest.mark.asyncio - async def test_disable_write(self, decorator, decorator_call): + async def test_cache_write_disabled(self, decorator, decorator_call): decorator.cache.get = CoroutineMock(return_value=None) - await decorator_call(aiocache_disable_write=True) + await decorator_call(cache_write=False) assert decorator.cache.get.call_count == 1 assert decorator.cache.set.call_count == 0 @@ -118,10 +116,7 @@ async def test_disable_write(self, decorator, decorator_call): async def test_disable_params_not_propagated(self, decorator, decorator_call): decorator.cache.get = CoroutineMock(return_value=None) - await decorator_call( - aiocache_disable_read=True, - aiocache_disable_write=True, - ) + await decorator_call(cache_read=False, cache_write=False) stub.assert_called_once_with() @@ -466,20 +461,18 @@ async def test_calls_fn_raises_exception(self, mocker, decorator, decorator_call assert await decorator_call(keys=[]) @pytest.mark.asyncio - async def test_disable_read(self, decorator, decorator_call): - decorator.cache.multi_get = CoroutineMock(return_value=[1, 2]) - - await decorator_call(1, keys=['a', 'b'], aiocache_disable_read=True) + async def test_cache_read_disabled(self, decorator, decorator_call): + await decorator_call(1, keys=['a', 'b'], cache_read=False) assert decorator.cache.multi_get.call_count == 0 assert decorator.cache.multi_set.call_count == 1 assert stub_dict.call_count == 1 @pytest.mark.asyncio - async def test_disable_write(self, decorator, decorator_call): + async def test_cache_write_disabled(self, decorator, decorator_call): decorator.cache.multi_get = CoroutineMock(return_value=[None, None]) - await decorator_call(1, keys=['a', 'b'], aiocache_disable_write=True) + await decorator_call(1, keys=['a', 'b'], cache_write=False) assert decorator.cache.multi_get.call_count == 1 assert decorator.cache.multi_set.call_count == 0 @@ -489,11 +482,7 @@ async def test_disable_write(self, decorator, decorator_call): async def test_disable_params_not_propagated(self, decorator, decorator_call): decorator.cache.multi_get = CoroutineMock(return_value=[None, None]) - await decorator_call( - 1, keys=['a', 'b'], - aiocache_disable_read=True, - aiocache_disable_write=True, - ) + await decorator_call(1, keys=['a', 'b'], cache_read=False, cache_write=False) stub_dict.assert_called_once_with(1, keys=['a', 'b'])