Source code for tcutility.cache
import time
import functools
import json
import os
import platformdirs
_timed_func_cache = {}
_time_since_cache = {}
_func_cache = {}
_general_cache = {}
_cache_dir = platformdirs.user_cache_dir(appname='TCutility', appauthor='TheoCheM', ensure_exists=True)
[docs]
def timed_cache(delay: float):
'''
Decorator that creates a timed cache for the function or method.
This cache will expire after a chosen amount of time.
Args:
delay: the expiry time for the function cache.
'''
def decorator(func):
# this is the main decorator returned by timed_cache
@functools.wraps(func)
def inner_decorator(*args, **kwargs):
# we have to create a tuple of the kwargs items to ensure we can hash the arguments
arguments = args, tuple(kwargs.items())
# the time since the last caching
dT = time.perf_counter() - _time_since_cache[func]
# if the cache has expired, we remove it from the cache
# and set the new time_since_cache
if dT >= delay:
_timed_func_cache[func].pop(arguments, None)
_time_since_cache[func] = time.perf_counter()
# check if the arguments were called before
if arguments in _timed_func_cache[func]:
return _timed_func_cache[func][arguments]
# if it is not present we add it to the cache
res = func(*args, **kwargs)
_timed_func_cache[func][arguments] = res
return res
# initialize the cache and time_since_cache
_timed_func_cache[func] = {}
_time_since_cache[func] = -delay
return inner_decorator
return decorator
[docs]
def cache(func):
'''
Function decorator that stores results from previous calls to the function or method.
'''
@functools.wraps(func)
def inner_decorator(*args, **kwargs):
# we have to create a tuple of the kwargs items to ensure we can hash the arguments
arguments = args, tuple(kwargs.items())
# check if the arguments were called before
if arguments in _func_cache[func]:
return _func_cache[func][arguments]
# if it is not present we add it to the cache
res = func(*args, **kwargs)
_func_cache[func][arguments] = res
return res
_func_cache[func] = {}
return inner_decorator
def _get_from_cache_file(file, func, args, kwargs):
'''
Retrieve results from a JSON file. Return `None` if not found.
'''
# open the file and parse the data
with open(os.path.join(_cache_dir, file)) as cfile:
data = json.loads(cfile.read())
# we now go through the data
# it is formatted as a list of dicts
# each dict has the 'func', 'args', 'kwargs' and 'value' keys
for datum in data:
# func is set as the function qualname
if datum['func'] != func.__qualname__:
continue
# args is retrieved as a list
if datum['args'] != list(args):
continue
# kwargs is simply a dict
if datum['kwargs'] != kwargs:
continue
# if we did not exit the loop yet we return the value
return datum['value']
def _write_to_cache_file(file, func, args, kwargs, value):
'''
Write results to the file.
'''
# we open the file to get the data
with open(os.path.join(_cache_dir, file)) as cfile:
data = json.loads(cfile.read())
# add the new results to the file
new = {
'func': func.__qualname__,
'args': args,
'kwargs': kwargs,
'value': value
}
data.append(new)
with open(os.path.join(_cache_dir, file), 'w+') as cfile:
cfile.write(json.dumps(data, indent=4))
def _clear_cache_file(file):
'''
Function that clears a file and writes a new beginning of a list.
'''
os.makedirs(_cache_dir, exist_ok=True)
with open(os.path.join(_cache_dir, file), 'w+') as cfile:
cfile.write('[]')
[docs]
def cache_file(file):
'''
Function decorator that stores results of a function to a file.
Because results are written to a file the values persist between Python sessions.
This is useful, for example, for online API calls.
Args:
file: the filepath to store function call results to.
Files will be stored in the platform dependent temporary file directory.
.. seealso::
`platformdirs.user_cache_dir <https://platformdirs.readthedocs.io/en/latest/api.html#platformdirs.user_cache_dir>`_ for information on the temporary directory.
'''
def decorator(func):
# make the file if it doesnt exist
if not os.path.exists(os.path.join(_cache_dir, file)):
_clear_cache_file(file)
@functools.wraps(func)
def inner_decorator(*args, **kwargs):
# check if the arguments were called before
cached = _get_from_cache_file(file, func, args, kwargs)
if cached is not None:
return cached
# if it is not present we add it to the cache file
res = func(*args, **kwargs)
_write_to_cache_file(file, func, args, kwargs, res)
return res
return inner_decorator
return decorator