##// END OF EJS Templates
Address review comments @takluyver
Jonathan Frederic -
Show More
@@ -1,289 +1,292
1 1 """Contains eventful dict and list implementations."""
2 2
3 # void function used as a callback placeholder.
4 def _void(*p, **k): return None
5
3 6 class EventfulDict(dict):
4 7 """Eventful dictionary.
5 8
6 9 This class inherits from the Python intrinsic dictionary class, dict. It
7 10 adds events to the get, set, and del actions and optionally allows you to
8 11 intercept and cancel these actions. The eventfulness isn't recursive. In
9 12 other words, if you add a dict as a child, the events of that dict won't be
10 13 listened to. If you find you need something recursive, listen to the `add`
11 14 and `set` methods, and then cancel `dict` values from being set, and instead
12 15 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
13 16 to the same handlers if necessary.
14 17
15 18 See the on_add, on_set, and on_del methods for registering an event
16 19 handler."""
17 20
18 21 def __init__(self, *args, **kwargs):
19 22 """Public constructor"""
20 self._add_callback = None
21 self._del_callback = None
22 self._set_callback = None
23 self._add_callback = _void
24 self._del_callback = _void
25 self._set_callback = _void
23 26 dict.__init__(self, *args, **kwargs)
24 27
25 28 def on_add(self, callback):
26 29 """Register a callback for when an item is added to the dict.
27 30
28 31 Allows the listener to detect when items are added to the dictionary and
29 32 optionally cancel the addition.
30 33
31 34 callback: callable or None
32 35 If you want to ignore the addition event, pass None as the callback.
33 36 The callback should have a signature of callback(key, value). The
34 37 callback should return a boolean True if the additon should be
35 38 canceled, False or None otherwise."""
39 if callable(callback):
36 40 self._add_callback = callback
41 else:
42 self._add_callback = _void
37 43
38 44 def on_del(self, callback):
39 45 """Register a callback for when an item is deleted from the dict.
40 46
41 47 Allows the listener to detect when items are deleted from the dictionary
42 48 and optionally cancel the deletion.
43 49
44 50 callback: callable or None
45 51 If you want to ignore the deletion event, pass None as the callback.
46 52 The callback should have a signature of callback(key). The
47 53 callback should return a boolean True if the deletion should be
48 54 canceled, False or None otherwise."""
55 if callable(callback):
49 56 self._del_callback = callback
57 else:
58 self._del_callback = _void
50 59
51 60 def on_set(self, callback):
52 61 """Register a callback for when an item is changed in the dict.
53 62
54 63 Allows the listener to detect when items are changed in the dictionary
55 64 and optionally cancel the change.
56 65
57 66 callback: callable or None
58 67 If you want to ignore the change event, pass None as the callback.
59 68 The callback should have a signature of callback(key, value). The
60 69 callback should return a boolean True if the change should be
61 70 canceled, False or None otherwise."""
71 if callable(callback):
62 72 self._set_callback = callback
63
64 def _can_add(self, key, value):
65 """Check if the item can be added to the dict."""
66 if callable(self._add_callback):
67 return not bool(self._add_callback(key, value))
68 else:
69 return True
70
71 def _can_del(self, key):
72 """Check if the item can be deleted from the dict."""
73 if callable(self._del_callback):
74 return not bool(self._del_callback(key))
75 else:
76 return True
77
78 def _can_set(self, key, value):
79 """Check if the item can be changed in the dict."""
80 if callable(self._set_callback):
81 return not bool(self._set_callback(key, value))
82 73 else:
83 return True
74 self._set_callback = _void
84 75
85 76 def pop(self, key):
86 77 """Returns the value of an item in the dictionary and then deletes the
87 78 item from the dictionary."""
88 79 if self._can_del(key):
89 80 return dict.pop(self, key)
90 81 else:
91 82 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
92 83
93 84 def popitem(self):
94 85 """Pop the next key/value pair from the dictionary."""
95 key = dict.keys(self)[0]
86 key = next(iter(self))
96 87 return key, self.pop(key)
97 88
98 89 def update(self, other_dict):
99 90 """Copy the key/value pairs from another dictionary into this dictionary,
100 91 overwriting any conflicting keys in this dictionary."""
101 92 for (key, value) in other_dict.items():
102 93 self[key] = value
103 94
104 95 def clear(self):
105 96 """Clear the dictionary."""
106 97 for key in list(self.keys()):
107 98 del self[key]
108 99
109 100 def __setitem__(self, key, value):
110 101 if (key in self and self._can_set(key, value)) or \
111 102 (key not in self and self._can_add(key, value)):
112 103 return dict.__setitem__(self, key, value)
113 104
114 105 def __delitem__(self, key):
115 106 if self._can_del(key):
116 107 return dict.__delitem__(self, key)
117 108
109 def _can_add(self, key, value):
110 """Check if the item can be added to the dict."""
111 return not bool(self._add_callback(key, value))
112
113 def _can_del(self, key):
114 """Check if the item can be deleted from the dict."""
115 return not bool(self._del_callback(key))
116
117 def _can_set(self, key, value):
118 """Check if the item can be changed in the dict."""
119 return not bool(self._set_callback(key, value))
120
118 121
119 122 class EventfulList(list):
120 123 """Eventful list.
121 124
122 125 This class inherits from the Python intrinsic `list` class. It adds events
123 126 that allow you to listen for actions that modify the list. You can
124 127 optionally cancel the actions.
125 128
126 129 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
127 130 registering an event handler.
128 131
129 132 Some of the method docstrings were taken from the Python documentation at
130 133 https://docs.python.org/2/tutorial/datastructures.html"""
131 134
132 135 def __init__(self, *pargs, **kwargs):
133 136 """Public constructor"""
134 self._insert_callback = None
135 self._set_callback = None
136 self._del_callback = None
137 self._sort_callback = None
138 self._reverse_callback = None
137 self._insert_callback = _void
138 self._set_callback = _void
139 self._del_callback = _void
140 self._sort_callback = _void
141 self._reverse_callback = _void
139 142 list.__init__(self, *pargs, **kwargs)
140 143
141 144 def on_insert(self, callback):
142 145 """Register a callback for when an item is inserted into the list.
143 146
144 147 Allows the listener to detect when items are inserted into the list and
145 148 optionally cancel the insertion.
146 149
147 150 callback: callable or None
148 151 If you want to ignore the insertion event, pass None as the callback.
149 152 The callback should have a signature of callback(index, value). The
150 153 callback should return a boolean True if the insertion should be
151 154 canceled, False or None otherwise."""
155 if callable(callback):
152 156 self._insert_callback = callback
157 else:
158 self._insert_callback = _void
153 159
154 160 def on_del(self, callback):
155 161 """Register a callback for item deletion.
156 162
157 163 Allows the listener to detect when items are deleted from the list and
158 164 optionally cancel the deletion.
159 165
160 166 callback: callable or None
161 167 If you want to ignore the deletion event, pass None as the callback.
162 168 The callback should have a signature of callback(index). The
163 169 callback should return a boolean True if the deletion should be
164 170 canceled, False or None otherwise."""
171 if callable(callback):
165 172 self._del_callback = callback
173 else:
174 self._del_callback = _void
166 175
167 176 def on_set(self, callback):
168 177 """Register a callback for items are set.
169 178
170 179 Allows the listener to detect when items are set and optionally cancel
171 180 the setting. Note, `set` is also called when one or more items are
172 181 added to the end of the list.
173 182
174 183 callback: callable or None
175 184 If you want to ignore the set event, pass None as the callback.
176 185 The callback should have a signature of callback(index, value). The
177 186 callback should return a boolean True if the set should be
178 187 canceled, False or None otherwise."""
188 if callable(callback):
179 189 self._set_callback = callback
190 else:
191 self._set_callback = _void
180 192
181 193 def on_reverse(self, callback):
182 194 """Register a callback for list reversal.
183 195
184 196 callback: callable or None
185 197 If you want to ignore the reverse event, pass None as the callback.
186 198 The callback should have a signature of callback(). The
187 199 callback should return a boolean True if the reverse should be
188 200 canceled, False or None otherwise."""
201 if callable(callback):
189 202 self._reverse_callback = callback
203 else:
204 self._reverse_callback = _void
190 205
191 206 def on_sort(self, callback):
192 207 """Register a callback for sortting of the list.
193 208
194 209 callback: callable or None
195 210 If you want to ignore the sort event, pass None as the callback.
196 211 The callback signature should match that of Python list's `.sort`
197 212 method or `callback(*pargs, **kwargs)` as a catch all. The callback
198 213 should return a boolean True if the reverse should be canceled,
199 214 False or None otherwise."""
215 if callable(callback):
200 216 self._sort_callback = callback
201
202 def _can_insert(self, index, value):
203 """Check if the item can be inserted."""
204 if callable(self._insert_callback):
205 return not bool(self._insert_callback(index, value))
206 217 else:
207 return True
208
209 def _can_del(self, index):
210 """Check if the item can be deleted."""
211 if callable(self._del_callback):
212 return not bool(self._del_callback(index))
213 else:
214 return True
215
216 def _can_set(self, index, value):
217 """Check if the item can be set."""
218 if callable(self._set_callback):
219 return not bool(self._set_callback(index, value))
220 else:
221 return True
222
223 def _can_reverse(self):
224 """Check if the list can be reversed."""
225 if callable(self._reverse_callback):
226 return not bool(self._reverse_callback())
227 else:
228 return True
229
230 def _can_sort(self, *pargs, **kwargs):
231 """Check if the list can be sorted."""
232 if callable(self._sort_callback):
233 return not bool(self._sort_callback(*pargs, **kwargs))
234 else:
235 return True
218 self._sort_callback = _void
236 219
237 220 def append(self, x):
238 221 """Add an item to the end of the list."""
239 222 self[len(self):] = [x]
240 223
241 224 def extend(self, L):
242 225 """Extend the list by appending all the items in the given list."""
243 226 self[len(self):] = L
244 227
245 228 def remove(self, x):
246 229 """Remove the first item from the list whose value is x. It is an error
247 230 if there is no such item."""
248 231 del self[self.index(x)]
249 232
250 233 def pop(self, i=None):
251 234 """Remove the item at the given position in the list, and return it. If
252 235 no index is specified, a.pop() removes and returns the last item in the
253 236 list."""
254 237 if i is None:
255 238 i = len(self) - 1
256 239 val = self[i]
257 240 del self[i]
258 241 return val
259 242
260 243 def reverse(self):
261 244 """Reverse the elements of the list, in place."""
262 245 if self._can_reverse():
263 246 list.reverse(self)
264 247
265 248 def insert(self, index, value):
266 249 """Insert an item at a given position. The first argument is the index
267 250 of the element before which to insert, so a.insert(0, x) inserts at the
268 251 front of the list, and a.insert(len(a), x) is equivalent to
269 252 a.append(x)."""
270 253 if self._can_insert(index, value):
271 254 list.insert(self, index, value)
272 255
273 256 def sort(self, *pargs, **kwargs):
274 257 """Sort the items of the list in place (the arguments can be used for
275 258 sort customization, see Python's sorted() for their explanation)."""
276 259 if self._can_sort(*pargs, **kwargs):
277 260 list.sort(self, *pargs, **kwargs)
278 261
279 262 def __delitem__(self, index):
280 263 if self._can_del(index):
281 264 list.__delitem__(self, index)
282 265
283 266 def __setitem__(self, index, value):
284 267 if self._can_set(index, value):
285 268 list.__setitem__(self, index, value)
286 269
287 270 def __setslice__(self, start, end, value):
288 271 if self._can_set(slice(start, end), value):
289 272 list.__setslice__(self, start, end, value)
273
274 def _can_insert(self, index, value):
275 """Check if the item can be inserted."""
276 return not bool(self._insert_callback(index, value))
277
278 def _can_del(self, index):
279 """Check if the item can be deleted."""
280 return not bool(self._del_callback(index))
281
282 def _can_set(self, index, value):
283 """Check if the item can be set."""
284 return not bool(self._set_callback(index, value))
285
286 def _can_reverse(self):
287 """Check if the list can be reversed."""
288 return not bool(self._reverse_callback())
289
290 def _can_sort(self, *pargs, **kwargs):
291 """Check if the list can be sorted."""
292 return not bool(self._sort_callback(*pargs, **kwargs))
General Comments 0
You need to be logged in to leave comments. Login now