first commit
This commit is contained in:
86
app/cache.py
Normal file
86
app/cache.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""LRU cache wrapper with per-ticker invalidation.
|
||||
|
||||
Provides an in-memory cache (max 1000 entries) keyed on
|
||||
(ticker, start_date, end_date, indicator_type). Supports selective
|
||||
invalidation of all entries for a given ticker — needed when new
|
||||
OHLCV data is ingested.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Hashable
|
||||
|
||||
CacheKey = tuple[str, Any, Any, str] # (ticker, start_date, end_date, indicator_type)
|
||||
|
||||
_DEFAULT_MAX_SIZE = 1000
|
||||
|
||||
|
||||
class LRUCache:
|
||||
"""Simple LRU cache backed by an ``OrderedDict``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_size:
|
||||
Maximum number of entries. When exceeded the least-recently-used
|
||||
entry is evicted. Defaults to 1000.
|
||||
"""
|
||||
|
||||
def __init__(self, max_size: int = _DEFAULT_MAX_SIZE) -> None:
|
||||
self._max_size = max_size
|
||||
self._store: OrderedDict[Hashable, Any] = OrderedDict()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Public API
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def get(self, key: CacheKey) -> Any | None:
|
||||
"""Return cached value or ``None`` on miss.
|
||||
|
||||
Accessing an entry promotes it to most-recently-used.
|
||||
"""
|
||||
if key not in self._store:
|
||||
return None
|
||||
self._store.move_to_end(key)
|
||||
return self._store[key]
|
||||
|
||||
def set(self, key: CacheKey, value: Any) -> None:
|
||||
"""Insert or update *key* with *value*.
|
||||
|
||||
If the cache is full the least-recently-used entry is evicted.
|
||||
"""
|
||||
if key in self._store:
|
||||
self._store.move_to_end(key)
|
||||
self._store[key] = value
|
||||
return
|
||||
if len(self._store) >= self._max_size:
|
||||
self._store.popitem(last=False) # evict LRU
|
||||
self._store[key] = value
|
||||
|
||||
def invalidate_ticker(self, ticker: str) -> int:
|
||||
"""Remove all entries whose first key element matches *ticker*.
|
||||
|
||||
Returns the number of evicted entries.
|
||||
"""
|
||||
keys_to_remove = [k for k in self._store if k[0] == ticker]
|
||||
for k in keys_to_remove:
|
||||
del self._store[k]
|
||||
return len(keys_to_remove)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove all entries."""
|
||||
self._store.clear()
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
"""Current number of cached entries."""
|
||||
return len(self._store)
|
||||
|
||||
@property
|
||||
def max_size(self) -> int:
|
||||
"""Maximum capacity."""
|
||||
return self._max_size
|
||||
|
||||
|
||||
# Module-level singleton used by the indicator service.
|
||||
indicator_cache = LRUCache()
|
||||
Reference in New Issue
Block a user