123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- "Base Cache class."
- import time
- import warnings
- from django.core.exceptions import ImproperlyConfigured
- from django.utils.module_loading import import_string
- class InvalidCacheBackendError(ImproperlyConfigured):
- pass
- class CacheKeyWarning(RuntimeWarning):
- pass
- # Stub class to ensure not passing in a `timeout` argument results in
- # the default timeout
- DEFAULT_TIMEOUT = object()
- # Memcached does not accept keys longer than this.
- MEMCACHE_MAX_KEY_LENGTH = 250
- def default_key_func(key, key_prefix, version):
- """
- Default function to generate keys.
- Construct the key used by all other methods. By default, prepend
- the `key_prefix'. KEY_FUNCTION can be used to specify an alternate
- function with custom key making behavior.
- """
- return '%s:%s:%s' % (key_prefix, version, key)
- def get_key_func(key_func):
- """
- Function to decide which key function to use.
- Default to ``default_key_func``.
- """
- if key_func is not None:
- if callable(key_func):
- return key_func
- else:
- return import_string(key_func)
- return default_key_func
- class BaseCache:
- def __init__(self, params):
- timeout = params.get('timeout', params.get('TIMEOUT', 300))
- if timeout is not None:
- try:
- timeout = int(timeout)
- except (ValueError, TypeError):
- timeout = 300
- self.default_timeout = timeout
- options = params.get('OPTIONS', {})
- max_entries = params.get('max_entries', options.get('MAX_ENTRIES', 300))
- try:
- self._max_entries = int(max_entries)
- except (ValueError, TypeError):
- self._max_entries = 300
- cull_frequency = params.get('cull_frequency', options.get('CULL_FREQUENCY', 3))
- try:
- self._cull_frequency = int(cull_frequency)
- except (ValueError, TypeError):
- self._cull_frequency = 3
- self.key_prefix = params.get('KEY_PREFIX', '')
- self.version = params.get('VERSION', 1)
- self.key_func = get_key_func(params.get('KEY_FUNCTION'))
- def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
- """
- Return the timeout value usable by this backend based upon the provided
- timeout.
- """
- if timeout == DEFAULT_TIMEOUT:
- timeout = self.default_timeout
- elif timeout == 0:
- # ticket 21147 - avoid time.time() related precision issues
- timeout = -1
- return None if timeout is None else time.time() + timeout
- def make_key(self, key, version=None):
- """
- Construct the key used by all other methods. By default, use the
- key_func to generate a key (which, by default, prepends the
- `key_prefix' and 'version'). A different key function can be provided
- at the time of cache construction; alternatively, you can subclass the
- cache backend to provide custom key making behavior.
- """
- if version is None:
- version = self.version
- new_key = self.key_func(key, self.key_prefix, version)
- return new_key
- def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
- """
- Set a value in the cache if the key does not already exist. If
- timeout is given, use that timeout for the key; otherwise use the
- default cache timeout.
- Return True if the value was stored, False otherwise.
- """
- raise NotImplementedError('subclasses of BaseCache must provide an add() method')
- def get(self, key, default=None, version=None):
- """
- Fetch a given key from the cache. If the key does not exist, return
- default, which itself defaults to None.
- """
- raise NotImplementedError('subclasses of BaseCache must provide a get() method')
- def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
- """
- Set a value in the cache. If timeout is given, use that timeout for the
- key; otherwise use the default cache timeout.
- """
- raise NotImplementedError('subclasses of BaseCache must provide a set() method')
- def delete(self, key, version=None):
- """
- Delete a key from the cache, failing silently.
- """
- raise NotImplementedError('subclasses of BaseCache must provide a delete() method')
- def get_many(self, keys, version=None):
- """
- Fetch a bunch of keys from the cache. For certain backends (memcached,
- pgsql) this can be *much* faster when fetching multiple values.
- Return a dict mapping each key in keys to its value. If the given
- key is missing, it will be missing from the response dict.
- """
- d = {}
- for k in keys:
- val = self.get(k, version=version)
- if val is not None:
- d[k] = val
- return d
- def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
- """
- Fetch a given key from the cache. If the key does not exist,
- add the key and set it to the default value. The default value can
- also be any callable. If timeout is given, use that timeout for the
- key; otherwise use the default cache timeout.
- Return the value of the key stored or retrieved.
- """
- val = self.get(key, version=version)
- if val is None and default is not None:
- if callable(default):
- default = default()
- self.add(key, default, timeout=timeout, version=version)
- # Fetch the value again to avoid a race condition if another caller
- # added a value between the first get() and the add() above.
- return self.get(key, default, version=version)
- return val
- def has_key(self, key, version=None):
- """
- Return True if the key is in the cache and has not expired.
- """
- return self.get(key, version=version) is not None
- def incr(self, key, delta=1, version=None):
- """
- Add delta to value in the cache. If the key does not exist, raise a
- ValueError exception.
- """
- value = self.get(key, version=version)
- if value is None:
- raise ValueError("Key '%s' not found" % key)
- new_value = value + delta
- self.set(key, new_value, version=version)
- return new_value
- def decr(self, key, delta=1, version=None):
- """
- Subtract delta from value in the cache. If the key does not exist, raise
- a ValueError exception.
- """
- return self.incr(key, -delta, version=version)
- def __contains__(self, key):
- """
- Return True if the key is in the cache and has not expired.
- """
- # This is a separate method, rather than just a copy of has_key(),
- # so that it always has the same functionality as has_key(), even
- # if a subclass overrides it.
- return self.has_key(key)
- def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
- """
- Set a bunch of values in the cache at once from a dict of key/value
- pairs. For certain backends (memcached), this is much more efficient
- than calling set() multiple times.
- If timeout is given, use that timeout for the key; otherwise use the
- default cache timeout.
- On backends that support it, return a list of keys that failed
- insertion, or an empty list if all keys were inserted successfully.
- """
- for key, value in data.items():
- self.set(key, value, timeout=timeout, version=version)
- return []
- def delete_many(self, keys, version=None):
- """
- Delete a bunch of values in the cache at once. For certain backends
- (memcached), this is much more efficient than calling delete() multiple
- times.
- """
- for key in keys:
- self.delete(key, version=version)
- def clear(self):
- """Remove *all* values from the cache at once."""
- raise NotImplementedError('subclasses of BaseCache must provide a clear() method')
- def validate_key(self, key):
- """
- Warn about keys that would not be portable to the memcached
- backend. This encourages (but does not force) writing backend-portable
- cache code.
- """
- if len(key) > MEMCACHE_MAX_KEY_LENGTH:
- warnings.warn(
- 'Cache key will cause errors if used with memcached: %r '
- '(longer than %s)' % (key, MEMCACHE_MAX_KEY_LENGTH), CacheKeyWarning
- )
- for char in key:
- if ord(char) < 33 or ord(char) == 127:
- warnings.warn(
- 'Cache key contains characters that will cause errors if '
- 'used with memcached: %r' % key, CacheKeyWarning
- )
- break
- def incr_version(self, key, delta=1, version=None):
- """
- Add delta to the cache version for the supplied key. Return the new
- version.
- """
- if version is None:
- version = self.version
- value = self.get(key, version=version)
- if value is None:
- raise ValueError("Key '%s' not found" % key)
- self.set(key, value, version=version + delta)
- self.delete(key, version=version)
- return version + delta
- def decr_version(self, key, delta=1, version=None):
- """
- Subtract delta from the cache version for the supplied key. Return the
- new version.
- """
- return self.incr_version(key, -delta, version)
- def close(self, **kwargs):
- """Close the cache connection"""
- pass
|