##// END OF EJS Templates
Mostly final version of display data....
Brian Granger -
Show More
@@ -1,5 +1,5 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """An interface for publishing data related to the display of objects.
2 """An interface for publishing rich data to frontends.
3
3
4 Authors:
4 Authors:
5
5
@@ -35,6 +35,69 b' class DisplayPublisher(Configurable):'
35 raise TypeError('metadata must be a dict, got: %r' % data)
35 raise TypeError('metadata must be a dict, got: %r' % data)
36
36
37 def publish(self, source, data, metadata=None):
37 def publish(self, source, data, metadata=None):
38 """Publish data and metadata to all frontends."""
38 """Publish data and metadata to all frontends.
39 pass
40
39
40 See the ``display_data`` message in the messaging documentation for
41 more details about this message type.
42
43 Parameters
44 ----------
45 source : str
46 A string that give the function or method that created the data,
47 such as 'IPython.core.page'.
48 data : dict
49 A dictionary having keys that are valid MIME types (like
50 'text/plain' or 'image/svg+xml') and values that are the data for
51 that MIME type. The data itself must be a JSON'able data
52 structure. Minimally all data should have the 'text/plain' data,
53 which can be displayed by all frontends. If more than the plain
54 text is given, it is up to the frontend to decide which
55 representation to use.
56 metadata : dict
57 A dictionary for metadata related to the data. This can contain
58 arbitrary key, value pairs that frontends can use to interpret
59 the data.
60 """
61 from IPython.utils import io
62 # The default is to simply write the plain text data using io.Term.
63 if data.has_key('text/plain'):
64 print >>io.Term.cout, data['text/plain']
65
66
67 def publish_display_data(source, text, svg=None, png=None,
68 html=None, metadata=None):
69 """Publish a display data to the frontends.
70
71 This function is a high level helper for the publishing of display data.
72 It handle a number of common MIME types in a clean API. For other MIME
73 types, use ``get_ipython().display_pub.publish`` directly.
74
75 Parameters
76 ----------
77 text : str/unicode
78 The string representation of the plot.
79
80 svn : str/unicode
81 The raw svg data of the plot.
82
83 png : ???
84 The raw png data of the plot.
85
86 metadata : dict, optional [default empty]
87 Allows for specification of additional information about the plot data.
88 """
89 from IPython.core.interactiveshell import InteractiveShell
90
91 data_dict = {}
92 data_dict['text/plain'] = text
93 if svg is not None:
94 data_dict['image/svg+xml'] = svg
95 if png is not None:
96 data_dict['image/png'] = png
97 if html is not None:
98 data_dict['text/html'] = html
99 InteractiveShell.instance().display_pub.publish(
100 source,
101 data_dict,
102 metadata
103 )
@@ -41,6 +41,7 b' from IPython.core.builtin_trap import BuiltinTrap'
41 from IPython.core.compilerop import CachingCompiler
41 from IPython.core.compilerop import CachingCompiler
42 from IPython.core.display_trap import DisplayTrap
42 from IPython.core.display_trap import DisplayTrap
43 from IPython.core.displayhook import DisplayHook
43 from IPython.core.displayhook import DisplayHook
44 from IPython.core.displaypub import DisplayPublisher
44 from IPython.core.error import TryNext, UsageError
45 from IPython.core.error import TryNext, UsageError
45 from IPython.core.extensions import ExtensionManager
46 from IPython.core.extensions import ExtensionManager
46 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
47 from IPython.core.fakemodule import FakeModule, init_fakemod_dict
@@ -150,6 +151,8 b' class InteractiveShell(Configurable, Magic):'
150 debug = CBool(False, config=True)
151 debug = CBool(False, config=True)
151 deep_reload = CBool(False, config=True)
152 deep_reload = CBool(False, config=True)
152 displayhook_class = Type(DisplayHook)
153 displayhook_class = Type(DisplayHook)
154 display_pub_class = Type(DisplayPublisher)
155
153 exit_now = CBool(False)
156 exit_now = CBool(False)
154 # Monotonically increasing execution counter
157 # Monotonically increasing execution counter
155 execution_count = Int(1)
158 execution_count = Int(1)
@@ -284,6 +287,7 b' class InteractiveShell(Configurable, Magic):'
284 self.init_io()
287 self.init_io()
285 self.init_traceback_handlers(custom_exceptions)
288 self.init_traceback_handlers(custom_exceptions)
286 self.init_prompts()
289 self.init_prompts()
290 self.init_display_pub()
287 self.init_displayhook()
291 self.init_displayhook()
288 self.init_reload_doctest()
292 self.init_reload_doctest()
289 self.init_magics()
293 self.init_magics()
@@ -481,6 +485,9 b' class InteractiveShell(Configurable, Magic):'
481 # will initialize that object and all prompt related information.
485 # will initialize that object and all prompt related information.
482 pass
486 pass
483
487
488 def init_display_pub(self):
489 self.display_pub = self.display_pub_class(config=self.config)
490
484 def init_displayhook(self):
491 def init_displayhook(self):
485 # Initialize displayhook, set in/out prompts and printing system
492 # Initialize displayhook, set in/out prompts and printing system
486 self.displayhook = self.displayhook_class(
493 self.displayhook = self.displayhook_class(
@@ -183,6 +183,21 b' class IPythonWidget(FrontendWidget):'
183 self._append_html(self._make_out_prompt(prompt_number))
183 self._append_html(self._make_out_prompt(prompt_number))
184 self._append_plain_text(content['data']+self.output_sep2)
184 self._append_plain_text(content['data']+self.output_sep2)
185
185
186 def _handle_display_data(self, msg):
187 """ The base handler for the ``display_data`` message.
188 """
189 # For now, we don't display data from other frontends, but we
190 # eventually will as this allows all frontends to monitor the display
191 # data. But we need to figure out how to handle this in the GUI.
192 if not self._hidden and self._is_from_this_session(msg):
193 source = msg['content']['source']
194 data = msg['content']['data']
195 metadata = msg['content']['metadata']
196 # In the regular IPythonWidget, we simply print the plain text
197 # representation.
198 if data.has_key('text/plain'):
199 self._append_plain_text(data['text/plain'])
200
186 def _started_channels(self):
201 def _started_channels(self):
187 """ Reimplemented to make a history request.
202 """ Reimplemented to make a history request.
188 """
203 """
@@ -444,7 +459,7 b' class IPythonWidget(FrontendWidget):'
444 else:
459 else:
445 self._page(item['text'], html=False)
460 self._page(item['text'], html=False)
446
461
447 #------ Trait change handlers ---------------------------------------------
462 #------ Trait change handlers --------------------------------------------
448
463
449 def _style_sheet_changed(self):
464 def _style_sheet_changed(self):
450 """ Set the style sheets of the underlying widgets.
465 """ Set the style sheets of the underlying widgets.
@@ -464,4 +479,4 b' class IPythonWidget(FrontendWidget):'
464 self._highlighter.set_style(self.syntax_style)
479 self._highlighter.set_style(self.syntax_style)
465 else:
480 else:
466 self._highlighter.set_style_sheet(self.style_sheet)
481 self._highlighter.set_style_sheet(self.style_sheet)
467
482
@@ -56,7 +56,31 b' class RichIPythonWidget(IPythonWidget):'
56 menu.addAction('Save SVG As...',
56 menu.addAction('Save SVG As...',
57 lambda: save_svg(svg, self._control))
57 lambda: save_svg(svg, self._control))
58 return menu
58 return menu
59
59
60 #---------------------------------------------------------------------------
61 # 'BaseFrontendMixin' abstract interface
62 #---------------------------------------------------------------------------
63
64 def _handle_display_data(self, msg):
65 """ A handler for ``display_data`` message that handles html and svg.
66 """
67 if not self._hidden and self._is_from_this_session(msg):
68 source = msg['content']['source']
69 data = msg['content']['data']
70 metadata = msg['content']['metadata']
71 # Try to use the svg or html representations.
72 # FIXME: Is this the right ordering of things to try?
73 if data.has_key('image/svg+xml'):
74 svg = data['image/svg+xml']
75 # TODO: try/except this call.
76 self._append_svg(svg)
77 elif data.has_key('text/html'):
78 html = data['text/html']
79 self._append_html(html)
80 else:
81 # Default back to the plain text representation.
82 return super(RichIPythonWidget, self)._handle_display_data(msg)
83
60 #---------------------------------------------------------------------------
84 #---------------------------------------------------------------------------
61 # 'FrontendWidget' protected interface
85 # 'FrontendWidget' protected interface
62 #---------------------------------------------------------------------------
86 #---------------------------------------------------------------------------
@@ -65,20 +89,11 b' class RichIPythonWidget(IPythonWidget):'
65 """ Reimplemented to handle matplotlib plot payloads.
89 """ Reimplemented to handle matplotlib plot payloads.
66 """
90 """
67 if item['source'] == self._payload_source_plot:
91 if item['source'] == self._payload_source_plot:
92 # TODO: remove this as all plot data is coming back through the
93 # display_data message type.
68 if item['format'] == 'svg':
94 if item['format'] == 'svg':
69 svg = item['data']
95 svg = item['data']
70 try:
96 self._append_svg(svg)
71 image = svg_to_image(svg)
72 except ValueError:
73 self._append_plain_text('Received invalid plot data.')
74 else:
75 format = self._add_image(image)
76 self._name_to_svg[str(format.name())] = svg
77 format.setProperty(self._svg_text_format_property, svg)
78 cursor = self._get_end_cursor()
79 cursor.insertBlock()
80 cursor.insertImage(format)
81 cursor.insertBlock()
82 return True
97 return True
83 else:
98 else:
84 # Add other plot formats here!
99 # Add other plot formats here!
@@ -90,6 +105,22 b' class RichIPythonWidget(IPythonWidget):'
90 # 'RichIPythonWidget' protected interface
105 # 'RichIPythonWidget' protected interface
91 #---------------------------------------------------------------------------
106 #---------------------------------------------------------------------------
92
107
108 def _append_svg(self, svg):
109 """ Append raw svg data to the widget.
110 """
111 try:
112 image = svg_to_image(svg)
113 except ValueError:
114 self._append_plain_text('Received invalid plot data.')
115 else:
116 format = self._add_image(image)
117 self._name_to_svg[str(format.name())] = svg
118 format.setProperty(self._svg_text_format_property, svg)
119 cursor = self._get_end_cursor()
120 cursor.insertBlock()
121 cursor.insertImage(format)
122 cursor.insertBlock()
123
93 def _add_image(self, image):
124 def _add_image(self, image):
94 """ Adds the specified QImage to the document and returns a
125 """ Adds the specified QImage to the document and returns a
95 QTextImageFormat that references it.
126 QTextImageFormat that references it.
@@ -192,4 +223,4 b' class RichIPythonWidget(IPythonWidget):'
192
223
193 else:
224 else:
194 return '<b>Unrecognized image format</b>'
225 return '<b>Unrecognized image format</b>'
195
226
@@ -101,6 +101,9 b' class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):'
101 # Emitted when a message of type 'pyerr' is received.
101 # Emitted when a message of type 'pyerr' is received.
102 pyerr_received = QtCore.pyqtSignal(object)
102 pyerr_received = QtCore.pyqtSignal(object)
103
103
104 # Emitted when a message of type 'display_data' is received
105 display_data_received = QtCore.pyqtSignal(object)
106
104 # Emitted when a crash report message is received from the kernel's
107 # Emitted when a crash report message is received from the kernel's
105 # last-resort sys.excepthook.
108 # last-resort sys.excepthook.
106 crash_received = QtCore.pyqtSignal(object)
109 crash_received = QtCore.pyqtSignal(object)
@@ -117,7 +120,6 b' class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):'
117 """
120 """
118 # Emit the generic signal.
121 # Emit the generic signal.
119 self.message_received.emit(msg)
122 self.message_received.emit(msg)
120
121 # Emit signals for specialized message types.
123 # Emit signals for specialized message types.
122 msg_type = msg['msg_type']
124 msg_type = msg['msg_type']
123 signal = getattr(self, msg_type + '_received', None)
125 signal = getattr(self, msg_type + '_received', None)
@@ -91,6 +91,8 b' class Kernel(Configurable):'
91 self.shell = ZMQInteractiveShell.instance()
91 self.shell = ZMQInteractiveShell.instance()
92 self.shell.displayhook.session = self.session
92 self.shell.displayhook.session = self.session
93 self.shell.displayhook.pub_socket = self.pub_socket
93 self.shell.displayhook.pub_socket = self.pub_socket
94 self.shell.display_pub.session = self.session
95 self.shell.display_pub.pub_socket = self.pub_socket
94
96
95 # TMP - hack while developing
97 # TMP - hack while developing
96 self.shell._reply_content = None
98 self.shell._reply_content = None
@@ -194,6 +196,7 b' class Kernel(Configurable):'
194
196
195 # Set the parent message of the display hook and out streams.
197 # Set the parent message of the display hook and out streams.
196 shell.displayhook.set_parent(parent)
198 shell.displayhook.set_parent(parent)
199 shell.display_pub.set_parent(parent)
197 sys.stdout.set_parent(parent)
200 sys.stdout.set_parent(parent)
198 sys.stderr.set_parent(parent)
201 sys.stderr.set_parent(parent)
199
202
@@ -14,7 +14,7 b' from matplotlib.backends.backend_svg import new_figure_manager'
14 from matplotlib._pylab_helpers import Gcf
14 from matplotlib._pylab_helpers import Gcf
15
15
16 # Local imports.
16 # Local imports.
17 from backend_payload import add_plot_payload
17 from IPython.core.displaypub import publish_display_data
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Functions
20 # Functions
@@ -85,7 +85,11 b' def send_svg_canvas(canvas):'
85 canvas.figure.set_facecolor('white')
85 canvas.figure.set_facecolor('white')
86 canvas.figure.set_edgecolor('white')
86 canvas.figure.set_edgecolor('white')
87 try:
87 try:
88 add_plot_payload('svg', svg_from_canvas(canvas))
88 publish_display_data(
89 'IPython.zmq.pylab.backend_inline.send_svg_canvas',
90 '<Matplotlib Plot>',
91 svg=svg_from_canvas(canvas)
92 )
89 finally:
93 finally:
90 canvas.figure.set_facecolor(fc)
94 canvas.figure.set_facecolor(fc)
91 canvas.figure.set_edgecolor(ec)
95 canvas.figure.set_edgecolor(ec)
@@ -26,6 +26,7 b' from IPython.core.interactiveshell import ('
26 )
26 )
27 from IPython.core import page
27 from IPython.core import page
28 from IPython.core.displayhook import DisplayHook
28 from IPython.core.displayhook import DisplayHook
29 from IPython.core.displaypub import DisplayPublisher
29 from IPython.core.macro import Macro
30 from IPython.core.macro import Macro
30 from IPython.core.payloadpage import install_payload_page
31 from IPython.core.payloadpage import install_payload_page
31 from IPython.utils import io
32 from IPython.utils import io
@@ -75,10 +76,34 b' class ZMQDisplayHook(DisplayHook):'
75 self.msg = None
76 self.msg = None
76
77
77
78
79 class ZMQDisplayPublisher(DisplayPublisher):
80 """A ``DisplayPublisher`` that published data using a ZeroMQ PUB socket."""
81
82 session = Instance(Session)
83 pub_socket = Instance('zmq.Socket')
84 parent_header = Dict({})
85
86 def set_parent(self, parent):
87 """Set the parent for outbound messages."""
88 self.parent_header = extract_header(parent)
89
90 def publish(self, source, data, metadata=None):
91 if metadata is None:
92 metadata = {}
93 self._validate_data(source, data, metadata)
94 msg = self.session.msg(u'display_data', {}, parent=self.parent_header)
95 msg['content']['source'] = source
96 msg['content']['data'] = data
97 msg['content']['metadata'] = metadata
98 self.pub_socket.send_json(msg)
99
100
78 class ZMQInteractiveShell(InteractiveShell):
101 class ZMQInteractiveShell(InteractiveShell):
79 """A subclass of InteractiveShell for ZMQ."""
102 """A subclass of InteractiveShell for ZMQ."""
80
103
81 displayhook_class = Type(ZMQDisplayHook)
104 displayhook_class = Type(ZMQDisplayHook)
105 display_pub_class = Type(ZMQDisplayPublisher)
106
82 keepkernel_on_exit = None
107 keepkernel_on_exit = None
83
108
84 def init_environment(self):
109 def init_environment(self):
@@ -700,30 +700,32 b" socket with the names 'stdin' and 'stdin_reply'. This will allow other clients"
700 to monitor/display kernel interactions and possibly replay them to their user
700 to monitor/display kernel interactions and possibly replay them to their user
701 or otherwise expose them.
701 or otherwise expose them.
702
702
703 Representation Data
703 Display Data
704 -------------------
704 ------------
705
705
706 This type of message is used to bring back representations (text, html, svg,
706 This type of message is used to bring back data that should be diplayed (text,
707 etc.) of Python objects to the frontend. Each message can have multiple
707 html, svg, etc.) in the frontends. This data is published to all frontends.
708 representations of the object; it is up to the frontend to decide which to use
708 Each message can have multiple representations of the data; it is up to the
709 and how. A single message should contain the different representations of a
709 frontend to decide which to use and how. A single message should contain all
710 single Python object. Each representation should be a JSON'able data structure,
710 possible representations of the same information. Each representation should
711 and should be a valid MIME type.
711 be a JSON'able data structure, and should be a valid MIME type.
712
712
713 Some questions remain about this design:
713 Some questions remain about this design:
714
714
715 * Do we use this message type for pyout/displayhook?
715 * Do we use this message type for pyout/displayhook? Probably not, because
716 * What is the best way to organize the content dict of the message?
716 the displayhook also has to handle the Out prompt display. On the other hand
717 we could put that information into the metadata secion.
717
718
718 Message type: ``repr_data``::
719 Message type: ``display_data``::
719
720
720 # Option 1: if we only allow a single source.
721 content = {
721 content = {
722 'source' : str # Who create the data
722 'source' : str # Who create the data
723 'data' : dict # {'mimetype1' : data1, 'mimetype2' : data2}
723 'data' : dict # {'mimetype1' : data1, 'mimetype2' : data2}
724 'metadata' : dict # Any metadata that describes the data
724 'metadata' : dict # Any metadata that describes the data
725 }
725 }
726
726
727 Other options for ``display_data`` content::
728
727 # Option 2: allowing for a different source for each representation,
729 # Option 2: allowing for a different source for each representation,
728 but not keyed by anything.
730 but not keyed by anything.
729 content = {
731 content = {
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now