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