django-groupcache/groupcache/decorators.py

170 lines
6.5 KiB
Python

from django.db.models.signals import post_save, post_delete
from django.utils.decorators import decorator_from_middleware_with_args
from groupcache.middleware import CacheWithLocalVersionMiddleware
from groupcache.core import get_view_name, \
get_local_key_mapping, get_local_key_from_mapping, bump_local_prefix
import inspect, functools, logging
#-------------------------------------------------------------------------------
def to_fields(*local_keywords):
'''
Decorator to simplify introspection of to_fields callables, used by
cache_page_for_model below:
@to_fields("square", "inverse")
def compute_fields(value = None):
value = int(value)
return {"square": value*value, "inverse": -value}
>>> print compute_fields.local_keywords
("square", "inverse")
'''
def to_fields_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper.local_keywords = local_keywords
return wrapper
return to_fields_decorator
#-------------------------------------------------------------------------------
def cache_versionned_page(group = None, cache_timeout = None,
vary_on_view = True):
'''
This is the most generic decorator in groupcache. Use it to
implement whatever versionning policy you wish to have.
'''
def cache_versionned_page_decorator(view_func):
return decorator_from_middleware_with_args(
CacheWithLocalVersionMiddleware)(
cache_timeout = cache_timeout,
group = group,
view_func = view_func,
vary_on_view = True)(view_func)
return cache_versionned_page_decorator
def cache_tagged_page(tag, cache_timeout = None):
'''
Decorator for extremely basic caching based on string labeling.
from groupcache.decorators import cache_tagged_page
@cache_tagged_page('some tag')
def my_view(request, ...):
...
Then, you can do from anywhere:
from groupcache.utils import uncache_tag
uncache_tag('some tag')
An every cached page marked with the tag will get invalidated.
'''
assert(isinstance(tag, basestring))
def cache_tagged_page_decorator(view_func):
return decorator_from_middleware_with_args(
CacheWithLocalVersionMiddleware)(
cache_timeout = cache_timeout,
group = tag,
view_func = view_func,
vary_on_view = False)(view_func)
return cache_tagged_page_decorator
#-------------------------------------------------------------------------------
def cache_page_against_model(model, cache_timeout = None, to_fields = None):
'''
If you got some one-to-one relationship between a view and a model,
you can apply this decorator to handle automatic caching invalidation
when the entity used in a given call to the view changes.
In the most common scenario, it\'s used like this:
from django.http.shortcuts import get_object_or_404
from groupcache.decorators import cache_page_for_model
@cache_page_for_model(MyModel):
def my_view(request, pk):
obj = get_object_or_404(MyModel, pk=pk)
...
This functionnality is the reason django-groupcache was written in
the first place.
'''
def cache_page_against_model_decorator(func):
# Retrieve django-style view name from view func
view_name = get_view_name(func)
# Map the view keywords to model fields using the to_fields group
if callable(to_fields):
raise ValueError(
'to_fields is a specialized group that cannot be a callable')
if to_fields is None:
# If to_fields is not given, we have no choice but getting the
# keywords by inspecting the view. Convenient, but less than
# ideal, as it means that *args, **kwargs style views signature
# will not get properly detected -- this is what might happens
# with views having cascading decorators.
modelfields = inspect.getargspec(func)[0][1:]
else:
# Other sequences and mappings only needs to be saved, as it's
# assumed there is a perfect relationship to model fields.
modelfields = list(to_fields)
if len(modelfields) == 0:
raise ValueError(
'there is no field left to match model entities against: '
'if all you are interested in is invalidating all pages '
'associated with a given view whenever an entity '
'from a given model is changed, use '
'the cache_page_for_models() decorator instead')
def invalidate(sender, instance, signal, *args, **kwargs):
# Construct the mapping from model instance
mapping = dict((field, getattr(instance, field))
for field in modelfields)
# Get the corresponding local key, and bump it.
local_key = get_local_key_from_mapping(mapping, view_name)
print 'local key', local_key
bump_local_prefix(local_key)
print 'Invalidating:', instance, signal, args, kwargs
post_save.connect(invalidate, sender = model, weak = False,
dispatch_uid = view_name)
return decorator_from_middleware_with_args(
CacheWithLocalVersionMiddleware)(
cache_timeout = cache_timeout,
group = to_fields,
view_func = func,
vary_on_view = True)(func)
return cache_page_against_model_decorator
#-------------------------------------------------------------------------------
def cache_page_against_models(*models, **kwargs):
def cache_page_against_models_decorator(func):
view_name = get_view_name(func)
def invalidate(sender, instance, signal, *args, **kwargs):
bump_local_prefix(get_local_key_from_mapping({}, view_name))
for model in models:
for sig in (post_save, post_delete):
sig.connect(invalidate, sender = model, weak = False,
dispatch_uid = view_name)
return decorator_from_middleware_with_args(
CacheWithLocalVersionMiddleware)(
cache_timeout = kwargs.get('cache_timeout'),
group = (),
view_func = func,
vary_on_view = True)(func)
return cache_page_against_models_decorator
#-------------------------------------------------------------------------------