I wanted to have something from the Python standard library like shelve, but also the parameters from cachier. So I built upon the answer of @nehem and @thegreendroid.
import datetime
import os
import shelve
import pickle
from functools import wraps
def shelve_it(cache_dir='/tmp/cache', stale_after=None):
'''
A decorator to cache the results of a function.
Args:
- cache_dir (str): The directory where the cache will be stored.
- stale_after (timedelta): The duration after which the cache is considered stale.
'''
cache_file = os.path.join(cache_dir, 'cache.shelve')
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
def decorator(func):
@wraps(func)
def new_func(*args, **kwargs):
cache_key = pickle.dumps((args, kwargs))
cache_key_str = cache_key.hex()
with shelve.open(cache_file) as d:
if cache_key_str in d:
if stale_after and 'timestamp' in d[cache_key_str]:
timestamp = d[cache_key_str]['timestamp']
if datetime.datetime.now() - timestamp > stale_after:
del d[cache_key_str]
print(f'Cache for {cache_key_str} is stale, recalculating...')
else:
return d[cache_key_str]['result']
else:
return d[cache_key_str]
result = func(*args, **kwargs)
if stale_after:
d[cache_key_str] = {'result': result, 'timestamp': datetime.datetime.now()}
else:
d[cache_key_str] = result
return result
return new_func
return decorator
Usage
@shelve_it(cache_dir='/tmp/my_cache', stale_after=datetime.timedelta(days=2))
def expensive_function(param, multiplier=2):
import time
time.sleep(2)
return param * multiplier
print(expensive_function('test')) # This will take 2 seconds
print(expensive_function('test')) # This will be instant, using the cache