aboutsummaryrefslogtreecommitdiff
path: root/internal/cache_utils.py
blob: 7be70e572ee504f331b1e68c926f6ebfa1f15381 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from django.core.cache import cache
from django_redis import get_redis_connection
from functools import wraps
import hashlib
import logging

logger = logging.getLogger(__name__)


def generate_cache_key(*args, prefix="", **kwargs):
    """Generate a unique cache key based on args and kwargs"""
    # Sort kwargs to ensure consistent key generation
    sorted_kwargs = sorted(kwargs.items())

    # Convert all arguments to strings and join them
    key_parts = [str(arg) for arg in args] + [f"{k}:{v}" for k, v in sorted_kwargs]
    key_string = ":".join(key_parts)

    # Create an MD5 hash of the key string to ensure safe key length
    hash_object = hashlib.md5(key_string.encode())
    hashed_key = hash_object.hexdigest()

    return f"{prefix}:{hashed_key}" if prefix else hashed_key


def safe_cache_get(key):
    """Safely get data from cache with error handling"""
    try:
        return cache.get(key)
    except Exception as e:
        logger.error(f"Cache get error for key {key}: {str(e)}")
        return None


def safe_cache_set(key, value, timeout=None):
    """Safely set data in cache with error handling"""
    try:
        cache.set(key, value, timeout)
        return True
    except Exception as e:
        logger.error(f"Cache set error for key {key}: {str(e)}")
        return False


def cache_data(prefix, timeout=60 * 15):
    """
    Generic cache decorator that can be used for any function.

    Args:
        prefix (str): Prefix for the cache key (e.g., 'anime_data', 'streaming_data')
        timeout (int): Cache timeout in seconds
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Generate cache key using all arguments
            cache_key = generate_cache_key(*args, prefix=prefix, **kwargs)

            # Try to get from cache
            cached_data = safe_cache_get(cache_key)
            if cached_data is not None:
                return cached_data

            try:
                # Get fresh data
                result = func(*args, **kwargs)
                # Cache the result
                safe_cache_set(cache_key, result, timeout)
                return result
            except Exception as e:
                logger.error(
                    f"Error in {prefix} for args {args}, kwargs {kwargs}: {str(e)}"
                )
                # On error, return fresh data without caching
                return func(*args, **kwargs)

        return wrapper

    return decorator


def clear_cache(pattern=None, prefix=None):
    """
    Clear cache entries based on a pattern or prefix.

    Args:
        pattern (str): Direct Redis key pattern to match (e.g., '*anime_id*')
        prefix (str): Cache prefix to clear (e.g., 'anime_data')
    """
    try:
        redis_conn = get_redis_connection("default")

        if not pattern and not prefix:
            # Clear all known prefixes if neither pattern nor prefix specified
            prefixes = ["anime_data:*", "streaming_data:*", "search_results:*"]
            total_cleared = 0
            for p in prefixes:
                keys = redis_conn.keys(p)
                if keys:
                    redis_conn.delete(*keys)
                    total_cleared += len(keys)
                    logger.info(f"Cleared {len(keys)} entries for pattern {p}")
            return total_cleared

        if prefix:
            pattern = f"{prefix}:*"

        keys = redis_conn.keys(pattern)
        if keys:
            redis_conn.delete(*keys)
            logger.info(f"Cleared {len(keys)} cache entries matching {pattern}")
            return len(keys)
        return 0

    except Exception as e:
        logger.error(f"Error clearing cache with pattern {pattern}: {str(e)}")
        return 0