|
|
- import collections
- import time
-
- from .cache import Cache
-
-
- class _Link(object):
-
- __slots__ = ('key', 'expire', 'next', 'prev')
-
- def __init__(self, key=None, expire=None):
- self.key = key
- self.expire = expire
-
- def __reduce__(self):
- return _Link, (self.key, self.expire)
-
- def unlink(self):
- next = self.next
- prev = self.prev
- prev.next = next
- next.prev = prev
-
-
- class _Timer(object):
-
- def __init__(self, timer):
- self.__timer = timer
- self.__nesting = 0
-
- def __call__(self):
- if self.__nesting == 0:
- return self.__timer()
- else:
- return self.__time
-
- def __enter__(self):
- if self.__nesting == 0:
- self.__time = time = self.__timer()
- else:
- time = self.__time
- self.__nesting += 1
- return time
-
- def __exit__(self, *exc):
- self.__nesting -= 1
-
- def __reduce__(self):
- return _Timer, (self.__timer,)
-
- def __getattr__(self, name):
- return getattr(self.__timer, name)
-
-
- class TTLCache(Cache):
- """LRU Cache implementation with per-item time-to-live (TTL) value."""
-
- def __init__(self, maxsize, ttl, timer=time.monotonic, getsizeof=None):
- Cache.__init__(self, maxsize, getsizeof)
- self.__root = root = _Link()
- root.prev = root.next = root
- self.__links = collections.OrderedDict()
- self.__timer = _Timer(timer)
- self.__ttl = ttl
-
- def __contains__(self, key):
- try:
- link = self.__links[key] # no reordering
- except KeyError:
- return False
- else:
- return not (link.expire < self.__timer())
-
- def __getitem__(self, key, cache_getitem=Cache.__getitem__):
- try:
- link = self.__getlink(key)
- except KeyError:
- expired = False
- else:
- expired = link.expire < self.__timer()
- if expired:
- return self.__missing__(key)
- else:
- return cache_getitem(self, key)
-
- def __setitem__(self, key, value, cache_setitem=Cache.__setitem__):
- with self.__timer as time:
- self.expire(time)
- cache_setitem(self, key, value)
- try:
- link = self.__getlink(key)
- except KeyError:
- self.__links[key] = link = _Link(key)
- else:
- link.unlink()
- link.expire = time + self.__ttl
- link.next = root = self.__root
- link.prev = prev = root.prev
- prev.next = root.prev = link
-
- def __delitem__(self, key, cache_delitem=Cache.__delitem__):
- cache_delitem(self, key)
- link = self.__links.pop(key)
- link.unlink()
- if link.expire < self.__timer():
- raise KeyError(key)
-
- def __iter__(self):
- root = self.__root
- curr = root.next
- while curr is not root:
- # "freeze" time for iterator access
- with self.__timer as time:
- if not (curr.expire < time):
- yield curr.key
- curr = curr.next
-
- def __len__(self):
- root = self.__root
- curr = root.next
- time = self.__timer()
- count = len(self.__links)
- while curr is not root and curr.expire < time:
- count -= 1
- curr = curr.next
- return count
-
- def __setstate__(self, state):
- self.__dict__.update(state)
- root = self.__root
- root.prev = root.next = root
- for link in sorted(self.__links.values(), key=lambda obj: obj.expire):
- link.next = root
- link.prev = prev = root.prev
- prev.next = root.prev = link
- self.expire(self.__timer())
-
- def __repr__(self, cache_repr=Cache.__repr__):
- with self.__timer as time:
- self.expire(time)
- return cache_repr(self)
-
- @property
- def currsize(self):
- with self.__timer as time:
- self.expire(time)
- return super(TTLCache, self).currsize
-
- @property
- def timer(self):
- """The timer function used by the cache."""
- return self.__timer
-
- @property
- def ttl(self):
- """The time-to-live value of the cache's items."""
- return self.__ttl
-
- def expire(self, time=None):
- """Remove expired items from the cache."""
- if time is None:
- time = self.__timer()
- root = self.__root
- curr = root.next
- links = self.__links
- cache_delitem = Cache.__delitem__
- while curr is not root and curr.expire < time:
- cache_delitem(self, curr.key)
- del links[curr.key]
- next = curr.next
- curr.unlink()
- curr = next
-
- def clear(self):
- with self.__timer as time:
- self.expire(time)
- Cache.clear(self)
-
- def get(self, *args, **kwargs):
- with self.__timer:
- return Cache.get(self, *args, **kwargs)
-
- def pop(self, *args, **kwargs):
- with self.__timer:
- return Cache.pop(self, *args, **kwargs)
-
- def setdefault(self, *args, **kwargs):
- with self.__timer:
- return Cache.setdefault(self, *args, **kwargs)
-
- def popitem(self):
- """Remove and return the `(key, value)` pair least recently used that
- has not already expired.
-
- """
- with self.__timer as time:
- self.expire(time)
- try:
- key = next(iter(self.__links))
- except StopIteration:
- msg = '%s is empty' % self.__class__.__name__
- raise KeyError(msg) from None
- else:
- return (key, self.pop(key))
-
- def __getlink(self, key):
- value = self.__links[key]
- self.__links.move_to_end(key)
- return value
|