##// END OF EJS Templates
Merge master
Gordon Ball -
r17684:d1de54f3 merge
parent child Browse files
Show More
@@ -0,0 +1,299 b''
1 """Contains eventful dict and list implementations."""
2
3 # void function used as a callback placeholder.
4 def _void(*p, **k): return None
5
6 class EventfulDict(dict):
7 """Eventful dictionary.
8
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
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
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
15 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
16 to the same handlers if necessary.
17
18 See the on_events, on_add, on_set, and on_del methods for registering
19 event handlers."""
20
21 def __init__(self, *args, **kwargs):
22 """Public constructor"""
23 self._add_callback = _void
24 self._del_callback = _void
25 self._set_callback = _void
26 dict.__init__(self, *args, **kwargs)
27
28 def on_events(self, add_callback=None, set_callback=None, del_callback=None):
29 """Register callbacks for add, set, and del actions.
30
31 See the doctstrings for on_(add/set/del) for details about each
32 callback.
33
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)
40
41 def on_add(self, callback):
42 """Register a callback for when an item is added to the dict.
43
44 Allows the listener to detect when items are added to the dictionary and
45 optionally cancel the addition.
46
47 callback: callable or None
48 If you want to ignore the addition event, pass None as the callback.
49 The callback should have a signature of callback(key, value). The
50 callback should return a boolean True if the additon should be
51 canceled, False or None otherwise."""
52 self._add_callback = callback if callable(callback) else _void
53
54 def on_del(self, callback):
55 """Register a callback for when an item is deleted from the dict.
56
57 Allows the listener to detect when items are deleted from the dictionary
58 and optionally cancel the deletion.
59
60 callback: callable or None
61 If you want to ignore the deletion event, pass None as the callback.
62 The callback should have a signature of callback(key). The
63 callback should return a boolean True if the deletion should be
64 canceled, False or None otherwise."""
65 self._del_callback = callback if callable(callback) else _void
66
67 def on_set(self, callback):
68 """Register a callback for when an item is changed in the dict.
69
70 Allows the listener to detect when items are changed in the dictionary
71 and optionally cancel the change.
72
73 callback: callable or None
74 If you want to ignore the change event, pass None as the callback.
75 The callback should have a signature of callback(key, value). The
76 callback should return a boolean True if the change should be
77 canceled, False or None otherwise."""
78 self._set_callback = callback if callable(callback) else _void
79
80 def pop(self, key):
81 """Returns the value of an item in the dictionary and then deletes the
82 item from the dictionary."""
83 if self._can_del(key):
84 return dict.pop(self, key)
85 else:
86 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
87
88 def popitem(self):
89 """Pop the next key/value pair from the dictionary."""
90 key = next(iter(self))
91 return key, self.pop(key)
92
93 def update(self, other_dict):
94 """Copy the key/value pairs from another dictionary into this dictionary,
95 overwriting any conflicting keys in this dictionary."""
96 for (key, value) in other_dict.items():
97 self[key] = value
98
99 def clear(self):
100 """Clear the dictionary."""
101 for key in list(self.keys()):
102 del self[key]
103
104 def __setitem__(self, key, value):
105 if (key in self and self._can_set(key, value)) or \
106 (key not in self and self._can_add(key, value)):
107 return dict.__setitem__(self, key, value)
108
109 def __delitem__(self, key):
110 if self._can_del(key):
111 return dict.__delitem__(self, key)
112
113 def _can_add(self, key, value):
114 """Check if the item can be added to the dict."""
115 return not bool(self._add_callback(key, value))
116
117 def _can_del(self, key):
118 """Check if the item can be deleted from the dict."""
119 return not bool(self._del_callback(key))
120
121 def _can_set(self, key, value):
122 """Check if the item can be changed in the dict."""
123 return not bool(self._set_callback(key, value))
124
125
126 class EventfulList(list):
127 """Eventful list.
128
129 This class inherits from the Python intrinsic `list` class. It adds events
130 that allow you to listen for actions that modify the list. You can
131 optionally cancel the actions.
132
133 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
134 registering an event handler.
135
136 Some of the method docstrings were taken from the Python documentation at
137 https://docs.python.org/2/tutorial/datastructures.html"""
138
139 def __init__(self, *pargs, **kwargs):
140 """Public constructor"""
141 self._insert_callback = _void
142 self._set_callback = _void
143 self._del_callback = _void
144 self._sort_callback = _void
145 self._reverse_callback = _void
146 list.__init__(self, *pargs, **kwargs)
147
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.
151
152 See the doctstrings for on_(insert/set/del/reverse/sort) for details
153 about each callback.
154
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 self.on_insert(insert_callback)
161 self.on_set(set_callback)
162 self.on_del(del_callback)
163 self.on_reverse(reverse_callback)
164 self.on_sort(sort_callback)
165
166 def on_insert(self, callback):
167 """Register a callback for when an item is inserted into the list.
168
169 Allows the listener to detect when items are inserted into the list and
170 optionally cancel the insertion.
171
172 callback: callable or None
173 If you want to ignore the insertion event, pass None as the callback.
174 The callback should have a signature of callback(index, value). The
175 callback should return a boolean True if the insertion should be
176 canceled, False or None otherwise."""
177 self._insert_callback = callback if callable(callback) else _void
178
179 def on_del(self, callback):
180 """Register a callback for item deletion.
181
182 Allows the listener to detect when items are deleted from the list and
183 optionally cancel the deletion.
184
185 callback: callable or None
186 If you want to ignore the deletion event, pass None as the callback.
187 The callback should have a signature of callback(index). The
188 callback should return a boolean True if the deletion should be
189 canceled, False or None otherwise."""
190 self._del_callback = callback if callable(callback) else _void
191
192 def on_set(self, callback):
193 """Register a callback for items are set.
194
195 Allows the listener to detect when items are set and optionally cancel
196 the setting. Note, `set` is also called when one or more items are
197 added to the end of the list.
198
199 callback: callable or None
200 If you want to ignore the set event, pass None as the callback.
201 The callback should have a signature of callback(index, value). The
202 callback should return a boolean True if the set should be
203 canceled, False or None otherwise."""
204 self._set_callback = callback if callable(callback) else _void
205
206 def on_reverse(self, callback):
207 """Register a callback for list reversal.
208
209 callback: callable or None
210 If you want to ignore the reverse event, pass None as the callback.
211 The callback should have a signature of callback(). The
212 callback should return a boolean True if the reverse should be
213 canceled, False or None otherwise."""
214 self._reverse_callback = callback if callable(callback) else _void
215
216 def on_sort(self, callback):
217 """Register a callback for sortting of the list.
218
219 callback: callable or None
220 If you want to ignore the sort event, pass None as the callback.
221 The callback signature should match that of Python list's `.sort`
222 method or `callback(*pargs, **kwargs)` as a catch all. The callback
223 should return a boolean True if the reverse should be canceled,
224 False or None otherwise."""
225 self._sort_callback = callback if callable(callback) else _void
226
227 def append(self, x):
228 """Add an item to the end of the list."""
229 self[len(self):] = [x]
230
231 def extend(self, L):
232 """Extend the list by appending all the items in the given list."""
233 self[len(self):] = L
234
235 def remove(self, x):
236 """Remove the first item from the list whose value is x. It is an error
237 if there is no such item."""
238 del self[self.index(x)]
239
240 def pop(self, i=None):
241 """Remove the item at the given position in the list, and return it. If
242 no index is specified, a.pop() removes and returns the last item in the
243 list."""
244 if i is None:
245 i = len(self) - 1
246 val = self[i]
247 del self[i]
248 return val
249
250 def reverse(self):
251 """Reverse the elements of the list, in place."""
252 if self._can_reverse():
253 list.reverse(self)
254
255 def insert(self, index, value):
256 """Insert an item at a given position. The first argument is the index
257 of the element before which to insert, so a.insert(0, x) inserts at the
258 front of the list, and a.insert(len(a), x) is equivalent to
259 a.append(x)."""
260 if self._can_insert(index, value):
261 list.insert(self, index, value)
262
263 def sort(self, *pargs, **kwargs):
264 """Sort the items of the list in place (the arguments can be used for
265 sort customization, see Python's sorted() for their explanation)."""
266 if self._can_sort(*pargs, **kwargs):
267 list.sort(self, *pargs, **kwargs)
268
269 def __delitem__(self, index):
270 if self._can_del(index):
271 list.__delitem__(self, index)
272
273 def __setitem__(self, index, value):
274 if self._can_set(index, value):
275 list.__setitem__(self, index, value)
276
277 def __setslice__(self, start, end, value):
278 if self._can_set(slice(start, end), value):
279 list.__setslice__(self, start, end, value)
280
281 def _can_insert(self, index, value):
282 """Check if the item can be inserted."""
283 return not bool(self._insert_callback(index, value))
284
285 def _can_del(self, index):
286 """Check if the item can be deleted."""
287 return not bool(self._del_callback(index))
288
289 def _can_set(self, index, value):
290 """Check if the item can be set."""
291 return not bool(self._set_callback(index, value))
292
293 def _can_reverse(self):
294 """Check if the list can be reversed."""
295 return not bool(self._reverse_callback())
296
297 def _can_sort(self, *pargs, **kwargs):
298 """Check if the list can be sorted."""
299 return not bool(self._sort_callback(*pargs, **kwargs))
@@ -911,7 +911,8 b' class InteractiveShell(SingletonConfigurable):'
911 try:
911 try:
912 main_mod = self._main_mod_cache[filename]
912 main_mod = self._main_mod_cache[filename]
913 except KeyError:
913 except KeyError:
914 main_mod = self._main_mod_cache[filename] = types.ModuleType(modname,
914 main_mod = self._main_mod_cache[filename] = types.ModuleType(
915 py3compat.cast_bytes_py2(modname),
915 doc="Module created for script run in IPython")
916 doc="Module created for script run in IPython")
916 else:
917 else:
917 main_mod.__dict__.clear()
918 main_mod.__dict__.clear()
@@ -472,6 +472,12 b' class InteractiveShellTestCase(unittest.TestCase):'
472 with open(filename, 'r') as f:
472 with open(filename, 'r') as f:
473 self.assertEqual(f.read(), 'blah')
473 self.assertEqual(f.read(), 'blah')
474
474
475 def test_new_main_mod(self):
476 # Smoketest to check that this accepts a unicode module name
477 name = u'jiefmw'
478 mod = ip.new_main_mod(u'%s.py' % name, name)
479 self.assertEqual(mod.__name__, name)
480
475 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
481 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
476
482
477 @onlyif_unicode_paths
483 @onlyif_unicode_paths
@@ -805,8 +805,10 b' class VerboseTB(TBTools):'
805 elif token_type == tokenize.NEWLINE:
805 elif token_type == tokenize.NEWLINE:
806 break
806 break
807
807
808 except (IndexError, UnicodeDecodeError):
808 except (IndexError, UnicodeDecodeError, SyntaxError):
809 # signals exit of tokenizer
809 # signals exit of tokenizer
810 # SyntaxError can occur if the file is not actually Python
811 # - see gh-6300
810 pass
812 pass
811 except tokenize.TokenError as msg:
813 except tokenize.TokenError as msg:
812 _m = ("An unexpected error occurred while tokenizing input\n"
814 _m = ("An unexpected error occurred while tokenizing input\n"
@@ -235,12 +235,13 b' class IPythonHandler(AuthenticatedHandler):'
235 raise web.HTTPError(400, u'Invalid JSON in body of request')
235 raise web.HTTPError(400, u'Invalid JSON in body of request')
236 return model
236 return model
237
237
238 def get_error_html(self, status_code, **kwargs):
238 def write_error(self, status_code, **kwargs):
239 """render custom error pages"""
239 """render custom error pages"""
240 exception = kwargs.get('exception')
240 exc_info = kwargs.get('exc_info')
241 message = ''
241 message = ''
242 status_message = responses.get(status_code, 'Unknown HTTP Error')
242 status_message = responses.get(status_code, 'Unknown HTTP Error')
243 if exception:
243 if exc_info:
244 exception = exc_info[1]
244 # get the custom message, if defined
245 # get the custom message, if defined
245 try:
246 try:
246 message = exception.log_message % exception.args
247 message = exception.log_message % exception.args
@@ -260,13 +261,16 b' class IPythonHandler(AuthenticatedHandler):'
260 exception=exception,
261 exception=exception,
261 )
262 )
262
263
264 self.set_header('Content-Type', 'text/html')
263 # render the template
265 # render the template
264 try:
266 try:
265 html = self.render_template('%s.html' % status_code, **ns)
267 html = self.render_template('%s.html' % status_code, **ns)
266 except TemplateNotFound:
268 except TemplateNotFound:
267 self.log.debug("No template for %d", status_code)
269 self.log.debug("No template for %d", status_code)
268 html = self.render_template('error.html', **ns)
270 html = self.render_template('error.html', **ns)
269 return html
271
272 self.write(html)
273
270
274
271
275
272 class Template404(IPythonHandler):
276 class Template404(IPythonHandler):
@@ -514,15 +514,20 b' define(['
514 }
514 }
515 };
515 };
516
516
517 var ajax_error_msg = function (jqXHR) {
518 // Return a JSON error message if there is one,
519 // otherwise the basic HTTP status text.
520 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
521 return jqXHR.responseJSON.message;
522 } else {
523 return jqXHR.statusText;
524 }
525 }
517 var log_ajax_error = function (jqXHR, status, error) {
526 var log_ajax_error = function (jqXHR, status, error) {
518 // log ajax failures with informative messages
527 // log ajax failures with informative messages
519 var msg = "API request failed (" + jqXHR.status + "): ";
528 var msg = "API request failed (" + jqXHR.status + "): ";
520 console.log(jqXHR);
529 console.log(jqXHR);
521 if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
530 msg += ajax_error_msg(jqXHR);
522 msg += jqXHR.responseJSON.message;
523 } else {
524 msg += jqXHR.statusText;
525 }
526 console.log(msg);
531 console.log(msg);
527 };
532 };
528
533
@@ -547,6 +552,7 b' define(['
547 platform: platform,
552 platform: platform,
548 is_or_has : is_or_has,
553 is_or_has : is_or_has,
549 is_focused : is_focused,
554 is_focused : is_focused,
555 ajax_error_msg : ajax_error_msg,
550 log_ajax_error : log_ajax_error,
556 log_ajax_error : log_ajax_error,
551 };
557 };
552
558
@@ -2286,13 +2286,14 b' define(['
2286 */
2286 */
2287 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2287 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
2288 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2288 this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
2289 utils.log_ajax_error(xhr, status, error);
2289 var msg;
2290 var msg;
2290 if (xhr.status === 400) {
2291 if (xhr.status === 400) {
2291 msg = error;
2292 msg = escape(utils.ajax_error_msg(xhr));
2292 } else if (xhr.status === 500) {
2293 } else if (xhr.status === 500) {
2293 msg = "An unknown error occurred while loading this notebook. " +
2294 msg = "An unknown error occurred while loading this notebook. " +
2294 "This version can load notebook formats " +
2295 "This version can load notebook formats " +
2295 "v" + this.nbformat + " or earlier.";
2296 "v" + this.nbformat + " or earlier. See the server log for details.";
2296 }
2297 }
2297 dialog.modal({
2298 dialog.modal({
2298 notebook: this,
2299 notebook: this,
@@ -2567,10 +2568,10 b' define(['
2567 * @method delete_checkpoint_error
2568 * @method delete_checkpoint_error
2568 * @param {jqXHR} xhr jQuery Ajax object
2569 * @param {jqXHR} xhr jQuery Ajax object
2569 * @param {String} status Description of response status
2570 * @param {String} status Description of response status
2570 * @param {String} error_msg HTTP error message
2571 * @param {String} error HTTP error message
2571 */
2572 */
2572 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2573 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
2573 this.events.trigger('checkpoint_delete_failed.Notebook');
2574 this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
2574 };
2575 };
2575
2576
2576
2577
@@ -62,22 +62,35 b' define(['
62 // handle in the vertical slider is always
62 // handle in the vertical slider is always
63 // consistent.
63 // consistent.
64 var orientation = this.model.get('orientation');
64 var orientation = this.model.get('orientation');
65 var value = this.model.get('min');
65 var min = this.model.get('min');
66 var max = this.model.get('max');
66 if (this.model.get('range')) {
67 if (this.model.get('range')) {
67 this.$slider.slider('option', 'values', [value, value]);
68 this.$slider.slider('option', 'values', [min, min]);
68 } else {
69 } else {
69 this.$slider.slider('option', 'value', value);
70 this.$slider.slider('option', 'value', min);
70 }
71 }
71 this.$slider.slider('option', 'orientation', orientation);
72 this.$slider.slider('option', 'orientation', orientation);
72 value = this.model.get('value');
73 var value = this.model.get('value');
73 if (this.model.get('range')) {
74 if (this.model.get('range')) {
75 // values for the range case are validated python-side in
76 // _Bounded{Int,Float}RangeWidget._validate
74 this.$slider.slider('option', 'values', value);
77 this.$slider.slider('option', 'values', value);
75 this.$readout.text(value.join("-"));
78 this.$readout.text(value.join("-"));
76 } else {
79 } else {
80 if(value > max) {
81 value = max;
82 }
83 else if(value < min){
84 value = min;
85 }
77 this.$slider.slider('option', 'value', value);
86 this.$slider.slider('option', 'value', value);
78 this.$readout.text(value);
87 this.$readout.text(value);
79 }
88 }
80
89
90 if(this.model.get('value')!=value) {
91 this.model.set('value', value, {updated_view: this});
92 this.touch();
93 }
81
94
82 // Use the right CSS classes for vertical & horizontal sliders
95 // Use the right CSS classes for vertical & horizontal sliders
83 if (orientation=='vertical') {
96 if (orientation=='vertical') {
@@ -80,5 +80,5 b' default_handlers = ['
80 (r"/tree%s" % notebook_path_regex, TreeHandler),
80 (r"/tree%s" % notebook_path_regex, TreeHandler),
81 (r"/tree%s" % path_regex, TreeHandler),
81 (r"/tree%s" % path_regex, TreeHandler),
82 (r"/tree", TreeHandler),
82 (r"/tree", TreeHandler),
83 (r"", TreeRedirectHandler),
83 (r"/?", TreeRedirectHandler),
84 ]
84 ]
@@ -13,11 +13,12 b' in the IPython notebook front-end.'
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16
17
17 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
18 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
19 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int, Set
21 from IPython.utils.py3compat import string_types
22 from IPython.utils.py3compat import string_types
22
23
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
@@ -98,9 +99,9 b' class Widget(LoggingConfigurable):'
98 #-------------------------------------------------------------------------
99 #-------------------------------------------------------------------------
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
100 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
100 registered in the front-end to create and sync this widget with.""")
101 registered in the front-end to create and sync this widget with.""")
101 _view_name = Unicode(help="""Default view registered in the front-end
102 _view_name = Unicode('WidgetView', help="""Default view registered in the front-end
102 to use to represent the widget.""", sync=True)
103 to use to represent the widget.""", sync=True)
103 _comm = Instance('IPython.kernel.comm.Comm')
104 comm = Instance('IPython.kernel.comm.Comm')
104
105
105 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
106 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
106 front-end can send before receiving an idle msg from the back-end.""")
107 front-end can send before receiving an idle msg from the back-end.""")
@@ -110,7 +111,8 b' class Widget(LoggingConfigurable):'
110 return [name for name in self.traits(sync=True)]
111 return [name for name in self.traits(sync=True)]
111
112
112 _property_lock = Tuple((None, None))
113 _property_lock = Tuple((None, None))
113
114 _send_state_lock = Int(0)
115 _states_to_send = Set(allow_none=False)
114 _display_callbacks = Instance(CallbackDispatcher, ())
116 _display_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
117 _msg_callbacks = Instance(CallbackDispatcher, ())
116
118
@@ -119,10 +121,12 b' class Widget(LoggingConfigurable):'
119 #-------------------------------------------------------------------------
121 #-------------------------------------------------------------------------
120 def __init__(self, **kwargs):
122 def __init__(self, **kwargs):
121 """Public constructor"""
123 """Public constructor"""
124 self._model_id = kwargs.pop('model_id', None)
122 super(Widget, self).__init__(**kwargs)
125 super(Widget, self).__init__(**kwargs)
123
126
124 self.on_trait_change(self._handle_property_changed, self.keys)
127 self.on_trait_change(self._handle_property_changed, self.keys)
125 Widget._call_widget_constructed(self)
128 Widget._call_widget_constructed(self)
129 self.open()
126
130
127 def __del__(self):
131 def __del__(self):
128 """Object disposal"""
132 """Object disposal"""
@@ -132,20 +136,19 b' class Widget(LoggingConfigurable):'
132 # Properties
136 # Properties
133 #-------------------------------------------------------------------------
137 #-------------------------------------------------------------------------
134
138
135 @property
139 def open(self):
136 def comm(self):
140 """Open a comm to the frontend if one isn't already open."""
137 """Gets the Comm associated with this widget.
141 if self.comm is None:
138
142 if self._model_id is None:
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
143 self.comm = Comm(target_name=self._model_name)
140 if self._comm is None:
144 self._model_id = self.model_id
141 # Create a comm.
145 else:
142 self._comm = Comm(target_name=self._model_name)
146 self.comm = Comm(target_name=self._model_name, comm_id=self._model_id)
143 self._comm.on_msg(self._handle_msg)
147 self.comm.on_msg(self._handle_msg)
144 Widget.widgets[self.model_id] = self
148 Widget.widgets[self.model_id] = self
145
149
146 # first update
150 # first update
147 self.send_state()
151 self.send_state()
148 return self._comm
149
152
150 @property
153 @property
151 def model_id(self):
154 def model_id(self):
@@ -164,22 +167,22 b' class Widget(LoggingConfigurable):'
164 Closes the underlying comm.
167 Closes the underlying comm.
165 When the comm is closed, all of the widget views are automatically
168 When the comm is closed, all of the widget views are automatically
166 removed from the front-end."""
169 removed from the front-end."""
167 if self._comm is not None:
170 if self.comm is not None:
168 Widget.widgets.pop(self.model_id, None)
171 Widget.widgets.pop(self.model_id, None)
169 self._comm.close()
172 self.comm.close()
170 self._comm = None
173 self.comm = None
171
174
172 def send_state(self, key=None):
175 def send_state(self, key=None):
173 """Sends the widget state, or a piece of it, to the front-end.
176 """Sends the widget state, or a piece of it, to the front-end.
174
177
175 Parameters
178 Parameters
176 ----------
179 ----------
177 key : unicode (optional)
180 key : unicode, or iterable (optional)
178 A single property's name to sync with the front-end.
181 A single property's name or iterable of property names to sync with the front-end.
179 """
182 """
180 self._send({
183 self._send({
181 "method" : "update",
184 "method" : "update",
182 "state" : self.get_state()
185 "state" : self.get_state(key=key)
183 })
186 })
184
187
185 def get_state(self, key=None):
188 def get_state(self, key=None):
@@ -187,10 +190,17 b' class Widget(LoggingConfigurable):'
187
190
188 Parameters
191 Parameters
189 ----------
192 ----------
190 key : unicode (optional)
193 key : unicode or iterable (optional)
191 A single property's name to get.
194 A single property's name or iterable of property names to get.
192 """
195 """
193 keys = self.keys if key is None else [key]
196 if key is None:
197 keys = self.keys
198 elif isinstance(key, string_types):
199 keys = [key]
200 elif isinstance(key, collections.Iterable):
201 keys = key
202 else:
203 raise ValueError("key must be a string, an iterable of keys, or None")
194 state = {}
204 state = {}
195 for k in keys:
205 for k in keys:
196 f = self.trait_metadata(k, 'to_json')
206 f = self.trait_metadata(k, 'to_json')
@@ -255,10 +265,29 b' class Widget(LoggingConfigurable):'
255 finally:
265 finally:
256 self._property_lock = (None, None)
266 self._property_lock = (None, None)
257
267
268 @contextmanager
269 def hold_sync(self):
270 """Hold syncing any state until the context manager is released"""
271 # We increment a value so that this can be nested. Syncing will happen when
272 # all levels have been released.
273 self._send_state_lock += 1
274 try:
275 yield
276 finally:
277 self._send_state_lock -=1
278 if self._send_state_lock == 0:
279 self.send_state(self._states_to_send)
280 self._states_to_send.clear()
281
258 def _should_send_property(self, key, value):
282 def _should_send_property(self, key, value):
259 """Check the property lock (property_lock)"""
283 """Check the property lock (property_lock)"""
260 return key != self._property_lock[0] or \
284 if (key == self._property_lock[0] and value == self._property_lock[1]):
261 value != self._property_lock[1]
285 return False
286 elif self._send_state_lock > 0:
287 self._states_to_send.add(key)
288 return False
289 else:
290 return True
262
291
263 # Event handlers
292 # Event handlers
264 @_show_traceback
293 @_show_traceback
@@ -388,7 +417,10 b' class DOMWidget(Widget):'
388 selector: unicode (optional, kwarg only)
417 selector: unicode (optional, kwarg only)
389 JQuery selector to use to apply the CSS key/value. If no selector
418 JQuery selector to use to apply the CSS key/value. If no selector
390 is provided, an empty selector is used. An empty selector makes the
419 is provided, an empty selector is used. An empty selector makes the
391 front-end try to apply the css to the top-level element.
420 front-end try to apply the css to a default element. The default
421 element is an attribute unique to each view, which is a DOM element
422 of the view that should be styled with common CSS (see
423 `$el_to_style` in the Javascript code).
392 """
424 """
393 if value is None:
425 if value is None:
394 css_dict = dict_or_key
426 css_dict = dict_or_key
@@ -23,7 +23,7 b' class Comm(LoggingConfigurable):'
23 return self.shell.kernel.iopub_socket
23 return self.shell.kernel.iopub_socket
24 session = Instance('IPython.kernel.zmq.session.Session')
24 session = Instance('IPython.kernel.zmq.session.Session')
25 def _session_default(self):
25 def _session_default(self):
26 if self.shell is None:
26 if self.shell is None or not hasattr(self.shell, 'kernel'):
27 return
27 return
28 return self.shell.kernel.session
28 return self.shell.kernel.session
29
29
@@ -56,6 +56,7 b' class Comm(LoggingConfigurable):'
56
56
57 def _publish_msg(self, msg_type, data=None, metadata=None, **keys):
57 def _publish_msg(self, msg_type, data=None, metadata=None, **keys):
58 """Helper for sending a comm message on IOPub"""
58 """Helper for sending a comm message on IOPub"""
59 if self.session is not None:
59 data = {} if data is None else data
60 data = {} if data is None else data
60 metadata = {} if metadata is None else metadata
61 metadata = {} if metadata is None else metadata
61 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
62 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
@@ -77,7 +78,9 b' class Comm(LoggingConfigurable):'
77 if data is None:
78 if data is None:
78 data = self._open_data
79 data = self._open_data
79 self._closed = False
80 self._closed = False
80 get_ipython().comm_manager.register_comm(self)
81 ip = get_ipython()
82 if hasattr(ip, 'comm_manager'):
83 ip.comm_manager.register_comm(self)
81 self._publish_msg('comm_open', data, metadata, target_name=self.target_name)
84 self._publish_msg('comm_open', data, metadata, target_name=self.target_name)
82
85
83 def close(self, data=None, metadata=None):
86 def close(self, data=None, metadata=None):
@@ -88,7 +91,9 b' class Comm(LoggingConfigurable):'
88 if data is None:
91 if data is None:
89 data = self._close_data
92 data = self._close_data
90 self._publish_msg('comm_close', data, metadata)
93 self._publish_msg('comm_close', data, metadata)
91 get_ipython().comm_manager.unregister_comm(self)
94 ip = get_ipython()
95 if hasattr(ip, 'comm_manager'):
96 ip.comm_manager.unregister_comm(self)
92 self._closed = True
97 self._closed = True
93
98
94 def send(self, data=None, metadata=None):
99 def send(self, data=None, metadata=None):
@@ -211,7 +211,9 b' class InputHookManager(object):'
211 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
211 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
212
212
213 from IPython.lib.inputhookwx import inputhook_wx
213 from IPython.lib.inputhookwx import inputhook_wx
214 from IPython.external.appnope import nope
214 self.set_inputhook(inputhook_wx)
215 self.set_inputhook(inputhook_wx)
216 nope()
215 self._current_gui = GUI_WX
217 self._current_gui = GUI_WX
216 import wx
218 import wx
217 if app is None:
219 if app is None:
@@ -227,9 +229,11 b' class InputHookManager(object):'
227
229
228 This merely sets PyOS_InputHook to NULL.
230 This merely sets PyOS_InputHook to NULL.
229 """
231 """
232 from IPython.external.appnope import nap
230 if GUI_WX in self._apps:
233 if GUI_WX in self._apps:
231 self._apps[GUI_WX]._in_event_loop = False
234 self._apps[GUI_WX]._in_event_loop = False
232 self.clear_inputhook()
235 self.clear_inputhook()
236 nap()
233
237
234 def enable_qt4(self, app=None):
238 def enable_qt4(self, app=None):
235 """Enable event loop integration with PyQt4.
239 """Enable event loop integration with PyQt4.
@@ -254,8 +258,10 b' class InputHookManager(object):'
254 app = QtGui.QApplication(sys.argv)
258 app = QtGui.QApplication(sys.argv)
255 """
259 """
256 from IPython.lib.inputhookqt4 import create_inputhook_qt4
260 from IPython.lib.inputhookqt4 import create_inputhook_qt4
261 from IPython.external.appnope import nope
257 app, inputhook_qt4 = create_inputhook_qt4(self, app)
262 app, inputhook_qt4 = create_inputhook_qt4(self, app)
258 self.set_inputhook(inputhook_qt4)
263 self.set_inputhook(inputhook_qt4)
264 nope()
259
265
260 self._current_gui = GUI_QT4
266 self._current_gui = GUI_QT4
261 app._in_event_loop = True
267 app._in_event_loop = True
@@ -267,9 +273,11 b' class InputHookManager(object):'
267
273
268 This merely sets PyOS_InputHook to NULL.
274 This merely sets PyOS_InputHook to NULL.
269 """
275 """
276 from IPython.external.appnope import nap
270 if GUI_QT4 in self._apps:
277 if GUI_QT4 in self._apps:
271 self._apps[GUI_QT4]._in_event_loop = False
278 self._apps[GUI_QT4]._in_event_loop = False
272 self.clear_inputhook()
279 self.clear_inputhook()
280 nap()
273
281
274 def enable_gtk(self, app=None):
282 def enable_gtk(self, app=None):
275 """Enable event loop integration with PyGTK.
283 """Enable event loop integration with PyGTK.
@@ -80,6 +80,8 b' def reads(s, **kwargs):'
80 nb : NotebookNode
80 nb : NotebookNode
81 The notebook that was read.
81 The notebook that was read.
82 """
82 """
83 from .current import NBFormatError
84
83 nb_dict = parse_json(s, **kwargs)
85 nb_dict = parse_json(s, **kwargs)
84 (major, minor) = get_version(nb_dict)
86 (major, minor) = get_version(nb_dict)
85 if major in versions:
87 if major in versions:
@@ -19,7 +19,8 b' from IPython.utils.traitlets import ('
19 HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict,
19 HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
21 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 ObjectName, DottedObjectName, CRegExp, link
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
23 EventfulList, EventfulDict
23 )
24 )
24 from IPython.utils import py3compat
25 from IPython.utils import py3compat
25 from IPython.testing.decorators import skipif
26 from IPython.testing.decorators import skipif
@@ -1034,7 +1035,7 b' class TestCRegExp(TraitTestBase):'
1034
1035
1035 _default_value = re.compile(r'')
1036 _default_value = re.compile(r'')
1036 _good_values = [r'\d+', re.compile(r'\d+')]
1037 _good_values = [r'\d+', re.compile(r'\d+')]
1037 _bad_values = [r'(', None, ()]
1038 _bad_values = ['(', None, ()]
1038
1039
1039 class DictTrait(HasTraits):
1040 class DictTrait(HasTraits):
1040 value = Dict()
1041 value = Dict()
@@ -1147,6 +1148,71 b' class TestLink(TestCase):'
1147 self.assertEqual(''.join(callback_count), 'ab')
1148 self.assertEqual(''.join(callback_count), 'ab')
1148 del callback_count[:]
1149 del callback_count[:]
1149
1150
1151 class TestDirectionalLink(TestCase):
1152 def test_connect_same(self):
1153 """Verify two traitlets of the same type can be linked together using directional_link."""
1154
1155 # Create two simple classes with Int traitlets.
1156 class A(HasTraits):
1157 value = Int()
1158 a = A(value=9)
1159 b = A(value=8)
1160
1161 # Conenct the two classes.
1162 c = directional_link((a, 'value'), (b, 'value'))
1163
1164 # Make sure the values are the same at the point of linking.
1165 self.assertEqual(a.value, b.value)
1166
1167 # Change one the value of the source and check that it synchronizes the target.
1168 a.value = 5
1169 self.assertEqual(b.value, 5)
1170 # Change one the value of the target and check that it has no impact on the source
1171 b.value = 6
1172 self.assertEqual(a.value, 5)
1173
1174 def test_link_different(self):
1175 """Verify two traitlets of different types can be linked together using link."""
1176
1177 # Create two simple classes with Int traitlets.
1178 class A(HasTraits):
1179 value = Int()
1180 class B(HasTraits):
1181 count = Int()
1182 a = A(value=9)
1183 b = B(count=8)
1184
1185 # Conenct the two classes.
1186 c = directional_link((a, 'value'), (b, 'count'))
1187
1188 # Make sure the values are the same at the point of linking.
1189 self.assertEqual(a.value, b.count)
1190
1191 # Change one the value of the source and check that it synchronizes the target.
1192 a.value = 5
1193 self.assertEqual(b.count, 5)
1194 # Change one the value of the target and check that it has no impact on the source
1195 b.value = 6
1196 self.assertEqual(a.value, 5)
1197
1198 def test_unlink(self):
1199 """Verify two linked traitlets can be unlinked."""
1200
1201 # Create two simple classes with Int traitlets.
1202 class A(HasTraits):
1203 value = Int()
1204 a = A(value=9)
1205 b = A(value=8)
1206
1207 # Connect the two classes.
1208 c = directional_link((a, 'value'), (b, 'value'))
1209 a.value = 4
1210 c.unlink()
1211
1212 # Change one of the values to make sure they don't stay in sync.
1213 a.value = 5
1214 self.assertNotEqual(a.value, b.value)
1215
1150 class Pickleable(HasTraits):
1216 class Pickleable(HasTraits):
1151 i = Int()
1217 i = Int()
1152 j = Int()
1218 j = Int()
@@ -1172,3 +1238,64 b' def test_pickle_hastraits():'
1172 nt.assert_equal(c2.i, c.i)
1238 nt.assert_equal(c2.i, c.i)
1173 nt.assert_equal(c2.j, c.j)
1239 nt.assert_equal(c2.j, c.j)
1174
1240
1241 class TestEventful(TestCase):
1242
1243 def test_list(self):
1244 """Does the EventfulList work?"""
1245 event_cache = []
1246
1247 class A(HasTraits):
1248 x = EventfulList([c for c in 'abc'])
1249 a = A()
1250 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1251 lambda i, x: event_cache.append('set'), \
1252 lambda i: event_cache.append('del'), \
1253 lambda: event_cache.append('reverse'), \
1254 lambda *p, **k: event_cache.append('sort'))
1255
1256 a.x.remove('c')
1257 # ab
1258 a.x.insert(0, 'z')
1259 # zab
1260 del a.x[1]
1261 # zb
1262 a.x.reverse()
1263 # bz
1264 a.x[1] = 'o'
1265 # bo
1266 a.x.append('a')
1267 # boa
1268 a.x.sort()
1269 # abo
1270
1271 # Were the correct events captured?
1272 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1273
1274 # Is the output correct?
1275 self.assertEqual(a.x, [c for c in 'abo'])
1276
1277 def test_dict(self):
1278 """Does the EventfulDict work?"""
1279 event_cache = []
1280
1281 class A(HasTraits):
1282 x = EventfulDict({c: c for c in 'abc'})
1283 a = A()
1284 a.x.on_events(lambda k, v: event_cache.append('add'), \
1285 lambda k, v: event_cache.append('set'), \
1286 lambda k: event_cache.append('del'))
1287
1288 del a.x['c']
1289 # ab
1290 a.x['z'] = 1
1291 # abz
1292 a.x['z'] = 'z'
1293 # abz
1294 a.x.pop('a')
1295 # bz
1296
1297 # Were the correct events captured?
1298 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1299
1300 # Is the output correct?
1301 self.assertEqual(a.x, {c: c for c in 'bz'})
@@ -54,6 +54,7 b' except:'
54
54
55 from .importstring import import_item
55 from .importstring import import_item
56 from IPython.utils import py3compat
56 from IPython.utils import py3compat
57 from IPython.utils import eventful
57 from IPython.utils.py3compat import iteritems
58 from IPython.utils.py3compat import iteritems
58 from IPython.testing.skipdoctest import skip_doctest
59 from IPython.testing.skipdoctest import skip_doctest
59
60
@@ -226,6 +227,61 b' class link(object):'
226 (obj,attr) = key
227 (obj,attr) = key
227 obj.on_trait_change(callback, attr, remove=True)
228 obj.on_trait_change(callback, attr, remove=True)
228
229
230 @skip_doctest
231 class directional_link(object):
232 """Link the trait of a source object with traits of target objects.
233
234 Parameters
235 ----------
236 source : pair of object, name
237 targets : pairs of objects/attributes
238
239 Examples
240 --------
241
242 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
243 >>> src.value = 5 # updates target objects
244 >>> tgt1.value = 6 # does not update other objects
245 """
246 updating = False
247
248 def __init__(self, source, *targets):
249 self.source = source
250 self.targets = targets
251
252 # Update current value
253 src_attr_value = getattr(source[0], source[1])
254 for obj, attr in targets:
255 if getattr(obj, attr) != src_attr_value:
256 setattr(obj, attr, src_attr_value)
257
258 # Wire
259 self.source[0].on_trait_change(self._update, self.source[1])
260
261 @contextlib.contextmanager
262 def _busy_updating(self):
263 self.updating = True
264 try:
265 yield
266 finally:
267 self.updating = False
268
269 def _update(self, name, old, new):
270 if self.updating:
271 return
272 with self._busy_updating():
273 for obj, attr in self.targets:
274 setattr(obj, attr, new)
275
276 def unlink(self):
277 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
278 self.source = None
279 self.targets = []
280
281 def dlink(source, *targets):
282 """Shorter helper function returning a directional_link object"""
283 return directional_link(source, *targets)
284
229 #-----------------------------------------------------------------------------
285 #-----------------------------------------------------------------------------
230 # Base TraitType for all traits
286 # Base TraitType for all traits
231 #-----------------------------------------------------------------------------
287 #-----------------------------------------------------------------------------
@@ -1490,6 +1546,49 b' class Dict(Instance):'
1490 super(Dict,self).__init__(klass=dict, args=args,
1546 super(Dict,self).__init__(klass=dict, args=args,
1491 allow_none=allow_none, **metadata)
1547 allow_none=allow_none, **metadata)
1492
1548
1549
1550 class EventfulDict(Instance):
1551 """An instance of an EventfulDict."""
1552
1553 def __init__(self, default_value=None, allow_none=True, **metadata):
1554 """Create a EventfulDict trait type from a dict.
1555
1556 The default value is created by doing
1557 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1558 ``default_value``.
1559 """
1560 if default_value is None:
1561 args = ((),)
1562 elif isinstance(default_value, dict):
1563 args = (default_value,)
1564 elif isinstance(default_value, SequenceTypes):
1565 args = (default_value,)
1566 else:
1567 raise TypeError('default value of EventfulDict was %s' % default_value)
1568
1569 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1570 allow_none=allow_none, **metadata)
1571
1572
1573 class EventfulList(Instance):
1574 """An instance of an EventfulList."""
1575
1576 def __init__(self, default_value=None, allow_none=True, **metadata):
1577 """Create a EventfulList trait type from a dict.
1578
1579 The default value is created by doing
1580 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1581 ``default_value``.
1582 """
1583 if default_value is None:
1584 args = ((),)
1585 else:
1586 args = (default_value,)
1587
1588 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1589 allow_none=allow_none, **metadata)
1590
1591
1493 class TCPAddress(TraitType):
1592 class TCPAddress(TraitType):
1494 """A trait for an (ip, port) tuple.
1593 """A trait for an (ip, port) tuple.
1495
1594
@@ -7,6 +7,6 b' rmagic'
7 .. note::
7 .. note::
8
8
9 The rmagic extension has been moved to `rpy2 <http://rpy.sourceforge.net/rpy2.html>`_
9 The rmagic extension has been moved to `rpy2 <http://rpy.sourceforge.net/rpy2.html>`_
10 as :mod:`rpy2.interactive.ipython`.
10 as :mod:`rpy2.ipython`.
11
11
12 .. automodule:: IPython.extensions.rmagic
12 .. automodule:: IPython.extensions.rmagic
@@ -73,7 +73,7 b' Example'
73 language_version = '0.1'
73 language_version = '0.1'
74 banner = "Echo kernel - as useful as a parrot"
74 banner = "Echo kernel - as useful as a parrot"
75
75
76 def do_execute(self, code, silent, store_history=True, user_experssions=None,
76 def do_execute(self, code, silent, store_history=True, user_expressions=None,
77 allow_stdin=False):
77 allow_stdin=False):
78 if not silent:
78 if not silent:
79 stream_content = {'name': 'stdout', 'data':code}
79 stream_content = {'name': 'stdout', 'data':code}
@@ -1,4 +1,4 b''
1 #!/bin/sh
1 #!/bin/bash
2
2
3 git submodule init
3 git submodule init
4 git submodule update
4 git submodule update
@@ -3,6 +3,7 b''
3 """
3 """
4
4
5 import os
5 import os
6 from shutil import rmtree
6
7
7 from toollib import *
8 from toollib import *
8
9
@@ -20,7 +21,7 b' compile_tree()'
20 for d in ['build', 'dist', pjoin('docs', 'build'), pjoin('docs', 'dist'),
21 for d in ['build', 'dist', pjoin('docs', 'build'), pjoin('docs', 'dist'),
21 pjoin('docs', 'source', 'api', 'generated')]:
22 pjoin('docs', 'source', 'api', 'generated')]:
22 if os.path.isdir(d):
23 if os.path.isdir(d):
23 remove_tree(d)
24 rmtree(d)
24
25
25 # Build source and binary distros
26 # Build source and binary distros
26 sh(sdists)
27 sh(sdists)
@@ -56,7 +56,7 b" sh('./setup.py register')"
56 # Upload all files
56 # Upload all files
57 sh(sdists + ' upload')
57 sh(sdists + ' upload')
58 for py in ('2.7', '3.4'):
58 for py in ('2.7', '3.4'):
59 sh('python%s setupegg.py bdist_wheel' % py)
59 sh('python%s setupegg.py bdist_wheel upload' % py)
60
60
61 cd(distdir)
61 cd(distdir)
62 print( 'Uploading distribution files...')
62 print( 'Uploading distribution files...')
@@ -42,7 +42,7 b' def get_ipdir():'
42 try:
42 try:
43 ipdir = sys.argv[1]
43 ipdir = sys.argv[1]
44 except IndexError:
44 except IndexError:
45 ipdir = '..'
45 ipdir = pjoin(os.path.dirname(__file__), os.pardir)
46
46
47 ipdir = os.path.abspath(ipdir)
47 ipdir = os.path.abspath(ipdir)
48
48
General Comments 0
You need to be logged in to leave comments. Login now