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
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
1# core/cache_store.py
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
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
18 self.memory_cache = MemoryCache() if cache_cfg.get("memory_cache_enabled", True) else None
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
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}")
41 def get_raw(self, key: str) -> Optional[Tuple[dict, str]] | None:
42 """메모리 또는 파일 캐시에서 (timestamp + data) 반환"""
43 # 메모리 캐시에 timestamp 포함된 wrapper가 저장되어 있다고 가정
44 raw = None
45 cache_type = ""
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}")
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}")
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
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
87 return raw, cache_type
89 def get(self, key: str):
90 # 내부적으로 get_raw를 호출하거나 구현체에서 오버라이드
91 pass
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)
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)
105 def clear(self):
106 if self.memory_cache:
107 self.memory_cache.clear()
108 if self.file_cache:
109 self.file_cache.clear()