Last active
April 6, 2018 18:23
-
-
Save yelsayed/bd35809d5a3f12adf1dfe9ef3b0e6b19 to your computer and use it in GitHub Desktop.
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
from threading import Thread | |
from django.core.cache import cache | |
from django.conf import settings | |
def _check_and_update_cache(func, args, kwargs, cache_key, | |
timeout=settings.DEFAULT_CACHE_TIMEOUT): | |
""" | |
Runs the function that has been cached to see if the output is | |
the same as the one in the cache, if the output is not the same | |
then it will update the cache with the new result... should be fun | |
""" | |
new_result = func(*args, **kwargs) | |
old_result = cache.get(cache_key) | |
if new_result != old_result: | |
cache.set(cache_key, new_result, timeout) | |
class _HashedSeq(list): | |
""" | |
This class guarantees that hash() will be called no more than once per element. | |
""" | |
__slots__ = 'hashvalue' | |
def __init__(self, tup): | |
self[:] = tup | |
def create_key(args, kwargs, unique_arg, func_name): | |
""" | |
Make a cache key from optionally typed positional and keyword arguments | |
The key is constructed in a way that is flat as possible rather than | |
as a nested structure that would take more memory. | |
If there is only a single argument and its data type is known to cache | |
its hash value, then that argument is returned without a wrapper. This | |
saves space and improves lookup speed. | |
""" | |
fasttypes = {int, str, frozenset, type(None)} | |
if unique_arg: | |
temp_args = args | |
kwargs = {} | |
args = (temp_args[unique_arg],) | |
key = args | |
if kwargs: | |
sorted_items = sorted(kwargs.items()) | |
key += (object(),) | |
for item in sorted_items: | |
key += item | |
elif len(key) == 1 and type(key[0]) in fasttypes: | |
key += (func_name,) | |
return key | |
key += (func_name,) | |
key = map(str, key) | |
return _HashedSeq(key) | |
def _cache_wrapper(func, timeout, klass, unique_arg): | |
def wrapper(*args, **kwargs): | |
""" | |
Wrapper that checks the cache if it has anything then | |
""" | |
# If this is in a testing environment then don't do anything special | |
if settings.TESTING: | |
return func(*args, **kwargs) | |
key = create_key(args, kwargs, unique_arg, func.__name__) | |
result = cache.get(key) | |
if result is not None: | |
t = Thread(target=_check_and_update_cache, args=(func, args, kwargs, key,)) | |
t.start() | |
if result is None: | |
result = func(*args, **kwargs) | |
cache.set(key, result, timeout) | |
return result | |
def update_cache(*args, **kwargs): | |
""" | |
Manually updates the cache in case the code needs a manual refresh | |
""" | |
key = create_key(args, kwargs, unique_arg, func.__name__) | |
cache.delete(key) | |
result = func(*args, **kwargs) | |
cache.set(key, result, timeout) | |
def clear_cache(*args, **kwargs): | |
""" | |
Manually clear the cache in case the code needs a fresh return | |
""" | |
key = create_key(args, kwargs, unique_arg, func.__name__) | |
cache.delete(key) | |
def plain_run(*args, **kwargs): | |
""" | |
Just in case the code doesn't want to interact with the cache, | |
this could be useful when wanting to test a particular functionality | |
""" | |
return func(*args, **kwargs) | |
wrapper.clear_cache = clear_cache | |
wrapper.update_cache = update_cache | |
wrapper.plain_run = plain_run | |
return wrapper | |
def cache_output(timeout=60 * 60, klass="", unique_arg=None): | |
""" | |
Cache that utilizes the Django caching framework to do the LRU cache | |
but on a global level for all the threads to benefit from this. This | |
will take the return value of a function, create a key from it's parameters | |
and then write it to the cache DB. This also takes into consideration | |
the popping of the cache, whenever a pop is supposed to happen | |
""" | |
def decorator_function(func): | |
return _cache_wrapper(func, timeout, klass, unique_arg) | |
return decorator_function |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment