##// END OF EJS Templates
review fixes
MinRK -
Show More
@@ -1,568 +1,568 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7
7
8 # System library imports
8 # System library imports
9 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
10 from PyQt4 import QtCore, QtGui
10 from PyQt4 import QtCore, QtGui
11
11
12 # Local imports
12 # Local imports
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
14 from IPython.core.oinspect import call_tip
14 from IPython.core.oinspect import call_tip
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
16 from IPython.utils.traitlets import Bool
16 from IPython.utils.traitlets import Bool
17 from bracket_matcher import BracketMatcher
17 from bracket_matcher import BracketMatcher
18 from call_tip_widget import CallTipWidget
18 from call_tip_widget import CallTipWidget
19 from completion_lexer import CompletionLexer
19 from completion_lexer import CompletionLexer
20 from history_console_widget import HistoryConsoleWidget
20 from history_console_widget import HistoryConsoleWidget
21 from pygments_highlighter import PygmentsHighlighter
21 from pygments_highlighter import PygmentsHighlighter
22
22
23
23
24 class FrontendHighlighter(PygmentsHighlighter):
24 class FrontendHighlighter(PygmentsHighlighter):
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
25 """ A PygmentsHighlighter that can be turned on and off and that ignores
26 prompts.
26 prompts.
27 """
27 """
28
28
29 def __init__(self, frontend):
29 def __init__(self, frontend):
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 self._current_offset = 0
31 self._current_offset = 0
32 self._frontend = frontend
32 self._frontend = frontend
33 self.highlighting_on = False
33 self.highlighting_on = False
34
34
35 def highlightBlock(self, qstring):
35 def highlightBlock(self, qstring):
36 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """
37 """
38 if not self.highlighting_on:
38 if not self.highlighting_on:
39 return
39 return
40
40
41 # The input to this function is unicode string that may contain
41 # The input to this function is unicode string that may contain
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # the string as plain text so we can compare it.
43 # the string as plain text so we can compare it.
44 current_block = self.currentBlock()
44 current_block = self.currentBlock()
45 string = self._frontend._get_block_plain_text(current_block)
45 string = self._frontend._get_block_plain_text(current_block)
46
46
47 # Decide whether to check for the regular or continuation prompt.
47 # Decide whether to check for the regular or continuation prompt.
48 if current_block.contains(self._frontend._prompt_pos):
48 if current_block.contains(self._frontend._prompt_pos):
49 prompt = self._frontend._prompt
49 prompt = self._frontend._prompt
50 else:
50 else:
51 prompt = self._frontend._continuation_prompt
51 prompt = self._frontend._continuation_prompt
52
52
53 # Don't highlight the part of the string that contains the prompt.
53 # Don't highlight the part of the string that contains the prompt.
54 if string.startswith(prompt):
54 if string.startswith(prompt):
55 self._current_offset = len(prompt)
55 self._current_offset = len(prompt)
56 qstring.remove(0, len(prompt))
56 qstring.remove(0, len(prompt))
57 else:
57 else:
58 self._current_offset = 0
58 self._current_offset = 0
59
59
60 PygmentsHighlighter.highlightBlock(self, qstring)
60 PygmentsHighlighter.highlightBlock(self, qstring)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 PygmentsHighlighter.setFormat(self, start, count, format)
74 PygmentsHighlighter.setFormat(self, start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
82 # interrupt behavior.
82 # interrupt behavior.
83 custom_interrupt = Bool(False)
83 custom_interrupt = Bool(False)
84 custom_interrupt_requested = QtCore.pyqtSignal()
84 custom_interrupt_requested = QtCore.pyqtSignal()
85
85
86 # An option and corresponding signals for overriding the default kernel
86 # An option and corresponding signals for overriding the default kernel
87 # restart behavior.
87 # restart behavior.
88 custom_restart = Bool(False)
88 custom_restart = Bool(False)
89 custom_restart_kernel_died = QtCore.pyqtSignal(float)
89 custom_restart_kernel_died = QtCore.pyqtSignal(float)
90 custom_restart_requested = QtCore.pyqtSignal()
90 custom_restart_requested = QtCore.pyqtSignal()
91
91
92 # Emitted when an 'execute_reply' has been received from the kernel and
92 # Emitted when an 'execute_reply' has been received from the kernel and
93 # processed by the FrontendWidget.
93 # processed by the FrontendWidget.
94 executed = QtCore.pyqtSignal(object)
94 executed = QtCore.pyqtSignal(object)
95
95
96 # Emitted when an exit request has been received from the kernel.
96 # Emitted when an exit request has been received from the kernel.
97 exit_requested = QtCore.pyqtSignal()
97 exit_requested = QtCore.pyqtSignal()
98
98
99 # Protected class variables.
99 # Protected class variables.
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
103 _input_splitter_class = InputSplitter
103 _input_splitter_class = InputSplitter
104
104
105 #---------------------------------------------------------------------------
105 #---------------------------------------------------------------------------
106 # 'object' interface
106 # 'object' interface
107 #---------------------------------------------------------------------------
107 #---------------------------------------------------------------------------
108
108
109 def __init__(self, *args, **kw):
109 def __init__(self, *args, **kw):
110 super(FrontendWidget, self).__init__(*args, **kw)
110 super(FrontendWidget, self).__init__(*args, **kw)
111
111
112 # FrontendWidget protected variables.
112 # FrontendWidget protected variables.
113 self._bracket_matcher = BracketMatcher(self._control)
113 self._bracket_matcher = BracketMatcher(self._control)
114 self._call_tip_widget = CallTipWidget(self._control)
114 self._call_tip_widget = CallTipWidget(self._control)
115 self._completion_lexer = CompletionLexer(PythonLexer())
115 self._completion_lexer = CompletionLexer(PythonLexer())
116 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
116 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
117 self._hidden = False
117 self._hidden = False
118 self._highlighter = FrontendHighlighter(self)
118 self._highlighter = FrontendHighlighter(self)
119 self._input_splitter = self._input_splitter_class(input_mode='cell')
119 self._input_splitter = self._input_splitter_class(input_mode='cell')
120 self._kernel_manager = None
120 self._kernel_manager = None
121 self._request_info = {}
121 self._request_info = {}
122
122
123 # Configure the ConsoleWidget.
123 # Configure the ConsoleWidget.
124 self.tab_width = 4
124 self.tab_width = 4
125 self._set_continuation_prompt('... ')
125 self._set_continuation_prompt('... ')
126
126
127 # Configure the CallTipWidget.
127 # Configure the CallTipWidget.
128 self._call_tip_widget.setFont(self.font)
128 self._call_tip_widget.setFont(self.font)
129 self.font_changed.connect(self._call_tip_widget.setFont)
129 self.font_changed.connect(self._call_tip_widget.setFont)
130
130
131 # Configure actions.
131 # Configure actions.
132 action = self._copy_raw_action
132 action = self._copy_raw_action
133 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
133 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
134 action.setEnabled(False)
134 action.setEnabled(False)
135 action.setShortcut(QtGui.QKeySequence(key))
135 action.setShortcut(QtGui.QKeySequence(key))
136 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
136 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
137 action.triggered.connect(self.copy_raw)
137 action.triggered.connect(self.copy_raw)
138 self.copy_available.connect(action.setEnabled)
138 self.copy_available.connect(action.setEnabled)
139 self.addAction(action)
139 self.addAction(action)
140
140
141 # Connect signal handlers.
141 # Connect signal handlers.
142 document = self._control.document()
142 document = self._control.document()
143 document.contentsChange.connect(self._document_contents_change)
143 document.contentsChange.connect(self._document_contents_change)
144
144
145 #---------------------------------------------------------------------------
145 #---------------------------------------------------------------------------
146 # 'ConsoleWidget' public interface
146 # 'ConsoleWidget' public interface
147 #---------------------------------------------------------------------------
147 #---------------------------------------------------------------------------
148
148
149 def copy(self):
149 def copy(self):
150 """ Copy the currently selected text to the clipboard, removing prompts.
150 """ Copy the currently selected text to the clipboard, removing prompts.
151 """
151 """
152 text = unicode(self._control.textCursor().selection().toPlainText())
152 text = unicode(self._control.textCursor().selection().toPlainText())
153 if text:
153 if text:
154 lines = map(transform_classic_prompt, text.splitlines())
154 lines = map(transform_classic_prompt, text.splitlines())
155 text = '\n'.join(lines)
155 text = '\n'.join(lines)
156 QtGui.QApplication.clipboard().setText(text)
156 QtGui.QApplication.clipboard().setText(text)
157
157
158 #---------------------------------------------------------------------------
158 #---------------------------------------------------------------------------
159 # 'ConsoleWidget' abstract interface
159 # 'ConsoleWidget' abstract interface
160 #---------------------------------------------------------------------------
160 #---------------------------------------------------------------------------
161
161
162 def _is_complete(self, source, interactive):
162 def _is_complete(self, source, interactive):
163 """ Returns whether 'source' can be completely processed and a new
163 """ Returns whether 'source' can be completely processed and a new
164 prompt created. When triggered by an Enter/Return key press,
164 prompt created. When triggered by an Enter/Return key press,
165 'interactive' is True; otherwise, it is False.
165 'interactive' is True; otherwise, it is False.
166 """
166 """
167 complete = self._input_splitter.push(source)
167 complete = self._input_splitter.push(source)
168 if interactive:
168 if interactive:
169 complete = not self._input_splitter.push_accepts_more()
169 complete = not self._input_splitter.push_accepts_more()
170 return complete
170 return complete
171
171
172 def _execute(self, source, hidden):
172 def _execute(self, source, hidden):
173 """ Execute 'source'. If 'hidden', do not show any output.
173 """ Execute 'source'. If 'hidden', do not show any output.
174
174
175 See parent class :meth:`execute` docstring for full details.
175 See parent class :meth:`execute` docstring for full details.
176 """
176 """
177 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
177 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
178 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
178 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
179 self._hidden = hidden
179 self._hidden = hidden
180
180
181 def _prompt_started_hook(self):
181 def _prompt_started_hook(self):
182 """ Called immediately after a new prompt is displayed.
182 """ Called immediately after a new prompt is displayed.
183 """
183 """
184 if not self._reading:
184 if not self._reading:
185 self._highlighter.highlighting_on = True
185 self._highlighter.highlighting_on = True
186
186
187 def _prompt_finished_hook(self):
187 def _prompt_finished_hook(self):
188 """ Called immediately after a prompt is finished, i.e. when some input
188 """ Called immediately after a prompt is finished, i.e. when some input
189 will be processed and a new prompt displayed.
189 will be processed and a new prompt displayed.
190 """
190 """
191 if not self._reading:
191 if not self._reading:
192 self._highlighter.highlighting_on = False
192 self._highlighter.highlighting_on = False
193
193
194 def _tab_pressed(self):
194 def _tab_pressed(self):
195 """ Called when the tab key is pressed. Returns whether to continue
195 """ Called when the tab key is pressed. Returns whether to continue
196 processing the event.
196 processing the event.
197 """
197 """
198 # Perform tab completion if:
198 # Perform tab completion if:
199 # 1) The cursor is in the input buffer.
199 # 1) The cursor is in the input buffer.
200 # 2) There is a non-whitespace character before the cursor.
200 # 2) There is a non-whitespace character before the cursor.
201 text = self._get_input_buffer_cursor_line()
201 text = self._get_input_buffer_cursor_line()
202 if text is None:
202 if text is None:
203 return False
203 return False
204 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
204 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
205 if complete:
205 if complete:
206 self._complete()
206 self._complete()
207 return not complete
207 return not complete
208
208
209 #---------------------------------------------------------------------------
209 #---------------------------------------------------------------------------
210 # 'ConsoleWidget' protected interface
210 # 'ConsoleWidget' protected interface
211 #---------------------------------------------------------------------------
211 #---------------------------------------------------------------------------
212
212
213 def _context_menu_make(self, pos):
213 def _context_menu_make(self, pos):
214 """ Reimplemented to add an action for raw copy.
214 """ Reimplemented to add an action for raw copy.
215 """
215 """
216 menu = super(FrontendWidget, self)._context_menu_make(pos)
216 menu = super(FrontendWidget, self)._context_menu_make(pos)
217 for before_action in menu.actions():
217 for before_action in menu.actions():
218 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
218 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
219 QtGui.QKeySequence.ExactMatch:
219 QtGui.QKeySequence.ExactMatch:
220 menu.insertAction(before_action, self._copy_raw_action)
220 menu.insertAction(before_action, self._copy_raw_action)
221 break
221 break
222 return menu
222 return menu
223
223
224 def _event_filter_console_keypress(self, event):
224 def _event_filter_console_keypress(self, event):
225 """ Reimplemented for execution interruption and smart backspace.
225 """ Reimplemented for execution interruption and smart backspace.
226 """
226 """
227 key = event.key()
227 key = event.key()
228 if self._control_key_down(event.modifiers(), include_command=False):
228 if self._control_key_down(event.modifiers(), include_command=False):
229
229
230 if key == QtCore.Qt.Key_C and self._executing:
230 if key == QtCore.Qt.Key_C and self._executing:
231 self.interrupt_kernel()
231 self.interrupt_kernel()
232 return True
232 return True
233
233
234 elif key == QtCore.Qt.Key_Period:
234 elif key == QtCore.Qt.Key_Period:
235 message = 'Are you sure you want to restart the kernel?'
235 message = 'Are you sure you want to restart the kernel?'
236 self.restart_kernel(message, now=False)
236 self.restart_kernel(message, now=False)
237 return True
237 return True
238
238
239 elif not event.modifiers() & QtCore.Qt.AltModifier:
239 elif not event.modifiers() & QtCore.Qt.AltModifier:
240
240
241 # Smart backspace: remove four characters in one backspace if:
241 # Smart backspace: remove four characters in one backspace if:
242 # 1) everything left of the cursor is whitespace
242 # 1) everything left of the cursor is whitespace
243 # 2) the four characters immediately left of the cursor are spaces
243 # 2) the four characters immediately left of the cursor are spaces
244 if key == QtCore.Qt.Key_Backspace:
244 if key == QtCore.Qt.Key_Backspace:
245 col = self._get_input_buffer_cursor_column()
245 col = self._get_input_buffer_cursor_column()
246 cursor = self._control.textCursor()
246 cursor = self._control.textCursor()
247 if col > 3 and not cursor.hasSelection():
247 if col > 3 and not cursor.hasSelection():
248 text = self._get_input_buffer_cursor_line()[:col]
248 text = self._get_input_buffer_cursor_line()[:col]
249 if text.endswith(' ') and not text.strip():
249 if text.endswith(' ') and not text.strip():
250 cursor.movePosition(QtGui.QTextCursor.Left,
250 cursor.movePosition(QtGui.QTextCursor.Left,
251 QtGui.QTextCursor.KeepAnchor, 4)
251 QtGui.QTextCursor.KeepAnchor, 4)
252 cursor.removeSelectedText()
252 cursor.removeSelectedText()
253 return True
253 return True
254
254
255 return super(FrontendWidget, self)._event_filter_console_keypress(event)
255 return super(FrontendWidget, self)._event_filter_console_keypress(event)
256
256
257 def _insert_continuation_prompt(self, cursor):
257 def _insert_continuation_prompt(self, cursor):
258 """ Reimplemented for auto-indentation.
258 """ Reimplemented for auto-indentation.
259 """
259 """
260 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
260 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
261 cursor.insertText(' ' * self._input_splitter.indent_spaces)
261 cursor.insertText(' ' * self._input_splitter.indent_spaces)
262
262
263 #---------------------------------------------------------------------------
263 #---------------------------------------------------------------------------
264 # 'BaseFrontendMixin' abstract interface
264 # 'BaseFrontendMixin' abstract interface
265 #---------------------------------------------------------------------------
265 #---------------------------------------------------------------------------
266
266
267 def _handle_complete_reply(self, rep):
267 def _handle_complete_reply(self, rep):
268 """ Handle replies for tab completion.
268 """ Handle replies for tab completion.
269 """
269 """
270 cursor = self._get_cursor()
270 cursor = self._get_cursor()
271 info = self._request_info.get('complete')
271 info = self._request_info.get('complete')
272 if info and info.id == rep['parent_header']['msg_id'] and \
272 if info and info.id == rep['parent_header']['msg_id'] and \
273 info.pos == cursor.position():
273 info.pos == cursor.position():
274 text = '.'.join(self._get_context())
274 text = '.'.join(self._get_context())
275 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
275 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
276 self._complete_with_items(cursor, rep['content']['matches'])
276 self._complete_with_items(cursor, rep['content']['matches'])
277
277
278 def _handle_execute_reply(self, msg):
278 def _handle_execute_reply(self, msg):
279 """ Handles replies for code execution.
279 """ Handles replies for code execution.
280 """
280 """
281 info = self._request_info.get('execute')
281 info = self._request_info.get('execute')
282 if info and info.id == msg['parent_header']['msg_id'] and \
282 if info and info.id == msg['parent_header']['msg_id'] and \
283 info.kind == 'user' and not self._hidden:
283 info.kind == 'user' and not self._hidden:
284 # Make sure that all output from the SUB channel has been processed
284 # Make sure that all output from the SUB channel has been processed
285 # before writing a new prompt.
285 # before writing a new prompt.
286 self.kernel_manager.sub_channel.flush()
286 self.kernel_manager.sub_channel.flush()
287
287
288 # Reset the ANSI style information to prevent bad text in stdout
288 # Reset the ANSI style information to prevent bad text in stdout
289 # from messing up our colors. We're not a true terminal so we're
289 # from messing up our colors. We're not a true terminal so we're
290 # allowed to do this.
290 # allowed to do this.
291 if self.ansi_codes:
291 if self.ansi_codes:
292 self._ansi_processor.reset_sgr()
292 self._ansi_processor.reset_sgr()
293
293
294 content = msg['content']
294 content = msg['content']
295 status = content['status']
295 status = content['status']
296 if status == 'ok':
296 if status == 'ok':
297 self._process_execute_ok(msg)
297 self._process_execute_ok(msg)
298 elif status == 'error':
298 elif status == 'error':
299 self._process_execute_error(msg)
299 self._process_execute_error(msg)
300 elif status == 'abort':
300 elif status == 'abort':
301 self._process_execute_abort(msg)
301 self._process_execute_abort(msg)
302
302
303 self._show_interpreter_prompt_for_reply(msg)
303 self._show_interpreter_prompt_for_reply(msg)
304 self.executed.emit(msg)
304 self.executed.emit(msg)
305
305
306 def _handle_input_request(self, msg):
306 def _handle_input_request(self, msg):
307 """ Handle requests for raw_input.
307 """ Handle requests for raw_input.
308 """
308 """
309 if self._hidden:
309 if self._hidden:
310 raise RuntimeError('Request for raw input during hidden execution.')
310 raise RuntimeError('Request for raw input during hidden execution.')
311
311
312 # Make sure that all output from the SUB channel has been processed
312 # Make sure that all output from the SUB channel has been processed
313 # before entering readline mode.
313 # before entering readline mode.
314 self.kernel_manager.sub_channel.flush()
314 self.kernel_manager.sub_channel.flush()
315
315
316 def callback(line):
316 def callback(line):
317 self.kernel_manager.rep_channel.input(line)
317 self.kernel_manager.rep_channel.input(line)
318 self._readline(msg['content']['prompt'], callback=callback)
318 self._readline(msg['content']['prompt'], callback=callback)
319
319
320 def _handle_kernel_died(self, since_last_heartbeat):
320 def _handle_kernel_died(self, since_last_heartbeat):
321 """ Handle the kernel's death by asking if the user wants to restart.
321 """ Handle the kernel's death by asking if the user wants to restart.
322 """
322 """
323 if self.custom_restart:
323 if self.custom_restart:
324 self.custom_restart_kernel_died.emit(since_last_heartbeat)
324 self.custom_restart_kernel_died.emit(since_last_heartbeat)
325 else:
325 else:
326 message = 'The kernel heartbeat has been inactive for %.2f ' \
326 message = 'The kernel heartbeat has been inactive for %.2f ' \
327 'seconds. Do you want to restart the kernel? You may ' \
327 'seconds. Do you want to restart the kernel? You may ' \
328 'first want to check the network connection.' % \
328 'first want to check the network connection.' % \
329 since_last_heartbeat
329 since_last_heartbeat
330 self.restart_kernel(message, now=True)
330 self.restart_kernel(message, now=True)
331
331
332 def _handle_object_info_reply(self, rep):
332 def _handle_object_info_reply(self, rep):
333 """ Handle replies for call tips.
333 """ Handle replies for call tips.
334 """
334 """
335 cursor = self._get_cursor()
335 cursor = self._get_cursor()
336 info = self._request_info.get('call_tip')
336 info = self._request_info.get('call_tip')
337 if info and info.id == rep['parent_header']['msg_id'] and \
337 if info and info.id == rep['parent_header']['msg_id'] and \
338 info.pos == cursor.position():
338 info.pos == cursor.position():
339 # Get the information for a call tip. For now we format the call
339 # Get the information for a call tip. For now we format the call
340 # line as string, later we can pass False to format_call and
340 # line as string, later we can pass False to format_call and
341 # syntax-highlight it ourselves for nicer formatting in the
341 # syntax-highlight it ourselves for nicer formatting in the
342 # calltip.
342 # calltip.
343 call_info, doc = call_tip(rep['content'], format_call=True)
343 call_info, doc = call_tip(rep['content'], format_call=True)
344 if call_info or doc:
344 if call_info or doc:
345 self._call_tip_widget.show_call_info(call_info, doc)
345 self._call_tip_widget.show_call_info(call_info, doc)
346
346
347 def _handle_pyout(self, msg):
347 def _handle_pyout(self, msg):
348 """ Handle display hook output.
348 """ Handle display hook output.
349 """
349 """
350 if not self._hidden and self._is_from_this_session(msg):
350 if not self._hidden and self._is_from_this_session(msg):
351 self._append_plain_text(msg['content']['data'] + '\n')
351 self._append_plain_text(msg['content']['data'] + '\n')
352
352
353 def _handle_stream(self, msg):
353 def _handle_stream(self, msg):
354 """ Handle stdout, stderr, and stdin.
354 """ Handle stdout, stderr, and stdin.
355 """
355 """
356 if not self._hidden and self._is_from_this_session(msg):
356 if not self._hidden and self._is_from_this_session(msg):
357 # Most consoles treat tabs as being 8 space characters. Convert tabs
357 # Most consoles treat tabs as being 8 space characters. Convert tabs
358 # to spaces so that output looks as expected regardless of this
358 # to spaces so that output looks as expected regardless of this
359 # widget's tab width.
359 # widget's tab width.
360 text = msg['content']['data'].expandtabs(8)
360 text = msg['content']['data'].expandtabs(8)
361
361
362 self._append_plain_text(text)
362 self._append_plain_text(text)
363 self._control.moveCursor(QtGui.QTextCursor.End)
363 self._control.moveCursor(QtGui.QTextCursor.End)
364
364
365 def _handle_shutdown_reply(self, msg):
365 def _handle_shutdown_reply(self, msg):
366 """ Handle shutdown signal, only if from other console.
366 """ Handle shutdown signal, only if from other console.
367 """
367 """
368 if not self._hidden and not self._is_from_this_session(msg):
368 if not self._hidden and not self._is_from_this_session(msg):
369 if not msg['content']['restart']:
369 if not msg['content']['restart']:
370 sys.exit(0)
370 sys.exit(0)
371 else:
371 else:
372 # we just got notified of a restart!
372 # we just got notified of a restart!
373 time.sleep(0.25) # wait 1/4 sec to reest
373 time.sleep(0.25) # wait 1/4 sec to reset
374 # lest the request for a new prompt
374 # lest the request for a new prompt
375 # goes to the old kernel
375 # goes to the old kernel
376 self.reset()
376 self.reset()
377
377
378 def _started_channels(self):
378 def _started_channels(self):
379 """ Called when the KernelManager channels have started listening or
379 """ Called when the KernelManager channels have started listening or
380 when the frontend is assigned an already listening KernelManager.
380 when the frontend is assigned an already listening KernelManager.
381 """
381 """
382 self.reset()
382 self.reset()
383
383
384 #---------------------------------------------------------------------------
384 #---------------------------------------------------------------------------
385 # 'FrontendWidget' public interface
385 # 'FrontendWidget' public interface
386 #---------------------------------------------------------------------------
386 #---------------------------------------------------------------------------
387
387
388 def copy_raw(self):
388 def copy_raw(self):
389 """ Copy the currently selected text to the clipboard without attempting
389 """ Copy the currently selected text to the clipboard without attempting
390 to remove prompts or otherwise alter the text.
390 to remove prompts or otherwise alter the text.
391 """
391 """
392 self._control.copy()
392 self._control.copy()
393
393
394 def execute_file(self, path, hidden=False):
394 def execute_file(self, path, hidden=False):
395 """ Attempts to execute file with 'path'. If 'hidden', no output is
395 """ Attempts to execute file with 'path'. If 'hidden', no output is
396 shown.
396 shown.
397 """
397 """
398 self.execute('execfile("%s")' % path, hidden=hidden)
398 self.execute('execfile("%s")' % path, hidden=hidden)
399
399
400 def interrupt_kernel(self):
400 def interrupt_kernel(self):
401 """ Attempts to interrupt the running kernel.
401 """ Attempts to interrupt the running kernel.
402 """
402 """
403 if self.custom_interrupt:
403 if self.custom_interrupt:
404 self.custom_interrupt_requested.emit()
404 self.custom_interrupt_requested.emit()
405 elif self.kernel_manager.has_kernel:
405 elif self.kernel_manager.has_kernel:
406 self.kernel_manager.interrupt_kernel()
406 self.kernel_manager.interrupt_kernel()
407 else:
407 else:
408 self._append_plain_text('Kernel process is either remote or '
408 self._append_plain_text('Kernel process is either remote or '
409 'unspecified. Cannot interrupt.\n')
409 'unspecified. Cannot interrupt.\n')
410
410
411 def reset(self):
411 def reset(self):
412 """ Resets the widget to its initial state. Similar to ``clear``, but
412 """ Resets the widget to its initial state. Similar to ``clear``, but
413 also re-writes the banner and aborts execution if necessary.
413 also re-writes the banner and aborts execution if necessary.
414 """
414 """
415 if self._executing:
415 if self._executing:
416 self._executing = False
416 self._executing = False
417 self._request_info['execute'] = None
417 self._request_info['execute'] = None
418 self._reading = False
418 self._reading = False
419 self._highlighter.highlighting_on = False
419 self._highlighter.highlighting_on = False
420
420
421 self._control.clear()
421 self._control.clear()
422 self._append_plain_text(self._get_banner())
422 self._append_plain_text(self._get_banner())
423 self._show_interpreter_prompt()
423 self._show_interpreter_prompt()
424
424
425 def restart_kernel(self, message, now=False):
425 def restart_kernel(self, message, now=False):
426 """ Attempts to restart the running kernel.
426 """ Attempts to restart the running kernel.
427 """
427 """
428 # FIXME: now should be configurable via a checkbox in the dialog. Right
428 # FIXME: now should be configurable via a checkbox in the dialog. Right
429 # now at least the heartbeat path sets it to True and the manual restart
429 # now at least the heartbeat path sets it to True and the manual restart
430 # to False. But those should just be the pre-selected states of a
430 # to False. But those should just be the pre-selected states of a
431 # checkbox that the user could override if so desired. But I don't know
431 # checkbox that the user could override if so desired. But I don't know
432 # enough Qt to go implementing the checkbox now.
432 # enough Qt to go implementing the checkbox now.
433
433
434 if self.custom_restart:
434 if self.custom_restart:
435 self.custom_restart_requested.emit()
435 self.custom_restart_requested.emit()
436
436
437 elif self.kernel_manager.has_kernel:
437 elif self.kernel_manager.has_kernel:
438 # Pause the heart beat channel to prevent further warnings.
438 # Pause the heart beat channel to prevent further warnings.
439 self.kernel_manager.hb_channel.pause()
439 self.kernel_manager.hb_channel.pause()
440
440
441 # Prompt the user to restart the kernel. Un-pause the heartbeat if
441 # Prompt the user to restart the kernel. Un-pause the heartbeat if
442 # they decline. (If they accept, the heartbeat will be un-paused
442 # they decline. (If they accept, the heartbeat will be un-paused
443 # automatically when the kernel is restarted.)
443 # automatically when the kernel is restarted.)
444 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
444 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
445 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
445 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
446 message, buttons)
446 message, buttons)
447 if result == QtGui.QMessageBox.Yes:
447 if result == QtGui.QMessageBox.Yes:
448 try:
448 try:
449 self.kernel_manager.restart_kernel(now=now)
449 self.kernel_manager.restart_kernel(now=now)
450 except RuntimeError:
450 except RuntimeError:
451 self._append_plain_text('Kernel started externally. '
451 self._append_plain_text('Kernel started externally. '
452 'Cannot restart.\n')
452 'Cannot restart.\n')
453 else:
453 else:
454 self.reset()
454 self.reset()
455 else:
455 else:
456 self.kernel_manager.hb_channel.unpause()
456 self.kernel_manager.hb_channel.unpause()
457
457
458 else:
458 else:
459 self._append_plain_text('Kernel process is either remote or '
459 self._append_plain_text('Kernel process is either remote or '
460 'unspecified. Cannot restart.\n')
460 'unspecified. Cannot restart.\n')
461
461
462 #---------------------------------------------------------------------------
462 #---------------------------------------------------------------------------
463 # 'FrontendWidget' protected interface
463 # 'FrontendWidget' protected interface
464 #---------------------------------------------------------------------------
464 #---------------------------------------------------------------------------
465
465
466 def _call_tip(self):
466 def _call_tip(self):
467 """ Shows a call tip, if appropriate, at the current cursor location.
467 """ Shows a call tip, if appropriate, at the current cursor location.
468 """
468 """
469 # Decide if it makes sense to show a call tip
469 # Decide if it makes sense to show a call tip
470 cursor = self._get_cursor()
470 cursor = self._get_cursor()
471 cursor.movePosition(QtGui.QTextCursor.Left)
471 cursor.movePosition(QtGui.QTextCursor.Left)
472 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
472 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
473 return False
473 return False
474 context = self._get_context(cursor)
474 context = self._get_context(cursor)
475 if not context:
475 if not context:
476 return False
476 return False
477
477
478 # Send the metadata request to the kernel
478 # Send the metadata request to the kernel
479 name = '.'.join(context)
479 name = '.'.join(context)
480 msg_id = self.kernel_manager.xreq_channel.object_info(name)
480 msg_id = self.kernel_manager.xreq_channel.object_info(name)
481 pos = self._get_cursor().position()
481 pos = self._get_cursor().position()
482 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
482 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
483 return True
483 return True
484
484
485 def _complete(self):
485 def _complete(self):
486 """ Performs completion at the current cursor location.
486 """ Performs completion at the current cursor location.
487 """
487 """
488 context = self._get_context()
488 context = self._get_context()
489 if context:
489 if context:
490 # Send the completion request to the kernel
490 # Send the completion request to the kernel
491 msg_id = self.kernel_manager.xreq_channel.complete(
491 msg_id = self.kernel_manager.xreq_channel.complete(
492 '.'.join(context), # text
492 '.'.join(context), # text
493 self._get_input_buffer_cursor_line(), # line
493 self._get_input_buffer_cursor_line(), # line
494 self._get_input_buffer_cursor_column(), # cursor_pos
494 self._get_input_buffer_cursor_column(), # cursor_pos
495 self.input_buffer) # block
495 self.input_buffer) # block
496 pos = self._get_cursor().position()
496 pos = self._get_cursor().position()
497 info = self._CompletionRequest(msg_id, pos)
497 info = self._CompletionRequest(msg_id, pos)
498 self._request_info['complete'] = info
498 self._request_info['complete'] = info
499
499
500 def _get_banner(self):
500 def _get_banner(self):
501 """ Gets a banner to display at the beginning of a session.
501 """ Gets a banner to display at the beginning of a session.
502 """
502 """
503 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
503 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
504 '"license" for more information.'
504 '"license" for more information.'
505 return banner % (sys.version, sys.platform)
505 return banner % (sys.version, sys.platform)
506
506
507 def _get_context(self, cursor=None):
507 def _get_context(self, cursor=None):
508 """ Gets the context for the specified cursor (or the current cursor
508 """ Gets the context for the specified cursor (or the current cursor
509 if none is specified).
509 if none is specified).
510 """
510 """
511 if cursor is None:
511 if cursor is None:
512 cursor = self._get_cursor()
512 cursor = self._get_cursor()
513 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
513 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
514 QtGui.QTextCursor.KeepAnchor)
514 QtGui.QTextCursor.KeepAnchor)
515 text = unicode(cursor.selection().toPlainText())
515 text = unicode(cursor.selection().toPlainText())
516 return self._completion_lexer.get_context(text)
516 return self._completion_lexer.get_context(text)
517
517
518 def _process_execute_abort(self, msg):
518 def _process_execute_abort(self, msg):
519 """ Process a reply for an aborted execution request.
519 """ Process a reply for an aborted execution request.
520 """
520 """
521 self._append_plain_text("ERROR: execution aborted\n")
521 self._append_plain_text("ERROR: execution aborted\n")
522
522
523 def _process_execute_error(self, msg):
523 def _process_execute_error(self, msg):
524 """ Process a reply for an execution request that resulted in an error.
524 """ Process a reply for an execution request that resulted in an error.
525 """
525 """
526 content = msg['content']
526 content = msg['content']
527 traceback = ''.join(content['traceback'])
527 traceback = ''.join(content['traceback'])
528 self._append_plain_text(traceback)
528 self._append_plain_text(traceback)
529
529
530 def _process_execute_ok(self, msg):
530 def _process_execute_ok(self, msg):
531 """ Process a reply for a successful execution equest.
531 """ Process a reply for a successful execution equest.
532 """
532 """
533 payload = msg['content']['payload']
533 payload = msg['content']['payload']
534 for item in payload:
534 for item in payload:
535 if not self._process_execute_payload(item):
535 if not self._process_execute_payload(item):
536 warning = 'Warning: received unknown payload of type %s'
536 warning = 'Warning: received unknown payload of type %s'
537 print(warning % repr(item['source']))
537 print(warning % repr(item['source']))
538
538
539 def _process_execute_payload(self, item):
539 def _process_execute_payload(self, item):
540 """ Process a single payload item from the list of payload items in an
540 """ Process a single payload item from the list of payload items in an
541 execution reply. Returns whether the payload was handled.
541 execution reply. Returns whether the payload was handled.
542 """
542 """
543 # The basic FrontendWidget doesn't handle payloads, as they are a
543 # The basic FrontendWidget doesn't handle payloads, as they are a
544 # mechanism for going beyond the standard Python interpreter model.
544 # mechanism for going beyond the standard Python interpreter model.
545 return False
545 return False
546
546
547 def _show_interpreter_prompt(self):
547 def _show_interpreter_prompt(self):
548 """ Shows a prompt for the interpreter.
548 """ Shows a prompt for the interpreter.
549 """
549 """
550 self._show_prompt('>>> ')
550 self._show_prompt('>>> ')
551
551
552 def _show_interpreter_prompt_for_reply(self, msg):
552 def _show_interpreter_prompt_for_reply(self, msg):
553 """ Shows a prompt for the interpreter given an 'execute_reply' message.
553 """ Shows a prompt for the interpreter given an 'execute_reply' message.
554 """
554 """
555 self._show_interpreter_prompt()
555 self._show_interpreter_prompt()
556
556
557 #------ Signal handlers ----------------------------------------------------
557 #------ Signal handlers ----------------------------------------------------
558
558
559 def _document_contents_change(self, position, removed, added):
559 def _document_contents_change(self, position, removed, added):
560 """ Called whenever the document's content changes. Display a call tip
560 """ Called whenever the document's content changes. Display a call tip
561 if appropriate.
561 if appropriate.
562 """
562 """
563 # Calculate where the cursor should be *after* the change:
563 # Calculate where the cursor should be *after* the change:
564 position += added
564 position += added
565
565
566 document = self._control.document()
566 document = self._control.document()
567 if position == self._get_cursor().position():
567 if position == self._get_cursor().position():
568 self._call_tip()
568 self._call_tip()
@@ -1,157 +1,159 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Imports
5 # Imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # Systemm library imports
8 # Systemm library imports
9 from PyQt4 import QtGui
9 from PyQt4 import QtGui
10
10
11 # Local imports
11 # Local imports
12 from IPython.external.argparse import ArgumentParser
12 from IPython.external.argparse import ArgumentParser
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
16 from IPython.frontend.qt.kernelmanager import QtKernelManager
16 from IPython.frontend.qt.kernelmanager import QtKernelManager
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Constants
19 # Constants
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 LOCALHOST = '127.0.0.1'
22 LOCALHOST = '127.0.0.1'
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 class MainWindow(QtGui.QMainWindow):
28 class MainWindow(QtGui.QMainWindow):
29
29
30 #---------------------------------------------------------------------------
30 #---------------------------------------------------------------------------
31 # 'object' interface
31 # 'object' interface
32 #---------------------------------------------------------------------------
32 #---------------------------------------------------------------------------
33
33
34 def __init__(self, frontend, existing=False):
34 def __init__(self, frontend, existing=False):
35 """ Create a MainWindow for the specified FrontendWidget.
35 """ Create a MainWindow for the specified FrontendWidget.
36
37 If existing is True, then this Window does not own the Kernel.
36 """
38 """
37 super(MainWindow, self).__init__()
39 super(MainWindow, self).__init__()
38 self._frontend = frontend
40 self._frontend = frontend
39 self._existing = existing
41 self._existing = existing
40 self._frontend.exit_requested.connect(self.close)
42 self._frontend.exit_requested.connect(self.close)
41 self.setCentralWidget(frontend)
43 self.setCentralWidget(frontend)
42
44
43 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
44 # QWidget interface
46 # QWidget interface
45 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
46
48
47 def closeEvent(self, event):
49 def closeEvent(self, event):
48 """ Reimplemented to prompt the user and close the kernel cleanly.
50 """ Reimplemented to prompt the user and close the kernel cleanly.
49 """
51 """
50 kernel_manager = self._frontend.kernel_manager
52 kernel_manager = self._frontend.kernel_manager
51 # closeall =
53 # closeall =
52 if kernel_manager and kernel_manager.channels_running:
54 if kernel_manager and kernel_manager.channels_running:
53 title = self.window().windowTitle()
55 title = self.window().windowTitle()
54 reply = QtGui.QMessageBox.question(self, title,
56 reply = QtGui.QMessageBox.question(self, title,
55 "Close just this console, or shutdown the kernel and close "+
57 "Close just this console, or shutdown the kernel and close "+
56 "all windows attached to it?",
58 "all windows attached to it?",
57 'Cancel', 'Close Console', 'Close All')
59 'Cancel', 'Close Console', 'Close All')
58 print reply
60 print reply
59 if reply == 2:
61 if reply == 2:
60 kernel_manager.shutdown_kernel()
62 kernel_manager.shutdown_kernel()
61 #kernel_manager.stop_channels()
63 #kernel_manager.stop_channels()
62 event.accept()
64 event.accept()
63 elif reply == 1:
65 elif reply == 1:
64 if self._existing:
66 if self._existing:
65 # I don't have the Kernel, I can shutdown
67 # I don't have the Kernel, I can shutdown
66 event.accept()
68 event.accept()
67 else:
69 else:
68 # only destroy the Window, save the Kernel
70 # only destroy the Window, save the Kernel
69 self.destroy()
71 self.destroy()
70 event.ignore()
72 event.ignore()
71 else:
73 else:
72 event.ignore()
74 event.ignore()
73
75
74 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
75 # Main entry point
77 # Main entry point
76 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
77
79
78 def main():
80 def main():
79 """ Entry point for application.
81 """ Entry point for application.
80 """
82 """
81 # Parse command line arguments.
83 # Parse command line arguments.
82 parser = ArgumentParser()
84 parser = ArgumentParser()
83 kgroup = parser.add_argument_group('kernel options')
85 kgroup = parser.add_argument_group('kernel options')
84 kgroup.add_argument('-e', '--existing', action='store_true',
86 kgroup.add_argument('-e', '--existing', action='store_true',
85 help='connect to an existing kernel')
87 help='connect to an existing kernel')
86 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
88 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
87 help='set the kernel\'s IP address [default localhost]')
89 help='set the kernel\'s IP address [default localhost]')
88 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
90 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
89 help='set the XREQ channel port [default random]')
91 help='set the XREQ channel port [default random]')
90 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
92 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
91 help='set the SUB channel port [default random]')
93 help='set the SUB channel port [default random]')
92 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
94 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
93 help='set the REP channel port [default random]')
95 help='set the REP channel port [default random]')
94 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
96 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
95 help='set the heartbeat port [default: random]')
97 help='set the heartbeat port [default: random]')
96
98
97 egroup = kgroup.add_mutually_exclusive_group()
99 egroup = kgroup.add_mutually_exclusive_group()
98 egroup.add_argument('--pure', action='store_true', help = \
100 egroup.add_argument('--pure', action='store_true', help = \
99 'use a pure Python kernel instead of an IPython kernel')
101 'use a pure Python kernel instead of an IPython kernel')
100 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
102 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
101 const='auto', help = \
103 const='auto', help = \
102 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
104 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
103 given, the GUI backend is matplotlib's, otherwise use one of: \
105 given, the GUI backend is matplotlib's, otherwise use one of: \
104 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
106 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
105
107
106 wgroup = parser.add_argument_group('widget options')
108 wgroup = parser.add_argument_group('widget options')
107 wgroup.add_argument('--paging', type=str, default='inside',
109 wgroup.add_argument('--paging', type=str, default='inside',
108 choices = ['inside', 'hsplit', 'vsplit', 'none'],
110 choices = ['inside', 'hsplit', 'vsplit', 'none'],
109 help='set the paging style [default inside]')
111 help='set the paging style [default inside]')
110 wgroup.add_argument('--rich', action='store_true',
112 wgroup.add_argument('--rich', action='store_true',
111 help='enable rich text support')
113 help='enable rich text support')
112 wgroup.add_argument('--gui-completion', action='store_true',
114 wgroup.add_argument('--gui-completion', action='store_true',
113 help='use a GUI widget for tab completion')
115 help='use a GUI widget for tab completion')
114
116
115 args = parser.parse_args()
117 args = parser.parse_args()
116
118
117 # Don't let Qt or ZMQ swallow KeyboardInterupts.
119 # Don't let Qt or ZMQ swallow KeyboardInterupts.
118 import signal
120 import signal
119 signal.signal(signal.SIGINT, signal.SIG_DFL)
121 signal.signal(signal.SIGINT, signal.SIG_DFL)
120
122
121 # Create a KernelManager and start a kernel.
123 # Create a KernelManager and start a kernel.
122 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
124 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
123 sub_address=(args.ip, args.sub),
125 sub_address=(args.ip, args.sub),
124 rep_address=(args.ip, args.rep),
126 rep_address=(args.ip, args.rep),
125 hb_address=(args.ip, args.hb))
127 hb_address=(args.ip, args.hb))
126 if args.ip == LOCALHOST and not args.existing:
128 if args.ip == LOCALHOST and not args.existing:
127 if args.pure:
129 if args.pure:
128 kernel_manager.start_kernel(ipython=False)
130 kernel_manager.start_kernel(ipython=False)
129 elif args.pylab:
131 elif args.pylab:
130 kernel_manager.start_kernel(pylab=args.pylab)
132 kernel_manager.start_kernel(pylab=args.pylab)
131 else:
133 else:
132 kernel_manager.start_kernel()
134 kernel_manager.start_kernel()
133 kernel_manager.start_channels()
135 kernel_manager.start_channels()
134
136
135 # Create the widget.
137 # Create the widget.
136 app = QtGui.QApplication([])
138 app = QtGui.QApplication([])
137 if args.pure:
139 if args.pure:
138 kind = 'rich' if args.rich else 'plain'
140 kind = 'rich' if args.rich else 'plain'
139 widget = FrontendWidget(kind=kind, paging=args.paging)
141 widget = FrontendWidget(kind=kind, paging=args.paging)
140 elif args.rich or args.pylab:
142 elif args.rich or args.pylab:
141 widget = RichIPythonWidget(paging=args.paging)
143 widget = RichIPythonWidget(paging=args.paging)
142 else:
144 else:
143 widget = IPythonWidget(paging=args.paging)
145 widget = IPythonWidget(paging=args.paging)
144 widget.gui_completion = args.gui_completion
146 widget.gui_completion = args.gui_completion
145 widget.kernel_manager = kernel_manager
147 widget.kernel_manager = kernel_manager
146
148
147 # Create the main window.
149 # Create the main window.
148 window = MainWindow(widget, args.existing)
150 window = MainWindow(widget, args.existing)
149 window.setWindowTitle('Python' if args.pure else 'IPython')
151 window.setWindowTitle('Python' if args.pure else 'IPython')
150 window.show()
152 window.show()
151
153
152 # Start the application main loop.
154 # Start the application main loop.
153 app.exec_()
155 app.exec_()
154
156
155
157
156 if __name__ == '__main__':
158 if __name__ == '__main__':
157 main()
159 main()
@@ -1,871 +1,873 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Introduction
8 Introduction
9 ============
9 ============
10
10
11 This document explains the basic communications design and messaging
11 This document explains the basic communications design and messaging
12 specification for how the various IPython objects interact over a network
12 specification for how the various IPython objects interact over a network
13 transport. The current implementation uses the ZeroMQ_ library for messaging
13 transport. The current implementation uses the ZeroMQ_ library for messaging
14 within and between hosts.
14 within and between hosts.
15
15
16 .. Note::
16 .. Note::
17
17
18 This document should be considered the authoritative description of the
18 This document should be considered the authoritative description of the
19 IPython messaging protocol, and all developers are strongly encouraged to
19 IPython messaging protocol, and all developers are strongly encouraged to
20 keep it updated as the implementation evolves, so that we have a single
20 keep it updated as the implementation evolves, so that we have a single
21 common reference for all protocol details.
21 common reference for all protocol details.
22
22
23 The basic design is explained in the following diagram:
23 The basic design is explained in the following diagram:
24
24
25 .. image:: frontend-kernel.png
25 .. image:: frontend-kernel.png
26 :width: 450px
26 :width: 450px
27 :alt: IPython kernel/frontend messaging architecture.
27 :alt: IPython kernel/frontend messaging architecture.
28 :align: center
28 :align: center
29 :target: ../_images/frontend-kernel.png
29 :target: ../_images/frontend-kernel.png
30
30
31 A single kernel can be simultaneously connected to one or more frontends. The
31 A single kernel can be simultaneously connected to one or more frontends. The
32 kernel has three sockets that serve the following functions:
32 kernel has three sockets that serve the following functions:
33
33
34 1. REQ: this socket is connected to a *single* frontend at a time, and it allows
34 1. REQ: this socket is connected to a *single* frontend at a time, and it allows
35 the kernel to request input from a frontend when :func:`raw_input` is called.
35 the kernel to request input from a frontend when :func:`raw_input` is called.
36 The frontend holding the matching REP socket acts as a 'virtual keyboard'
36 The frontend holding the matching REP socket acts as a 'virtual keyboard'
37 for the kernel while this communication is happening (illustrated in the
37 for the kernel while this communication is happening (illustrated in the
38 figure by the black outline around the central keyboard). In practice,
38 figure by the black outline around the central keyboard). In practice,
39 frontends may display such kernel requests using a special input widget or
39 frontends may display such kernel requests using a special input widget or
40 otherwise indicating that the user is to type input for the kernel instead
40 otherwise indicating that the user is to type input for the kernel instead
41 of normal commands in the frontend.
41 of normal commands in the frontend.
42
42
43 2. XREP: this single sockets allows multiple incoming connections from
43 2. XREP: this single sockets allows multiple incoming connections from
44 frontends, and this is the socket where requests for code execution, object
44 frontends, and this is the socket where requests for code execution, object
45 information, prompts, etc. are made to the kernel by any frontend. The
45 information, prompts, etc. are made to the kernel by any frontend. The
46 communication on this socket is a sequence of request/reply actions from
46 communication on this socket is a sequence of request/reply actions from
47 each frontend and the kernel.
47 each frontend and the kernel.
48
48
49 3. PUB: this socket is the 'broadcast channel' where the kernel publishes all
49 3. PUB: this socket is the 'broadcast channel' where the kernel publishes all
50 side effects (stdout, stderr, etc.) as well as the requests coming from any
50 side effects (stdout, stderr, etc.) as well as the requests coming from any
51 client over the XREP socket and its own requests on the REP socket. There
51 client over the XREP socket and its own requests on the REP socket. There
52 are a number of actions in Python which generate side effects: :func:`print`
52 are a number of actions in Python which generate side effects: :func:`print`
53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
54 a multi-client scenario, we want all frontends to be able to know what each
54 a multi-client scenario, we want all frontends to be able to know what each
55 other has sent to the kernel (this can be useful in collaborative scenarios,
55 other has sent to the kernel (this can be useful in collaborative scenarios,
56 for example). This socket allows both side effects and the information
56 for example). This socket allows both side effects and the information
57 about communications taking place with one client over the XREQ/XREP channel
57 about communications taking place with one client over the XREQ/XREP channel
58 to be made available to all clients in a uniform manner.
58 to be made available to all clients in a uniform manner.
59
59
60 All messages are tagged with enough information (details below) for clients
60 All messages are tagged with enough information (details below) for clients
61 to know which messages come from their own interaction with the kernel and
61 to know which messages come from their own interaction with the kernel and
62 which ones are from other clients, so they can display each type
62 which ones are from other clients, so they can display each type
63 appropriately.
63 appropriately.
64
64
65 The actual format of the messages allowed on each of these channels is
65 The actual format of the messages allowed on each of these channels is
66 specified below. Messages are dicts of dicts with string keys and values that
66 specified below. Messages are dicts of dicts with string keys and values that
67 are reasonably representable in JSON. Our current implementation uses JSON
67 are reasonably representable in JSON. Our current implementation uses JSON
68 explicitly as its message format, but this shouldn't be considered a permanent
68 explicitly as its message format, but this shouldn't be considered a permanent
69 feature. As we've discovered that JSON has non-trivial performance issues due
69 feature. As we've discovered that JSON has non-trivial performance issues due
70 to excessive copying, we may in the future move to a pure pickle-based raw
70 to excessive copying, we may in the future move to a pure pickle-based raw
71 message format. However, it should be possible to easily convert from the raw
71 message format. However, it should be possible to easily convert from the raw
72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
73 As long as it's easy to make a JSON version of the objects that is a faithful
73 As long as it's easy to make a JSON version of the objects that is a faithful
74 representation of all the data, we can communicate with such clients.
74 representation of all the data, we can communicate with such clients.
75
75
76 .. Note::
76 .. Note::
77
77
78 Not all of these have yet been fully fleshed out, but the key ones are, see
78 Not all of these have yet been fully fleshed out, but the key ones are, see
79 kernel and frontend files for actual implementation details.
79 kernel and frontend files for actual implementation details.
80
80
81
81
82 Python functional API
82 Python functional API
83 =====================
83 =====================
84
84
85 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
85 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
86 should develop, at a few key points, functional forms of all the requests that
86 should develop, at a few key points, functional forms of all the requests that
87 take arguments in this manner and automatically construct the necessary dict
87 take arguments in this manner and automatically construct the necessary dict
88 for sending.
88 for sending.
89
89
90
90
91 General Message Format
91 General Message Format
92 ======================
92 ======================
93
93
94 All messages send or received by any IPython process should have the following
94 All messages send or received by any IPython process should have the following
95 generic structure::
95 generic structure::
96
96
97 {
97 {
98 # The message header contains a pair of unique identifiers for the
98 # The message header contains a pair of unique identifiers for the
99 # originating session and the actual message id, in addition to the
99 # originating session and the actual message id, in addition to the
100 # username for the process that generated the message. This is useful in
100 # username for the process that generated the message. This is useful in
101 # collaborative settings where multiple users may be interacting with the
101 # collaborative settings where multiple users may be interacting with the
102 # same kernel simultaneously, so that frontends can label the various
102 # same kernel simultaneously, so that frontends can label the various
103 # messages in a meaningful way.
103 # messages in a meaningful way.
104 'header' : { 'msg_id' : uuid,
104 'header' : { 'msg_id' : uuid,
105 'username' : str,
105 'username' : str,
106 'session' : uuid
106 'session' : uuid
107 },
107 },
108
108
109 # In a chain of messages, the header from the parent is copied so that
109 # In a chain of messages, the header from the parent is copied so that
110 # clients can track where messages come from.
110 # clients can track where messages come from.
111 'parent_header' : dict,
111 'parent_header' : dict,
112
112
113 # All recognized message type strings are listed below.
113 # All recognized message type strings are listed below.
114 'msg_type' : str,
114 'msg_type' : str,
115
115
116 # The actual content of the message must be a dict, whose structure
116 # The actual content of the message must be a dict, whose structure
117 # depends on the message type.x
117 # depends on the message type.x
118 'content' : dict,
118 'content' : dict,
119 }
119 }
120
120
121 For each message type, the actual content will differ and all existing message
121 For each message type, the actual content will differ and all existing message
122 types are specified in what follows of this document.
122 types are specified in what follows of this document.
123
123
124
124
125 Messages on the XREP/XREQ socket
125 Messages on the XREP/XREQ socket
126 ================================
126 ================================
127
127
128 .. _execute:
128 .. _execute:
129
129
130 Execute
130 Execute
131 -------
131 -------
132
132
133 This message type is used by frontends to ask the kernel to execute code on
133 This message type is used by frontends to ask the kernel to execute code on
134 behalf of the user, in a namespace reserved to the user's variables (and thus
134 behalf of the user, in a namespace reserved to the user's variables (and thus
135 separate from the kernel's own internal code and variables).
135 separate from the kernel's own internal code and variables).
136
136
137 Message type: ``execute_request``::
137 Message type: ``execute_request``::
138
138
139 content = {
139 content = {
140 # Source code to be executed by the kernel, one or more lines.
140 # Source code to be executed by the kernel, one or more lines.
141 'code' : str,
141 'code' : str,
142
142
143 # A boolean flag which, if True, signals the kernel to execute this
143 # A boolean flag which, if True, signals the kernel to execute this
144 # code as quietly as possible. This means that the kernel will compile
144 # code as quietly as possible. This means that the kernel will compile
145 # the code witIPython/core/tests/h 'exec' instead of 'single' (so
145 # the code witIPython/core/tests/h 'exec' instead of 'single' (so
146 # sys.displayhook will not fire), and will *not*:
146 # sys.displayhook will not fire), and will *not*:
147 # - broadcast exceptions on the PUB socket
147 # - broadcast exceptions on the PUB socket
148 # - do any logging
148 # - do any logging
149 # - populate any history
149 # - populate any history
150 #
150 #
151 # The default is False.
151 # The default is False.
152 'silent' : bool,
152 'silent' : bool,
153
153
154 # A list of variable names from the user's namespace to be retrieved. What
154 # A list of variable names from the user's namespace to be retrieved. What
155 # returns is a JSON string of the variable's repr(), not a python object.
155 # returns is a JSON string of the variable's repr(), not a python object.
156 'user_variables' : list,
156 'user_variables' : list,
157
157
158 # Similarly, a dict mapping names to expressions to be evaluated in the
158 # Similarly, a dict mapping names to expressions to be evaluated in the
159 # user's dict.
159 # user's dict.
160 'user_expressions' : dict,
160 'user_expressions' : dict,
161 }
161 }
162
162
163 The ``code`` field contains a single string (possibly multiline). The kernel
163 The ``code`` field contains a single string (possibly multiline). The kernel
164 is responsible for splitting this into one or more independent execution blocks
164 is responsible for splitting this into one or more independent execution blocks
165 and deciding whether to compile these in 'single' or 'exec' mode (see below for
165 and deciding whether to compile these in 'single' or 'exec' mode (see below for
166 detailed execution semantics).
166 detailed execution semantics).
167
167
168 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
168 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
169 the notion of a prompt string that allowed arbitrary code to be evaluated, and
169 the notion of a prompt string that allowed arbitrary code to be evaluated, and
170 this was put to good use by many in creating prompts that displayed system
170 this was put to good use by many in creating prompts that displayed system
171 status, path information, and even more esoteric uses like remote instrument
171 status, path information, and even more esoteric uses like remote instrument
172 status aqcuired over the network. But now that IPython has a clean separation
172 status aqcuired over the network. But now that IPython has a clean separation
173 between the kernel and the clients, the kernel has no prompt knowledge; prompts
173 between the kernel and the clients, the kernel has no prompt knowledge; prompts
174 are a frontend-side feature, and it should be even possible for different
174 are a frontend-side feature, and it should be even possible for different
175 frontends to display different prompts while interacting with the same kernel.
175 frontends to display different prompts while interacting with the same kernel.
176
176
177 The kernel now provides the ability to retrieve data from the user's namespace
177 The kernel now provides the ability to retrieve data from the user's namespace
178 after the execution of the main ``code``, thanks to two fields in the
178 after the execution of the main ``code``, thanks to two fields in the
179 ``execute_request`` message:
179 ``execute_request`` message:
180
180
181 - ``user_variables``: If only variables from the user's namespace are needed, a
181 - ``user_variables``: If only variables from the user's namespace are needed, a
182 list of variable names can be passed and a dict with these names as keys and
182 list of variable names can be passed and a dict with these names as keys and
183 their :func:`repr()` as values will be returned.
183 their :func:`repr()` as values will be returned.
184
184
185 - ``user_expressions``: For more complex expressions that require function
185 - ``user_expressions``: For more complex expressions that require function
186 evaluations, a dict can be provided with string keys and arbitrary python
186 evaluations, a dict can be provided with string keys and arbitrary python
187 expressions as values. The return message will contain also a dict with the
187 expressions as values. The return message will contain also a dict with the
188 same keys and the :func:`repr()` of the evaluated expressions as value.
188 same keys and the :func:`repr()` of the evaluated expressions as value.
189
189
190 With this information, frontends can display any status information they wish
190 With this information, frontends can display any status information they wish
191 in the form that best suits each frontend (a status line, a popup, inline for a
191 in the form that best suits each frontend (a status line, a popup, inline for a
192 terminal, etc).
192 terminal, etc).
193
193
194 .. Note::
194 .. Note::
195
195
196 In order to obtain the current execution counter for the purposes of
196 In order to obtain the current execution counter for the purposes of
197 displaying input prompts, frontends simply make an execution request with an
197 displaying input prompts, frontends simply make an execution request with an
198 empty code string and ``silent=True``.
198 empty code string and ``silent=True``.
199
199
200 Execution semantics
200 Execution semantics
201 ~~~~~~~~~~~~~~~~~~~
201 ~~~~~~~~~~~~~~~~~~~
202
202
203 When the silent flag is false, the execution of use code consists of the
203 When the silent flag is false, the execution of use code consists of the
204 following phases (in silent mode, only the ``code`` field is executed):
204 following phases (in silent mode, only the ``code`` field is executed):
205
205
206 1. Run the ``pre_runcode_hook``.
206 1. Run the ``pre_runcode_hook``.
207
207
208 2. Execute the ``code`` field, see below for details.
208 2. Execute the ``code`` field, see below for details.
209
209
210 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
210 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
211 computed. This ensures that any error in the latter don't harm the main
211 computed. This ensures that any error in the latter don't harm the main
212 code execution.
212 code execution.
213
213
214 4. Call any method registered with :meth:`register_post_execute`.
214 4. Call any method registered with :meth:`register_post_execute`.
215
215
216 .. warning::
216 .. warning::
217
217
218 The API for running code before/after the main code block is likely to
218 The API for running code before/after the main code block is likely to
219 change soon. Both the ``pre_runcode_hook`` and the
219 change soon. Both the ``pre_runcode_hook`` and the
220 :meth:`register_post_execute` are susceptible to modification, as we find a
220 :meth:`register_post_execute` are susceptible to modification, as we find a
221 consistent model for both.
221 consistent model for both.
222
222
223 To understand how the ``code`` field is executed, one must know that Python
223 To understand how the ``code`` field is executed, one must know that Python
224 code can be compiled in one of three modes (controlled by the ``mode`` argument
224 code can be compiled in one of three modes (controlled by the ``mode`` argument
225 to the :func:`compile` builtin):
225 to the :func:`compile` builtin):
226
226
227 *single*
227 *single*
228 Valid for a single interactive statement (though the source can contain
228 Valid for a single interactive statement (though the source can contain
229 multiple lines, such as a for loop). When compiled in this mode, the
229 multiple lines, such as a for loop). When compiled in this mode, the
230 generated bytecode contains special instructions that trigger the calling of
230 generated bytecode contains special instructions that trigger the calling of
231 :func:`sys.displayhook` for any expression in the block that returns a value.
231 :func:`sys.displayhook` for any expression in the block that returns a value.
232 This means that a single statement can actually produce multiple calls to
232 This means that a single statement can actually produce multiple calls to
233 :func:`sys.displayhook`, if for example it contains a loop where each
233 :func:`sys.displayhook`, if for example it contains a loop where each
234 iteration computes an unassigned expression would generate 10 calls::
234 iteration computes an unassigned expression would generate 10 calls::
235
235
236 for i in range(10):
236 for i in range(10):
237 i**2
237 i**2
238
238
239 *exec*
239 *exec*
240 An arbitrary amount of source code, this is how modules are compiled.
240 An arbitrary amount of source code, this is how modules are compiled.
241 :func:`sys.displayhook` is *never* implicitly called.
241 :func:`sys.displayhook` is *never* implicitly called.
242
242
243 *eval*
243 *eval*
244 A single expression that returns a value. :func:`sys.displayhook` is *never*
244 A single expression that returns a value. :func:`sys.displayhook` is *never*
245 implicitly called.
245 implicitly called.
246
246
247
247
248 The ``code`` field is split into individual blocks each of which is valid for
248 The ``code`` field is split into individual blocks each of which is valid for
249 execution in 'single' mode, and then:
249 execution in 'single' mode, and then:
250
250
251 - If there is only a single block: it is executed in 'single' mode.
251 - If there is only a single block: it is executed in 'single' mode.
252
252
253 - If there is more than one block:
253 - If there is more than one block:
254
254
255 * if the last one is a single line long, run all but the last in 'exec' mode
255 * if the last one is a single line long, run all but the last in 'exec' mode
256 and the very last one in 'single' mode. This makes it easy to type simple
256 and the very last one in 'single' mode. This makes it easy to type simple
257 expressions at the end to see computed values.
257 expressions at the end to see computed values.
258
258
259 * if the last one is no more than two lines long, run all but the last in
259 * if the last one is no more than two lines long, run all but the last in
260 'exec' mode and the very last one in 'single' mode. This makes it easy to
260 'exec' mode and the very last one in 'single' mode. This makes it easy to
261 type simple expressions at the end to see computed values. - otherwise
261 type simple expressions at the end to see computed values. - otherwise
262 (last one is also multiline), run all in 'exec' mode
262 (last one is also multiline), run all in 'exec' mode
263
263
264 * otherwise (last one is also multiline), run all in 'exec' mode as a single
264 * otherwise (last one is also multiline), run all in 'exec' mode as a single
265 unit.
265 unit.
266
266
267 Any error in retrieving the ``user_variables`` or evaluating the
267 Any error in retrieving the ``user_variables`` or evaluating the
268 ``user_expressions`` will result in a simple error message in the return fields
268 ``user_expressions`` will result in a simple error message in the return fields
269 of the form::
269 of the form::
270
270
271 [ERROR] ExceptionType: Exception message
271 [ERROR] ExceptionType: Exception message
272
272
273 The user can simply send the same variable name or expression for evaluation to
273 The user can simply send the same variable name or expression for evaluation to
274 see a regular traceback.
274 see a regular traceback.
275
275
276 Errors in any registered post_execute functions are also reported similarly,
276 Errors in any registered post_execute functions are also reported similarly,
277 and the failing function is removed from the post_execution set so that it does
277 and the failing function is removed from the post_execution set so that it does
278 not continue triggering failures.
278 not continue triggering failures.
279
279
280 Upon completion of the execution request, the kernel *always* sends a reply,
280 Upon completion of the execution request, the kernel *always* sends a reply,
281 with a status code indicating what happened and additional data depending on
281 with a status code indicating what happened and additional data depending on
282 the outcome. See :ref:`below <execution_results>` for the possible return
282 the outcome. See :ref:`below <execution_results>` for the possible return
283 codes and associated data.
283 codes and associated data.
284
284
285
285
286 Execution counter (old prompt number)
286 Execution counter (old prompt number)
287 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
287 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288
288
289 The kernel has a single, monotonically increasing counter of all execution
289 The kernel has a single, monotonically increasing counter of all execution
290 requests that are made with ``silent=False``. This counter is used to populate
290 requests that are made with ``silent=False``. This counter is used to populate
291 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
291 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
292 display it in some form to the user, which will typically (but not necessarily)
292 display it in some form to the user, which will typically (but not necessarily)
293 be done in the prompts. The value of this counter will be returned as the
293 be done in the prompts. The value of this counter will be returned as the
294 ``execution_count`` field of all ``execute_reply`` messages.
294 ``execution_count`` field of all ``execute_reply`` messages.
295
295
296 .. _execution_results:
296 .. _execution_results:
297
297
298 Execution results
298 Execution results
299 ~~~~~~~~~~~~~~~~~
299 ~~~~~~~~~~~~~~~~~
300
300
301 Message type: ``execute_reply``::
301 Message type: ``execute_reply``::
302
302
303 content = {
303 content = {
304 # One of: 'ok' OR 'error' OR 'abort'
304 # One of: 'ok' OR 'error' OR 'abort'
305 'status' : str,
305 'status' : str,
306
306
307 # The global kernel counter that increases by one with each non-silent
307 # The global kernel counter that increases by one with each non-silent
308 # executed request. This will typically be used by clients to display
308 # executed request. This will typically be used by clients to display
309 # prompt numbers to the user. If the request was a silent one, this will
309 # prompt numbers to the user. If the request was a silent one, this will
310 # be the current value of the counter in the kernel.
310 # be the current value of the counter in the kernel.
311 'execution_count' : int,
311 'execution_count' : int,
312 }
312 }
313
313
314 When status is 'ok', the following extra fields are present::
314 When status is 'ok', the following extra fields are present::
315
315
316 {
316 {
317 # The execution payload is a dict with string keys that may have been
317 # The execution payload is a dict with string keys that may have been
318 # produced by the code being executed. It is retrieved by the kernel at
318 # produced by the code being executed. It is retrieved by the kernel at
319 # the end of the execution and sent back to the front end, which can take
319 # the end of the execution and sent back to the front end, which can take
320 # action on it as needed. See main text for further details.
320 # action on it as needed. See main text for further details.
321 'payload' : dict,
321 'payload' : dict,
322
322
323 # Results for the user_variables and user_expressions.
323 # Results for the user_variables and user_expressions.
324 'user_variables' : dict,
324 'user_variables' : dict,
325 'user_expressions' : dict,
325 'user_expressions' : dict,
326
326
327 # The kernel will often transform the input provided to it. If the
327 # The kernel will often transform the input provided to it. If the
328 # '---->' transform had been applied, this is filled, otherwise it's the
328 # '---->' transform had been applied, this is filled, otherwise it's the
329 # empty string. So transformations like magics don't appear here, only
329 # empty string. So transformations like magics don't appear here, only
330 # autocall ones.
330 # autocall ones.
331 'transformed_code' : str,
331 'transformed_code' : str,
332 }
332 }
333
333
334 .. admonition:: Execution payloads
334 .. admonition:: Execution payloads
335
335
336 The notion of an 'execution payload' is different from a return value of a
336 The notion of an 'execution payload' is different from a return value of a
337 given set of code, which normally is just displayed on the pyout stream
337 given set of code, which normally is just displayed on the pyout stream
338 through the PUB socket. The idea of a payload is to allow special types of
338 through the PUB socket. The idea of a payload is to allow special types of
339 code, typically magics, to populate a data container in the IPython kernel
339 code, typically magics, to populate a data container in the IPython kernel
340 that will be shipped back to the caller via this channel. The kernel will
340 that will be shipped back to the caller via this channel. The kernel will
341 have an API for this, probably something along the lines of::
341 have an API for this, probably something along the lines of::
342
342
343 ip.exec_payload_add(key, value)
343 ip.exec_payload_add(key, value)
344
344
345 though this API is still in the design stages. The data returned in this
345 though this API is still in the design stages. The data returned in this
346 payload will allow frontends to present special views of what just happened.
346 payload will allow frontends to present special views of what just happened.
347
347
348
348
349 When status is 'error', the following extra fields are present::
349 When status is 'error', the following extra fields are present::
350
350
351 {
351 {
352 'exc_name' : str, # Exception name, as a string
352 'exc_name' : str, # Exception name, as a string
353 'exc_value' : str, # Exception value, as a string
353 'exc_value' : str, # Exception value, as a string
354
354
355 # The traceback will contain a list of frames, represented each as a
355 # The traceback will contain a list of frames, represented each as a
356 # string. For now we'll stick to the existing design of ultraTB, which
356 # string. For now we'll stick to the existing design of ultraTB, which
357 # controls exception level of detail statefully. But eventually we'll
357 # controls exception level of detail statefully. But eventually we'll
358 # want to grow into a model where more information is collected and
358 # want to grow into a model where more information is collected and
359 # packed into the traceback object, with clients deciding how little or
359 # packed into the traceback object, with clients deciding how little or
360 # how much of it to unpack. But for now, let's start with a simple list
360 # how much of it to unpack. But for now, let's start with a simple list
361 # of strings, since that requires only minimal changes to ultratb as
361 # of strings, since that requires only minimal changes to ultratb as
362 # written.
362 # written.
363 'traceback' : list,
363 'traceback' : list,
364 }
364 }
365
365
366
366
367 When status is 'abort', there are for now no additional data fields. This
367 When status is 'abort', there are for now no additional data fields. This
368 happens when the kernel was interrupted by a signal.
368 happens when the kernel was interrupted by a signal.
369
369
370 Kernel attribute access
370 Kernel attribute access
371 -----------------------
371 -----------------------
372
372
373 .. warning::
373 .. warning::
374
374
375 This part of the messaging spec is not actually implemented in the kernel
375 This part of the messaging spec is not actually implemented in the kernel
376 yet.
376 yet.
377
377
378 While this protocol does not specify full RPC access to arbitrary methods of
378 While this protocol does not specify full RPC access to arbitrary methods of
379 the kernel object, the kernel does allow read (and in some cases write) access
379 the kernel object, the kernel does allow read (and in some cases write) access
380 to certain attributes.
380 to certain attributes.
381
381
382 The policy for which attributes can be read is: any attribute of the kernel, or
382 The policy for which attributes can be read is: any attribute of the kernel, or
383 its sub-objects, that belongs to a :class:`Configurable` object and has been
383 its sub-objects, that belongs to a :class:`Configurable` object and has been
384 declared at the class-level with Traits validation, is in principle accessible
384 declared at the class-level with Traits validation, is in principle accessible
385 as long as its name does not begin with a leading underscore. The attribute
385 as long as its name does not begin with a leading underscore. The attribute
386 itself will have metadata indicating whether it allows remote read and/or write
386 itself will have metadata indicating whether it allows remote read and/or write
387 access. The message spec follows for attribute read and write requests.
387 access. The message spec follows for attribute read and write requests.
388
388
389 Message type: ``getattr_request``::
389 Message type: ``getattr_request``::
390
390
391 content = {
391 content = {
392 # The (possibly dotted) name of the attribute
392 # The (possibly dotted) name of the attribute
393 'name' : str,
393 'name' : str,
394 }
394 }
395
395
396 When a ``getattr_request`` fails, there are two possible error types:
396 When a ``getattr_request`` fails, there are two possible error types:
397
397
398 - AttributeError: this type of error was raised when trying to access the
398 - AttributeError: this type of error was raised when trying to access the
399 given name by the kernel itself. This means that the attribute likely
399 given name by the kernel itself. This means that the attribute likely
400 doesn't exist.
400 doesn't exist.
401
401
402 - AccessError: the attribute exists but its value is not readable remotely.
402 - AccessError: the attribute exists but its value is not readable remotely.
403
403
404
404
405 Message type: ``getattr_reply``::
405 Message type: ``getattr_reply``::
406
406
407 content = {
407 content = {
408 # One of ['ok', 'AttributeError', 'AccessError'].
408 # One of ['ok', 'AttributeError', 'AccessError'].
409 'status' : str,
409 'status' : str,
410 # If status is 'ok', a JSON object.
410 # If status is 'ok', a JSON object.
411 'value' : object,
411 'value' : object,
412 }
412 }
413
413
414 Message type: ``setattr_request``::
414 Message type: ``setattr_request``::
415
415
416 content = {
416 content = {
417 # The (possibly dotted) name of the attribute
417 # The (possibly dotted) name of the attribute
418 'name' : str,
418 'name' : str,
419
419
420 # A JSON-encoded object, that will be validated by the Traits
420 # A JSON-encoded object, that will be validated by the Traits
421 # information in the kernel
421 # information in the kernel
422 'value' : object,
422 'value' : object,
423 }
423 }
424
424
425 When a ``setattr_request`` fails, there are also two possible error types with
425 When a ``setattr_request`` fails, there are also two possible error types with
426 similar meanings as those of the ``getattr_request`` case, but for writing.
426 similar meanings as those of the ``getattr_request`` case, but for writing.
427
427
428 Message type: ``setattr_reply``::
428 Message type: ``setattr_reply``::
429
429
430 content = {
430 content = {
431 # One of ['ok', 'AttributeError', 'AccessError'].
431 # One of ['ok', 'AttributeError', 'AccessError'].
432 'status' : str,
432 'status' : str,
433 }
433 }
434
434
435
435
436
436
437 Object information
437 Object information
438 ------------------
438 ------------------
439
439
440 One of IPython's most used capabilities is the introspection of Python objects
440 One of IPython's most used capabilities is the introspection of Python objects
441 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
441 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
442 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
442 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
443 enough that it warrants an explicit message type, especially because frontends
443 enough that it warrants an explicit message type, especially because frontends
444 may want to get object information in response to user keystrokes (like Tab or
444 may want to get object information in response to user keystrokes (like Tab or
445 F1) besides from the user explicitly typing code like ``x??``.
445 F1) besides from the user explicitly typing code like ``x??``.
446
446
447 Message type: ``object_info_request``::
447 Message type: ``object_info_request``::
448
448
449 content = {
449 content = {
450 # The (possibly dotted) name of the object to be searched in all
450 # The (possibly dotted) name of the object to be searched in all
451 # relevant namespaces
451 # relevant namespaces
452 'name' : str,
452 'name' : str,
453
453
454 # The level of detail desired. The default (0) is equivalent to typing
454 # The level of detail desired. The default (0) is equivalent to typing
455 # 'x?' at the prompt, 1 is equivalent to 'x??'.
455 # 'x?' at the prompt, 1 is equivalent to 'x??'.
456 'detail_level' : int,
456 'detail_level' : int,
457 }
457 }
458
458
459 The returned information will be a dictionary with keys very similar to the
459 The returned information will be a dictionary with keys very similar to the
460 field names that IPython prints at the terminal.
460 field names that IPython prints at the terminal.
461
461
462 Message type: ``object_info_reply``::
462 Message type: ``object_info_reply``::
463
463
464 content = {
464 content = {
465 # The name the object was requested under
465 # The name the object was requested under
466 'name' : str,
466 'name' : str,
467
467
468 # Boolean flag indicating whether the named object was found or not. If
468 # Boolean flag indicating whether the named object was found or not. If
469 # it's false, all other fields will be empty.
469 # it's false, all other fields will be empty.
470 'found' : bool,
470 'found' : bool,
471
471
472 # Flags for magics and system aliases
472 # Flags for magics and system aliases
473 'ismagic' : bool,
473 'ismagic' : bool,
474 'isalias' : bool,
474 'isalias' : bool,
475
475
476 # The name of the namespace where the object was found ('builtin',
476 # The name of the namespace where the object was found ('builtin',
477 # 'magics', 'alias', 'interactive', etc.)
477 # 'magics', 'alias', 'interactive', etc.)
478 'namespace' : str,
478 'namespace' : str,
479
479
480 # The type name will be type.__name__ for normal Python objects, but it
480 # The type name will be type.__name__ for normal Python objects, but it
481 # can also be a string like 'Magic function' or 'System alias'
481 # can also be a string like 'Magic function' or 'System alias'
482 'type_name' : str,
482 'type_name' : str,
483
483
484 'string_form' : str,
484 'string_form' : str,
485
485
486 # For objects with a __class__ attribute this will be set
486 # For objects with a __class__ attribute this will be set
487 'base_class' : str,
487 'base_class' : str,
488
488
489 # For objects with a __len__ attribute this will be set
489 # For objects with a __len__ attribute this will be set
490 'length' : int,
490 'length' : int,
491
491
492 # If the object is a function, class or method whose file we can find,
492 # If the object is a function, class or method whose file we can find,
493 # we give its full path
493 # we give its full path
494 'file' : str,
494 'file' : str,
495
495
496 # For pure Python callable objects, we can reconstruct the object
496 # For pure Python callable objects, we can reconstruct the object
497 # definition line which provides its call signature. For convenience this
497 # definition line which provides its call signature. For convenience this
498 # is returned as a single 'definition' field, but below the raw parts that
498 # is returned as a single 'definition' field, but below the raw parts that
499 # compose it are also returned as the argspec field.
499 # compose it are also returned as the argspec field.
500 'definition' : str,
500 'definition' : str,
501
501
502 # The individual parts that together form the definition string. Clients
502 # The individual parts that together form the definition string. Clients
503 # with rich display capabilities may use this to provide a richer and more
503 # with rich display capabilities may use this to provide a richer and more
504 # precise representation of the definition line (e.g. by highlighting
504 # precise representation of the definition line (e.g. by highlighting
505 # arguments based on the user's cursor position). For non-callable
505 # arguments based on the user's cursor position). For non-callable
506 # objects, this field is empty.
506 # objects, this field is empty.
507 'argspec' : { # The names of all the arguments
507 'argspec' : { # The names of all the arguments
508 args : list,
508 args : list,
509 # The name of the varargs (*args), if any
509 # The name of the varargs (*args), if any
510 varargs : str,
510 varargs : str,
511 # The name of the varkw (**kw), if any
511 # The name of the varkw (**kw), if any
512 varkw : str,
512 varkw : str,
513 # The values (as strings) of all default arguments. Note
513 # The values (as strings) of all default arguments. Note
514 # that these must be matched *in reverse* with the 'args'
514 # that these must be matched *in reverse* with the 'args'
515 # list above, since the first positional args have no default
515 # list above, since the first positional args have no default
516 # value at all.
516 # value at all.
517 defaults : list,
517 defaults : list,
518 },
518 },
519
519
520 # For instances, provide the constructor signature (the definition of
520 # For instances, provide the constructor signature (the definition of
521 # the __init__ method):
521 # the __init__ method):
522 'init_definition' : str,
522 'init_definition' : str,
523
523
524 # Docstrings: for any object (function, method, module, package) with a
524 # Docstrings: for any object (function, method, module, package) with a
525 # docstring, we show it. But in addition, we may provide additional
525 # docstring, we show it. But in addition, we may provide additional
526 # docstrings. For example, for instances we will show the constructor
526 # docstrings. For example, for instances we will show the constructor
527 # and class docstrings as well, if available.
527 # and class docstrings as well, if available.
528 'docstring' : str,
528 'docstring' : str,
529
529
530 # For instances, provide the constructor and class docstrings
530 # For instances, provide the constructor and class docstrings
531 'init_docstring' : str,
531 'init_docstring' : str,
532 'class_docstring' : str,
532 'class_docstring' : str,
533
533
534 # If it's a callable object whose call method has a separate docstring and
534 # If it's a callable object whose call method has a separate docstring and
535 # definition line:
535 # definition line:
536 'call_def' : str,
536 'call_def' : str,
537 'call_docstring' : str,
537 'call_docstring' : str,
538
538
539 # If detail_level was 1, we also try to find the source code that
539 # If detail_level was 1, we also try to find the source code that
540 # defines the object, if possible. The string 'None' will indicate
540 # defines the object, if possible. The string 'None' will indicate
541 # that no source was found.
541 # that no source was found.
542 'source' : str,
542 'source' : str,
543 }
543 }
544 '
544 '
545
545
546 Complete
546 Complete
547 --------
547 --------
548
548
549 Message type: ``complete_request``::
549 Message type: ``complete_request``::
550
550
551 content = {
551 content = {
552 # The text to be completed, such as 'a.is'
552 # The text to be completed, such as 'a.is'
553 'text' : str,
553 'text' : str,
554
554
555 # The full line, such as 'print a.is'. This allows completers to
555 # The full line, such as 'print a.is'. This allows completers to
556 # make decisions that may require information about more than just the
556 # make decisions that may require information about more than just the
557 # current word.
557 # current word.
558 'line' : str,
558 'line' : str,
559
559
560 # The entire block of text where the line is. This may be useful in the
560 # The entire block of text where the line is. This may be useful in the
561 # case of multiline completions where more context may be needed. Note: if
561 # case of multiline completions where more context may be needed. Note: if
562 # in practice this field proves unnecessary, remove it to lighten the
562 # in practice this field proves unnecessary, remove it to lighten the
563 # messages.
563 # messages.
564
564
565 'block' : str,
565 'block' : str,
566
566
567 # The position of the cursor where the user hit 'TAB' on the line.
567 # The position of the cursor where the user hit 'TAB' on the line.
568 'cursor_pos' : int,
568 'cursor_pos' : int,
569 }
569 }
570
570
571 Message type: ``complete_reply``::
571 Message type: ``complete_reply``::
572
572
573 content = {
573 content = {
574 # The list of all matches to the completion request, such as
574 # The list of all matches to the completion request, such as
575 # ['a.isalnum', 'a.isalpha'] for the above example.
575 # ['a.isalnum', 'a.isalpha'] for the above example.
576 'matches' : list
576 'matches' : list
577 }
577 }
578
578
579
579
580 History
580 History
581 -------
581 -------
582
582
583 For clients to explicitly request history from a kernel. The kernel has all
583 For clients to explicitly request history from a kernel. The kernel has all
584 the actual execution history stored in a single location, so clients can
584 the actual execution history stored in a single location, so clients can
585 request it from the kernel when needed.
585 request it from the kernel when needed.
586
586
587 Message type: ``history_request``::
587 Message type: ``history_request``::
588
588
589 content = {
589 content = {
590
590
591 # If True, also return output history in the resulting dict.
591 # If True, also return output history in the resulting dict.
592 'output' : bool,
592 'output' : bool,
593
593
594 # If True, return the raw input history, else the transformed input.
594 # If True, return the raw input history, else the transformed input.
595 'raw' : bool,
595 'raw' : bool,
596
596
597 # This parameter can be one of: A number, a pair of numbers, None
597 # This parameter can be one of: A number, a pair of numbers, None
598 # If not given, last 40 are returned.
598 # If not given, last 40 are returned.
599 # - number n: return the last n entries.
599 # - number n: return the last n entries.
600 # - pair n1, n2: return entries in the range(n1, n2).
600 # - pair n1, n2: return entries in the range(n1, n2).
601 # - None: return all history
601 # - None: return all history
602 'index' : n or (n1, n2) or None,
602 'index' : n or (n1, n2) or None,
603 }
603 }
604
604
605 Message type: ``history_reply``::
605 Message type: ``history_reply``::
606
606
607 content = {
607 content = {
608 # A dict with prompt numbers as keys and either (input, output) or input
608 # A dict with prompt numbers as keys and either (input, output) or input
609 # as the value depending on whether output was True or False,
609 # as the value depending on whether output was True or False,
610 # respectively.
610 # respectively.
611 'history' : dict,
611 'history' : dict,
612 }
612 }
613
613
614
614
615 Connect
615 Connect
616 -------
616 -------
617
617
618 When a client connects to the request/reply socket of the kernel, it can issue
618 When a client connects to the request/reply socket of the kernel, it can issue
619 a connect request to get basic information about the kernel, such as the ports
619 a connect request to get basic information about the kernel, such as the ports
620 the other ZeroMQ sockets are listening on. This allows clients to only have
620 the other ZeroMQ sockets are listening on. This allows clients to only have
621 to know about a single port (the XREQ/XREP channel) to connect to a kernel.
621 to know about a single port (the XREQ/XREP channel) to connect to a kernel.
622
622
623 Message type: ``connect_request``::
623 Message type: ``connect_request``::
624
624
625 content = {
625 content = {
626 }
626 }
627
627
628 Message type: ``connect_reply``::
628 Message type: ``connect_reply``::
629
629
630 content = {
630 content = {
631 'xrep_port' : int # The port the XREP socket is listening on.
631 'xrep_port' : int # The port the XREP socket is listening on.
632 'pub_port' : int # The port the PUB socket is listening on.
632 'pub_port' : int # The port the PUB socket is listening on.
633 'req_port' : int # The port the REQ socket is listening on.
633 'req_port' : int # The port the REQ socket is listening on.
634 'hb_port' : int # The port the heartbeat socket is listening on.
634 'hb_port' : int # The port the heartbeat socket is listening on.
635 }
635 }
636
636
637
637
638
638
639 Kernel shutdown
639 Kernel shutdown
640 ---------------
640 ---------------
641
641
642 The clients can request the kernel to shut itself down; this is used in
642 The clients can request the kernel to shut itself down; this is used in
643 multiple cases:
643 multiple cases:
644
644
645 - when the user chooses to close the client application via a menu or window
645 - when the user chooses to close the client application via a menu or window
646 control.
646 control.
647 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
647 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
648 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
648 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
649 IPythonQt client) to force a kernel restart to get a clean kernel without
649 IPythonQt client) to force a kernel restart to get a clean kernel without
650 losing client-side state like history or inlined figures.
650 losing client-side state like history or inlined figures.
651
651
652 The client sends a shutdown request to the kernel, and once it receives the
652 The client sends a shutdown request to the kernel, and once it receives the
653 reply message (which is otherwise empty), it can assume that the kernel has
653 reply message (which is otherwise empty), it can assume that the kernel has
654 completed shutdown safely.
654 completed shutdown safely.
655
655
656 Upon their own shutdown, client applications will typically execute a last
656 Upon their own shutdown, client applications will typically execute a last
657 minute sanity check and forcefully terminate any kernel that is still alive, to
657 minute sanity check and forcefully terminate any kernel that is still alive, to
658 avoid leaving stray processes in the user's machine.
658 avoid leaving stray processes in the user's machine.
659
659
660 For both shutdown request and reply, there is no actual content that needs to
660 For both shutdown request and reply, there is no actual content that needs to
661 be sent, so the content dict is empty.
661 be sent, so the content dict is empty.
662
662
663 Message type: ``shutdown_request``::
663 Message type: ``shutdown_request``::
664
664
665 content = {
665 content = {
666 'restart' : bool # whether the shutdown is final, or precedes a restart
666 }
667 }
667
668
668 Message type: ``shutdown_reply``::
669 Message type: ``shutdown_reply``::
669
670
670 content = {
671 content = {
672 'restart' : bool # whether the shutdown is final, or precedes a restart
671 }
673 }
672
674
673 .. Note::
675 .. Note::
674
676
675 When the clients detect a dead kernel thanks to inactivity on the heartbeat
677 When the clients detect a dead kernel thanks to inactivity on the heartbeat
676 socket, they simply send a forceful process termination signal, since a dead
678 socket, they simply send a forceful process termination signal, since a dead
677 process is unlikely to respond in any useful way to messages.
679 process is unlikely to respond in any useful way to messages.
678
680
679
681
680 Messages on the PUB/SUB socket
682 Messages on the PUB/SUB socket
681 ==============================
683 ==============================
682
684
683 Streams (stdout, stderr, etc)
685 Streams (stdout, stderr, etc)
684 ------------------------------
686 ------------------------------
685
687
686 Message type: ``stream``::
688 Message type: ``stream``::
687
689
688 content = {
690 content = {
689 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
691 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
690 'name' : str,
692 'name' : str,
691
693
692 # The data is an arbitrary string to be written to that stream
694 # The data is an arbitrary string to be written to that stream
693 'data' : str,
695 'data' : str,
694 }
696 }
695
697
696 When a kernel receives a raw_input call, it should also broadcast it on the pub
698 When a kernel receives a raw_input call, it should also broadcast it on the pub
697 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
699 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
698 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
699 or otherwise expose them.
701 or otherwise expose them.
700
702
701 Python inputs
703 Python inputs
702 -------------
704 -------------
703
705
704 These messages are the re-broadcast of the ``execute_request``.
706 These messages are the re-broadcast of the ``execute_request``.
705
707
706 Message type: ``pyin``::
708 Message type: ``pyin``::
707
709
708 content = {
710 content = {
709 # Source code to be executed, one or more lines
711 # Source code to be executed, one or more lines
710 'code' : str
712 'code' : str
711 }
713 }
712
714
713 Python outputs
715 Python outputs
714 --------------
716 --------------
715
717
716 When Python produces output from code that has been compiled in with the
718 When Python produces output from code that has been compiled in with the
717 'single' flag to :func:`compile`, any expression that produces a value (such as
719 'single' flag to :func:`compile`, any expression that produces a value (such as
718 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
720 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
719 this value whatever it wants. The default behavior of ``sys.displayhook`` in
721 this value whatever it wants. The default behavior of ``sys.displayhook`` in
720 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
722 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
721 the value as long as it is not ``None`` (which isn't printed at all). In our
723 the value as long as it is not ``None`` (which isn't printed at all). In our
722 case, the kernel instantiates as ``sys.displayhook`` an object which has
724 case, the kernel instantiates as ``sys.displayhook`` an object which has
723 similar behavior, but which instead of printing to stdout, broadcasts these
725 similar behavior, but which instead of printing to stdout, broadcasts these
724 values as ``pyout`` messages for clients to display appropriately.
726 values as ``pyout`` messages for clients to display appropriately.
725
727
726 Message type: ``pyout``::
728 Message type: ``pyout``::
727
729
728 content = {
730 content = {
729 # The data is typically the repr() of the object.
731 # The data is typically the repr() of the object.
730 'data' : str,
732 'data' : str,
731
733
732 # The counter for this execution is also provided so that clients can
734 # The counter for this execution is also provided so that clients can
733 # display it, since IPython automatically creates variables called _N (for
735 # display it, since IPython automatically creates variables called _N (for
734 # prompt N).
736 # prompt N).
735 'execution_count' : int,
737 'execution_count' : int,
736 }
738 }
737
739
738 Python errors
740 Python errors
739 -------------
741 -------------
740
742
741 When an error occurs during code execution
743 When an error occurs during code execution
742
744
743 Message type: ``pyerr``::
745 Message type: ``pyerr``::
744
746
745 content = {
747 content = {
746 # Similar content to the execute_reply messages for the 'error' case,
748 # Similar content to the execute_reply messages for the 'error' case,
747 # except the 'status' field is omitted.
749 # except the 'status' field is omitted.
748 }
750 }
749
751
750 Kernel status
752 Kernel status
751 -------------
753 -------------
752
754
753 This message type is used by frontends to monitor the status of the kernel.
755 This message type is used by frontends to monitor the status of the kernel.
754
756
755 Message type: ``status``::
757 Message type: ``status``::
756
758
757 content = {
759 content = {
758 # When the kernel starts to execute code, it will enter the 'busy'
760 # When the kernel starts to execute code, it will enter the 'busy'
759 # state and when it finishes, it will enter the 'idle' state.
761 # state and when it finishes, it will enter the 'idle' state.
760 execution_state : ('busy', 'idle')
762 execution_state : ('busy', 'idle')
761 }
763 }
762
764
763 Kernel crashes
765 Kernel crashes
764 --------------
766 --------------
765
767
766 When the kernel has an unexpected exception, caught by the last-resort
768 When the kernel has an unexpected exception, caught by the last-resort
767 sys.excepthook, we should broadcast the crash handler's output before exiting.
769 sys.excepthook, we should broadcast the crash handler's output before exiting.
768 This will allow clients to notice that a kernel died, inform the user and
770 This will allow clients to notice that a kernel died, inform the user and
769 propose further actions.
771 propose further actions.
770
772
771 Message type: ``crash``::
773 Message type: ``crash``::
772
774
773 content = {
775 content = {
774 # Similarly to the 'error' case for execute_reply messages, this will
776 # Similarly to the 'error' case for execute_reply messages, this will
775 # contain exc_name, exc_type and traceback fields.
777 # contain exc_name, exc_type and traceback fields.
776
778
777 # An additional field with supplementary information such as where to
779 # An additional field with supplementary information such as where to
778 # send the crash message
780 # send the crash message
779 'info' : str,
781 'info' : str,
780 }
782 }
781
783
782
784
783 Future ideas
785 Future ideas
784 ------------
786 ------------
785
787
786 Other potential message types, currently unimplemented, listed below as ideas.
788 Other potential message types, currently unimplemented, listed below as ideas.
787
789
788 Message type: ``file``::
790 Message type: ``file``::
789
791
790 content = {
792 content = {
791 'path' : 'cool.jpg',
793 'path' : 'cool.jpg',
792 'mimetype' : str,
794 'mimetype' : str,
793 'data' : str,
795 'data' : str,
794 }
796 }
795
797
796
798
797 Messages on the REQ/REP socket
799 Messages on the REQ/REP socket
798 ==============================
800 ==============================
799
801
800 This is a socket that goes in the opposite direction: from the kernel to a
802 This is a socket that goes in the opposite direction: from the kernel to a
801 *single* frontend, and its purpose is to allow ``raw_input`` and similar
803 *single* frontend, and its purpose is to allow ``raw_input`` and similar
802 operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
804 operations that read from ``sys.stdin`` on the kernel to be fulfilled by the
803 client. For now we will keep these messages as simple as possible, since they
805 client. For now we will keep these messages as simple as possible, since they
804 basically only mean to convey the ``raw_input(prompt)`` call.
806 basically only mean to convey the ``raw_input(prompt)`` call.
805
807
806 Message type: ``input_request``::
808 Message type: ``input_request``::
807
809
808 content = { 'prompt' : str }
810 content = { 'prompt' : str }
809
811
810 Message type: ``input_reply``::
812 Message type: ``input_reply``::
811
813
812 content = { 'value' : str }
814 content = { 'value' : str }
813
815
814 .. Note::
816 .. Note::
815
817
816 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
818 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
817 practice the kernel should behave like an interactive program. When a
819 practice the kernel should behave like an interactive program. When a
818 program is opened on the console, the keyboard effectively takes over the
820 program is opened on the console, the keyboard effectively takes over the
819 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
821 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
820 Since the IPython kernel effectively behaves like a console program (albeit
822 Since the IPython kernel effectively behaves like a console program (albeit
821 one whose "keyboard" is actually living in a separate process and
823 one whose "keyboard" is actually living in a separate process and
822 transported over the zmq connection), raw ``stdin`` isn't expected to be
824 transported over the zmq connection), raw ``stdin`` isn't expected to be
823 available.
825 available.
824
826
825
827
826 Heartbeat for kernels
828 Heartbeat for kernels
827 =====================
829 =====================
828
830
829 Initially we had considered using messages like those above over ZMQ for a
831 Initially we had considered using messages like those above over ZMQ for a
830 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
832 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
831 alive at all, even if it may be busy executing user code). But this has the
833 alive at all, even if it may be busy executing user code). But this has the
832 problem that if the kernel is locked inside extension code, it wouldn't execute
834 problem that if the kernel is locked inside extension code, it wouldn't execute
833 the python heartbeat code. But it turns out that we can implement a basic
835 the python heartbeat code. But it turns out that we can implement a basic
834 heartbeat with pure ZMQ, without using any Python messaging at all.
836 heartbeat with pure ZMQ, without using any Python messaging at all.
835
837
836 The monitor sends out a single zmq message (right now, it is a str of the
838 The monitor sends out a single zmq message (right now, it is a str of the
837 monitor's lifetime in seconds), and gets the same message right back, prefixed
839 monitor's lifetime in seconds), and gets the same message right back, prefixed
838 with the zmq identity of the XREQ socket in the heartbeat process. This can be
840 with the zmq identity of the XREQ socket in the heartbeat process. This can be
839 a uuid, or even a full message, but there doesn't seem to be a need for packing
841 a uuid, or even a full message, but there doesn't seem to be a need for packing
840 up a message when the sender and receiver are the exact same Python object.
842 up a message when the sender and receiver are the exact same Python object.
841
843
842 The model is this::
844 The model is this::
843
845
844 monitor.send(str(self.lifetime)) # '1.2345678910'
846 monitor.send(str(self.lifetime)) # '1.2345678910'
845
847
846 and the monitor receives some number of messages of the form::
848 and the monitor receives some number of messages of the form::
847
849
848 ['uuid-abcd-dead-beef', '1.2345678910']
850 ['uuid-abcd-dead-beef', '1.2345678910']
849
851
850 where the first part is the zmq.IDENTITY of the heart's XREQ on the engine, and
852 where the first part is the zmq.IDENTITY of the heart's XREQ on the engine, and
851 the rest is the message sent by the monitor. No Python code ever has any
853 the rest is the message sent by the monitor. No Python code ever has any
852 access to the message between the monitor's send, and the monitor's recv.
854 access to the message between the monitor's send, and the monitor's recv.
853
855
854
856
855 ToDo
857 ToDo
856 ====
858 ====
857
859
858 Missing things include:
860 Missing things include:
859
861
860 * Important: finish thinking through the payload concept and API.
862 * Important: finish thinking through the payload concept and API.
861
863
862 * Important: ensure that we have a good solution for magics like %edit. It's
864 * Important: ensure that we have a good solution for magics like %edit. It's
863 likely that with the payload concept we can build a full solution, but not
865 likely that with the payload concept we can build a full solution, but not
864 100% clear yet.
866 100% clear yet.
865
867
866 * Finishing the details of the heartbeat protocol.
868 * Finishing the details of the heartbeat protocol.
867
869
868 * Signal handling: specify what kind of information kernel should broadcast (or
870 * Signal handling: specify what kind of information kernel should broadcast (or
869 not) when it receives signals.
871 not) when it receives signals.
870
872
871 .. include:: ../links.rst
873 .. include:: ../links.rst
General Comments 0
You need to be logged in to leave comments. Login now