Show More
@@ -1,5 +1,5 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 |
"""An interface for publishing |
|
|
2 | """An interface for publishing rich data to frontends. | |
|
3 | 3 | |
|
4 | 4 | Authors: |
|
5 | 5 | |
@@ -35,6 +35,69 b' class DisplayPublisher(Configurable):' | |||
|
35 | 35 | raise TypeError('metadata must be a dict, got: %r' % data) |
|
36 | 36 | |
|
37 | 37 | def publish(self, source, data, metadata=None): |
|
38 |
"""Publish data and metadata to all frontends. |
|
|
39 | pass | |
|
38 | """Publish data and metadata to all frontends. | |
|
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 | 41 | from IPython.core.compilerop import CachingCompiler |
|
42 | 42 | from IPython.core.display_trap import DisplayTrap |
|
43 | 43 | from IPython.core.displayhook import DisplayHook |
|
44 | from IPython.core.displaypub import DisplayPublisher | |
|
44 | 45 | from IPython.core.error import TryNext, UsageError |
|
45 | 46 | from IPython.core.extensions import ExtensionManager |
|
46 | 47 | from IPython.core.fakemodule import FakeModule, init_fakemod_dict |
@@ -150,6 +151,8 b' class InteractiveShell(Configurable, Magic):' | |||
|
150 | 151 | debug = CBool(False, config=True) |
|
151 | 152 | deep_reload = CBool(False, config=True) |
|
152 | 153 | displayhook_class = Type(DisplayHook) |
|
154 | display_pub_class = Type(DisplayPublisher) | |
|
155 | ||
|
153 | 156 | exit_now = CBool(False) |
|
154 | 157 | # Monotonically increasing execution counter |
|
155 | 158 | execution_count = Int(1) |
@@ -284,6 +287,7 b' class InteractiveShell(Configurable, Magic):' | |||
|
284 | 287 | self.init_io() |
|
285 | 288 | self.init_traceback_handlers(custom_exceptions) |
|
286 | 289 | self.init_prompts() |
|
290 | self.init_display_pub() | |
|
287 | 291 | self.init_displayhook() |
|
288 | 292 | self.init_reload_doctest() |
|
289 | 293 | self.init_magics() |
@@ -481,6 +485,9 b' class InteractiveShell(Configurable, Magic):' | |||
|
481 | 485 | # will initialize that object and all prompt related information. |
|
482 | 486 | pass |
|
483 | 487 | |
|
488 | def init_display_pub(self): | |
|
489 | self.display_pub = self.display_pub_class(config=self.config) | |
|
490 | ||
|
484 | 491 | def init_displayhook(self): |
|
485 | 492 | # Initialize displayhook, set in/out prompts and printing system |
|
486 | 493 | self.displayhook = self.displayhook_class( |
@@ -183,6 +183,21 b' class IPythonWidget(FrontendWidget):' | |||
|
183 | 183 | self._append_html(self._make_out_prompt(prompt_number)) |
|
184 | 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 | 201 | def _started_channels(self): |
|
187 | 202 | """ Reimplemented to make a history request. |
|
188 | 203 | """ |
@@ -444,7 +459,7 b' class IPythonWidget(FrontendWidget):' | |||
|
444 | 459 | else: |
|
445 | 460 | self._page(item['text'], html=False) |
|
446 | 461 | |
|
447 |
#------ Trait change handlers -------------------------------------------- |
|
|
462 | #------ Trait change handlers -------------------------------------------- | |
|
448 | 463 | |
|
449 | 464 | def _style_sheet_changed(self): |
|
450 | 465 | """ Set the style sheets of the underlying widgets. |
@@ -464,4 +479,4 b' class IPythonWidget(FrontendWidget):' | |||
|
464 | 479 | self._highlighter.set_style(self.syntax_style) |
|
465 | 480 | else: |
|
466 | 481 | self._highlighter.set_style_sheet(self.style_sheet) |
|
467 | ||
|
482 |
@@ -56,7 +56,31 b' class RichIPythonWidget(IPythonWidget):' | |||
|
56 | 56 | menu.addAction('Save SVG As...', |
|
57 | 57 | lambda: save_svg(svg, self._control)) |
|
58 | 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 | 85 | # 'FrontendWidget' protected interface |
|
62 | 86 | #--------------------------------------------------------------------------- |
@@ -65,20 +89,11 b' class RichIPythonWidget(IPythonWidget):' | |||
|
65 | 89 | """ Reimplemented to handle matplotlib plot payloads. |
|
66 | 90 | """ |
|
67 | 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 | 94 | if item['format'] == 'svg': |
|
69 | 95 | svg = item['data'] |
|
70 | try: | |
|
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() | |
|
96 | self._append_svg(svg) | |
|
82 | 97 | return True |
|
83 | 98 | else: |
|
84 | 99 | # Add other plot formats here! |
@@ -90,6 +105,22 b' class RichIPythonWidget(IPythonWidget):' | |||
|
90 | 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 | 124 | def _add_image(self, image): |
|
94 | 125 | """ Adds the specified QImage to the document and returns a |
|
95 | 126 | QTextImageFormat that references it. |
@@ -192,4 +223,4 b' class RichIPythonWidget(IPythonWidget):' | |||
|
192 | 223 | |
|
193 | 224 | else: |
|
194 | 225 | return '<b>Unrecognized image format</b>' |
|
195 | ||
|
226 |
@@ -101,6 +101,9 b' class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):' | |||
|
101 | 101 | # Emitted when a message of type 'pyerr' is received. |
|
102 | 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 | 107 | # Emitted when a crash report message is received from the kernel's |
|
105 | 108 | # last-resort sys.excepthook. |
|
106 | 109 | crash_received = QtCore.pyqtSignal(object) |
@@ -117,7 +120,6 b' class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):' | |||
|
117 | 120 | """ |
|
118 | 121 | # Emit the generic signal. |
|
119 | 122 | self.message_received.emit(msg) |
|
120 | ||
|
121 | 123 | # Emit signals for specialized message types. |
|
122 | 124 | msg_type = msg['msg_type'] |
|
123 | 125 | signal = getattr(self, msg_type + '_received', None) |
@@ -91,6 +91,8 b' class Kernel(Configurable):' | |||
|
91 | 91 | self.shell = ZMQInteractiveShell.instance() |
|
92 | 92 | self.shell.displayhook.session = self.session |
|
93 | 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 | 97 | # TMP - hack while developing |
|
96 | 98 | self.shell._reply_content = None |
@@ -194,6 +196,7 b' class Kernel(Configurable):' | |||
|
194 | 196 | |
|
195 | 197 | # Set the parent message of the display hook and out streams. |
|
196 | 198 | shell.displayhook.set_parent(parent) |
|
199 | shell.display_pub.set_parent(parent) | |
|
197 | 200 | sys.stdout.set_parent(parent) |
|
198 | 201 | sys.stderr.set_parent(parent) |
|
199 | 202 |
@@ -14,7 +14,7 b' from matplotlib.backends.backend_svg import new_figure_manager' | |||
|
14 | 14 | from matplotlib._pylab_helpers import Gcf |
|
15 | 15 | |
|
16 | 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 | 20 | # Functions |
@@ -85,7 +85,11 b' def send_svg_canvas(canvas):' | |||
|
85 | 85 | canvas.figure.set_facecolor('white') |
|
86 | 86 | canvas.figure.set_edgecolor('white') |
|
87 | 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 | 93 | finally: |
|
90 | 94 | canvas.figure.set_facecolor(fc) |
|
91 | 95 | canvas.figure.set_edgecolor(ec) |
@@ -26,6 +26,7 b' from IPython.core.interactiveshell import (' | |||
|
26 | 26 | ) |
|
27 | 27 | from IPython.core import page |
|
28 | 28 | from IPython.core.displayhook import DisplayHook |
|
29 | from IPython.core.displaypub import DisplayPublisher | |
|
29 | 30 | from IPython.core.macro import Macro |
|
30 | 31 | from IPython.core.payloadpage import install_payload_page |
|
31 | 32 | from IPython.utils import io |
@@ -75,10 +76,34 b' class ZMQDisplayHook(DisplayHook):' | |||
|
75 | 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 | 101 | class ZMQInteractiveShell(InteractiveShell): |
|
79 | 102 | """A subclass of InteractiveShell for ZMQ.""" |
|
80 | 103 | |
|
81 | 104 | displayhook_class = Type(ZMQDisplayHook) |
|
105 | display_pub_class = Type(ZMQDisplayPublisher) | |
|
106 | ||
|
82 | 107 | keepkernel_on_exit = None |
|
83 | 108 | |
|
84 | 109 | def init_environment(self): |
@@ -700,30 +700,32 b" socket with the names 'stdin' and 'stdin_reply'. This will allow other clients" | |||
|
700 | 700 | to monitor/display kernel interactions and possibly replay them to their user |
|
701 | 701 | or otherwise expose them. |
|
702 | 702 | |
|
703 | Representation Data | |
|
704 |
------------ |
|
|
703 | Display Data | |
|
704 | ------------ | |
|
705 | 705 | |
|
706 |
This type of message is used to bring back |
|
|
707 | etc.) of Python objects to the frontend. Each message can have multiple | |
|
708 | representations of the object; it is up to the frontend to decide which to use | |
|
709 | and how. A single message should contain the different representations of a | |
|
710 | single Python object. Each representation should be a JSON'able data structure, | |
|
711 | and should be a valid MIME type. | |
|
706 | This type of message is used to bring back data that should be diplayed (text, | |
|
707 | html, svg, etc.) in the frontends. This data is published to all frontends. | |
|
708 | Each message can have multiple representations of the data; it is up to the | |
|
709 | frontend to decide which to use and how. A single message should contain all | |
|
710 | possible representations of the same information. Each representation should | |
|
711 | be a JSON'able data structure, and should be a valid MIME type. | |
|
712 | 712 | |
|
713 | 713 | Some questions remain about this design: |
|
714 | 714 | |
|
715 | * Do we use this message type for pyout/displayhook? | |
|
716 | * What is the best way to organize the content dict of the message? | |
|
715 | * Do we use this message type for pyout/displayhook? Probably not, because | |
|
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: `` |
|
|
719 | Message type: ``display_data``:: | |
|
719 | 720 | |
|
720 | # Option 1: if we only allow a single source. | |
|
721 | 721 | content = { |
|
722 | 722 | 'source' : str # Who create the data |
|
723 | 723 | 'data' : dict # {'mimetype1' : data1, 'mimetype2' : data2} |
|
724 | 724 | 'metadata' : dict # Any metadata that describes the data |
|
725 | 725 | } |
|
726 | 726 | |
|
727 | Other options for ``display_data`` content:: | |
|
728 | ||
|
727 | 729 | # Option 2: allowing for a different source for each representation, |
|
728 | 730 | but not keyed by anything. |
|
729 | 731 | content = { |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now