Skip to content

Commit

Permalink
Fix small eviction bug; add the ability to track evictions
Browse files Browse the repository at this point in the history
  • Loading branch information
pts-csheppard committed Apr 27, 2018
1 parent 7500480 commit 824788f
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
17 changes: 14 additions & 3 deletions expiringdict/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@


class ExpiringDict(OrderedDict):
def __init__(self, max_len, max_age_seconds):
def __init__(self, max_len, max_age_seconds, eviction_counter=None):
assert max_age_seconds >= 0
assert max_len >= 1

OrderedDict.__init__(self)
self.max_len = max_len
self.max_age = max_age_seconds
self.evictions = eviction_counter
self.lock = RLock()

if sys.version_info >= (3, 5):
Expand Down Expand Up @@ -74,12 +75,15 @@ def __getitem__(self, key, with_age=False):
def __setitem__(self, key, value):
""" Set d[key] to value. """
with self.lock:
if len(self) == self.max_len:
OrderedDict.__setitem__(self, key, (value, time.time()))
original_overage = len(self) - self.max_len
while len(self) > self.max_len:
try:
self.popitem(last=False)
except KeyError:
pass
OrderedDict.__setitem__(self, key, (value, time.time()))
if self.evictions is not None:
self.evictions += original_overage

def pop(self, key, default=None):
""" Get item from the dict and remove it.
Expand Down Expand Up @@ -137,6 +141,13 @@ def values(self):
pass
return r

def reset_evictions(self, new_value=0):
""" Reset the eviction count. Returns the current value """
with self.lock:
evictions = self.evictions
self.evictions = new_value
return evictions

def fromkeys(self):
" Create a new dictionary with keys from seq and values set to value. "
raise NotImplementedError()
Expand Down
37 changes: 37 additions & 0 deletions tests/expiringdict_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def test_repr():
sleep(0.01)
eq_(str(d), "ExpiringDict([])")

def test_eviction_on_wrong_count():
d = ExpiringDict(max_len=3, max_age_seconds=20)
d['a'] = 'x'
d['b'] = 'y'
d['c'] = 'z'
d['b'] = 'y'
eq_([k for k in d], ['a', 'b', 'c'])
eq_([k for k in d.values()], ['x', 'y', 'z'])

def test_iter():
d = ExpiringDict(max_len=10, max_age_seconds=0.01)
Expand Down Expand Up @@ -113,6 +121,35 @@ def test_setdefault():
eq_('y', d.setdefault('a', 'y'))


def test_eviction_counting():
d = ExpiringDict(max_len=1, max_age_seconds=100, eviction_counter=0)

d['a'] = 'A'
d['b'] = 'B'
d['c'] = 'C'

eq_(len(d), 1)
eq_(d.evictions, 2)


def test_eviction_reset():
d = ExpiringDict(max_len=1, max_age_seconds=100, eviction_counter=0)

d['a'] = 'A'
d['b'] = 'B'
d['c'] = 'C'

current_eviction_count = d.reset_evictions()
eq_(len(d), 1)
eq_(current_eviction_count, 2)
eq_(d.evictions, 0)
check_eviction_count = d.reset_evictions(1) # anything that supports +=
d['d'] = 'D'
eq_(len(d), 1)
eq_(check_eviction_count, 0) # no operations before reset
eq_(d.evictions, 2) # since we started at 1


def test_not_implemented():
d = ExpiringDict(max_len=10, max_age_seconds=10)
assert_raises(NotImplementedError, d.fromkeys)
Expand Down

0 comments on commit 824788f

Please sign in to comment.