Login

Caching Decorator

Author:
spenczar
Posted:
April 25, 2012
Language:
Python
Version:
1.4
Score:
2 (after 2 ratings)

This is a decorator which will gets Django to try the cache before computing the result of a function. It automatically builds the cache key as a hash of the function name and inputs, and allows you to set whatever timeout you want.

 1
 2
 3
 4
 5
 6
 7
 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
44
45
46
47
48
49
50
51
52
53
54
import hashlib
from django.core.cache import cache


def try_cache_first(timeout=300):
    """
    Tries to get the output of the function from the cache firsts. Otherwise, 
    computes the function and stores the result in the cache.

    You can also pass the timeout in seconds for the cached value. By default, 
    this is 5 minutes.

    Example usage, which holds the value of expensive_function in the cache for
    10 minutes:

        @try_cache_first(timeout=600)
        def expensive_function():
            <do expensive stuff>
            return result

    All results are indexed by a hash of the function name and parameters,
    so changes to function inputs should refresh the cache automatically.
    """
    def decorator(func):

        def wrapper(*args, **kwargs):
            # Build keys from function name and arguments
            caching_keys = [func.__name__]

            if args is not None: 
                caching_keys.extend(args)

            if kwargs is not None:
                caching_keys.extend(sorted(kwargs.iteritems()))
                # have to sort the caching keys because kwargs can be in random
                # order which screws with hashing the inputs.

            # Convert the keys to a big string and hash it
            caching_keys = map(str, caching_keys)
            cache_key = '_'.join(caching_keys)
            cache_key = hashlib.sha512(cache_key).hexdigest()

            cache_key = cache_key[:250]  # max size of caching keys in memcached

            # Fetch from cache
            output = cache.get(cache_key)
            if output is None:
                output = func(*args, **kwargs)
                cache.set(cache_key, output, timeout)
            return output

        return wrapper

    return decorator

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

demanzano (on May 25, 2012):

nice, but what if the real result to be cached is None ? IMHO you shoud encode None-result differ than None-absence-in-cache...

#

dimyG (on July 29, 2016):

Always use functools wraps(). It's a convenience function that makes the wrapper function (i.e., the return value from the decorator) look like the function it is wrapping. This involves copying/updating a bunch of the double underscore attributes specifically module, name, doc, and dict. Suppose that the decorated function (the func in this example) is named long_calc(). If you don't use wraps() then the name variable of the decorated function will be "wrapper" instead of "long_calc"! This might generate some bugs if you intend to use it's name variable.

from functools import wraps

def wrapper(args, *kwargs): ... return wraps(func)(wrapper)

#

Please login first before commenting.