Spaces:
Running
Running
Commit ·
b4eaf42
1
Parent(s): 024c6e1
fix: replace unbounded caches with TTLCache to prevent memory leaks
Browse files- fetcher._realtime_cache: dict → TTLCache(maxsize=50, ttl=30)
- news_service._news_cache: dict → TTLCache(maxsize=50, ttl=1800)
- LIGHTWEIGHT_MODE already exists (200→50 trees), just needs env var on Render
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- data/fetcher.py +2 -1
- services/news_service.py +8 -9
- tests/test_arch_deploy.py +2 -1
data/fetcher.py
CHANGED
|
@@ -277,7 +277,8 @@ def get_stock_info(stock_no: str) -> dict:
|
|
| 277 |
# non-trading-hours requests (twstock price=None) return stale but valid data.
|
| 278 |
# ---------------------------------------------------------------------------
|
| 279 |
|
| 280 |
-
|
|
|
|
| 281 |
|
| 282 |
|
| 283 |
def get_realtime_quote(stock_no: str) -> dict:
|
|
|
|
| 277 |
# non-trading-hours requests (twstock price=None) return stale but valid data.
|
| 278 |
# ---------------------------------------------------------------------------
|
| 279 |
|
| 280 |
+
from cachetools import TTLCache
|
| 281 |
+
_realtime_cache: TTLCache = TTLCache(maxsize=50, ttl=30)
|
| 282 |
|
| 283 |
|
| 284 |
def get_realtime_quote(stock_no: str) -> dict:
|
services/news_service.py
CHANGED
|
@@ -18,9 +18,9 @@ logger = logging.getLogger(__name__)
|
|
| 18 |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
|
| 19 |
_GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
|
| 20 |
|
| 21 |
-
# In-memory cache
|
| 22 |
-
|
| 23 |
-
|
| 24 |
|
| 25 |
|
| 26 |
def get_stock_news(stock_no: str, name: str = "", lang: str = "zh-TW") -> Optional[dict]:
|
|
@@ -39,12 +39,11 @@ def get_stock_news(stock_no: str, name: str = "", lang: str = "zh-TW") -> Option
|
|
| 39 |
if not GEMINI_API_KEY:
|
| 40 |
return None
|
| 41 |
|
| 42 |
-
# Check cache
|
| 43 |
now = time.time()
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
return {**cached, "cached": True}
|
| 48 |
|
| 49 |
stock_label = f"{name} ({stock_no})" if name and name != stock_no else stock_no
|
| 50 |
|
|
@@ -95,7 +94,7 @@ def get_stock_news(stock_no: str, name: str = "", lang: str = "zh-TW") -> Option
|
|
| 95 |
"summary": summary,
|
| 96 |
"source": "gemini",
|
| 97 |
}
|
| 98 |
-
_news_cache[stock_no] =
|
| 99 |
return {**result, "cached": False}
|
| 100 |
|
| 101 |
except Exception as exc:
|
|
|
|
| 18 |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
|
| 19 |
_GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
|
| 20 |
|
| 21 |
+
# In-memory cache with TTL and size limit
|
| 22 |
+
from cachetools import TTLCache
|
| 23 |
+
_news_cache: TTLCache = TTLCache(maxsize=50, ttl=1800)
|
| 24 |
|
| 25 |
|
| 26 |
def get_stock_news(stock_no: str, name: str = "", lang: str = "zh-TW") -> Optional[dict]:
|
|
|
|
| 39 |
if not GEMINI_API_KEY:
|
| 40 |
return None
|
| 41 |
|
| 42 |
+
# Check cache (TTLCache handles expiry automatically)
|
| 43 |
now = time.time()
|
| 44 |
+
cached = _news_cache.get(stock_no)
|
| 45 |
+
if cached is not None:
|
| 46 |
+
return {**cached, "cached": True}
|
|
|
|
| 47 |
|
| 48 |
stock_label = f"{name} ({stock_no})" if name and name != stock_no else stock_no
|
| 49 |
|
|
|
|
| 94 |
"summary": summary,
|
| 95 |
"source": "gemini",
|
| 96 |
}
|
| 97 |
+
_news_cache[stock_no] = result
|
| 98 |
return {**result, "cached": False}
|
| 99 |
|
| 100 |
except Exception as exc:
|
tests/test_arch_deploy.py
CHANGED
|
@@ -227,7 +227,8 @@ class TestRealtimeQuoteCache:
|
|
| 227 |
|
| 228 |
def test_realtime_cache_exists(self):
|
| 229 |
from data.fetcher import _realtime_cache
|
| 230 |
-
|
|
|
|
| 231 |
|
| 232 |
def test_cache_and_return_stores_data(self):
|
| 233 |
"""get_realtime_quote should store results in _realtime_cache."""
|
|
|
|
| 227 |
|
| 228 |
def test_realtime_cache_exists(self):
|
| 229 |
from data.fetcher import _realtime_cache
|
| 230 |
+
from collections.abc import MutableMapping
|
| 231 |
+
assert isinstance(_realtime_cache, MutableMapping)
|
| 232 |
|
| 233 |
def test_cache_and_return_stores_data(self):
|
| 234 |
"""get_realtime_quote should store results in _realtime_cache."""
|