Show More
@@ -1,5 +1,5 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
"""An interface for publishing |
|
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 |
|
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: `` |
|
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