diff --git a/diskcache/djangocache.py b/diskcache/djangocache.py index 329b966..01ca56c 100644 --- a/diskcache/djangocache.py +++ b/diskcache/djangocache.py @@ -10,10 +10,10 @@ DEFAULT_TIMEOUT = 300 from .core import ENOVAL, args_to_key, full_name -from .fanout import FanoutCache +from .fanout import FanoutCache, FanoutMixin -class DjangoCache(BaseCache): +class DjangoCache(BaseCache, FanoutMixin): "Django-compatible disk and file backed cache." def __init__(self, directory, params): """Initialize DjangoCache instance. @@ -29,42 +29,6 @@ def __init__(self, directory, params): self._cache = FanoutCache(directory, shards, timeout, **options) - @property - def directory(self): - """Cache directory.""" - return self._cache.directory - - - def cache(self, name): - """Return Cache with given `name` in subdirectory. - - :param str name: subdirectory name for Cache - :return: Cache with given name - - """ - return self._cache.cache(name) - - - def deque(self, name): - """Return Deque with given `name` in subdirectory. - - :param str name: subdirectory name for Deque - :return: Deque with given name - - """ - return self._cache.deque(name) - - - def index(self, name): - """Return Index with given `name` in subdirectory. - - :param str name: subdirectory name for Index - :return: Index with given name - - """ - return self._cache.index(name) - - def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None, read=False, tag=None, retry=True): """Set a value in the cache if the key does not already exist. If diff --git a/diskcache/fanout.py b/diskcache/fanout.py index a579a17..3fefd61 100644 --- a/diskcache/fanout.py +++ b/diskcache/fanout.py @@ -651,3 +651,40 @@ def index(self, name): FanoutCache.memoize = Cache.memoize + + +class FanoutMixin: + @property + def directory(self): + """Cache directory.""" + return self._cache.directory + + + def cache(self, name): + """Return Cache with given `name` in subdirectory. + + :param str name: subdirectory name for Cache + :return: Cache with given name + + """ + return self._cache.cache(name) + + + def deque(self, name): + """Return Deque with given `name` in subdirectory. + + :param str name: subdirectory name for Deque + :return: Deque with given name + + """ + return self._cache.deque(name) + + + def index(self, name): + """Return Index with given `name` in subdirectory. + + :param str name: subdirectory name for Index + :return: Index with given name + + """ + return self._cache.index(name) diff --git a/diskcache/flaskcache.py b/diskcache/flaskcache.py new file mode 100644 index 0000000..4290a64 --- /dev/null +++ b/diskcache/flaskcache.py @@ -0,0 +1,180 @@ +"Flask-Caching compatible disk and file backed cache." + +from flask_caching.backends.cache import BaseCache + +from .fanout import FanoutCache, FanoutMixin + + +class FlaskCache(BaseCache, FanoutMixin): + def __init__(self, directory, params=None, default_timeout=300): + """Initialize FlaskCache instance. + + :param str directory: cache directory + :param dict params: cache parameters + + """ + super().__init__(default_timeout=default_timeout) + params = {} if params is None else params + shards = params.get('SHARDS', 8) + timeout = params.get('DATABASE_TIMEOUT', 0.010) + options = params.get('OPTIONS', {}) + self._cache = FanoutCache(directory, shards, timeout, **options) + + +# TODO: Complete the methods below and add tests. + + + def get(self, key): + """Look up key in the cache and return the value for it. + + :param key: the key to be looked up. + :returns: The value if it exists and is readable, else ``None``. + """ + return None + + def delete(self, key): + """Delete `key` from the cache. + + :param key: the key to delete. + :returns: Whether the key existed and has been deleted. + :rtype: boolean + """ + return True + + def get_many(self, *keys): + """Returns a list of values for the given keys. + For each key an item in the list is created:: + + foo, bar = cache.get_many("foo", "bar") + + Has the same error handling as :meth:`get`. + + :param keys: The function accepts multiple keys as positional + arguments. + """ + return [self.get(k) for k in keys] + + def get_dict(self, *keys): + """Like :meth:`get_many` but return a dict:: + + d = cache.get_dict("foo", "bar") + foo = d["foo"] + bar = d["bar"] + + :param keys: The function accepts multiple keys as positional + arguments. + """ + return dict(zip(keys, self.get_many(*keys))) + + def set(self, key, value, timeout=None): + """Add a new key/value to the cache (overwrites value, if key already + exists in the cache). + + :param key: the key to set + :param value: the value for the key + :param timeout: the cache timeout for the key in seconds (if not + specified, it uses the default timeout). A timeout of + 0 indicates that the cache never expires. + :returns: ``True`` if key has been updated, ``False`` for backend + errors. Pickling errors, however, will raise a subclass of + ``pickle.PickleError``. + :rtype: boolean + """ + return True + + def add(self, key, value, timeout=None): + """Works like :meth:`set` but does not overwrite the values of already + existing keys. + + :param key: the key to set + :param value: the value for the key + :param timeout: the cache timeout for the key in seconds (if not + specified, it uses the default timeout). A timeout of + 0 idicates that the cache never expires. + :returns: Same as :meth:`set`, but also ``False`` for already + existing keys. + :rtype: boolean + """ + return True + + def set_many(self, mapping, timeout=None): + """Sets multiple keys and values from a mapping. + + :param mapping: a mapping with the keys/values to set. + :param timeout: the cache timeout for the key in seconds (if not + specified, it uses the default timeout). A timeout of + 0 idicates that the cache never expires. + :returns: Whether all given keys have been set. + :rtype: boolean + """ + rv = True + for key, value in iteritems_wrapper(mapping): + if not self.set(key, value, timeout): + rv = False + return rv + + def delete_many(self, *keys): + """Deletes multiple keys at once. + + :param keys: The function accepts multiple keys as positional + arguments. + :returns: Whether all given keys have been deleted. + :rtype: boolean + """ + if self.ignore_errors: + return all([self.delete(key) for key in keys]) + return all(self.delete(key) for key in keys) + + def has(self, key): + """Checks if a key exists in the cache without returning it. This is a + cheap operation that bypasses loading the actual data on the backend. + + This method is optional and may not be implemented on all caches. + + :param key: the key to check + """ + raise NotImplementedError( + "%s doesn't have an efficient implementation of `has`. That " + "means it is impossible to check whether a key exists without " + "fully loading the key's data. Consider using `self.get` " + "explicitly if you don't care about performance." + ) + + def clear(self): + """Clears the cache. Keep in mind that not all caches support + completely clearing the cache. + + :returns: Whether the cache has been cleared. + :rtype: boolean + """ + return True + + def inc(self, key, delta=1): + """Increments the value of a key by `delta`. If the key does + not yet exist it is initialized with `delta`. + + For supporting caches this is an atomic operation. + + :param key: the key to increment. + :param delta: the delta to add. + :returns: The new value or ``None`` for backend errors. + """ + value = (self.get(key) or 0) + delta + return value if self.set(key, value) else None + + def dec(self, key, delta=1): + """Decrements the value of a key by `delta`. If the key does + not yet exist it is initialized with `-delta`. + + For supporting caches this is an atomic operation. + + :param key: the key to increment. + :param delta: the delta to subtract. + :returns: The new value or `None` for backend errors. + """ + value = (self.get(key) or 0) - delta + return value if self.set(key, value) else None + + +def make_cache(app, config, args, kwargs): + return FlaskCache(*args, **kwargs)