Skip to content

Commit 137cc45

Browse files
committed
WIP
1 parent 865eb2f commit 137cc45

File tree

5 files changed

+307
-99
lines changed

5 files changed

+307
-99
lines changed

aiomisc/cache/base.py

Lines changed: 17 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,29 @@
1-
import asyncio
2-
import threading
31
from abc import ABC, abstractmethod
4-
from typing import Any, Union, Hashable, Dict, Optional
5-
6-
from llist import dllist, dllistnode
2+
from typing import Any, Hashable
73

84

95
class CacheBase(ABC):
10-
def __init__(self, max_size: int = 0):
11-
self._max_size: int = max_size
12-
self._loop: Optional[asyncio.AbstractEventLoop] = None
13-
self.usages: dllist = dllist()
14-
self.cache: Dict[Hashable, Any] = dict()
15-
self.lock = threading.RLock()
16-
17-
@property
18-
def is_overflow(self) -> bool:
19-
if self._max_size == 0:
20-
return False
21-
22-
if self._max_size < len(self.usages):
23-
return True
6+
__slots__ = "max_size",
247

25-
return False
26-
27-
@property
28-
def loop(self) -> asyncio.AbstractEventLoop:
29-
if self._loop is None:
30-
self._loop = asyncio.get_event_loop()
31-
return self._loop
8+
def __init__(self, max_size: int = 0):
9+
self.max_size = max_size
3210

3311
@abstractmethod
34-
def _on_set(self, node: dllistnode) -> None:
35-
pass
36-
37-
def _on_expires(self, node: dllistnode) -> None:
38-
pass
12+
def get(self, key: Hashable):
13+
raise NotImplementedError
3914

4015
@abstractmethod
41-
def _on_get(self, node: dllistnode) -> Any:
42-
pass
43-
44-
def __contains__(self, item: Hashable) -> bool:
45-
return item in self.cache
16+
def remove(self, key: Hashable):
17+
raise NotImplementedError
4618

47-
def get(self, item: Hashable) -> Any:
48-
with self.lock:
49-
node: dllistnode = self.cache[item]
50-
self.loop.call_soon(self._on_get, node)
51-
return node.value[1]
52-
53-
def expire(self, node: dllistnode):
54-
with self.lock:
55-
item, value = node.value
56-
node: Optional[dllistnode] = self.cache.pop(item, None)
57-
58-
if node is None:
59-
return
60-
61-
self.loop.call_soon(self._on_expires, node)
62-
self.usages.remove(node)
63-
64-
def set(self, item: Hashable, value: Any,
65-
expiration: Union[int, float] = None) -> None:
66-
with self.lock:
67-
node: dllistnode = self.usages.append((item, value))
68-
self.cache[item] = node
19+
@abstractmethod
20+
def set(self, key: Hashable, value: Any):
21+
raise NotImplementedError
6922

70-
self.loop.call_soon(self._on_set, node)
23+
@abstractmethod
24+
def __contains__(self, key: Hashable):
25+
raise NotImplementedError
7126

72-
if expiration is not None:
73-
self.loop.call_later(expiration, self.expire, node)
27+
@abstractmethod
28+
def __len__(self):
29+
raise NotImplementedError

aiomisc/cache/lfu.py

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from dataclasses import dataclass
21
from threading import RLock
3-
from typing import Any, Hashable, Optional, Set
2+
from typing import Any, Hashable, Optional, Dict, Set
3+
from aiomisc.cache.base import CacheBase
44

55

6+
# noinspection PyShadowingBuiltins
67
class Node:
7-
__slots__ = ('prev', 'next', 'items')
8+
__slots__ = 'prev', 'next', 'items'
89

