lazy import ast lazy from collections.abc import Callable lazy from functools import wraps lazy from inspect import getsource, signature, unwrap lazy from pathlib import Path lazy from sys import version_info lazy from textwrap import dedent lazy from types import FunctionType lazy from uuid import uuid4 lazy import dill lazy from blake3 import blake3 lazy from loguru import logger BASE_FOLDER = Path( f"./.cache/{version_info.major}.{version_info.minor}.{version_info.micro}/" ) def _function_hash[**P, R](fn: Callable[P, R]) -> str: fn = unwrap(fn) if not isinstance(fn, FunctionType): raise ValueError(f"fn {fn} is not an instance of types.FunctionType") source = getsource(fn) parsed = ast.parse(dedent(source)) code = ast.dump(parsed) sig = signature(fn) name: str = fn.__qualname__ hasher = blake3() hasher.update(name.encode("utf-8")) hasher.update(sig.format().encode("utf-8")) hasher.update(code.encode("utf-8")) return hasher.hexdigest() def _parameter_hash[**P, R]( fn: Callable[P, R], *args: P.args, **kwargs: P.kwargs ) -> str: fn = unwrap(fn) sig = signature(fn) bound = sig.bind(*args, **kwargs) bound.apply_defaults() serialized = dill.dumps(bound.arguments) hasher = blake3(serialized) return hasher.hexdigest() def persistent[**P, R](func: Callable[P, R]) -> Callable[P, R]: fn_hash = _function_hash(func) folder = BASE_FOLDER / fn_hash folder.mkdir(parents=True, exist_ok=True) @wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: param_hash = _parameter_hash(func, *args, **kwargs) path = folder / f"{param_hash}.dill" if not path.exists(): logger.opt(colors=True).debug( f"[CACHE] [MISS] {func.__qualname__}({args}, {kwargs})" ) result = func(*args, **kwargs) payload = dill.dumps(result) temp_path = path.with_suffix(f".tmp.{uuid4().hex}") temp_path.write_bytes(payload) temp_path.replace(path) else: logger.opt(colors=True).debug( f"[CACHE] [HIT] {func.__qualname__}({args}, {kwargs})" ) payload = path.read_bytes() result = dill.loads(payload) return result return wrapper