diff --git a/IPython/core/displaypub.py b/IPython/core/displaypub.py
index 375ddb5..f3bcf39 100644
--- a/IPython/core/displaypub.py
+++ b/IPython/core/displaypub.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-"""An interface for publishing data related to the display of objects.
+"""An interface for publishing rich data to frontends.
Authors:
@@ -35,6 +35,69 @@ class DisplayPublisher(Configurable):
raise TypeError('metadata must be a dict, got: %r' % data)
def publish(self, source, data, metadata=None):
- """Publish data and metadata to all frontends."""
- pass
+ """Publish data and metadata to all frontends.
+ See the ``display_data`` message in the messaging documentation for
+ more details about this message type.
+
+ Parameters
+ ----------
+ source : str
+ A string that give the function or method that created the data,
+ such as 'IPython.core.page'.
+ data : dict
+ A dictionary having keys that are valid MIME types (like
+ 'text/plain' or 'image/svg+xml') and values that are the data for
+ that MIME type. The data itself must be a JSON'able data
+ structure. Minimally all data should have the 'text/plain' data,
+ which can be displayed by all frontends. If more than the plain
+ text is given, it is up to the frontend to decide which
+ representation to use.
+ metadata : dict
+ A dictionary for metadata related to the data. This can contain
+ arbitrary key, value pairs that frontends can use to interpret
+ the data.
+ """
+ from IPython.utils import io
+ # The default is to simply write the plain text data using io.Term.
+ if data.has_key('text/plain'):
+ print >>io.Term.cout, data['text/plain']
+
+
+def publish_display_data(source, text, svg=None, png=None,
+ html=None, metadata=None):
+ """Publish a display data to the frontends.
+
+ This function is a high level helper for the publishing of display data.
+ It handle a number of common MIME types in a clean API. For other MIME
+ types, use ``get_ipython().display_pub.publish`` directly.
+
+ Parameters
+ ----------
+ text : str/unicode
+ The string representation of the plot.
+
+ svn : str/unicode
+ The raw svg data of the plot.
+
+ png : ???
+ The raw png data of the plot.
+
+ metadata : dict, optional [default empty]
+ Allows for specification of additional information about the plot data.
+ """
+ from IPython.core.interactiveshell import InteractiveShell
+
+ data_dict = {}
+ data_dict['text/plain'] = text
+ if svg is not None:
+ data_dict['image/svg+xml'] = svg
+ if png is not None:
+ data_dict['image/png'] = png
+ if html is not None:
+ data_dict['text/html'] = html
+ InteractiveShell.instance().display_pub.publish(
+ source,
+ data_dict,
+ metadata
+ )
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index 52753b3..a2e646f 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -41,6 +41,7 @@ from IPython.core.builtin_trap import BuiltinTrap
from IPython.core.compilerop import CachingCompiler
from IPython.core.display_trap import DisplayTrap
from IPython.core.displayhook import DisplayHook
+from IPython.core.displaypub import DisplayPublisher
from IPython.core.error import TryNext, UsageError
from IPython.core.extensions import ExtensionManager
from IPython.core.fakemodule import FakeModule, init_fakemod_dict
@@ -150,6 +151,8 @@ class InteractiveShell(Configurable, Magic):
debug = CBool(False, config=True)
deep_reload = CBool(False, config=True)
displayhook_class = Type(DisplayHook)
+ display_pub_class = Type(DisplayPublisher)
+
exit_now = CBool(False)
# Monotonically increasing execution counter
execution_count = Int(1)
@@ -284,6 +287,7 @@ class InteractiveShell(Configurable, Magic):
self.init_io()
self.init_traceback_handlers(custom_exceptions)
self.init_prompts()
+ self.init_display_pub()
self.init_displayhook()
self.init_reload_doctest()
self.init_magics()
@@ -481,6 +485,9 @@ class InteractiveShell(Configurable, Magic):
# will initialize that object and all prompt related information.
pass
+ def init_display_pub(self):
+ self.display_pub = self.display_pub_class(config=self.config)
+
def init_displayhook(self):
# Initialize displayhook, set in/out prompts and printing system
self.displayhook = self.displayhook_class(
diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py
index f879131..bc7db31 100644
--- a/IPython/frontend/qt/console/ipython_widget.py
+++ b/IPython/frontend/qt/console/ipython_widget.py
@@ -183,6 +183,21 @@ class IPythonWidget(FrontendWidget):
self._append_html(self._make_out_prompt(prompt_number))
self._append_plain_text(content['data']+self.output_sep2)
+ def _handle_display_data(self, msg):
+ """ The base handler for the ``display_data`` message.
+ """
+ # For now, we don't display data from other frontends, but we
+ # eventually will as this allows all frontends to monitor the display
+ # data. But we need to figure out how to handle this in the GUI.
+ if not self._hidden and self._is_from_this_session(msg):
+ source = msg['content']['source']
+ data = msg['content']['data']
+ metadata = msg['content']['metadata']
+ # In the regular IPythonWidget, we simply print the plain text
+ # representation.
+ if data.has_key('text/plain'):
+ self._append_plain_text(data['text/plain'])
+
def _started_channels(self):
""" Reimplemented to make a history request.
"""
@@ -444,7 +459,7 @@ class IPythonWidget(FrontendWidget):
else:
self._page(item['text'], html=False)
- #------ Trait change handlers ---------------------------------------------
+ #------ Trait change handlers --------------------------------------------
def _style_sheet_changed(self):
""" Set the style sheets of the underlying widgets.
@@ -464,4 +479,4 @@ class IPythonWidget(FrontendWidget):
self._highlighter.set_style(self.syntax_style)
else:
self._highlighter.set_style_sheet(self.style_sheet)
-
+
diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py
index 98d0cb1..377e778 100644
--- a/IPython/frontend/qt/console/rich_ipython_widget.py
+++ b/IPython/frontend/qt/console/rich_ipython_widget.py
@@ -56,7 +56,31 @@ class RichIPythonWidget(IPythonWidget):
menu.addAction('Save SVG As...',
lambda: save_svg(svg, self._control))
return menu
-
+
+ #---------------------------------------------------------------------------
+ # 'BaseFrontendMixin' abstract interface
+ #---------------------------------------------------------------------------
+
+ def _handle_display_data(self, msg):
+ """ A handler for ``display_data`` message that handles html and svg.
+ """
+ if not self._hidden and self._is_from_this_session(msg):
+ source = msg['content']['source']
+ data = msg['content']['data']
+ metadata = msg['content']['metadata']
+ # Try to use the svg or html representations.
+ # FIXME: Is this the right ordering of things to try?
+ if data.has_key('image/svg+xml'):
+ svg = data['image/svg+xml']
+ # TODO: try/except this call.
+ self._append_svg(svg)
+ elif data.has_key('text/html'):
+ html = data['text/html']
+ self._append_html(html)
+ else:
+ # Default back to the plain text representation.
+ return super(RichIPythonWidget, self)._handle_display_data(msg)
+
#---------------------------------------------------------------------------
# 'FrontendWidget' protected interface
#---------------------------------------------------------------------------
@@ -65,20 +89,11 @@ class RichIPythonWidget(IPythonWidget):
""" Reimplemented to handle matplotlib plot payloads.
"""
if item['source'] == self._payload_source_plot:
+ # TODO: remove this as all plot data is coming back through the
+ # display_data message type.
if item['format'] == 'svg':
svg = item['data']
- try:
- image = svg_to_image(svg)
- except ValueError:
- self._append_plain_text('Received invalid plot data.')
- else:
- format = self._add_image(image)
- self._name_to_svg[str(format.name())] = svg
- format.setProperty(self._svg_text_format_property, svg)
- cursor = self._get_end_cursor()
- cursor.insertBlock()
- cursor.insertImage(format)
- cursor.insertBlock()
+ self._append_svg(svg)
return True
else:
# Add other plot formats here!
@@ -90,6 +105,22 @@ class RichIPythonWidget(IPythonWidget):
# 'RichIPythonWidget' protected interface
#---------------------------------------------------------------------------
+ def _append_svg(self, svg):
+ """ Append raw svg data to the widget.
+ """
+ try:
+ image = svg_to_image(svg)
+ except ValueError:
+ self._append_plain_text('Received invalid plot data.')
+ else:
+ format = self._add_image(image)
+ self._name_to_svg[str(format.name())] = svg
+ format.setProperty(self._svg_text_format_property, svg)
+ cursor = self._get_end_cursor()
+ cursor.insertBlock()
+ cursor.insertImage(format)
+ cursor.insertBlock()
+
def _add_image(self, image):
""" Adds the specified QImage to the document and returns a
QTextImageFormat that references it.
@@ -192,4 +223,4 @@ class RichIPythonWidget(IPythonWidget):
else:
return 'Unrecognized image format'
-
+
diff --git a/IPython/frontend/qt/kernelmanager.py b/IPython/frontend/qt/kernelmanager.py
index 98df284..79b8edb 100644
--- a/IPython/frontend/qt/kernelmanager.py
+++ b/IPython/frontend/qt/kernelmanager.py
@@ -101,6 +101,9 @@ class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
# Emitted when a message of type 'pyerr' is received.
pyerr_received = QtCore.pyqtSignal(object)
+ # Emitted when a message of type 'display_data' is received
+ display_data_received = QtCore.pyqtSignal(object)
+
# Emitted when a crash report message is received from the kernel's
# last-resort sys.excepthook.
crash_received = QtCore.pyqtSignal(object)
@@ -117,7 +120,6 @@ class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
"""
# Emit the generic signal.
self.message_received.emit(msg)
-
# Emit signals for specialized message types.
msg_type = msg['msg_type']
signal = getattr(self, msg_type + '_received', None)
diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py
index 011608f..018515c 100755
--- a/IPython/zmq/ipkernel.py
+++ b/IPython/zmq/ipkernel.py
@@ -91,6 +91,8 @@ class Kernel(Configurable):
self.shell = ZMQInteractiveShell.instance()
self.shell.displayhook.session = self.session
self.shell.displayhook.pub_socket = self.pub_socket
+ self.shell.display_pub.session = self.session
+ self.shell.display_pub.pub_socket = self.pub_socket
# TMP - hack while developing
self.shell._reply_content = None
@@ -194,6 +196,7 @@ class Kernel(Configurable):
# Set the parent message of the display hook and out streams.
shell.displayhook.set_parent(parent)
+ shell.display_pub.set_parent(parent)
sys.stdout.set_parent(parent)
sys.stderr.set_parent(parent)
diff --git a/IPython/zmq/pylab/backend_inline.py b/IPython/zmq/pylab/backend_inline.py
index f844d4c..745dbf6 100644
--- a/IPython/zmq/pylab/backend_inline.py
+++ b/IPython/zmq/pylab/backend_inline.py
@@ -14,7 +14,7 @@ from matplotlib.backends.backend_svg import new_figure_manager
from matplotlib._pylab_helpers import Gcf
# Local imports.
-from backend_payload import add_plot_payload
+from IPython.core.displaypub import publish_display_data
#-----------------------------------------------------------------------------
# Functions
@@ -85,7 +85,11 @@ def send_svg_canvas(canvas):
canvas.figure.set_facecolor('white')
canvas.figure.set_edgecolor('white')
try:
- add_plot_payload('svg', svg_from_canvas(canvas))
+ publish_display_data(
+ 'IPython.zmq.pylab.backend_inline.send_svg_canvas',
+ '',
+ svg=svg_from_canvas(canvas)
+ )
finally:
canvas.figure.set_facecolor(fc)
canvas.figure.set_edgecolor(ec)
diff --git a/IPython/zmq/pylab/backend_payload.py b/IPython/zmq/pylab/backend_payload.py
deleted file mode 100644
index 74d4de6..0000000
--- a/IPython/zmq/pylab/backend_payload.py
+++ /dev/null
@@ -1,26 +0,0 @@
-""" Provides basic funtionality for payload backends.
-"""
-
-# Local imports.
-from IPython.core.interactiveshell import InteractiveShell
-
-
-def add_plot_payload(format, data, metadata={}):
- """ Add a plot payload to the current execution reply.
-
- Parameters:
- -----------
- format : str
- Identifies the format of the plot data.
-
- data : str
- The raw plot data.
-
- metadata : dict, optional [default empty]
- Allows for specification of additional information about the plot data.
- """
- payload = dict(
- source='IPython.zmq.pylab.backend_payload.add_plot_payload',
- format=format, data=data, metadata=metadata
- )
- InteractiveShell.instance().payload_manager.write_payload(payload)
diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py
index 1eda3d9..66e5369 100644
--- a/IPython/zmq/zmqshell.py
+++ b/IPython/zmq/zmqshell.py
@@ -26,6 +26,7 @@ from IPython.core.interactiveshell import (
)
from IPython.core import page
from IPython.core.displayhook import DisplayHook
+from IPython.core.displaypub import DisplayPublisher
from IPython.core.macro import Macro
from IPython.core.payloadpage import install_payload_page
from IPython.utils import io
@@ -75,10 +76,34 @@ class ZMQDisplayHook(DisplayHook):
self.msg = None
+class ZMQDisplayPublisher(DisplayPublisher):
+ """A ``DisplayPublisher`` that published data using a ZeroMQ PUB socket."""
+
+ session = Instance(Session)
+ pub_socket = Instance('zmq.Socket')
+ parent_header = Dict({})
+
+ def set_parent(self, parent):
+ """Set the parent for outbound messages."""
+ self.parent_header = extract_header(parent)
+
+ def publish(self, source, data, metadata=None):
+ if metadata is None:
+ metadata = {}
+ self._validate_data(source, data, metadata)
+ msg = self.session.msg(u'display_data', {}, parent=self.parent_header)
+ msg['content']['source'] = source
+ msg['content']['data'] = data
+ msg['content']['metadata'] = metadata
+ self.pub_socket.send_json(msg)
+
+
class ZMQInteractiveShell(InteractiveShell):
"""A subclass of InteractiveShell for ZMQ."""
displayhook_class = Type(ZMQDisplayHook)
+ display_pub_class = Type(ZMQDisplayPublisher)
+
keepkernel_on_exit = None
def init_environment(self):
diff --git a/docs/source/development/messaging.txt b/docs/source/development/messaging.txt
index 15cbf02..59fdbc6 100644
--- a/docs/source/development/messaging.txt
+++ b/docs/source/development/messaging.txt
@@ -700,30 +700,32 @@ socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
to monitor/display kernel interactions and possibly replay them to their user
or otherwise expose them.
-Representation Data
--------------------
+Display Data
+------------
-This type of message is used to bring back representations (text, html, svg,
-etc.) of Python objects to the frontend. Each message can have multiple
-representations of the object; it is up to the frontend to decide which to use
-and how. A single message should contain the different representations of a
-single Python object. Each representation should be a JSON'able data structure,
-and should be a valid MIME type.
+This type of message is used to bring back data that should be diplayed (text,
+html, svg, etc.) in the frontends. This data is published to all frontends.
+Each message can have multiple representations of the data; it is up to the
+frontend to decide which to use and how. A single message should contain all
+possible representations of the same information. Each representation should
+be a JSON'able data structure, and should be a valid MIME type.
Some questions remain about this design:
-* Do we use this message type for pyout/displayhook?
-* What is the best way to organize the content dict of the message?
+* Do we use this message type for pyout/displayhook? Probably not, because
+ the displayhook also has to handle the Out prompt display. On the other hand
+ we could put that information into the metadata secion.
-Message type: ``repr_data``::
+Message type: ``display_data``::
- # Option 1: if we only allow a single source.
content = {
'source' : str # Who create the data
'data' : dict # {'mimetype1' : data1, 'mimetype2' : data2}
'metadata' : dict # Any metadata that describes the data
}
+Other options for ``display_data`` content::
+
# Option 2: allowing for a different source for each representation,
but not keyed by anything.
content = {