|
|
@@ -38,12 +38,16 @@ class TagCache: |
|
|
|
''' |
|
|
|
|
|
|
|
def __init__(self, tags=(), count=10, **kwargs): |
|
|
|
self._cache = collections.OrderedDict((x, None) for x in tags) |
|
|
|
values = kwargs.pop('values', None) |
|
|
|
if values is None: |
|
|
|
values = itertools.repeat((0, 0)) |
|
|
|
self._cache = collections.OrderedDict(zip(tags, values)) |
|
|
|
self._count = count |
|
|
|
self._modified = False |
|
|
|
|
|
|
|
# λ = ln(2) / t1/2 |
|
|
|
hl = kwargs.pop('half_life', None) |
|
|
|
self._half_life = hl |
|
|
|
self._lambda = None if not hl else math.log(2) / hl |
|
|
|
self._limit = kwargs.pop('limit', self._count) |
|
|
|
|
|
|
@@ -88,6 +92,10 @@ class TagCache: |
|
|
|
|
|
|
|
return self._modified |
|
|
|
|
|
|
|
def _new_values(self, t, oldval, oldtime, addnl): |
|
|
|
return (t, oldval * math.exp(-self._lambda * (t - |
|
|
|
oldtime)) + addnl) |
|
|
|
|
|
|
|
def add(self, tag): |
|
|
|
'''Add a tag (tuple) to the cache, possibly dropping ann older |
|
|
|
tag if necessary.''' |
|
|
@@ -100,8 +108,7 @@ class TagCache: |
|
|
|
|
|
|
|
# N(t) = N0 e^(-λt) |
|
|
|
if self._lambda: |
|
|
|
v = (t, oldval * math.exp(-self._lambda * (t - |
|
|
|
oldtime)) + 1) |
|
|
|
v = self._new_values(t, oldval, oldtime, 1) |
|
|
|
else: |
|
|
|
v = 0, 0 |
|
|
|
|
|
|
@@ -115,8 +122,7 @@ class TagCache: |
|
|
|
t = time.time() |
|
|
|
|
|
|
|
for k, (oldtime, oldval) in self._cache.items(): |
|
|
|
v = (t, oldval * math.exp(-self._lambda * (t - |
|
|
|
oldtime)) + 1) |
|
|
|
v = self._new_values(t, oldval, oldtime, 0) |
|
|
|
self._cache[k] = v |
|
|
|
|
|
|
|
def tags(self): |
|
|
@@ -128,7 +134,8 @@ class TagCache: |
|
|
|
self._cache[x][1], reverse=True)[:self._count]) |
|
|
|
else: |
|
|
|
return sorted(itertools.islice(self._cache.keys(), |
|
|
|
len(self._cache) - self._count, len(self._cache))) |
|
|
|
max(0, len(self._cache) - self._count), |
|
|
|
len(self._cache))) |
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
return 'TagCache(tags=%s, count=%d, limit=%d)' % \ |
|
|
@@ -151,13 +158,24 @@ class TagCache: |
|
|
|
|
|
|
|
return cls(**cache) |
|
|
|
|
|
|
|
def _to_dict(self): |
|
|
|
tags=list(self._cache.keys()) |
|
|
|
|
|
|
|
values = None |
|
|
|
if self._half_life: |
|
|
|
values = [ self._cache[x] for x in tags ] |
|
|
|
|
|
|
|
return dict(tags=tags, count=self._count, |
|
|
|
limit=self._limit, half_life=self._half_life, |
|
|
|
values=values) |
|
|
|
|
|
|
|
def store(self, fname): |
|
|
|
'''Store the cache to fname. The modified property will |
|
|
|
be cleared after this.''' |
|
|
|
|
|
|
|
self._modified = False |
|
|
|
|
|
|
|
cache = dict(tags=list(self._cache.keys()), count=self._count) |
|
|
|
cache = self._to_dict() |
|
|
|
|
|
|
|
with open(fname, 'wb') as fp: |
|
|
|
fp.write(_asn1coder.dumps(cache)) |
|
|
@@ -179,6 +197,30 @@ class _TestTagCache(unittest.TestCase): |
|
|
|
with self.assertRaises(TypeError): |
|
|
|
TagCache(randomkwargs=True) |
|
|
|
|
|
|
|
@unittest.mock.patch('time.time', return_value=5) |
|
|
|
def test_multipletagscalls(self, tt): |
|
|
|
# that a half_life tc. |
|
|
|
|
|
|
|
tcdict = dict(tags=[('foo', 'foo')], half_life=10, |
|
|
|
values=[( 5, 102.3 )], count=5, limit=5) |
|
|
|
|
|
|
|
tc = TagCache(**tcdict) |
|
|
|
|
|
|
|
# when tags is called multiple times |
|
|
|
tc.tags() |
|
|
|
tc.tags() |
|
|
|
tc.tags() |
|
|
|
tc.tags() |
|
|
|
|
|
|
|
# that it doesn't change |
|
|
|
self.assertEqual(tc._to_dict(), tcdict) |
|
|
|
|
|
|
|
@unittest.mock.patch('time.time', side_effect=lambda cnt= |
|
|
|
itertools.count(): next(cnt) * 1.) |
|
|
|
def test_timebackwards(self, tt): |
|
|
|
# test for if/when time goes backwards |
|
|
|
pass |
|
|
|
|
|
|
|
@unittest.mock.patch('time.time', side_effect=lambda cnt= |
|
|
|
itertools.count(): next(cnt) * 1.) |
|
|
|
def test_halflife(self, tt): |
|
|
@@ -198,6 +240,29 @@ class _TestTagCache(unittest.TestCase): |
|
|
|
# XXX - deal with limit better, that is, drop the small value, |
|
|
|
# not the last |
|
|
|
|
|
|
|
@unittest.mock.patch('time.time', side_effect=lambda cnt= |
|
|
|
itertools.count(): next(cnt) * 1.) |
|
|
|
def test_halflife(self, tt): |
|
|
|
tc = TagCache(count=2, limit=10, half_life=10) |
|
|
|
|
|
|
|
# that when it is added twice |
|
|
|
tc.add(('foo', 'foo')) |
|
|
|
tc.add(('foo', 'foo')) |
|
|
|
|
|
|
|
# and saved/reloaded |
|
|
|
cachefile = self.tempdir / 'somecache' |
|
|
|
tc.store(cachefile) |
|
|
|
|
|
|
|
# that it can be loaded |
|
|
|
tc = TagCache.load(cachefile) |
|
|
|
|
|
|
|
# and two others are added |
|
|
|
tc.add(('bar', 'bar')) |
|
|
|
tc.add(('baz', 'baz')) |
|
|
|
|
|
|
|
# it will have preference |
|
|
|
self.assertEqual(tc.tags(), [ ('baz', 'baz'), ('foo', 'foo'), ]) |
|
|
|
|
|
|
|
def test_limit(self): |
|
|
|
tc = TagCache(count=2, limit=3) |
|
|
|
|
|
|
@@ -237,6 +302,8 @@ class _TestTagCache(unittest.TestCase): |
|
|
|
|
|
|
|
tc.add(('foo', 'foo')) |
|
|
|
|
|
|
|
self.assertEqual(tc.tags(), [ ('foo', 'foo') ]) |
|
|
|
|
|
|
|
tc.add(('bar', 'bar')) |
|
|
|
|
|
|
|
tc.add(('baz', 'baz')) |
|
|
|