pylons.decorators.cache
Covered: 138 lines
Missed: 0 lines
Skipped 27 lines
Percent: 100 %
  1
"""Caching decorator"""
  2
import inspect
  3
import logging
  4
import time
  6
from decorator import decorator
  7
from paste.deploy.converters import asbool
  9
from pylons.decorators.util import get_pylons
 11
log = logging.getLogger(__name__)
 13
def beaker_cache(key="cache_default", expire="never", type=None,
 14
                 query_args=False,
 15
                 cache_headers=('content-type', 'content-length'),
 16
                 invalidate_on_startup=False, 
 17
                 cache_response=True, **b_kwargs):
 18
    """Cache decorator utilizing Beaker. Caches action or other
 19
    function that returns a pickle-able object as a result.
 21
    Optional arguments:
 23
    ``key``
 24
        None - No variable key, uses function name as key
 25
        "cache_default" - Uses all function arguments as the key
 26
        string - Use kwargs[key] as key
 27
        list - Use [kwargs[k] for k in list] as key
 28
    ``expire``
 29
        Time in seconds before cache expires, or the string "never". 
 30
        Defaults to "never"
 31
    ``type``
 32
        Type of cache to use: dbm, memory, file, memcached, or None for
 33
        Beaker's default
 34
    ``query_args``
 35
        Uses the query arguments as the key, defaults to False
 36
    ``cache_headers``
 37
        A tuple of header names indicating response headers that
 38
        will also be cached.
 39
    ``invalidate_on_startup``
 40
        If True, the cache will be invalidated each time the application
 41
        starts or is restarted.
 42
    ``cache_response``
 43
        Determines whether the response at the time beaker_cache is used
 44
        should be cached or not, defaults to True.
 46
        .. note::
 47
            When cache_response is set to False, the cache_headers
 48
            argument is ignored as none of the response is cached.
 50
    If cache_enabled is set to False in the .ini file, then cache is
 51
    disabled globally.
 53
    """
 54
    if invalidate_on_startup:
 55
        starttime = time.time()
 56
    else:
 57
        starttime = None
 58
    cache_headers = set(cache_headers)
 60
    def wrapper(func, *args, **kwargs):
 61
        """Decorator wrapper"""
 62
        pylons = get_pylons(args)
 63
        log.debug("Wrapped with key: %s, expire: %s, type: %s, query_args: %s",
 64
                  key, expire, type, query_args)
 65
        enabled = pylons.config.get("cache_enabled", "True")
 66
        if not asbool(enabled):
 67
            log.debug("Caching disabled, skipping cache lookup")
 68
            return func(*args, **kwargs)
 70
        if key:
 71
            key_dict = kwargs.copy()
 72
            key_dict.update(_make_dict_from_args(func, args))
 73
            if query_args:
 74
                key_dict.update(pylons.request.GET.mixed())
 76
            if key != "cache_default":
 77
                if isinstance(key, list):
 78
                    key_dict = dict((k, key_dict[k]) for k in key)
 79
                else:
 80
                    key_dict = {key: key_dict[key]}
 81
        else:
 82
            key_dict = None
 84
        self = None
 85
        if args:
 86
            self = args[0]
 87
        namespace, cache_key = create_cache_key(func, key_dict, self)
 89
        if type:
 90
            b_kwargs['type'] = type
 92
        cache_obj = getattr(pylons.app_globals, 'cache', None)
 93
        if not cache_obj:
 94
            cache_obj = getattr(pylons, 'cache', None)
 95
        if not cache_obj:
 96
            raise Exception('No CacheMiddleware or cache object on '
 97
                            ' app_globals was found')
 99
        my_cache = cache_obj.get_cache(namespace, **b_kwargs)
101
        if expire == "never":
102
            cache_expire = None
103
        else:
104
            cache_expire = expire
106
        def create_func():
107
            log.debug("Creating new cache copy with key: %s, type: %s",
108
                      cache_key, type)
109
            result = func(*args, **kwargs)
110
            glob_response = pylons.response
111
            headers = glob_response.headerlist
112
            status = glob_response.status
113
            full_response = dict(headers=headers, status=status,
114
                                 cookies=None, content=result)
115
            return full_response
117
        response = my_cache.get_value(cache_key, createfunc=create_func,
118
                                      expiretime=cache_expire,
119
                                      starttime=starttime)
120
        if cache_response:
121
            glob_response = pylons.response
122
            glob_response.headerlist = [header for header in response['headers']
123
                                        if header[0].lower() in cache_headers]
124
            glob_response.status = response['status']
126
        return response['content']
127
    return decorator(wrapper)
129
def create_cache_key(func, key_dict=None, self=None):
130
    """Get a cache namespace and key used by the beaker_cache decorator.
132
    Example::
133
        from pylons import cache
134
        from pylons.decorators.cache import create_cache_key
135
        namespace, key = create_cache_key(MyController.some_method)
136
        cache.get_cache(namespace).remove(key)
138
    """
139
    kls = None
140
    if hasattr(func, 'im_func'):
141
        kls = func.im_class
142
        func = func.im_func
143
        cache_key = func.__name__
144
    else:
145
        cache_key = func.__name__
146
    if key_dict:
147
        cache_key += " " + " ".join("%s=%s" % (k, v)
148
                                    for k, v in key_dict.iteritems())
150
    if not kls and self:
151
        kls = getattr(self, '__class__', None)
153
    if kls:
154
        return '%s.%s' % (kls.__module__, kls.__name__), cache_key
155
    else:
156
        return func.__module__, cache_key
158
def _make_dict_from_args(func, args):
159
    """Inspects function for name of args"""
160
    args_keys = {}
161
    for i, arg in enumerate(inspect.getargspec(func)[0]):
162
        if arg != "self":
163
            args_keys[arg] = args[i]
164
    return args_keys