diff --git a/aiocache/backends/memory.py b/aiocache/backends/memory.py index e2ddf03a..fdfd567d 100644 --- a/aiocache/backends/memory.py +++ b/aiocache/backends/memory.py @@ -79,9 +79,10 @@ async def _delete(self, key, _conn=None): return self.__delete(key) async def _clear(self, namespace=None, _conn=None): + """Empty the cache for the namespace provided or the entire cache""" if namespace: for key in list(SimpleMemoryBackend._cache): - if key.startswith(namespace): + if self.key_filter(key, namespace): self.__delete(key) else: SimpleMemoryBackend._cache = {} diff --git a/aiocache/base.py b/aiocache/base.py index d8481092..d3370415 100644 --- a/aiocache/base.py +++ b/aiocache/base.py @@ -98,6 +98,13 @@ class BaseCache: the backend. Default is None :param key_builder: alternative callable to build the key. Receives the key and the namespace as params and should return something that can be used as key by the underlying backend. + :param key_filter: callable that returns `True` if the specified key is + in the specified namespace. Callable signature must be: + `key_filter(key, namespace)`. + This callable is complementary to the `key_builder` callable. + If `key_builder` is provided, `key_filter` should also be provided. + If `key_filter` is not provided, default behavior will be compatible + with the default behavior of `key_builder`. :param timeout: int or float in seconds specifying maximum timeout for the operations to last. By default its 5. Use 0 or None if you want to disable it. :param ttl: int the expiration time in seconds to use as a default in all operations of @@ -107,12 +114,13 @@ class BaseCache: NAME: str def __init__( - self, serializer=None, plugins=None, namespace=None, key_builder=None, timeout=5, ttl=None + self, serializer=None, plugins=None, namespace=None, key_builder=None, key_filter=None, timeout=5, ttl=None ): self.timeout = float(timeout) if timeout is not None else timeout self.namespace = namespace self.ttl = float(ttl) if ttl is not None else ttl self.build_key = key_builder or self._build_key + self.key_filter = key_filter or self._key_filter self._serializer = None self.serializer = serializer or serializers.StringSerializer() @@ -488,6 +496,10 @@ def _build_key(self, key, namespace=None): return "{}{}".format(self.namespace, key) return key + def _key_filter(self, key, namespace): + """Return True if key is in namespace""" + return key.startswith(namespace) + def _get_ttl(self, ttl): return ttl if ttl is not SENTINEL else self.ttl diff --git a/aiocache/decorators.py b/aiocache/decorators.py index 202a45cc..90a39d04 100644 --- a/aiocache/decorators.py +++ b/aiocache/decorators.py @@ -136,14 +136,16 @@ def _key_from_args(self, func, args, kwargs): async def get_from_cache(self, key): try: - value = await self.cache.get(key) + namespace = self._kwargs.get("namespace", None) + value = await self.cache.get(key, namespace=namespace) return value except Exception: logger.exception("Couldn't retrieve %s, unexpected error", key) async def set_in_cache(self, key, value): try: - await self.cache.set(key, value, ttl=self.ttl) + namespace = self._kwargs.get("namespace", None) + await self.cache.set(key, value, namespace=namespace, ttl=self.ttl) except Exception: logger.exception("Couldn't set %s in key %s, unexpected error", value, key) @@ -205,7 +207,12 @@ async def decorator(self, f, *args, **kwargs): def _get_cache(cache=Cache.MEMORY, serializer=None, plugins=None, **cache_kwargs): - return cache(serializer=serializer, plugins=plugins, **cache_kwargs) + return Cache( + cache, + serializer=serializer, + plugins=plugins, + **cache_kwargs, + ) def _get_args_dict(func, args, kwargs): diff --git a/aiocache/plugins.py b/aiocache/plugins.py index efbb4311..efa11092 100644 --- a/aiocache/plugins.py +++ b/aiocache/plugins.py @@ -60,12 +60,15 @@ async def do_save_time(self, client, *args, took=0, **kwargs): ) -class HitMissRatioPlugin(BasePlugin): +class _HitMissRatioPlugin(BasePlugin): """ - Calculates the ratio of hits the cache has. The data is saved in the cache class as a dict - attribute called ``hit_miss_ratio``. For example, to access the hit ratio of the cache, - you can do ``cache.hit_miss_ratio['hit_ratio']``. It also provides the "total" and "hits" - keys. + Calculates the ratio of hits the cache has. The data is saved in the + cache class as a dict attribute called ``hit_miss_ratio``. + For example, to access the hit ratio of the cache, + you can do ``cache.hit_miss_ratio['hit_ratio']``. + It also provides the "total" and "hits" keys. + + :param key: str should already include the namespace, if available """ async def post_get(self, client, key, took=0, ret=None, **kwargs): @@ -96,3 +99,22 @@ async def post_multi_get(self, client, keys, took=0, ret=None, **kwargs): client.hit_miss_ratio["hit_ratio"] = ( client.hit_miss_ratio["hits"] / client.hit_miss_ratio["total"] ) + + +class HitMissRatioPlugin(_HitMissRatioPlugin): + """ + Calculates the ratio of hits the cache has. The data is saved in the + cache class as a dict attribute called ``hit_miss_ratio``. + For example, to access the hit ratio of the cache, + you can do ``cache.hit_miss_ratio['hit_ratio']``. + It also provides the "total" and "hits" keys. + + :param key: str should not include the namespace + """ + + async def post_get(self, client, key, took=0, ret=None, **kwargs): + """Update cache stats; use namespace if available""" + namespace = kwargs.get("namespace", None) + ns_key = client.build_key(key, namespace=namespace) + await super().post_get(client, ns_key, took=took, ret=ret, **kwargs) + return