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