"""cache utility function.""" import functools import hashlib import pickle from typing import Any from redis.exceptions import RedisError import sentry_sdk from moneyhub.connectors.redis import redis_client from moneyhub.constants.constants import ONE_DAY from moneyhub.constants.error import REDIS_ERROR from moneyhub.utils.logger import get_logger logger = get_logger() def _generate_func_key(func, *args, **kwargs) -> str: """Create a key for Redis. Args: func (function): function object Return str: A key for Redis to store function return values. """ func_dict = {'func_name': func.__qualname__, 'args': args, 'kwargs': kwargs} return func.__qualname__ + '_' + hashlib.sha256(repr(func_dict).encode()).hexdigest() def cache(ttl: int | None = None) -> Any: """Wrap a function with a TTL Redis cache. Args: ttl (int): an optional time to live in seconds """ def wrap(orig_func): @functools.wraps(orig_func) def wrapped(*args, **kwargs): client = redis_client() if kwargs.get('is_open_statement_period'): return orig_func(*args, **kwargs) cache_key = _generate_func_key(orig_func, *args, **kwargs) try: cached_result = client.get(cache_key) if cached_result: return pickle.loads(cached_result) result = orig_func(*args, **kwargs) if result is not None: cache_value = pickle.dumps(result, protocol=pickle.HIGHEST_PROTOCOL) if kwargs.get('is_subaccount'): nonlocal ttl ttl = ONE_DAY client.set(cache_key, cache_value, ex=ttl) return result except RedisError as e: sentry_sdk.capture_exception(e) logger.exception(f'{REDIS_ERROR} {e}') return orig_func(*args, **kwargs) return wrapped return wrap