1
+ import math
1
2
from abc import abstractmethod
2
3
from typing import Any , Callable , ClassVar , Iterable
3
4
4
5
from replete .consistent_hash import consistent_hash
5
6
6
7
from class_cache .backends import SQLiteBackend
8
+ from class_cache .lru_queue import LRUQueue
7
9
from class_cache .types import CacheInterface , IdType , KeyType , ValueType
8
10
9
11
DEFAULT_BACKEND_TYPE = SQLiteBackend
10
12
11
13
12
14
class Cache (CacheInterface [KeyType , ValueType ]):
15
+ """
16
+ :param max_items: Maximum number of items to keep in memory
17
+ :param flush_ratio: Amount of stored items to write to backend when memory if full
18
+ ceiling will be used to calculate the final amount
19
+ """
20
+
13
21
def __init__ (
14
22
self ,
15
23
id_ : IdType = None ,
16
24
backend_type : type [CacheInterface ] | Callable [[IdType ], CacheInterface ] = DEFAULT_BACKEND_TYPE ,
17
25
max_items = 128 ,
26
+ * ,
27
+ flush_ratio = 0.1 ,
18
28
) -> None :
19
29
super ().__init__ (id_ )
20
30
self ._backend = backend_type (id_ )
21
- # TODO: Implement max_size logic
31
+
32
+ self ._max_items = max_items
33
+ self ._flush_amount = math .ceil (self ._max_items * flush_ratio )
34
+ self ._lru_queue = LRUQueue ()
35
+
22
36
self ._data : dict [KeyType , ValueType ] = {}
23
37
self ._to_write = set ()
24
38
self ._to_delete = set ()
25
- self ._max_items = max_items
26
39
27
40
@property
28
41
def backend (self ) -> CacheInterface :
29
42
return self ._backend
30
43
31
44
def __contains__ (self , key : KeyType ) -> bool :
32
45
if key in self ._data :
46
+ self ._lru_queue .update (key )
33
47
return True
34
48
return key not in self ._to_delete and key in self ._backend
35
49
36
50
def __setitem__ (self , key : KeyType , value : ValueType ) -> None :
37
51
self ._data [key ] = value
38
52
self ._to_write .add (key )
53
+ self ._lru_queue .update (key )
54
+ self ._check_max_items ()
39
55
40
56
def __getitem__ (self , key : KeyType ) -> ValueType :
41
57
if key not in self ._data :
42
58
self ._data [key ] = self ._backend [key ]
59
+ self ._check_max_items ()
60
+ self ._lru_queue .update (key )
43
61
return self ._data [key ]
44
62
45
63
def __iter__ (self ) -> Iterable [KeyType ]:
@@ -54,6 +72,8 @@ def __delitem__(self, key: KeyType) -> None:
54
72
# Check that key is present. Can't check self._data, since it can be unloaded
55
73
if key not in self :
56
74
raise KeyError (key )
75
+ if key in self ._data :
76
+ del self ._lru_queue [key ]
57
77
self ._data .pop (key , None )
58
78
self ._to_delete .add (key )
59
79
@@ -69,8 +89,20 @@ def clear(self) -> None:
69
89
self ._data = {}
70
90
self ._to_write = set ()
71
91
self ._to_delete = set ()
92
+ self ._lru_queue .clear ()
93
+
94
+ def _check_max_items (self ) -> None :
95
+ if len (self ._data ) <= self ._max_items :
96
+ return
97
+
98
+ keys_to_free = self ._lru_queue .pop_many (self ._flush_amount )
99
+ if any (key in self ._to_write for key in keys_to_free ):
100
+ self .write ()
101
+ for key in keys_to_free :
102
+ self ._data .pop (key )
72
103
73
104
105
+ # TODO: Refactor this, this should use composition, not inheritance. Maybe a wrapper.
74
106
class CacheWithDefault (Cache [KeyType , ValueType ]):
75
107
VERSION = 0
76
108
NON_HASH_ATTRIBUTES : ClassVar [frozenset [str ]] = frozenset (
0 commit comments