Repository: ipython/ipython

Issue:
Output widget broken
Caused by the grand asynchronous movement of 2014.  Specifically, the asynchronous code in the comm that calls back the user registered message handling function.

The output assumes (now incorrectly) that the widget custom messages are received by the front-end in the same order as the stdout/display messages. 

@takluyver @jasongrout :horse: 



Discussion:
I marked this as a blocker since it doesn't make sense to ship a broken feature with IPython 3.0, especially since it's a new feature.

From the dev meeting: @jdfreder will try reimplementing this so that the state change is on the kernel side, and the context manager causes output to be sent to the widget as comm messages. If that's not ready for 3.0 then we may pull output widgets out rather than trying to hack around the async issues.

+1 to redirecting stdout on the python side with totally self-contained code, at least until we understand much better how things should work in the core notebook with output and redirection and things.


Resolving patch:
diff --git a/IPython/html/widgets/widget_output.py b/IPython/html/widgets/widget_output.py
--- a/IPython/html/widgets/widget_output.py
+++ b/IPython/html/widgets/widget_output.py
@@ -11,6 +11,7 @@
 from IPython.utils.traitlets import Unicode, List
 from IPython.display import clear_output
 from IPython.testing.skipdoctest import skip_doctest
+from IPython.kernel.zmq.session import Message
 
 @skip_doctest
 class Output(DOMWidget):
@@ -38,13 +39,40 @@ def clear_output(self, *pargs, **kwargs):
             clear_output(*pargs, **kwargs)
 
     def __enter__(self):
+        """Called upon entering output widget context manager."""
         self._flush()
-        self.send({'method': 'push'})
+        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
 
     def __exit__(self, exception_type, exception_value, traceback):
+        """Called upon exiting output widget context manager."""
         self._flush()
-        self.send({'method': 'pop'})
+        self._session.send = self._original_send
 
     def _flush(self):
+        """Flush stdout and stderr buffers."""
         sys.stdout.flush()
         sys.stderr.flush()


Test patch: