Index: client/third_party/cachetools/ttl.py |
diff --git a/client/third_party/cachetools/ttl.py b/client/third_party/cachetools/ttl.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d20cc0b22529836bbba26c53fcf4611f3c2bf572 |
--- /dev/null |
+++ b/client/third_party/cachetools/ttl.py |
@@ -0,0 +1,217 @@ |
+from __future__ import absolute_import |
+ |
+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.time, missing=None, |
+ getsizeof=None): |
+ Cache.__init__(self, maxsize, missing, 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: |
+ raise KeyError('%s is empty' % self.__class__.__name__) |
+ else: |
+ return (key, self.pop(key)) |
+ |
+ if hasattr(collections.OrderedDict, 'move_to_end'): |
+ def __getlink(self, key): |
+ value = self.__links[key] |
+ self.__links.move_to_end(key) |
+ return value |
+ else: |
+ def __getlink(self, key): |
+ value = self.__links.pop(key) |
+ self.__links[key] = value |
+ return value |