django-groupcache/groupcache/core.py

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 = '.')
#-------------------------------------------------------------------------------