| @@ -38,12 +38,16 @@ class TagCache: | |||||
| ''' | ''' | ||||
| def __init__(self, tags=(), count=10, **kwargs): | 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._count = count | ||||
| self._modified = False | self._modified = False | ||||
| # λ = ln(2) / t1/2 | # λ = ln(2) / t1/2 | ||||
| hl = kwargs.pop('half_life', None) | hl = kwargs.pop('half_life', None) | ||||
| self._half_life = hl | |||||
| self._lambda = None if not hl else math.log(2) / hl | self._lambda = None if not hl else math.log(2) / hl | ||||
| self._limit = kwargs.pop('limit', self._count) | self._limit = kwargs.pop('limit', self._count) | ||||
| @@ -88,6 +92,10 @@ class TagCache: | |||||
| return self._modified | 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): | def add(self, tag): | ||||
| '''Add a tag (tuple) to the cache, possibly dropping ann older | '''Add a tag (tuple) to the cache, possibly dropping ann older | ||||
| tag if necessary.''' | tag if necessary.''' | ||||
| @@ -100,8 +108,7 @@ class TagCache: | |||||
| # N(t) = N0 e^(-λt) | # N(t) = N0 e^(-λt) | ||||
| if self._lambda: | if self._lambda: | ||||
| v = (t, oldval * math.exp(-self._lambda * (t - | |||||
| oldtime)) + 1) | |||||
| v = self._new_values(t, oldval, oldtime, 1) | |||||
| else: | else: | ||||
| v = 0, 0 | v = 0, 0 | ||||
| @@ -115,8 +122,7 @@ class TagCache: | |||||
| t = time.time() | t = time.time() | ||||
| for k, (oldtime, oldval) in self._cache.items(): | 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 | self._cache[k] = v | ||||
| def tags(self): | def tags(self): | ||||
| @@ -128,7 +134,8 @@ class TagCache: | |||||
| self._cache[x][1], reverse=True)[:self._count]) | self._cache[x][1], reverse=True)[:self._count]) | ||||
| else: | else: | ||||
| return sorted(itertools.islice(self._cache.keys(), | 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): | def __repr__(self): | ||||
| return 'TagCache(tags=%s, count=%d, limit=%d)' % \ | return 'TagCache(tags=%s, count=%d, limit=%d)' % \ | ||||
| @@ -151,13 +158,24 @@ class TagCache: | |||||
| return cls(**cache) | 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): | def store(self, fname): | ||||
| '''Store the cache to fname. The modified property will | '''Store the cache to fname. The modified property will | ||||
| be cleared after this.''' | be cleared after this.''' | ||||
| self._modified = False | self._modified = False | ||||
| cache = dict(tags=list(self._cache.keys()), count=self._count) | |||||
| cache = self._to_dict() | |||||
| with open(fname, 'wb') as fp: | with open(fname, 'wb') as fp: | ||||
| fp.write(_asn1coder.dumps(cache)) | fp.write(_asn1coder.dumps(cache)) | ||||
| @@ -179,6 +197,30 @@ class _TestTagCache(unittest.TestCase): | |||||
| with self.assertRaises(TypeError): | with self.assertRaises(TypeError): | ||||
| TagCache(randomkwargs=True) | 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= | @unittest.mock.patch('time.time', side_effect=lambda cnt= | ||||
| itertools.count(): next(cnt) * 1.) | itertools.count(): next(cnt) * 1.) | ||||
| def test_halflife(self, tt): | def test_halflife(self, tt): | ||||
| @@ -198,6 +240,29 @@ class _TestTagCache(unittest.TestCase): | |||||
| # XXX - deal with limit better, that is, drop the small value, | # XXX - deal with limit better, that is, drop the small value, | ||||
| # not the last | # 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): | def test_limit(self): | ||||
| tc = TagCache(count=2, limit=3) | tc = TagCache(count=2, limit=3) | ||||
| @@ -237,6 +302,8 @@ class _TestTagCache(unittest.TestCase): | |||||
| tc.add(('foo', 'foo')) | tc.add(('foo', 'foo')) | ||||
| self.assertEqual(tc.tags(), [ ('foo', 'foo') ]) | |||||
| tc.add(('bar', 'bar')) | tc.add(('bar', 'bar')) | ||||
| tc.add(('baz', 'baz')) | tc.add(('baz', 'baz')) | ||||