diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py index a943894..3f30fd7 100644 --- a/IPython/html/widgets/interaction.py +++ b/IPython/html/widgets/interaction.py @@ -14,7 +14,7 @@ from inspect import getcallargs from IPython.core.getipython import get_ipython from IPython.html.widgets import (Widget, Text, FloatSlider, IntSlider, Checkbox, Dropdown, - Box, Button, DOMWidget) + Box, Button, DOMWidget, Output) from IPython.display import display, clear_output from IPython.utils.py3compat import string_types, unicode_type from IPython.utils.traitlets import HasTraits, Any, Unicode @@ -204,29 +204,34 @@ def interactive(__interact_f, **kwargs): if manual: manual_button = Button(description="Run %s" % f.__name__) c.append(manual_button) + + # Use an output widget to capture the output of interact. + output = Output() + c.append(output) container.children = c # Build the callback def call_f(name=None, old=None, new=None): - container.kwargs = {} - for widget in kwargs_widgets: - value = widget.value - container.kwargs[widget._kwarg] = value - if co: - clear_output(wait=True) - if manual: - manual_button.disabled = True - try: - container.result = f(**container.kwargs) - except Exception as e: - ip = get_ipython() - if ip is None: - container.log.warn("Exception in interact callback: %s", e, exc_info=True) - else: - ip.showtraceback() - finally: + with output: + container.kwargs = {} + for widget in kwargs_widgets: + value = widget.value + container.kwargs[widget._kwarg] = value + if co: + clear_output(wait=True) if manual: - manual_button.disabled = False + manual_button.disabled = True + try: + container.result = f(**container.kwargs) + except Exception as e: + ip = get_ipython() + if ip is None: + container.log.warn("Exception in interact callback: %s", e, exc_info=True) + else: + ip.showtraceback() + finally: + if manual: + manual_button.disabled = False # Wire up the widgets # If we are doing manual running, the callback is only triggered by the button diff --git a/IPython/html/widgets/tests/test_interaction.py b/IPython/html/widgets/tests/test_interaction.py index 774b25c..9d2a9eb 100644 --- a/IPython/html/widgets/tests/test_interaction.py +++ b/IPython/html/widgets/tests/test_interaction.py @@ -80,7 +80,8 @@ def check_widgets(container, **to_check): # build a widget dictionary, so it matches widgets = {} for w in container.children: - widgets[w.description] = w + if hasattr(w, 'description'): + widgets[w.description] = w for key, d in to_check.items(): nt.assert_in(key, widgets) @@ -138,7 +139,7 @@ def test_single_value_float(): def test_single_value_int(): for a in (1, 5, -3): c = interactive(f, a=a) - nt.assert_equal(len(c.children), 1) + nt.assert_equal(len(c.children), 2) w = c.children[0] check_widget(w, cls=widgets.IntSlider, @@ -157,7 +158,7 @@ def test_list_tuple_2_int(): c = interactive(f, tup=(1,-1)) for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]: c = interactive(f, tup=(min, max), lis=[min, max]) - nt.assert_equal(len(c.children), 2) + nt.assert_equal(len(c.children), 3) d = dict( cls=widgets.IntSlider, min=min, @@ -174,7 +175,7 @@ def test_list_tuple_3_int(): c = interactive(f, tup=(1,2,-1)) for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]: c = interactive(f, tup=(min, max, step), lis=[min, max, step]) - nt.assert_equal(len(c.children), 2) + nt.assert_equal(len(c.children), 3) d = dict( cls=widgets.IntSlider, min=min, @@ -191,7 +192,7 @@ def test_list_tuple_2_float(): c = interactive(f, tup=(0.5,-0.5)) for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]: c = interactive(f, tup=(min, max), lis=[min, max]) - nt.assert_equal(len(c.children), 2) + nt.assert_equal(len(c.children), 3) d = dict( cls=widgets.FloatSlider, min=min, @@ -210,7 +211,7 @@ def test_list_tuple_3_float(): c = interactive(f, tup=(1,2.,-1.)) for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]: c = interactive(f, tup=(min, max, step), lis=[min, max, step]) - nt.assert_equal(len(c.children), 2) + nt.assert_equal(len(c.children), 3) d = dict( cls=widgets.FloatSlider, min=min, @@ -224,7 +225,7 @@ def test_list_tuple_str(): values = ['hello', 'there', 'guy'] first = values[0] c = interactive(f, tup=tuple(values), lis=list(values)) - nt.assert_equal(len(c.children), 2) + nt.assert_equal(len(c.children), 3) d = dict( cls=widgets.Dropdown, value=first, @@ -471,7 +472,7 @@ def test_call_decorated_kwargs_on_trait_change(): def test_fixed(): c = interactive(f, a=widgets.fixed(5), b='text') - nt.assert_equal(len(c.children), 1) + nt.assert_equal(len(c.children), 2) w = c.children[0] check_widget(w, cls=widgets.Text, diff --git a/IPython/html/widgets/widget_output.py b/IPython/html/widgets/widget_output.py index 771ecb7..578df58 100644 --- a/IPython/html/widgets/widget_output.py +++ b/IPython/html/widgets/widget_output.py @@ -34,43 +34,53 @@ class Output(DOMWidget): print('prints to output widget')""" _view_name = Unicode('OutputView', sync=True) + def __init__(self, *args, **kwargs): + super(Output, self).__init__(*args, **kwargs) + from IPython import get_ipython + ip = get_ipython() + if ip is not None and hasattr(ip, 'kernel'): + self._kernel = ip.kernel + else: + self._kernel = None + def clear_output(self, *pargs, **kwargs): with self: clear_output(*pargs, **kwargs) def __enter__(self): """Called upon entering output widget context manager.""" - self._flush() - kernel = get_ipython().kernel - session = kernel.session - send = session.send - self._original_send = send - self._session = session - - def send_hook(stream, msg_or_type, content=None, parent=None, ident=None, - buffers=None, track=False, header=None, metadata=None): - - # Handle both prebuild messages and unbuilt messages. - if isinstance(msg_or_type, (Message, dict)): - msg_type = msg_or_type['msg_type'] - msg = dict(msg_or_type) - else: - msg_type = msg_or_type - msg = session.msg(msg_type, content=content, parent=parent, - header=header, metadata=metadata) - - # If this is a message type that we want to forward, forward it. - if stream is kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']: - self.send(msg) - else: - send(stream, msg, ident=ident, buffers=buffers, track=track) - - session.send = send_hook + if self._kernel is not None: + self._flush() + session = self._kernel.session + send = session.send + self._original_send = send + self._session = session + + def send_hook(stream, msg_or_type, content=None, parent=None, ident=None, + buffers=None, track=False, header=None, metadata=None): + + # Handle both prebuild messages and unbuilt messages. + if isinstance(msg_or_type, (Message, dict)): + msg_type = msg_or_type['msg_type'] + msg = dict(msg_or_type) + else: + msg_type = msg_or_type + msg = session.msg(msg_type, content=content, parent=parent, + header=header, metadata=metadata) + + # If this is a message type that we want to forward, forward it. + if stream is self._kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']: + self.send(msg) + else: + send(stream, msg, ident=ident, buffers=buffers, track=track) + + session.send = send_hook def __exit__(self, exception_type, exception_value, traceback): """Called upon exiting output widget context manager.""" - self._flush() - self._session.send = self._original_send + if self._kernel is not None: + self._flush() + self._session.send = self._original_send def _flush(self): """Flush stdout and stderr buffers."""