| @@ -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')) | |||