diff --git a/src/cachetools/__init__.py b/src/cachetools/__init__.py index 1b299c5..53739aa 100644 --- a/src/cachetools/__init__.py +++ b/src/cachetools/__init__.py @@ -625,10 +625,26 @@ def __getitem__(self, key, cache_getitem=Cache.__getitem__): else: return cache_getitem(self, key) - def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + def __setitem__( + self, + key, + value, + cache_setitem=Cache.__setitem__, + cache_delitem=Cache.__delitem__, + ): with self.timer as time: expires = self.__ttu(key, value, time) if not (time < expires): + # The new value is already expired, so it is not stored. + # If the key still holds a previous (possibly still valid) + # value, drop it as well -- otherwise the assignment would + # be silently ignored and the stale value would persist. + try: + self.__items.pop(key).removed = True + except KeyError: + pass + else: + cache_delitem(self, key) return # skip expired items self.expire(time) cache_setitem(self, key, value) diff --git a/tests/test_tlru.py b/tests/test_tlru.py index b1263d7..b0dac65 100644 --- a/tests/test_tlru.py +++ b/tests/test_tlru.py @@ -144,6 +144,26 @@ def ttu(_k, _v, t): self.assertEqual(cache[4], 4) self.assertEqual(cache[5], 5) + def test_overwrite_existing_with_expired(self): + # Overwriting an existing key with an already-expired value must not + # silently retain the previous value: the assignment cannot store the + # new (dead-on-arrival) value, so the key has to be evicted instead. + def ttu(_k, value, t): + return t + value + + cache = TLRUCache[int, int, int](maxsize=2, ttu=ttu, timer=Timer()) + cache[1] = 5 + self.assertEqual(cache[1], 5) + self.assertEqual(len(cache), 1) + self.assertEqual(cache.currsize, 1) + + cache[1] = 0 # ttu -> t, i.e. not (t < t): immediately expired + self.assertNotIn(1, cache) + self.assertIsNone(cache.get(1)) + self.assertEqual(len(cache), 0) + self.assertEqual(cache.currsize, 0) + self.assertEqual(set(cache), set()) + def test_ttu_expire(self): def ttu(_k, _v, t): return t + 3