Coverage for core / cache / cache_store.py: 89%

81 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-04 15:08 +0000

1# core/cache_store.py 

2 

3from typing import Any, Optional, Tuple 

4from datetime import datetime 

5from core.cache.cache_config import load_cache_config 

6from core.cache.memory_cache import MemoryCache 

7from core.cache.file_cache import FileCache 

8from core.cache.db_cache import DBCache 

9 

10 

11class CacheStore: 

12 def __init__(self, config: Optional[dict] = None): 

13 if config is None: 13 ↛ 14line 13 didn't jump to line 14 because the condition on line 13 was never true

14 config = load_cache_config() 

15 self.cache_cfg = config.get("cache", {}) 

16 cache_cfg = self.cache_cfg 

17 

18 self.memory_cache = MemoryCache() if cache_cfg.get("memory_cache_enabled", True) else None 

19 

20 if cache_cfg.get("file_cache_enabled", True): 

21 self.file_cache = DBCache(config) if cache_cfg.get("use_db_cache", False) else FileCache(config) 

22 else: 

23 self.file_cache = None 

24 self._logger = None 

25 

26 def set_logger(self, logger): 

27 self._logger = logger 

28 if self.memory_cache: 

29 self.memory_cache.set_logger(logger) 

30 if self.file_cache: 

31 self.file_cache.set_logger(logger) 

32 # 설정에서 보관 기간과 최대 용량을 가져옴 (기본값: 7일, 500MB) 

33 try: 

34 days = self.cache_cfg.get("retention_days", 7) 

35 max_size = self.cache_cfg.get("max_size_mb", 500) 

36 self.file_cache.cleanup_old_files(days=days, max_size_mb=max_size) 

37 except Exception as e: 

38 if self._logger: 

39 self._logger.warning(f"캐시 정리 중 오류 발생 (무시됨): {e}") 

40 

41 def get_raw(self, key: str) -> Optional[Tuple[dict, str]] | None: 

42 """메모리 또는 파일 캐시에서 (timestamp + data) 반환""" 

43 # 메모리 캐시에 timestamp 포함된 wrapper가 저장되어 있다고 가정 

44 raw = None 

45 cache_type = "" 

46 

47 # 1) 메모리 캐시 조회 (켜져 있을 때만) 

48 if self.memory_cache: 

49 raw_memory = self.memory_cache.get(key) 

50 if raw_memory: 

51 raw = raw_memory 

52 cache_type = "memory" 

53 if self._logger: 

54 self._logger.debug(f"🧠 Memory Cache HIT: {key}") 

55 else: 

56 if self._logger: 

57 self._logger.debug(f"🧠 Memory Cache MISS: {key}") 

58 

59 # 2) 파일 캐시 조회 (켜져 있고 아직 못 찾았을 때만) 

60 if raw is None and self.file_cache: 

61 raw_file = self.file_cache.get_raw(key) 

62 if raw_file: 

63 # ✅ 메모리 warm-up은 메모리 캐시가 켜져 있을 때만 

64 if self.memory_cache: 

65 self.memory_cache.set(key, raw_file) 

66 raw = raw_file 

67 cache_type = "file" 

68 if self._logger: 

69 self._logger.debug(f"📂 File Cache HIT: {key}") 

70 else: 

71 if self._logger: 

72 self._logger.debug(f"🚫 File Cache MISS: {key}") 

73 

74 if not isinstance(raw, dict) or "timestamp" not in raw or "data" not in raw: 

75 if self._logger: 

76 self._logger.warning(f"[CacheStore] ❌ 잘못된 캐시 구조 감지: {key} / 내용: {raw}") 

77 return None 

78 

79 try: 

80 # timestamp가 ISO 형식인지 유효성 검사 

81 datetime.fromisoformat(raw["timestamp"]) 

82 except Exception as e: 

83 if self._logger: 

84 self._logger.warning(f"[CacheStore] ❌ 잘못된 timestamp 형식: {raw['timestamp']}") 

85 return None 

86 

87 return raw, cache_type 

88 

89 def get(self, key: str): 

90 # 내부적으로 get_raw를 호출하거나 구현체에서 오버라이드 

91 pass 

92 

93 def set(self, key: str, value: Any, save_to_file: bool = False): 

94 if self.memory_cache: 

95 self.memory_cache.set(key, value) 

96 if save_to_file and self.file_cache: 

97 self.file_cache.set(key, value, save_to_file) 

98 

99 def delete(self, key: str): 

100 if self.memory_cache: 

101 self.memory_cache.delete(key) 

102 if self.file_cache: 

103 self.file_cache.delete(key) 

104 

105 def clear(self): 

106 if self.memory_cache: 

107 self.memory_cache.clear() 

108 if self.file_cache: 

109 self.file_cache.clear()