1 from __future__ import division
5 from twisted.internet import task
8 def __init__(self, contents, prev=None, next=None):
9 self.contents, self.prev, self.next = contents, prev, next
11 def insert_before(self, contents):
12 self.prev.next = self.prev = node = Node(contents, self.prev, self)
15 def insert_after(self, contents):
16 self.next.prev = self.next = node = Node(contents, self, self.next)
20 def connect(prev, next):
21 if prev.next is not None or next.prev is not None:
22 raise ValueError('node already connected')
23 prev.next, next.prev = next, prev
25 def replace(self, contents):
26 self.contents = contents
29 if self.prev.next is None or self.next.prev is None:
30 raise ValueError('node not connected')
31 self.prev.next, self.next.prev = self.next, self.prev
32 self.next = self.prev = None
35 class LinkedList(object):
36 def __init__(self, iterable=[]):
37 self.start, self.end = Node(None), Node(None)
38 Node.connect(self.start, self.end)
44 return 'LinkedList(%r)' % (list(self),)
47 return sum(1 for x in self)
51 while cur is not self.end:
54 yield cur2 # in case cur is deleted, but items inserted after are ignored
56 def __reversed__(self):
58 while cur is not self.start:
63 def __getitem__(self, index):
66 for i in xrange(-index):
69 raise IndexError('index out of range')
72 for i in xrange(index + 1):
75 raise IndexError('index out of range')
78 def appendleft(self, item):
79 return self.start.insert_after(item)
81 def append(self, item):
82 return self.end.insert_before(item)
85 node = self.start.next
87 raise IndexError('popleft from empty')
93 if node is self.start:
94 raise IndexError('pop from empty')
99 class ExpiringDict(object):
100 def __init__(self, expiry_time, get_touches=True):
101 self.expiry_time = expiry_time
102 self.get_touches = get_touches
104 self.expiry_deque = LinkedList()
105 self.d = dict() # key -> node, value
106 self._expire_loop = task.LoopingCall(self.expire)
107 self._expire_loop.start(1) # XXX use inlinecallbacks and stop expiring at some point
110 self._expire_loop.stop()
113 return 'ExpiringDict' + repr(self.__dict__)
119 def touch(self, key, value=_nothing):
120 'Updates expiry node, optionally replacing value, returning new value'
121 if value is self._nothing or key in self.d:
122 node, old_value = self.d[key]
125 new_value = old_value if value is self._nothing else value
126 self.d[key] = self.expiry_deque.append((time.time() + self.expiry_time, key)), new_value
131 for node in self.expiry_deque:
132 timestamp, key = node.contents
138 def __contains__(self, key):
141 def __getitem__(self, key):
143 value = self.touch(key)
145 node, value = self.d[key]
148 def __setitem__(self, key, value):
149 self.touch(key, value)
151 def __delitem__(self, key):
152 node, value = self.d.pop(key)
155 def get(self, key, default_value=None):
162 def setdefault(self, key, default_value):
166 self[key] = default_value
173 return [value for node, value in self.d.itervalues()]
175 def itervalues(self):
176 for node, value in self.d.itervalues():
179 if __name__ == '__main__':