Skip to content

Commit 608660e

Browse files
author
Michael Brewer
committed
refactor(LRUDict): Setting key to None moves to beginning
Alternative fix by just exposing get, __getitem__ and __setitem__ and when setting to None moves the item to the beginning
1 parent 9a0615d commit 608660e

File tree

3 files changed

+44
-56
lines changed

3 files changed

+44
-56
lines changed

Diff for: aws_lambda_powertools/shared/cache_dict.py

+19-18
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
from collections import OrderedDict
22

33

4-
class LRUDict(OrderedDict):
4+
class LRUDict:
55
"""
66
Cache implementation based on ordered dict with a maximum number of items. Last accessed item will be evicted
7-
first. Currently used by idempotency utility.
7+
first, unless the item is None which will be evicted first. Currently used only by idempotency utility.
88
"""
99

1010
def __init__(self, max_items=1024, *args, **kwargs):
1111
self.max_items = max_items
12-
super().__init__(*args, **kwargs)
12+
self._cache = OrderedDict(*args, **kwargs)
1313

14-
def __getitem__(self, key):
15-
value = super().__getitem__(key)
16-
self.move_to_end(key)
14+
def __getitem__(self, key: str):
15+
value = self._cache.__getitem__(key)
16+
if value:
17+
self._cache.move_to_end(key)
1718
return value
1819

19-
def __setitem__(self, key, value):
20-
if key in self:
21-
self.move_to_end(key)
22-
super().__setitem__(key, value)
23-
if len(self) > self.max_items:
24-
oldest = next(iter(self))
25-
del self[oldest]
20+
def __setitem__(self, key: str, value):
21+
self._cache.__setitem__(key, value)
22+
if key in self._cache:
23+
self._cache.move_to_end(key, last=value is not None)
24+
if len(self._cache) > self.max_items:
25+
oldest = next(iter(self._cache))
26+
del self._cache[oldest]
2627

27-
def get(self, key, *args, **kwargs):
28-
item = super(LRUDict, self).get(key, *args, **kwargs)
29-
if item:
30-
self.move_to_end(key=key)
31-
return item
28+
def get(self, key: str):
29+
value = self._cache.get(key)
30+
if value:
31+
self._cache.move_to_end(key)
32+
return value

Diff for: aws_lambda_powertools/utilities/idempotency/persistence/base.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,7 @@ def _retrieve_from_cache(self, idempotency_key: str):
272272
def _delete_from_cache(self, idempotency_key: str):
273273
if not self.use_local_cache:
274274
return
275-
if idempotency_key in self._cache:
276-
del self._cache[idempotency_key]
275+
self._cache[idempotency_key] = None
277276

278277
def save_success(self, event: Dict[str, Any], result: dict) -> None:
279278
"""

Diff for: tests/unit/test_lru_cache.py

+24-36
Original file line numberDiff line numberDiff line change
@@ -9,72 +9,60 @@
99

1010

1111
@pytest.fixture
12-
def populated_cache():
12+
def populated_cache() -> LRUDict:
1313
cache_dict = LRUDict(max_items=MAX_CACHE_ITEMS, **{f"key_{i}": f"val_{i}" for i in range(0, PREFILL_CACHE_ITEMS)})
1414
return cache_dict
1515

1616

17-
def test_cache_order_init(populated_cache):
18-
first_item = list(populated_cache)[0]
19-
last_item = list(populated_cache)[-1]
17+
def test_cache_order_init(populated_cache: LRUDict):
18+
first_item = list(populated_cache._cache)[0]
19+
last_item = list(populated_cache._cache)[-1]
2020

2121
assert first_item == "key_0"
2222
assert last_item == f"key_{MAX_CACHE_ITEMS - 1}"
2323

2424

25-
def test_cache_order_getitem(populated_cache):
25+
def test_cache_order_getitem(populated_cache: LRUDict):
2626
random_value = random.randrange(0, MAX_CACHE_ITEMS)
2727
_ = populated_cache[f"key_{random_value}"]
2828

29-
last_item = list(populated_cache)[-1]
29+
last_item = list(populated_cache._cache)[-1]
3030

3131
assert last_item == f"key_{random_value}"
3232

3333

34-
def test_cache_order_get(populated_cache):
34+
def test_cache_order_get(populated_cache: LRUDict):
3535
random_value = random.randrange(0, MAX_CACHE_ITEMS)
3636
_ = populated_cache.get(f"key_{random_value}")
3737

38-
last_item = list(populated_cache)[-1]
38+
last_item = list(populated_cache._cache)[-1]
3939

4040
assert last_item == f"key_{random_value}"
4141

4242

43-
def test_cache_evict_over_max_items(populated_cache):
44-
assert "key_0" in populated_cache
45-
assert len(populated_cache) == MAX_CACHE_ITEMS
43+
def test_cache_evict_over_max_items(populated_cache: LRUDict):
44+
assert "key_0" in populated_cache._cache
45+
assert len(populated_cache._cache) == MAX_CACHE_ITEMS
4646
populated_cache["new_item"] = "new_value"
47-
assert len(populated_cache) == MAX_CACHE_ITEMS
48-
assert "key_0" not in populated_cache
49-
assert "key_1" in populated_cache
47+
assert len(populated_cache._cache) == MAX_CACHE_ITEMS
48+
assert "key_0" not in populated_cache._cache
49+
assert "key_1" in populated_cache._cache
5050

5151

52-
def test_setitem_moves_to_end(populated_cache):
52+
def test_setitem_moves_to_end(populated_cache: LRUDict):
5353
random_value = random.randrange(0, MAX_CACHE_ITEMS)
5454
populated_cache[f"key_{random_value}"] = f"new_val_{random_value}"
55-
last_item = list(populated_cache)[-1]
55+
last_item = list(populated_cache._cache)[-1]
5656

5757
assert last_item == f"key_{random_value}"
5858
assert populated_cache[f"key_{random_value}"] == f"new_val_{random_value}"
5959

6060

61-
def test_lru_pop_failing():
62-
cache = LRUDict()
63-
key = "test"
64-
cache[key] = "value"
65-
try:
66-
cache.pop(key, None)
67-
pytest.fail("GitHub #300: LRUDict pop bug has been fixed :)")
68-
except KeyError as e:
69-
assert e.args[0] == key
70-
71-
72-
def test_lru_del():
73-
cache = LRUDict()
74-
key = "test"
75-
cache[key] = "value"
76-
assert len(cache) == 1
77-
if key in cache:
78-
del cache[key]
79-
assert key not in cache
80-
assert len(cache) == 0
61+
def test_setitem_none_not_moved(populated_cache: LRUDict):
62+
populated_cache["value_is_none"] = None
63+
64+
first_item = list(populated_cache._cache)[0]
65+
last_item = list(populated_cache._cache)[-1]
66+
67+
assert first_item == "key_0"
68+
assert last_item == f"key_{MAX_CACHE_ITEMS - 1}"

0 commit comments

Comments
 (0)