Skip to content

Instantly share code, notes, and snippets.

@yelsayed
Last active April 6, 2018 18:23
Show Gist options
  • Save yelsayed/bd35809d5a3f12adf1dfe9ef3b0e6b19 to your computer and use it in GitHub Desktop.
Save yelsayed/bd35809d5a3f12adf1dfe9ef3b0e6b19 to your computer and use it in GitHub Desktop.
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