def _make_key(self, group, key, hashkey=None): """ Generates a new cache key which belongs to a group, has _VERSION_PREFIX prepended and is shorter than memcached key length limit. """ key = _VERSION_PREFIX + key if group: if not hashkey: hashkey = self._get_hashkey(group) key = "%s:%s-%s" % (group, key, hashkey) return sanitize_memcached_key(key)
def cached(timeout, group=None, backend=None, key=None): """ Caching decorator. Can be applied to function, method or classmethod. Supports bulk cache invalidation and invalidation for exact parameter set. Cache keys are human-readable because they are constructed from callable's full name and arguments and then sanitized to make memcached happy. It can be used with or without group_backend. Without group_backend bulk invalidation is not supported. Wrapped callable gets `invalidate` methods. Call `invalidate` with same arguments as function and the result for these arguments will be invalidated. """ if key: def test(*args, **kwargs): args = list(args) args[0] = key return sanitize_memcached_key(_cache_key(*args, **kwargs)) _get_key = test else: _get_key = lambda *args, **kwargs: sanitize_memcached_key(_cache_key(*args, **kwargs)) if group: backend_kwargs = {'group': group} else: backend_kwargs = {} if backend: cache_backend = get_cache(backend) else: cache_backend = get_cache('default') def _cached(func): func_type = _func_type(func) @wraps(func) def wrapper(*args, **kwargs): full_name(*args) # try to get the value from cache key = _get_key(wrapper._full_name, func_type, args, kwargs) value = cache_backend.get(key, **backend_kwargs) # in case of cache miss recalculate the value and put it to the cache if value is None: logger.debug("Cache MISS: %s" % key) value = func(*args, **kwargs) cache_backend.set(key, value, timeout, **backend_kwargs) logger.debug("Cache SET: %s" % key) else: logger.debug("Cache HIT: %s" % key) return value def invalidate(*args, **kwargs): """ Invalidates cache result for function called with passed arguments """ if not hasattr(wrapper, '_full_name'): return key = _get_key(wrapper._full_name, 'function', args, kwargs) cache_backend.delete(key, **backend_kwargs) logger.debug("Cache DELETE: %s" % key) def force_recalc(*args, **kwargs): """ Forces a call to the function & sets the new value in the cache """ full_name(*args) key = _get_key(wrapper._full_name, func_type, args, kwargs) value = func(*args, **kwargs) cache_backend.set(key, value, timeout, **backend_kwargs) return value def full_name(*args): # full name is stored as attribute on first call if not hasattr(wrapper, '_full_name'): name, _args = _func_info(func, args) wrapper._full_name = name def require_cache(*args, **kwargs): """ Only pull from cache, do not attempt to calculate """ full_name(*args) key = _get_key(wrapper._full_name, func_type, args, kwargs) logger.debug("Require cache %s" % key) value = cache_backend.get(key, **backend_kwargs) if not value: logger.info("Could not find required cache %s" % key) raise NoCachedValueException return value def get_cache_key(*args, **kwargs): """ Returns name of cache key utilized """ full_name(*args) key = _get_key(wrapper._full_name, 'function', args, kwargs) return key wrapper.require_cache = require_cache wrapper.invalidate = invalidate wrapper.force_recalc = force_recalc wrapper.get_cache_key = get_cache_key return wrapper return _cached
def test(*args, **kwargs): args = list(args) args[0] = key return sanitize_memcached_key(_cache_key(*args, **kwargs))
def test_sanitize_keys(self): key = u"12345678901234567890123456789012345678901234567890" self.assertTrue(len(key) >= 40) key = sanitize_memcached_key(key, 40) self.assertTrue(len(key) <= 40)
def get_key(*args, **kwargs): return sanitize_memcached_key(_cache_key(*args, **kwargs))
def cached(timeout, group=None, backend=None, key=None): """ Caching decorator. Can be applied to function, method or classmethod. Supports bulk cache invalidation and invalidation for exact parameter set. Cache keys are human-readable because they are constructed from callable's full name and arguments and then sanitized to make memcached happy. It can be used with or without group_backend. Without group_backend bulk invalidation is not supported. Wrapped callable gets `invalidate` methods. Call `invalidate` with same arguments as function and the result for these arguments will be invalidated. """ if key: def test(*args, **kwargs): args = list(args) args[0] = key return sanitize_memcached_key(_cache_key(*args, **kwargs)) _get_key = test else: _get_key = lambda *args, **kwargs: sanitize_memcached_key( _cache_key(*args, **kwargs)) if group: backend_kwargs = {'group': group} else: backend_kwargs = {} if backend: cache_backend = caches[backend] else: cache_backend = caches['default'] def _cached(func): func_type = _func_type(func) @wraps(func) def wrapper(*args, **kwargs): full_name(*args) # try to get the value from cache key = _get_key(wrapper._full_name, func_type, args, kwargs) value = cache_backend.get(key, **backend_kwargs) # in case of cache miss recalculate the value and put it to the cache if value is None: logger.debug("Cache MISS: %s" % key) value = func(*args, **kwargs) cache_backend.set(key, value, timeout, **backend_kwargs) logger.debug("Cache SET: %s" % key) else: logger.debug("Cache HIT: %s" % key) return value def invalidate(*args, **kwargs): """ Invalidates cache result for function called with passed arguments """ full_name(*args) if not hasattr(wrapper, '_full_name'): return key = _get_key(wrapper._full_name, 'function', args, kwargs) cache_backend.delete(key, **backend_kwargs) logger.debug("Cache DELETE: %s" % key) def force_recalc(*args, **kwargs): """ Forces a call to the function & sets the new value in the cache """ full_name(*args) key = _get_key(wrapper._full_name, func_type, args, kwargs) value = func(*args, **kwargs) cache_backend.set(key, value, timeout, **backend_kwargs) return value def full_name(*args): # full name is stored as attribute on first call if not hasattr(wrapper, '_full_name'): name, _args = _func_info(func, args) wrapper._full_name = name def require_cache(*args, **kwargs): """ Only pull from cache, do not attempt to calculate """ full_name(*args) key = _get_key(wrapper._full_name, func_type, args, kwargs) logger.debug("Require cache %s" % key) value = cache_backend.get(key, **backend_kwargs) if not value: logger.info("Could not find required cache %s" % key) raise NoCachedValueException return value def get_cache_key(*args, **kwargs): """ Returns name of cache key utilized """ full_name(*args) key = _get_key(wrapper._full_name, 'function', args, kwargs) return key wrapper.require_cache = require_cache wrapper.invalidate = invalidate wrapper.force_recalc = force_recalc wrapper.get_cache_key = get_cache_key return wrapper return _cached