11"""A memory-based caching core for cachier."""
22
33import threading
4+ from collections import OrderedDict
45from datetime import datetime , timedelta
5- from typing import Any , Dict , Optional , Tuple
6+ from typing import Any , Optional , Tuple
67
78from .._types import HashFunc
89from ..config import CacheEntry
@@ -17,9 +18,18 @@ def __init__(
1718 hash_func : Optional [HashFunc ],
1819 wait_for_calc_timeout : Optional [int ],
1920 entry_size_limit : Optional [int ] = None ,
21+ cache_size_limit : Optional [int ] = None ,
22+ replacement_policy : str = "lru" ,
2023 ):
21- super ().__init__ (hash_func , wait_for_calc_timeout , entry_size_limit )
22- self .cache : Dict [str , CacheEntry ] = {}
24+ super ().__init__ (
25+ hash_func ,
26+ wait_for_calc_timeout ,
27+ entry_size_limit ,
28+ cache_size_limit ,
29+ replacement_policy ,
30+ )
31+ self .cache : "OrderedDict[str, CacheEntry]" = OrderedDict ()
32+ self ._cache_size = 0
2333
2434 def _hash_func_key (self , key : str ) -> str :
2535 return f"{ _get_func_str (self .func )} :{ key } "
@@ -28,18 +38,22 @@ def get_entry_by_key(
2838 self , key : str , reload = False
2939 ) -> Tuple [str , Optional [CacheEntry ]]:
3040 with self .lock :
31- return key , self .cache .get (self ._hash_func_key (key ), None )
41+ hkey = self ._hash_func_key (key )
42+ entry = self .cache .get (hkey , None )
43+ if entry is not None :
44+ self .cache .move_to_end (hkey )
45+ return key , entry
3246
3347 def set_entry (self , key : str , func_res : Any ) -> bool :
3448 if not self ._should_store (func_res ):
3549 return False
3650 hash_key = self ._hash_func_key (key )
51+ size = self ._estimate_size (func_res )
3752 with self .lock :
3853 try :
39- # we need to retain the existing condition so that
40- # mark_entry_not_calculated can notify all possibly-waiting
41- # threads about it
4254 cond = self .cache [hash_key ]._condition
55+ old_size = self ._estimate_size (self .cache [hash_key ].value )
56+ self ._cache_size -= old_size
4357 except KeyError : # pragma: no cover
4458 cond = None
4559 self .cache [hash_key ] = CacheEntry (
@@ -50,6 +64,12 @@ def set_entry(self, key: str, func_res: Any) -> bool:
5064 _condition = cond ,
5165 _completed = True ,
5266 )
67+ self .cache .move_to_end (hash_key )
68+ self ._cache_size += size
69+ if self .cache_size_limit is not None :
70+ while self ._cache_size > self .cache_size_limit and self .cache :
71+ old_key , old_entry = self .cache .popitem (last = False )
72+ self ._cache_size -= self ._estimate_size (old_entry .value )
5373 return True
5474
5575 def mark_entry_being_calculated (self , key : str ) -> None :
@@ -101,6 +121,7 @@ def wait_on_entry_calc(self, key: str) -> Any:
101121 def clear_cache (self ) -> None :
102122 with self .lock :
103123 self .cache .clear ()
124+ self ._cache_size = 0
104125
105126 def clear_being_calculated (self ) -> None :
106127 with self .lock :
@@ -116,4 +137,5 @@ def delete_stale_entries(self, stale_after: timedelta) -> None:
116137 k for k , v in self .cache .items () if now - v .time > stale_after
117138 ]
118139 for key in keys_to_delete :
119- del self .cache [key ]
140+ entry = self .cache .pop (key )
141+ self ._cache_size -= self ._estimate_size (entry .value )
0 commit comments