From 204233629e32fc8e7d552b90a6c9607e01d395ba Mon Sep 17 00:00:00 2001 From: John-Mark Gurney Date: Thu, 23 Feb 2023 16:01:47 -0800 Subject: [PATCH] better halflife support, fix some minor bugs.. --- ui/medashare/tags.py | 81 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/ui/medashare/tags.py b/ui/medashare/tags.py index 031cd5a..a4782b8 100644 --- a/ui/medashare/tags.py +++ b/ui/medashare/tags.py @@ -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'))