2010-06-20 22:50:13 -07:00
|
|
|
try:
|
|
|
|
import cPickle as pickle
|
|
|
|
except ImportError:
|
|
|
|
import pickle
|
2010-10-07 17:45:09 -07:00
|
|
|
from django.core.cache import cache
|
2010-06-20 22:50:13 -07:00
|
|
|
from django.utils.hashcompat import md5_constructor
|
|
|
|
|
2010-10-13 16:11:28 -07:00
|
|
|
CACHE_TIMEOUT = 1800
|
2011-04-07 14:52:52 -07:00
|
|
|
INVALIDATE_TIMEOUT = 10
|
|
|
|
CACHE_LATEST_PREFIX = 'cache_latest_'
|
2010-10-13 16:11:28 -07:00
|
|
|
|
2010-10-13 15:55:28 -07:00
|
|
|
def cache_function_key(func, args, kwargs):
|
|
|
|
raw = [func.__name__, func.__module__, args, kwargs]
|
|
|
|
pickled = pickle.dumps(raw, protocol=pickle.HIGHEST_PROTOCOL)
|
|
|
|
key = md5_constructor(pickled).hexdigest()
|
|
|
|
return 'cache_function.' + func.__name__ + '.' + key
|
|
|
|
|
2010-06-20 22:50:13 -07:00
|
|
|
def cache_function(length):
|
|
|
|
"""
|
|
|
|
A variant of the snippet posted by Jeff Wheeler at
|
|
|
|
http://www.djangosnippets.org/snippets/109/
|
|
|
|
|
|
|
|
Caches a function, using the function and its arguments as the key, and the
|
|
|
|
return value as the value saved. It passes all arguments on to the
|
|
|
|
function, as it should.
|
|
|
|
|
|
|
|
The decorator itself takes a length argument, which is the number of
|
|
|
|
seconds the cache will keep the result around.
|
|
|
|
"""
|
|
|
|
def decorator(func):
|
|
|
|
def inner_func(*args, **kwargs):
|
2010-10-13 15:55:28 -07:00
|
|
|
key = cache_function_key(func, args, kwargs)
|
2010-06-20 22:50:13 -07:00
|
|
|
value = cache.get(key)
|
|
|
|
if value is not None:
|
|
|
|
return value
|
|
|
|
else:
|
|
|
|
result = func(*args, **kwargs)
|
|
|
|
cache.set(key, result, length)
|
|
|
|
return result
|
|
|
|
return inner_func
|
|
|
|
return decorator
|
|
|
|
|
2010-10-13 15:55:28 -07:00
|
|
|
def clear_cache_function(func, args, kwargs):
|
|
|
|
key = cache_function_key(func, args, kwargs)
|
|
|
|
cache.delete(key)
|
|
|
|
|
2010-10-13 16:11:28 -07:00
|
|
|
# utility to make a pair of django choices
|
2008-10-07 11:53:56 -07:00
|
|
|
make_choice = lambda l: [(str(m), str(m)) for m in l]
|
2010-10-13 15:55:28 -07:00
|
|
|
|
2010-10-13 16:11:28 -07:00
|
|
|
# These are in here because we would be jumping around in some import circles
|
|
|
|
# and hoops otherwise. The only thing currently using these keys is the feed
|
|
|
|
# caching stuff.
|
|
|
|
|
2011-04-07 14:52:52 -07:00
|
|
|
def refresh_latest(**kwargs):
|
|
|
|
'''A post_save signal handler to clear out the cached latest value for a
|
|
|
|
given model.'''
|
|
|
|
cache_key = CACHE_LATEST_PREFIX + kwargs['sender'].__name__
|
2010-10-13 16:11:28 -07:00
|
|
|
# We could delete the value, but that could open a race condition
|
|
|
|
# where the new data wouldn't have been committed yet by the calling
|
|
|
|
# thread. Instead, explicitly set it to None for a short amount of time.
|
|
|
|
# Hopefully by the time it expires we will have committed, and the cache
|
|
|
|
# will be valid again. See "Scaling Django" by Mike Malone, slide 30.
|
2011-04-07 14:52:52 -07:00
|
|
|
cache.set(cache_key, None, INVALIDATE_TIMEOUT)
|
2010-10-13 16:11:28 -07:00
|
|
|
|
2011-04-07 14:52:52 -07:00
|
|
|
def retrieve_latest(sender):
|
|
|
|
# we could break this down based on the request url, but it would probably
|
|
|
|
# cost us more in query time to do so.
|
|
|
|
cache_key = CACHE_LATEST_PREFIX + sender.__name__
|
|
|
|
latest = cache.get(cache_key)
|
|
|
|
if latest:
|
|
|
|
return latest
|
|
|
|
try:
|
|
|
|
latest_by = sender._meta.get_latest_by
|
|
|
|
latest = sender.objects.values(latest_by).latest()[latest_by]
|
|
|
|
# Using add means "don't overwrite anything in there". What could be in
|
|
|
|
# there is an explicit None value that our refresh signal set, which
|
|
|
|
# means we want to avoid race condition possibilities for a bit.
|
|
|
|
cache.add(cache_key, latest, CACHE_TIMEOUT)
|
|
|
|
return latest
|
|
|
|
except sender.DoesNotExist:
|
|
|
|
pass
|
|
|
|
return None
|
2010-10-13 16:11:28 -07:00
|
|
|
|
2010-10-13 15:55:28 -07:00
|
|
|
# vim: set ts=4 sw=4 et:
|