140 lines
5.0 KiB
Python
140 lines
5.0 KiB
Python
"""
|
|
Low-level utilities to deal with generational caching.
|
|
|
|
Usual way to deal with the cache is this: you compute a key, and you
|
|
try to retrieve an item using it. Here, we add a single level of
|
|
indirection: you compute a first key out of a group definition and
|
|
keyworded values: it's the local key. You then fetch the value for it:
|
|
it's the so-called local version/generation. Finally, you use this version
|
|
to construct the 'real' key presumably associated with the cache item
|
|
you are ultimately after.
|
|
|
|
The strong point of this technique is that you can selectively
|
|
invalidate a custom group of keys sharing a version in a single sweep
|
|
just by bumping the version.
|
|
|
|
Given a group, view keywords, and an optional view_name. You can:
|
|
|
|
1) Compute the local key (see get_local_key()). If:
|
|
|
|
- group is None, use the hash of the view keywords as local key.
|
|
|
|
- group is a string, use a hash of it as local key.
|
|
|
|
- group is a callable, it's called with all the view_keywords (as
|
|
keywords), and it's expected to return a mapping of (key, value) pairs. The
|
|
hash of this mapping is the local key.
|
|
|
|
- group is a dictionnary specifying how to remap view keywords (from
|
|
new keywords to old keywords). The hash of the remapped keywords is the
|
|
local key.
|
|
|
|
- group is a sequence, a new dictionnary is formed from view_keywords
|
|
with only the keys listed in group. The hash of the remaining
|
|
keywords is the local key.
|
|
|
|
If a view name is passed along, it's used during hash computation.
|
|
|
|
2) Use this local key to resolve the local prefix (see get_local_prefix())
|
|
|
|
3) Invalidate the current local prefix (see bump_local_prefix()).
|
|
"""
|
|
from django.core.cache import cache
|
|
from django.conf import settings
|
|
|
|
import logging, random, hashlib
|
|
|
|
#-------------------------------------------------------------------------------
|
|
logger = logging.getLogger('groupcache') # Set up an application-level logger
|
|
|
|
if True:
|
|
import sys
|
|
logger.setLevel(logging.DEBUG)
|
|
_hdlr = logging.StreamHandler(sys.stdout)
|
|
_hdlr.setLevel(logging.DEBUG)
|
|
_hdlr.setFormatter(logging.Formatter(
|
|
'%(asctime)-15s GROUPCACHE (%(levelname)s) %(message)s'))
|
|
logger.addHandler(_hdlr)
|
|
|
|
#-------------------------------------------------------------------------------
|
|
def _join_prefixes(prefixes, terminal = ''):
|
|
return '%s%s' % (
|
|
'.'.join(pfx for pfx in prefixes if pfx is not None and len(pfx) > 0),
|
|
terminal)
|
|
|
|
def _hash_mapping(mapping, view_name = None):
|
|
ctx = hashlib.md5()
|
|
for k, v in sorted(mapping.iteritems()):
|
|
ctx.update(k)
|
|
ctx.update(v)
|
|
if view_name is not None:
|
|
ctx.update(view_name)
|
|
return ctx.hexdigest()
|
|
|
|
def _get_randval():
|
|
return random.randint(0, 10000)
|
|
|
|
def _get_key_prefix(key_prefix):
|
|
if key_prefix is None:
|
|
return getattr(settings, 'CACHE_MIDDLEWARE_KEY_PREFIX', '')
|
|
else:
|
|
return key_prefix
|
|
|
|
#-------------------------------------------------------------------------------
|
|
def get_view_name(view_func):
|
|
return '.'.join((view_func.__module__, view_func.__name__))
|
|
|
|
def get_local_key_mapping(group, view_keywords):
|
|
if group is None:
|
|
mapping = view_keywords
|
|
elif isinstance(group, basestring):
|
|
mapping = {'group': group}
|
|
elif callable(group):
|
|
mapping = group(**view_keywords)
|
|
elif isinstance(group, dict):
|
|
mapping = dict((new, view_keywords[old])
|
|
for new, old in group.iteritems())
|
|
else:
|
|
mapping = dict((k, view_keywords[k]) for k in group)
|
|
return mapping
|
|
|
|
def get_local_key_from_mapping(mapping, view_name = None, key_prefix = None):
|
|
return _join_prefixes(
|
|
(_get_key_prefix(key_prefix),
|
|
'local_key',
|
|
_hash_mapping(mapping, view_name)))
|
|
|
|
def get_local_key(group, view_keywords, view_name = None,
|
|
key_prefix = None):
|
|
return get_local_key_from_mapping(
|
|
get_local_key_mapping(group, view_keywords),
|
|
view_name, key_prefix)
|
|
|
|
return _join_prefixes(
|
|
_get_key_prefix(key_prefix), 'local_key',
|
|
_hash_mapping(get_local_key_mapping(group, view_keywords),
|
|
view_name))
|
|
|
|
def bump_local_prefix(local_key):
|
|
try:
|
|
cache.incr(local_key)
|
|
logger.debug(
|
|
'bumping local prefix for local key %s (%s)' % (
|
|
local_key, cache.get(local_key)))
|
|
except ValueError:
|
|
logger.debug(
|
|
'failed bumping local prefix for local_key %s, recreating' %
|
|
local_key)
|
|
cache.add(local_key, _get_randval())
|
|
|
|
def get_local_prefix(local_key, key_prefix = None):
|
|
version = cache.get(local_key)
|
|
#logger.debug('got local prefix for: %s (%s)' % (local_key, version))
|
|
if version is None:
|
|
version = _get_randval()
|
|
cache.set(local_key, version)
|
|
return _join_prefixes((_get_key_prefix(key_prefix), 'local', str(version)),
|
|
terminal = '.')
|
|
|
|
#-------------------------------------------------------------------------------
|