910
def __init__(self, prev: "Node" = None, next: "Node" = None,
1011
items: Optional[Set["Item"]] = None):
@@ -13,14 +14,16 @@ def __init__(self, prev: "Node" = None, next: "Node" = None,
1314
self.items = items or set()
1415

1516

16-
@dataclass(frozen=True)
1717
class Item:
18-
node: Node
19-
key: Hashable
20-
value: Any
18+
__slots__ = 'node', 'key', 'value'
2119

20+
def __init__(self, node: Node, key: Hashable, value: Any):
21+
self.node: Node = node
22+
self.key: Hashable = key
23+
self.value: Any = value
2224

23-
class LFUCache:
25+
26+
class LFUCache(CacheBase):
2427
"""
2528
LFU cache implementation
2629
@@ -29,20 +32,37 @@ class LFUCache:
2932
>>> assert "foo" in lfu
3033
>>> lfu.get('foo')
3134
'bar'
35+
36+
>>> lfu.remove('foo')
37+
>>> assert "foo" not in lfu
38+
>>> lfu.get("foo")
39+
Traceback (most recent call last):
40+
...
41+
KeyError: 'foo'
42+
>>> lfu.remove("foo")
43+
3244
>>> lfu.set("bar", "foo")
3345
>>> lfu.set("spam", "egg")
34-
46+
>>> lfu.set("foo", "bar")
47+
>>> lfu.get("foo")
48+
'bar'
49+
>>> lfu.get("spam")
50+
'egg'
51+
>>> assert len(lfu) == 3
52+
>>> lfu.set("egg", "spam")
53+
>>> assert len(lfu) == 3
3554
"""
3655

37-
def __init__(self, max_size: int = 0):
38-
self.cache = dict()
39-
self.usages: Node = Node(prev=None, next=None, items=set())
40-
self.lock = RLock()
41-
self.size = 0
42-
self.max_size = max_size
56+
__slots__ = "cache", "usages", "lock"
57+
58+
def __init__(self, max_size: int):
59+
super().__init__(max_size)
60+
self.cache: Dict[Hashable, Item] = dict()
61+
self.usages: Node = Node(prev=None, next=None)
62+
self.lock: RLock = RLock()
4363

4464
def _create_node(self) -> Node:
45-
node = Node(prev=self.usages, next=None, items=set())
65+
node = Node(prev=self.usages, next=None)
4666
self.usages.next = node
4767
return node
4868

@@ -55,11 +75,7 @@ def _update_usage(self, item: Item):
5575
new_node = self._create_node()
5676

5777
old_node.items.remove(item)
58-
item = Item(
59-
node=new_node,
60-
key=item.key,
61-
value=item.value,
62-
)
78+
item.node = new_node
6379
new_node.items.add(item)
6480
self.cache[item.key] = item
6581

@@ -69,11 +85,29 @@ def _update_usage(self, item: Item):
6985
self.usages = new_node
7086
self.usages.prev = None
7187

88+
def _remove_item(self, item: Item):
89+
with self.lock:
90+
if item.key in self.cache:
91+
self.cache.pop(item.key, None)
92+
93+
if item in item.node.items:
94+
item.node.items.remove(item)
95+
96+
item.node = None
97+
7298
def get(self, key: Hashable):
7399
item: Item = self.cache[key]
74100
self._update_usage(item)
75101
return item.value
76102

103+
def remove(self, key: Hashable):
104+
with self.lock:
105+
item: Optional[Item] = self.cache.pop(key, None)
106+
if item is None:
107+
return
108+
109+
self._remove_item(item)
110+
77111
def set(self, key: Hashable, value: Any):
78112
with self.lock:
79113
node: Optional[Node] = self.usages
@@ -85,8 +119,30 @@ def set(self, key: Hashable, value: Any):
85119
node.items.add(item)
86120
self.cache[key] = item
87121

88-
def __contains__(self, key) -> Any:
122+
if self._is_overflow():
123+
self._on_overflow()
124+
125+
def _on_overflow(self):
126+
with self.lock:
127+
while self._is_overflow():
128+
if not self.usages.items:
129+
if self.usages.next is not None:
130+
self.usages.next.prev = None
131+
self.usages = self.usages.next
132+
else:
133+
self.usages = Node(prev=None, next=None)
134+
135+
item = self.usages.items.pop()
136+
self._remove_item(item)
137+
138+
def _is_overflow(self) -> bool:
139+
return len(self.cache) > self.max_size
140+
141+
def __contains__(self, key: Hashable) -> Any:
89142
if key in self.cache:
90143
self._update_usage(self.cache[key])
91144
return True
92145
return False
146+
147+
def __len__(self):
148+
return len(self.cache)

0 commit comments

Comments
 (0)