Created
October 30, 2022 17:04
-
-
Save DrDougPhD/c1212257862553532791b9b7cfd5cbd6 to your computer and use it in GitHub Desktop.
Caching Decorator for Python
This file contains hidden or 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 os | |
import pathlib | |
import pickle | |
import logging | |
import numbers | |
logger = logging.getLogger(__name__) | |
enabled = True | |
def cache(function): | |
cache_location = pathlib.Path('./cache') | |
"""Return cached results when this project is in development instead of executing the expensive operation.""" | |
def wrapper(*args, **kwargs): | |
if not enabled: | |
return function(*args, **kwargs) | |
# Produce the cache if it doesn't exist and then return it | |
function_module_path = f'{function.__module__}.{function.__name__}' | |
stringified_arguments = '' | |
if len(args) > 0: | |
stringified_arguments += ''.join( | |
'*[' | |
",".join(stringify(positional_arg) for positional_arg in args), | |
']', | |
) | |
if len(kwargs) > 0: | |
if len(stringified_arguments) > 0: | |
stringified_arguments += ', ' | |
stringified_arguments += ', '.join( | |
f'{key}={stringify(value)}' | |
for key, value in kwargs.items() | |
) | |
function_call_repr = f'{function_module_path}({stringified_arguments})' | |
function_cache_path = cache_location / f'{path_safe(function_call_repr)}.pkl' | |
if not function_cache_path.exists(): | |
cache_location.mkdir(exist_ok=True, parents=True) | |
logger.info(f'Initializing development cache for `{function_call_repr}`') | |
results = function(*args, **kwargs) | |
with function_cache_path.open('wb') as pkl: | |
pickle.dump(results, pkl) | |
return results | |
# Reaching this code means this project is in development, and thus the cached result of the function should be returned. | |
logger.info(f'Loading development cache for {function_call_repr}') | |
with function_cache_path.open('rb') as pkl: | |
return pickle.load(pkl) | |
return wrapper | |
def stringify(argument) -> str: | |
if isinstance(argument, str): | |
return argument | |
if isinstance(argument, os.PathLike): | |
return str(argument) | |
if isinstance(argument, numbers.Number): | |
return str(argument) | |
# TODO: this might be a weak conversion | |
if '__name__' in dir(argument): | |
return argument.__name__ | |
raise ValueError(f'Stringification of type `{type(argument)}` is not defined. Value = {argument}.') | |
def path_safe(value: str) -> str: | |
return value.replace(os.sep, '_') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment