Last active
August 29, 2015 13:56
-
-
Save tuttle/9190308 to your computer and use it in GitHub Desktop.
Cached function decorator (Python, not mandatory Django)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import hashlib | |
from functools import wraps | |
from django.core import cache # Importing this way so debug_toolbar can patch it later. | |
from django.core.cache.backends.base import DEFAULT_TIMEOUT | |
ALLOWED_CACHED_FUNCTION_ARG_TYPES = set((type(None), int, float, long, bool, str, unicode)) | |
def cached_function(func=None, num_args_to_key=None, timeout=DEFAULT_TIMEOUT): | |
""" | |
We all write this tedious sequence often:: | |
key = make_key(arg1, arg2, arg3) | |
value = cache.get(key) | |
if value is None: | |
value = expensive(arg1, arg2, arg3) | |
cache.set(key, value, timeout=3600) | |
return value | |
This decorator tries to remove the need to do this. You only use the following:: | |
@cached_function(timeout=3600) | |
def expensive(arg1, arg2, arg3): | |
... | |
To use the Django default timeout, you can omit the arguments:: | |
@cached_function | |
The expensive() must accept the fixed number of positional arguments. All these args must be | |
basic and easily repr-esentable Python types, in order a solid cache key can be made from them | |
(as well as from the function name and containing module name). | |
If you pass arguments to expensive() that do not need to be part of the cache key, put them | |
to the end of args and limit the number of key-making args:: | |
@cached_function(num_args_to_key=2) | |
""" | |
def cached_function_inner(func): | |
if func.func_defaults: | |
raise RuntimeError("cached_function does not allow function to have default args " | |
"in order to keep the number of passed args fixed.") | |
@wraps(func) | |
def wrapper(*args): | |
key_args = args if num_args_to_key is None else args[:num_args_to_key] | |
unallowed_types = set(map(type, key_args)) - ALLOWED_CACHED_FUNCTION_ARG_TYPES | |
if unallowed_types: | |
raise RuntimeError("cached_function received unallowed types of keyable args: %s" \ | |
% ', '.join(map(str, unallowed_types))) | |
key_fmt = 'cached_function:%s.%s(%%s)' % (func.__module__, func.__name__) | |
key = key_fmt % ','.join(map(repr, key_args)) | |
if len(key) > 200: | |
key_args = hashlib.sha256(key_args).hexdigest() | |
key = key_fmt % key_args + '-hashed' | |
value = cache.cache.get(key) | |
if value is None: | |
value = func(*args) | |
cache.cache.set(key, value, timeout=timeout) | |
return value | |
return wrapper | |
if func is None: | |
# @cached_function(...) | |
return cached_function_inner | |
elif callable(func): | |
# @cached_function | |
return cached_function_inner(func) | |
else: | |
raise RuntimeError("Invalid arguments provided to cached_function decorator.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment