Add expiry seconds option to cacheclass.
This commit is contained in:
parent
593dfe5cb0
commit
166ed18004
1 changed files with 61 additions and 7 deletions
|
@ -1,42 +1,96 @@
|
|||
import collections
|
||||
import time
|
||||
|
||||
from voussoirkit import sentinel
|
||||
|
||||
NO_ITEM = sentinel.Sentinel('no item')
|
||||
|
||||
class Cache:
|
||||
def __init__(self, maxlen):
|
||||
def __init__(self, maxlen, expiry=float('inf')):
|
||||
self.maxlen = maxlen
|
||||
self.expiry = expiry
|
||||
self.cache = collections.OrderedDict()
|
||||
|
||||
# To prevent excessive purge loops during repeated setitem, only allow
|
||||
# a purge once every this many seconds.
|
||||
self.max_purge_frequency = 0.5
|
||||
|
||||
self._last_purge = 0
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.cache
|
||||
return self.get(key, fallback=NO_ITEM) is not NO_ITEM
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = self.cache.pop(key)
|
||||
self.cache[key] = value
|
||||
'''
|
||||
Return the key's value, or raise KeyError.
|
||||
'''
|
||||
# Let KeyError raise to caller.
|
||||
(value, timestamp) = self.cache.pop(key)
|
||||
|
||||
now = time.time()
|
||||
if (now - timestamp) > self.expiry:
|
||||
raise KeyError(key)
|
||||
|
||||
self.cache[key] = (value, timestamp)
|
||||
return value
|
||||
|
||||
def __len__(self):
|
||||
'''
|
||||
Purge expired items, then count the length.
|
||||
Due to the purge, this method is not O(1) as most len methods are.
|
||||
'''
|
||||
self._purge_expired()
|
||||
return len(self.cache)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# If the key was already present, we don't need to worry about maxlen
|
||||
# because the net change is zero. If it was not present (KeyError) we
|
||||
# check the maxlen and pop the oldest item if needed.
|
||||
# Either way we update the timestamp.
|
||||
try:
|
||||
self.cache.pop(key)
|
||||
except KeyError:
|
||||
if len(self.cache) >= self.maxlen:
|
||||
if len(self) >= self.maxlen:
|
||||
self.cache.popitem(last=False)
|
||||
self.cache[key] = value
|
||||
self.cache[key] = (value, time.time())
|
||||
|
||||
def _purge_expired(self):
|
||||
now = time.time()
|
||||
if now - self._last_purge < self.max_purge_frequency:
|
||||
return
|
||||
|
||||
for (key, (value, timestamp)) in list(self.cache.items()):
|
||||
if (now - timestamp) > self.expiry:
|
||||
self.cache.pop(key)
|
||||
|
||||
self._last_purge = now
|
||||
|
||||
def clear(self):
|
||||
'''
|
||||
Remove everything from the cache.
|
||||
'''
|
||||
self.cache.clear()
|
||||
|
||||
def get(self, key, fallback=None):
|
||||
'''
|
||||
Return the key's value, or fallback in case of KeyError.
|
||||
'''
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return fallback
|
||||
|
||||
def pop(self, key):
|
||||
return self.cache.pop(key)
|
||||
'''
|
||||
Remove the key and return its value, or raise KeyError.
|
||||
'''
|
||||
(value, timestamp) = self.cache.pop(key)
|
||||
return value
|
||||
|
||||
def remove(self, key):
|
||||
'''
|
||||
Remove the item and ignore KeyError.
|
||||
'''
|
||||
try:
|
||||
self.pop(key)
|
||||
except KeyError:
|
||||
|
|
Loading…
Reference in a new issue