Skip to content

Instantly share code, notes, and snippets.

@ewr
Created August 4, 2011 21:57
Show Gist options
  • Save ewr/1126400 to your computer and use it in GitHub Desktop.
Save ewr/1126400 to your computer and use it in GitHub Desktop.
Object-Aware Cache
from django.core.cache import cache
DEFAULT_TIMEOUT = 0
SET_PREFIX = "obj:"
FSET_PREFIX = "sobj:"
def set(key,val,timeout = DEFAULT_TIMEOUT,objects = []):
"""
Unlike the normal django / redis cache, ContentCache takes a collection
of objects that this cache depends on. This allows the cache to be
expired automatically when expire_obj() is called for one of those
objects. ContentBase models should automatically expire their caches
on save.
objects array can include stringified keys or objects that support a
obj_key() method (such as ContentBase models).
from contentbase import cache
cache.set(key,val,0,['news/story:21563',segmentObject])
"""
# first, set the object
cache.set(key,val,timeout)
# Make the full key
fullkey = cache.make_key(key)
# expire this key from existing sets
fset = cache._client.smembers( "%s%s"%(FSET_PREFIX,fullkey) )
if fset:
for skey in fset:
cache._client.srem(skey,fullkey)
# now add this key to each object's set
keys = []
for obj in objects:
if obj == None:
next
elif hasattr(obj,'obj_key'):
cache._client.sadd(SET_PREFIX+obj.obj_key(),fullkey)
keys.append(SET_PREFIX+obj.obj_key())
else:
cache._client.sadd(SET_PREFIX+obj,fullkey)
keys.append(SET_PREFIX+obj)
# create our forward mapping
cache._client.delete("%s%s"%(FSET_PREFIX,fullkey))
cache._client.sadd("%s%s"%(FSET_PREFIX,fullkey),keys)
#----------
def get(key, default=None, version=None):
return cache.get(key,default,version)
#----------
def expire_obj(obj):
"""
Expire all caches that depend on a given object.
from contentbase import cache
cache.expire_obj(obj)
obj can be a string or an object that supports a obj_key() method.
"""
key = obj
if hasattr(obj,'obj_key'):
key = obj.obj_key()
mem = cache._client.smembers( SET_PREFIX+key )
if mem:
cache._client.delete(*mem)
#----------
def expire_from_signal(sender, **kwargs):
expire_obj(kwargs['instance'])
from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
from django.template import resolve_variable
from mercer.contentbase import cache
from django.utils.encoding import force_unicode
from django.utils.http import urlquote
register = Library()
class CacheNode(Node):
def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
self.nodelist = nodelist
self.expire_time_var = Variable(expire_time_var)
self.fragment_name = fragment_name
self.vary_on = vary_on
def render(self, context):
try:
expire_time = self.expire_time_var.resolve(context)
except VariableDoesNotExist:
raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.expire_time_var.var)
try:
expire_time = int(expire_time)
except (ValueError, TypeError):
raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
# run nodelist looking for content objects
context.REGISTERED_OBJECTS = []
# Build a unicode key for this fragment and all vary-on's.
cache_key = self.fragment_name + ':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on])
value = cache.get(cache_key)
if value is None:
value = self.nodelist.render(context)
cache.set(cache_key, value, expire_time, context.REGISTERED_OBJECTS)
return value
#----------
class RegisterNode(Node):
def __init__(self, *args):
assert len(args)
self.args = list(args)
def render(self, context):
obj = resolve_variable(self.args[0],context)
context.REGISTERED_OBJECTS.append(obj)
return ''
#----------
def do_cache(parser, token):
nodelist = parser.parse(('endcache',))
parser.delete_first_token()
tokens = token.contents.split()
if len(tokens) < 3:
raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
#----------
def register_content(parser,token):
"""
Registers a ContentBase object to the current cache.
Usage:
{% register_content <content obj> [<scheme>] %}
"""
return RegisterNode(*token.contents.split()[1:])
register.tag('cache_content', do_cache)
register.tag('register_content', register_content)
{% cache_content 0 hsection:national %}
<h3>National News</h3>
<ul class="related-links">
{% for item in national|slice:":3" %}
{% register_content item %}
<li><a class="news-link" href="{{ item.get_absolute_url }}">{{ item.headline }}</a>
{% endfor %}
</ul>
{% endcache %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment