Memcache python decorators
Using memcache on google app engine was always recommended but now it’s a must. Since the GAE pricing changed it became a lot more important to keep the resources required to run your application as low as possible since you don’t want to throw your money away. Using memcache is a simple way to increase the performance of your application while lowering the daily costs of running your application. One particular use case of caching expensive computations is when running queries against the datastore. It’s always faster to get a value from memcache than it is from the datastore.
Memcache usage can be simplified down to getting and removing values using a unique key. When I tried integrating memcache into a small project I was working on, it became obvious that I will need a simple way to cache results from methods, and also invalidate these entries before running other methods. For example, the result of an expensive query will be cached but that result might need to deleted from the cache when a part of the result is modified in the datastore.
1 from functools import wraps
2 from google.appengine.api import memcache
3
4 def get_args(f, args, kwargs):
5 """Returns all passed arguments to a certain function."""
6
7 import inspect
8 args_names, _, _, defaults = inspect.getargspec(f)
9 passed_args = {}
10
11 if defaults is not None:
12 passed_args = dict(zip(args_names[-len(defaults):], defaults))
13
14 passed_args.update(dict(zip(args_names, args)))
15 passed_args.update(kwargs)
16
17 passed_varargs = args[len(args_names):]
18
19 return passed_args, passed_varargs
20
21
22 class cache_entry(object):
23 """Decorator for functions that return cacheable results."""
24
25 def __init__(self, name_format):
26 self.name_format = name_format
27
28 def __call__(self, f):
29 def wrapped(*args, **kwargs):
30 args_values, _ = get_args(f, args, kwargs)
31 entry_name = self.name_format.format(**args_values)
32 value = memcache.get(entry_name)
33
34 if value is None:
35 value = f(*args, **kwargs)
36 memcache.set(entry_name, value)
37
38 return value
39
40 return wrapped
41
42 class cache_invalidate(object):
43 """Decorator for functions that need to invalidate some cache
44 entries before they are executed."""
45
46 def __init__(self, name_formats):
47 self.name_formats = name_formats
48
49 def __call__(self, f):
50 def wrapped(*args, **kwargs):
51 args_values, _ = get_args(f, args, kwargs)
52
53 names = [ name.format(**args_values) for name in self.name_formats ]
54 memcache.delete_multi(names)
55
56 return f(*args, **kwargs)
57
58 return wrapped
Here are some examples of using these decorators:
1 @cache_entry("{param}_expensive_function")
2 def expensive_function(param):
3 pass
4
5 @cache_invalidate(["{param}_expensive_function"])
6 def another_function(param):
7 pass
As you can see we are using the cache_entry
decorator where we need to cache
method return value(s) and the cache_invalidate
decorator where we need to
delete cache entries before running the method. Key names are formed with
function arguments that can be used in the key name using python standard
string formatting options.
If you like this article, you might enjoy this one too: Free SMS notifications from Google