cache_utils.py 3.2 KB
Newer Older
1 2 3
"""
Utilities related to caching.
"""
4
import collections
5
import cPickle as pickle
6
import functools
7
import zlib
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
from xblock.core import XBlock


def memoize_in_request_cache(request_cache_attr_name=None):
    """
    Memoize a method call's results in the request_cache if there's one. Creates the cache key by
    joining the unicode of all the args with &; so, if your arg may use the default &, it may
    have false hits.

    Arguments:
        request_cache_attr_name - The name of the field or property in this method's containing
         class that stores the request_cache.
    """
    def _decorator(func):
        """Outer method decorator."""
        @functools.wraps(func)
        def _wrapper(self, *args, **kwargs):
            """
            Wraps a method to memoize results.
            """
            request_cache = getattr(self, request_cache_attr_name, None)
            if request_cache:
                cache_key = '&'.join([hashvalue(arg) for arg in args])
                if cache_key in request_cache.data.setdefault(func.__name__, {}):
                    return request_cache.data[func.__name__][cache_key]

                result = func(self, *args, **kwargs)

                request_cache.data[func.__name__][cache_key] = result
                return result
            else:
                return func(self, *args, **kwargs)
        return _wrapper
    return _decorator


44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
class memoized(object):  # pylint: disable=invalid-name
    """
    Decorator. Caches a function's return value each time it is called.
    If called later with the same arguments, the cached value is returned
    (not reevaluated).
    https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

    WARNING: Only use this memoized decorator for caching data that
    is constant throughout the lifetime of a gunicorn worker process,
    is costly to compute, and is required often.  Otherwise, it can lead to
    unwanted memory leakage.
    """

    def __init__(self, func):
        self.func = func
        self.cache = {}

    def __call__(self, *args):
        if not isinstance(args, collections.Hashable):
            # uncacheable. a list, for instance.
            # better to not cache than blow up.
            return self.func(*args)
        if args in self.cache:
            return self.cache[args]
        else:
            value = self.func(*args)
            self.cache[args] = value
            return value

    def __repr__(self):
        """
        Return the function's docstring.
        """
        return self.func.__doc__

    def __get__(self, obj, objtype):
        """
        Support instance methods.
        """
        return functools.partial(self.__call__, obj)


86 87 88 89 90 91 92 93
def hashvalue(arg):
    """
    If arg is an xblock, use its location. otherwise just turn it into a string
    """
    if isinstance(arg, XBlock):
        return unicode(arg.location)
    else:
        return unicode(arg)
94 95 96 97 98 99 100 101 102 103


def zpickle(data):
    """Given any data structure, returns a zlib compressed pickled serialization."""
    return zlib.compress(pickle.dumps(data, pickle.HIGHEST_PROTOCOL))


def zunpickle(zdata):
    """Given a zlib compressed pickled serialization, returns the deserialized data."""
    return pickle.loads(zlib.decompress(zdata))