awolverp / cachebox

The fastest memoizing and caching Python library written in Rust.
https://pypi.org/project/cachebox/
MIT License
258 stars 4 forks source link

Persistent cache #8

Closed Koowah closed 3 months ago

Koowah commented 3 months ago

Hi,

Is there a built-in way to persist decorated functions' cache (like this) ? Also, can we clear a decorated function's cache like this ?

It would help me tremendously for my use-case : I'd like to persist some compute heavy function's results throughout multiple runs.

Thank you ! Cheers

awolverp commented 3 months ago

Hi, No there's no built-in way to persist decorated functions' cache, but you can use this trick:

import cachebox as cb
import atexit
import pickle
import os.path

def custom_cached(filename, cache, *args, **kwargs):
    if os.path.exists(filename):
        with open(filename, "rb") as fd:
            cache = pickle.load(fd)

    def _save_pickle():
        with open(filename, "wb") as fd:
            pickle.dump(cache, fd)

    atexit.register(_save_pickle)

    return cb.cached(cache, *args, **kwargs)

@custom_cached("file.pickle", cb.FIFOCache(0))
def factorial(n: int) -> int:
    fact = 1
    for num in range(2, n + 1):
        fact *= num
    return fact

print(factorial(20))

And yes you can manage decorated functions' cache like this:

# clear cache
factorial.cache_clear()

# access to cache
print(factorial.cache)
Koowah commented 3 months ago

Thank you ! Your quick response is very much appreciated.

For anyone interested in saving intermediate results instead of atexit here's my workaround :

def custom_cached(filename=None, cache=cb.FIFOCache(2), *args, **kwargs):
    def decorator(func):
        @wraps(func)
        def wrapper(*func_args, **func_kwargs):
            loaded = False
            local_cache = cache
            local_filepath = (
                os.path.join(CACHE_DIR, f"{func.__name__}_cachebox.pkl")
                if filename is None
                else os.path.join(CACHE_DIR, filename)
            )

            if os.path.exists(local_filepath):
                with open(local_filepath, "rb") as fd:
                    local_cache = pickle.load(fd)
                loaded = True
                logger.debug(f"Loaded cache from {local_filepath}")

            # Execute the original function
            result = cb.cached(local_cache, *args, **kwargs)(func)(*func_args, **func_kwargs)

            # Updated cache if necessary
            if not loaded:
                with open(local_filepath, "wb") as fd:
                    pickle.dump(cache, fd)
                logger.debug(f"Saved cache to {local_filepath}")
            return result

        return wrapper

    return decorator
yuanjie-ai commented 3 months ago

Are you planning to support Redis

awolverp commented 3 months ago

@yuanjie-ai No I don't have a plan to support Redis