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