Source

django-cache-utils / cache_utils / decorators.py

Full commit
#coding: utf-8
from django.core.cache import cache
from django.utils.functional import wraps
from cache_utils.utils import _cache_key, _func_info, _func_type, sanitize_memcached_key

def cached(timeout, group=None, omit=0):
    """ Caching decorator. Can be applied to function, method or classmethod.
    Supports bulk cache invalidation and invalidation for exact parameter
    set. Cache keys are human-readable because they are constructed from
    callable's full name and arguments and then sanitized to make
    memcached happy.

    It can be used with or without group_backend. Without group_backend
    bulk invalidation is not supported.

    Wrapped callable gets `invalidate` methods. Call `invalidate` with
    same arguments as function and the result for these arguments will be
    invalidated.
    """

    if group:
        backend_kwargs = {'group': group}
        get_key = _cache_key
    else:
        backend_kwargs = {}
        def get_key(*args, **kwargs):
            return sanitize_memcached_key(_cache_key(*args, **kwargs))

    def _cached(func):

        func_type = _func_type(func)

        @wraps(func)
        def wrapper(*args, **kwargs):

            # full name is stored as attribute on first call
            if not hasattr(wrapper, '_full_name'):
                name, _args = _func_info(func, args)
                wrapper._full_name = name

            # sometimes we do not want to include first N args into cache key
            # for example, request or context
            n_args, n_kwargs = args, kwargs
            if omit:
                if type(omit) == int:
                    n_args = n_args[omit:]
                if type(omit) == str:
                    del n_kwargs[omit]

            # try to get the value from cache
            key = get_key(wrapper._full_name, func_type, n_args, n_kwargs)

            try:
                value = cache.get(key, **backend_kwargs)
            except:
                from django.core.mail import send_mail
                from django.contrib.gis.geos import Point
                import traceback
                msg = traceback.format_exc()
                msg += "\nkey: "+key
                if n_args and isinstance(n_args[0], Point):
                    msg += "\npoint: "+n_args[0].json
                send_mail('Caching fail', msg, ' info@goodbed.com', ['admin@goodbed.com'], fail_silently=False)

            # in case of cache miss recalculate the value and put it to the cache
            if value is None:
                value = func(*args, **kwargs)
                try:
                    cache.set(key, value, timeout, **backend_kwargs)
                except:
                    from django.core.mail import send_mail
                    from django.contrib.gis.geos import Point
                    import traceback
                    msg = traceback.format_exc()
                    msg += "\nkey: "+key
                    if n_args and isinstance(n_args[0], Point):
                        msg += "\npoint: "+n_args[0].json
                    send_mail('Caching fail', msg, ' info@goodbed.com', ['admin@goodbed.com'], fail_silently=False)

            return value

        def invalidate(*args, **kwargs):
            ''' invalidates cache result for function called with passed arguments '''
            if not hasattr(wrapper, '_full_name'):
                return
            key = get_key(wrapper._full_name, 'function', args, kwargs)
            cache.delete(key, **backend_kwargs)

        wrapper.invalidate = invalidate
        return wrapper
    return _cached