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

      5

      6

    from decorator import decorator

      7

    from paste.deploy.converters import asbool

      8

      9

    from pylons.decorators.util import get_pylons

     10

     11

    log = logging.getLogger(__name__)

     12

     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.

     20

     21

        Optional arguments:

     22

     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.

     45

     46

            .. note::

     47

                When cache_response is set to False, the cache_headers

     48

                argument is ignored as none of the response is cached.

     49

     50

        If cache_enabled is set to False in the .ini file, then cache is

     51

        disabled globally.

     52

     53

        """

     54

        if invalidate_on_startup:

     55

            starttime = time.time()

     56

        else:

     57

            starttime = None

     58

        cache_headers = set(cache_headers)

     59

     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)

     69

     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())

     75

     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

     83

     84

            self = None

     85

            if args:

     86

                self = args[0]

     87

            namespace, cache_key = create_cache_key(func, key_dict, self)

     88

     89

            if type:

     90

                b_kwargs['type'] = type

     91

     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')

     98

     99

            my_cache = cache_obj.get_cache(namespace, **b_kwargs)

    100

    101

            if expire == "never":

    102

                cache_expire = None

    103

            else:

    104

                cache_expire = expire

    105

    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

    116

    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']

    125

    126

            return response['content']

    127

        return decorator(wrapper)

    128

    129

    def create_cache_key(func, key_dict=None, self=None):

    130

        """Get a cache namespace and key used by the beaker_cache decorator.

    131

    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)

    137

    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())

    149

    150

        if not kls and self:

    151

            kls = getattr(self, '__class__', None)

    152

    153

        if kls:

    154

            return '%s.%s' % (kls.__module__, kls.__name__), cache_key

    155

        else:

    156

            return func.__module__, cache_key

    157

    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

    165