##// END OF EJS Templates
Merge branch 'keepkernel' of https://github.com/minrk/ipython into minrk-keepkernel...
Fernando Perez -
r3106:f16f5ccc merge
parent child Browse files
Show More
@@ -1,554 +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
7
7 # System library imports
8 # System library imports
8 from pygments.lexers import PythonLexer
9 from pygments.lexers import PythonLexer
9 from PyQt4 import QtCore, QtGui
10 from PyQt4 import QtCore, QtGui
10
11
11 # Local imports
12 # Local imports
12 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.core.oinspect import call_tip
14 from IPython.core.oinspect import call_tip
14 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
15 from IPython.utils.traitlets import Bool
16 from IPython.utils.traitlets import Bool
16 from bracket_matcher import BracketMatcher
17 from bracket_matcher import BracketMatcher
17 from call_tip_widget import CallTipWidget
18 from call_tip_widget import CallTipWidget
18 from completion_lexer import CompletionLexer
19 from completion_lexer import CompletionLexer
19 from history_console_widget import HistoryConsoleWidget
20 from history_console_widget import HistoryConsoleWidget
20 from pygments_highlighter import PygmentsHighlighter
21 from pygments_highlighter import PygmentsHighlighter
21
22
22
23
23 class FrontendHighlighter(PygmentsHighlighter):
24 class FrontendHighlighter(PygmentsHighlighter):
24 """ 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
25 prompts.
26 prompts.
26 """
27 """
27
28
28 def __init__(self, frontend):
29 def __init__(self, frontend):
29 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
30 self._current_offset = 0
31 self._current_offset = 0
31 self._frontend = frontend
32 self._frontend = frontend
32 self.highlighting_on = False
33 self.highlighting_on = False
33
34
34 def highlightBlock(self, qstring):
35 def highlightBlock(self, qstring):
35 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """ Highlight a block of text. Reimplemented to highlight selectively.
36 """
37 """
37 if not self.highlighting_on:
38 if not self.highlighting_on:
38 return
39 return
39
40
40 # The input to this function is unicode string that may contain
41 # The input to this function is unicode string that may contain
41 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
42 # the string as plain text so we can compare it.
43 # the string as plain text so we can compare it.
43 current_block = self.currentBlock()
44 current_block = self.currentBlock()
44 string = self._frontend._get_block_plain_text(current_block)
45 string = self._frontend._get_block_plain_text(current_block)
45
46
46 # Decide whether to check for the regular or continuation prompt.
47 # Decide whether to check for the regular or continuation prompt.
47 if current_block.contains(self._frontend._prompt_pos):
48 if current_block.contains(self._frontend._prompt_pos):
48 prompt = self._frontend._prompt
49 prompt = self._frontend._prompt
49 else:
50 else:
50 prompt = self._frontend._continuation_prompt
51 prompt = self._frontend._continuation_prompt
51
52
52 # 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.
53 if string.startswith(prompt):
54 if string.startswith(prompt):
54 self._current_offset = len(prompt)
55 self._current_offset = len(prompt)
55 qstring.remove(0, len(prompt))
56 qstring.remove(0, len(prompt))
56 else:
57 else:
57 self._current_offset = 0
58 self._current_offset = 0
58
59
59 PygmentsHighlighter.highlightBlock(self, qstring)
60 PygmentsHighlighter.highlightBlock(self, qstring)
60
61
61 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
62 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """
64 """
64 old = self.highlighting_on
65 old = self.highlighting_on
65 self.highlighting_on = True
66 self.highlighting_on = True
66 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 self.highlighting_on = old
68 self.highlighting_on = old
68
69
69 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
70 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
71 """
72 """
72 start += self._current_offset
73 start += self._current_offset
73 PygmentsHighlighter.setFormat(self, start, count, format)
74 PygmentsHighlighter.setFormat(self, start, count, format)
74
75
75
76
76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
78 """
79 """
79
80
80 # An option and corresponding signal for overriding the default kernel
81 # An option and corresponding signal for overriding the default kernel
81 # interrupt behavior.
82 # interrupt behavior.
82 custom_interrupt = Bool(False)
83 custom_interrupt = Bool(False)
83 custom_interrupt_requested = QtCore.pyqtSignal()
84 custom_interrupt_requested = QtCore.pyqtSignal()
84
85
85 # An option and corresponding signals for overriding the default kernel
86 # An option and corresponding signals for overriding the default kernel
86 # restart behavior.
87 # restart behavior.
87 custom_restart = Bool(False)
88 custom_restart = Bool(False)
88 custom_restart_kernel_died = QtCore.pyqtSignal(float)
89 custom_restart_kernel_died = QtCore.pyqtSignal(float)
89 custom_restart_requested = QtCore.pyqtSignal()
90 custom_restart_requested = QtCore.pyqtSignal()
90
91
91 # 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
92 # processed by the FrontendWidget.
93 # processed by the FrontendWidget.
93 executed = QtCore.pyqtSignal(object)
94 executed = QtCore.pyqtSignal(object)
94
95
95 # Emitted when an exit request has been received from the kernel.
96 # Emitted when an exit request has been received from the kernel.
96 exit_requested = QtCore.pyqtSignal()
97 exit_requested = QtCore.pyqtSignal()
97
98
98 # Protected class variables.
99 # Protected class variables.
99 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
100 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
100 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
101 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
101 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
102 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
102 _input_splitter_class = InputSplitter
103 _input_splitter_class = InputSplitter
103
104
104 #---------------------------------------------------------------------------
105 #---------------------------------------------------------------------------
105 # 'object' interface
106 # 'object' interface
106 #---------------------------------------------------------------------------
107 #---------------------------------------------------------------------------
107
108
108 def __init__(self, *args, **kw):
109 def __init__(self, *args, **kw):
109 super(FrontendWidget, self).__init__(*args, **kw)
110 super(FrontendWidget, self).__init__(*args, **kw)
110
111
111 # FrontendWidget protected variables.
112 # FrontendWidget protected variables.
112 self._bracket_matcher = BracketMatcher(self._control)
113 self._bracket_matcher = BracketMatcher(self._control)
113 self._call_tip_widget = CallTipWidget(self._control)
114 self._call_tip_widget = CallTipWidget(self._control)
114 self._completion_lexer = CompletionLexer(PythonLexer())
115 self._completion_lexer = CompletionLexer(PythonLexer())
115 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
116 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
116 self._hidden = False
117 self._hidden = False
117 self._highlighter = FrontendHighlighter(self)
118 self._highlighter = FrontendHighlighter(self)
118 self._input_splitter = self._input_splitter_class(input_mode='cell')
119 self._input_splitter = self._input_splitter_class(input_mode='cell')
119 self._kernel_manager = None
120 self._kernel_manager = None
120 self._request_info = {}
121 self._request_info = {}
121
122
122 # Configure the ConsoleWidget.
123 # Configure the ConsoleWidget.
123 self.tab_width = 4
124 self.tab_width = 4
124 self._set_continuation_prompt('... ')
125 self._set_continuation_prompt('... ')
125
126
126 # Configure the CallTipWidget.
127 # Configure the CallTipWidget.
127 self._call_tip_widget.setFont(self.font)
128 self._call_tip_widget.setFont(self.font)
128 self.font_changed.connect(self._call_tip_widget.setFont)
129 self.font_changed.connect(self._call_tip_widget.setFont)
129
130
130 # Configure actions.
131 # Configure actions.
131 action = self._copy_raw_action
132 action = self._copy_raw_action
132 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
133 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
133 action.setEnabled(False)
134 action.setEnabled(False)
134 action.setShortcut(QtGui.QKeySequence(key))
135 action.setShortcut(QtGui.QKeySequence(key))
135 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
136 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
136 action.triggered.connect(self.copy_raw)
137 action.triggered.connect(self.copy_raw)
137 self.copy_available.connect(action.setEnabled)
138 self.copy_available.connect(action.setEnabled)
138 self.addAction(action)
139 self.addAction(action)
139
140
140 # Connect signal handlers.
141 # Connect signal handlers.
141 document = self._control.document()
142 document = self._control.document()
142 document.contentsChange.connect(self._document_contents_change)
143 document.contentsChange.connect(self._document_contents_change)
143
144
144 #---------------------------------------------------------------------------
145 #---------------------------------------------------------------------------
145 # 'ConsoleWidget' public interface
146 # 'ConsoleWidget' public interface
146 #---------------------------------------------------------------------------
147 #---------------------------------------------------------------------------
147
148
148 def copy(self):
149 def copy(self):
149 """ Copy the currently selected text to the clipboard, removing prompts.
150 """ Copy the currently selected text to the clipboard, removing prompts.
150 """
151 """
151 text = unicode(self._control.textCursor().selection().toPlainText())
152 text = unicode(self._control.textCursor().selection().toPlainText())
152 if text:
153 if text:
153 lines = map(transform_classic_prompt, text.splitlines())
154 lines = map(transform_classic_prompt, text.splitlines())
154 text = '\n'.join(lines)
155 text = '\n'.join(lines)
155 QtGui.QApplication.clipboard().setText(text)
156 QtGui.QApplication.clipboard().setText(text)
156
157
157 #---------------------------------------------------------------------------
158 #---------------------------------------------------------------------------
158 # 'ConsoleWidget' abstract interface
159 # 'ConsoleWidget' abstract interface
159 #---------------------------------------------------------------------------
160 #---------------------------------------------------------------------------
160
161
161 def _is_complete(self, source, interactive):
162 def _is_complete(self, source, interactive):
162 """ Returns whether 'source' can be completely processed and a new
163 """ Returns whether 'source' can be completely processed and a new
163 prompt created. When triggered by an Enter/Return key press,
164 prompt created. When triggered by an Enter/Return key press,
164 'interactive' is True; otherwise, it is False.
165 'interactive' is True; otherwise, it is False.
165 """
166 """
166 complete = self._input_splitter.push(source)
167 complete = self._input_splitter.push(source)
167 if interactive:
168 if interactive:
168 complete = not self._input_splitter.push_accepts_more()
169 complete = not self._input_splitter.push_accepts_more()
169 return complete
170 return complete
170
171
171 def _execute(self, source, hidden):
172 def _execute(self, source, hidden):
172 """ Execute 'source'. If 'hidden', do not show any output.
173 """ Execute 'source'. If 'hidden', do not show any output.
173
174
174 See parent class :meth:`execute` docstring for full details.
175 See parent class :meth:`execute` docstring for full details.
175 """
176 """
176 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
177 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
177 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
178 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
178 self._hidden = hidden
179 self._hidden = hidden
179
180
180 def _prompt_started_hook(self):
181 def _prompt_started_hook(self):
181 """ Called immediately after a new prompt is displayed.
182 """ Called immediately after a new prompt is displayed.
182 """
183 """
183 if not self._reading:
184 if not self._reading:
184 self._highlighter.highlighting_on = True
185 self._highlighter.highlighting_on = True
185
186
186 def _prompt_finished_hook(self):
187 def _prompt_finished_hook(self):
187 """ 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
188 will be processed and a new prompt displayed.
189 will be processed and a new prompt displayed.
189 """
190 """
190 if not self._reading:
191 if not self._reading:
191 self._highlighter.highlighting_on = False
192 self._highlighter.highlighting_on = False
192
193
193 def _tab_pressed(self):
194 def _tab_pressed(self):
194 """ Called when the tab key is pressed. Returns whether to continue
195 """ Called when the tab key is pressed. Returns whether to continue
195 processing the event.
196 processing the event.
196 """
197 """
197 # Perform tab completion if:
198 # Perform tab completion if:
198 # 1) The cursor is in the input buffer.
199 # 1) The cursor is in the input buffer.
199 # 2) There is a non-whitespace character before the cursor.
200 # 2) There is a non-whitespace character before the cursor.
200 text = self._get_input_buffer_cursor_line()
201 text = self._get_input_buffer_cursor_line()
201 if text is None:
202 if text is None:
202 return False
203 return False
203 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
204 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
204 if complete:
205 if complete:
205 self._complete()
206 self._complete()
206 return not complete
207 return not complete
207
208
208 #---------------------------------------------------------------------------
209 #---------------------------------------------------------------------------
209 # 'ConsoleWidget' protected interface
210 # 'ConsoleWidget' protected interface
210 #---------------------------------------------------------------------------
211 #---------------------------------------------------------------------------
211
212
212 def _context_menu_make(self, pos):
213 def _context_menu_make(self, pos):
213 """ Reimplemented to add an action for raw copy.
214 """ Reimplemented to add an action for raw copy.
214 """
215 """
215 menu = super(FrontendWidget, self)._context_menu_make(pos)
216 menu = super(FrontendWidget, self)._context_menu_make(pos)
216 for before_action in menu.actions():
217 for before_action in menu.actions():
217 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
218 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
218 QtGui.QKeySequence.ExactMatch:
219 QtGui.QKeySequence.ExactMatch:
219 menu.insertAction(before_action, self._copy_raw_action)
220 menu.insertAction(before_action, self._copy_raw_action)
220 break
221 break
221 return menu
222 return menu
222
223
223 def _event_filter_console_keypress(self, event):
224 def _event_filter_console_keypress(self, event):
224 """ Reimplemented for execution interruption and smart backspace.
225 """ Reimplemented for execution interruption and smart backspace.
225 """
226 """
226 key = event.key()
227 key = event.key()
227 if self._control_key_down(event.modifiers(), include_command=False):
228 if self._control_key_down(event.modifiers(), include_command=False):
228
229
229 if key == QtCore.Qt.Key_C and self._executing:
230 if key == QtCore.Qt.Key_C and self._executing:
230 self.interrupt_kernel()
231 self.interrupt_kernel()
231 return True
232 return True
232
233
233 elif key == QtCore.Qt.Key_Period:
234 elif key == QtCore.Qt.Key_Period:
234 message = 'Are you sure you want to restart the kernel?'
235 message = 'Are you sure you want to restart the kernel?'
235 self.restart_kernel(message, now=False)
236 self.restart_kernel(message, now=False)
236 return True
237 return True
237
238
238 elif not event.modifiers() & QtCore.Qt.AltModifier:
239 elif not event.modifiers() & QtCore.Qt.AltModifier:
239
240
240 # Smart backspace: remove four characters in one backspace if:
241 # Smart backspace: remove four characters in one backspace if:
241 # 1) everything left of the cursor is whitespace
242 # 1) everything left of the cursor is whitespace
242 # 2) the four characters immediately left of the cursor are spaces
243 # 2) the four characters immediately left of the cursor are spaces
243 if key == QtCore.Qt.Key_Backspace:
244 if key == QtCore.Qt.Key_Backspace:
244 col = self._get_input_buffer_cursor_column()
245 col = self._get_input_buffer_cursor_column()
245 cursor = self._control.textCursor()
246 cursor = self._control.textCursor()
246 if col > 3 and not cursor.hasSelection():
247 if col > 3 and not cursor.hasSelection():
247 text = self._get_input_buffer_cursor_line()[:col]
248 text = self._get_input_buffer_cursor_line()[:col]
248 if text.endswith(' ') and not text.strip():
249 if text.endswith(' ') and not text.strip():
249 cursor.movePosition(QtGui.QTextCursor.Left,
250 cursor.movePosition(QtGui.QTextCursor.Left,
250 QtGui.QTextCursor.KeepAnchor, 4)
251 QtGui.QTextCursor.KeepAnchor, 4)
251 cursor.removeSelectedText()
252 cursor.removeSelectedText()
252 return True
253 return True
253
254
254 return super(FrontendWidget, self)._event_filter_console_keypress(event)
255 return super(FrontendWidget, self)._event_filter_console_keypress(event)
255
256
256 def _insert_continuation_prompt(self, cursor):
257 def _insert_continuation_prompt(self, cursor):
257 """ Reimplemented for auto-indentation.
258 """ Reimplemented for auto-indentation.
258 """
259 """
259 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
260 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
260 cursor.insertText(' ' * self._input_splitter.indent_spaces)
261 cursor.insertText(' ' * self._input_splitter.indent_spaces)
261
262
262 #---------------------------------------------------------------------------
263 #---------------------------------------------------------------------------
263 # 'BaseFrontendMixin' abstract interface
264 # 'BaseFrontendMixin' abstract interface
264 #---------------------------------------------------------------------------
265 #---------------------------------------------------------------------------
265
266
266 def _handle_complete_reply(self, rep):
267 def _handle_complete_reply(self, rep):
267 """ Handle replies for tab completion.
268 """ Handle replies for tab completion.
268 """
269 """
269 cursor = self._get_cursor()
270 cursor = self._get_cursor()
270 info = self._request_info.get('complete')
271 info = self._request_info.get('complete')
271 if info and info.id == rep['parent_header']['msg_id'] and \
272 if info and info.id == rep['parent_header']['msg_id'] and \
272 info.pos == cursor.position():
273 info.pos == cursor.position():
273 text = '.'.join(self._get_context())
274 text = '.'.join(self._get_context())
274 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
275 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
275 self._complete_with_items(cursor, rep['content']['matches'])
276 self._complete_with_items(cursor, rep['content']['matches'])
276
277
277 def _handle_execute_reply(self, msg):
278 def _handle_execute_reply(self, msg):
278 """ Handles replies for code execution.
279 """ Handles replies for code execution.
279 """
280 """
280 info = self._request_info.get('execute')
281 info = self._request_info.get('execute')
281 if info and info.id == msg['parent_header']['msg_id'] and \
282 if info and info.id == msg['parent_header']['msg_id'] and \
282 info.kind == 'user' and not self._hidden:
283 info.kind == 'user' and not self._hidden:
283 # 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
284 # before writing a new prompt.
285 # before writing a new prompt.
285 self.kernel_manager.sub_channel.flush()
286 self.kernel_manager.sub_channel.flush()
286
287
287 # Reset the ANSI style information to prevent bad text in stdout
288 # Reset the ANSI style information to prevent bad text in stdout
288 # 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
289 # allowed to do this.
290 # allowed to do this.
290 if self.ansi_codes:
291 if self.ansi_codes:
291 self._ansi_processor.reset_sgr()
292 self._ansi_processor.reset_sgr()
292
293
293 content = msg['content']
294 content = msg['content']
294 status = content['status']
295 status = content['status']
295 if status == 'ok':
296 if status == 'ok':
296 self._process_execute_ok(msg)
297 self._process_execute_ok(msg)
297 elif status == 'error':
298 elif status == 'error':
298 self._process_execute_error(msg)
299 self._process_execute_error(msg)
299 elif status == 'abort':
300 elif status == 'abort':
300 self._process_execute_abort(msg)
301 self._process_execute_abort(msg)
301
302
302 self._show_interpreter_prompt_for_reply(msg)
303 self._show_interpreter_prompt_for_reply(msg)
303 self.executed.emit(msg)
304 self.executed.emit(msg)
304
305
305 def _handle_input_request(self, msg):
306 def _handle_input_request(self, msg):
306 """ Handle requests for raw_input.
307 """ Handle requests for raw_input.
307 """
308 """
308 if self._hidden:
309 if self._hidden:
309 raise RuntimeError('Request for raw input during hidden execution.')
310 raise RuntimeError('Request for raw input during hidden execution.')
310
311
311 # 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
312 # before entering readline mode.
313 # before entering readline mode.
313 self.kernel_manager.sub_channel.flush()
314 self.kernel_manager.sub_channel.flush()
314
315
315 def callback(line):
316 def callback(line):
316 self.kernel_manager.rep_channel.input(line)
317 self.kernel_manager.rep_channel.input(line)
317 self._readline(msg['content']['prompt'], callback=callback)
318 self._readline(msg['content']['prompt'], callback=callback)
318
319
319 def _handle_kernel_died(self, since_last_heartbeat):
320 def _handle_kernel_died(self, since_last_heartbeat):
320 """ 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.
321 """
322 """
322 if self.custom_restart:
323 if self.custom_restart:
323 self.custom_restart_kernel_died.emit(since_last_heartbeat)
324 self.custom_restart_kernel_died.emit(since_last_heartbeat)
324 else:
325 else:
325 message = 'The kernel heartbeat has been inactive for %.2f ' \
326 message = 'The kernel heartbeat has been inactive for %.2f ' \
326 'seconds. Do you want to restart the kernel? You may ' \
327 'seconds. Do you want to restart the kernel? You may ' \
327 'first want to check the network connection.' % \
328 'first want to check the network connection.' % \
328 since_last_heartbeat
329 since_last_heartbeat
329 self.restart_kernel(message, now=True)
330 self.restart_kernel(message, now=True)
330
331
331 def _handle_object_info_reply(self, rep):
332 def _handle_object_info_reply(self, rep):
332 """ Handle replies for call tips.
333 """ Handle replies for call tips.
333 """
334 """
334 cursor = self._get_cursor()
335 cursor = self._get_cursor()
335 info = self._request_info.get('call_tip')
336 info = self._request_info.get('call_tip')
336 if info and info.id == rep['parent_header']['msg_id'] and \
337 if info and info.id == rep['parent_header']['msg_id'] and \
337 info.pos == cursor.position():
338 info.pos == cursor.position():
338 # 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
339 # 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
340 # syntax-highlight it ourselves for nicer formatting in the
341 # syntax-highlight it ourselves for nicer formatting in the
341 # calltip.
342 # calltip.
342 call_info, doc = call_tip(rep['content'], format_call=True)
343 call_info, doc = call_tip(rep['content'], format_call=True)
343 if call_info or doc:
344 if call_info or doc:
344 self._call_tip_widget.show_call_info(call_info, doc)
345 self._call_tip_widget.show_call_info(call_info, doc)
345
346
346 def _handle_pyout(self, msg):
347 def _handle_pyout(self, msg):
347 """ Handle display hook output.
348 """ Handle display hook output.
348 """
349 """
349 if not self._hidden and self._is_from_this_session(msg):
350 if not self._hidden and self._is_from_this_session(msg):
350 self._append_plain_text(msg['content']['data'] + '\n')
351 self._append_plain_text(msg['content']['data'] + '\n')
351
352
352 def _handle_stream(self, msg):
353 def _handle_stream(self, msg):
353 """ Handle stdout, stderr, and stdin.
354 """ Handle stdout, stderr, and stdin.
354 """
355 """
355 if not self._hidden and self._is_from_this_session(msg):
356 if not self._hidden and self._is_from_this_session(msg):
356 # Most consoles treat tabs as being 8 space characters. Convert tabs
357 # Most consoles treat tabs as being 8 space characters. Convert tabs
357 # to spaces so that output looks as expected regardless of this
358 # to spaces so that output looks as expected regardless of this
358 # widget's tab width.
359 # widget's tab width.
359 text = msg['content']['data'].expandtabs(8)
360 text = msg['content']['data'].expandtabs(8)
360
361
361 self._append_plain_text(text)
362 self._append_plain_text(text)
362 self._control.moveCursor(QtGui.QTextCursor.End)
363 self._control.moveCursor(QtGui.QTextCursor.End)
363
364
365 def _handle_shutdown_reply(self, msg):
366 """ Handle shutdown signal, only if from other console.
367 """
368 if not self._hidden and not self._is_from_this_session(msg):
369 if not msg['content']['restart']:
370 sys.exit(0)
371 else:
372 # we just got notified of a restart!
373 time.sleep(0.25) # wait 1/4 sec to reset
374 # lest the request for a new prompt
375 # goes to the old kernel
376 self.reset()
377
364 def _started_channels(self):
378 def _started_channels(self):
365 """ Called when the KernelManager channels have started listening or
379 """ Called when the KernelManager channels have started listening or
366 when the frontend is assigned an already listening KernelManager.
380 when the frontend is assigned an already listening KernelManager.
367 """
381 """
368 self.reset()
382 self.reset()
369
383
370 #---------------------------------------------------------------------------
384 #---------------------------------------------------------------------------
371 # 'FrontendWidget' public interface
385 # 'FrontendWidget' public interface
372 #---------------------------------------------------------------------------
386 #---------------------------------------------------------------------------
373
387
374 def copy_raw(self):
388 def copy_raw(self):
375 """ Copy the currently selected text to the clipboard without attempting
389 """ Copy the currently selected text to the clipboard without attempting
376 to remove prompts or otherwise alter the text.
390 to remove prompts or otherwise alter the text.
377 """
391 """
378 self._control.copy()
392 self._control.copy()
379
393
380 def execute_file(self, path, hidden=False):
394 def execute_file(self, path, hidden=False):
381 """ Attempts to execute file with 'path'. If 'hidden', no output is
395 """ Attempts to execute file with 'path'. If 'hidden', no output is
382 shown.
396 shown.
383 """
397 """
384 self.execute('execfile("%s")' % path, hidden=hidden)
398 self.execute('execfile("%s")' % path, hidden=hidden)
385
399
386 def interrupt_kernel(self):
400 def interrupt_kernel(self):
387 """ Attempts to interrupt the running kernel.
401 """ Attempts to interrupt the running kernel.
388 """
402 """
389 if self.custom_interrupt:
403 if self.custom_interrupt:
390 self.custom_interrupt_requested.emit()
404 self.custom_interrupt_requested.emit()
391 elif self.kernel_manager.has_kernel:
405 elif self.kernel_manager.has_kernel:
392 self.kernel_manager.interrupt_kernel()
406 self.kernel_manager.interrupt_kernel()
393 else:
407 else:
394 self._append_plain_text('Kernel process is either remote or '
408 self._append_plain_text('Kernel process is either remote or '
395 'unspecified. Cannot interrupt.\n')
409 'unspecified. Cannot interrupt.\n')
396
410
397 def reset(self):
411 def reset(self):
398 """ Resets the widget to its initial state. Similar to ``clear``, but
412 """ Resets the widget to its initial state. Similar to ``clear``, but
399 also re-writes the banner and aborts execution if necessary.
413 also re-writes the banner and aborts execution if necessary.
400 """
414 """
401 if self._executing:
415 if self._executing:
402 self._executing = False
416 self._executing = False
403 self._request_info['execute'] = None
417 self._request_info['execute'] = None
404 self._reading = False
418 self._reading = False
405 self._highlighter.highlighting_on = False
419 self._highlighter.highlighting_on = False
406
420
407 self._control.clear()
421 self._control.clear()
408 self._append_plain_text(self._get_banner())
422 self._append_plain_text(self._get_banner())
409 self._show_interpreter_prompt()
423 self._show_interpreter_prompt()
410
424
411 def restart_kernel(self, message, now=False):
425 def restart_kernel(self, message, now=False):
412 """ Attempts to restart the running kernel.
426 """ Attempts to restart the running kernel.
413 """
427 """
414 # 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
415 # 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
416 # 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
417 # 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
418 # enough Qt to go implementing the checkbox now.
432 # enough Qt to go implementing the checkbox now.
419
433
420 if self.custom_restart:
434 if self.custom_restart:
421 self.custom_restart_requested.emit()
435 self.custom_restart_requested.emit()
422
436
423 elif self.kernel_manager.has_kernel:
437 elif self.kernel_manager.has_kernel:
424 # Pause the heart beat channel to prevent further warnings.
438 # Pause the heart beat channel to prevent further warnings.
425 self.kernel_manager.hb_channel.pause()
439 self.kernel_manager.hb_channel.pause()
426
440
427 # 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
428 # they decline. (If they accept, the heartbeat will be un-paused
442 # they decline. (If they accept, the heartbeat will be un-paused
429 # automatically when the kernel is restarted.)
443 # automatically when the kernel is restarted.)
430 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
444 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
431 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
445 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
432 message, buttons)
446 message, buttons)
433 if result == QtGui.QMessageBox.Yes:
447 if result == QtGui.QMessageBox.Yes:
434 try:
448 try:
435 self.kernel_manager.restart_kernel(now=now)
449 self.kernel_manager.restart_kernel(now=now)
436 except RuntimeError:
450 except RuntimeError:
437 self._append_plain_text('Kernel started externally. '
451 self._append_plain_text('Kernel started externally. '
438 'Cannot restart.\n')
452 'Cannot restart.\n')
439 else:
453 else:
440 self.reset()
454 self.reset()
441 else:
455 else:
442 self.kernel_manager.hb_channel.unpause()
456 self.kernel_manager.hb_channel.unpause()
443
457
444 else:
458 else:
445 self._append_plain_text('Kernel process is either remote or '
459 self._append_plain_text('Kernel process is either remote or '
446 'unspecified. Cannot restart.\n')
460 'unspecified. Cannot restart.\n')
447
461
448 #---------------------------------------------------------------------------
462 #---------------------------------------------------------------------------
449 # 'FrontendWidget' protected interface
463 # 'FrontendWidget' protected interface
450 #---------------------------------------------------------------------------
464 #---------------------------------------------------------------------------
451
465
452 def _call_tip(self):
466 def _call_tip(self):
453 """ Shows a call tip, if appropriate, at the current cursor location.
467 """ Shows a call tip, if appropriate, at the current cursor location.
454 """
468 """
455 # Decide if it makes sense to show a call tip
469 # Decide if it makes sense to show a call tip
456 cursor = self._get_cursor()
470 cursor = self._get_cursor()
457 cursor.movePosition(QtGui.QTextCursor.Left)
471 cursor.movePosition(QtGui.QTextCursor.Left)
458 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
472 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
459 return False
473 return False
460 context = self._get_context(cursor)
474 context = self._get_context(cursor)
461 if not context:
475 if not context:
462 return False
476 return False
463
477
464 # Send the metadata request to the kernel
478 # Send the metadata request to the kernel
465 name = '.'.join(context)
479 name = '.'.join(context)
466 msg_id = self.kernel_manager.xreq_channel.object_info(name)
480 msg_id = self.kernel_manager.xreq_channel.object_info(name)
467 pos = self._get_cursor().position()
481 pos = self._get_cursor().position()
468 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
482 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
469 return True
483 return True
470
484
471 def _complete(self):
485 def _complete(self):
472 """ Performs completion at the current cursor location.
486 """ Performs completion at the current cursor location.
473 """
487 """
474 context = self._get_context()
488 context = self._get_context()
475 if context:
489 if context:
476 # Send the completion request to the kernel
490 # Send the completion request to the kernel
477 msg_id = self.kernel_manager.xreq_channel.complete(
491 msg_id = self.kernel_manager.xreq_channel.complete(
478 '.'.join(context), # text
492 '.'.join(context), # text
479 self._get_input_buffer_cursor_line(), # line
493 self._get_input_buffer_cursor_line(), # line
480 self._get_input_buffer_cursor_column(), # cursor_pos
494 self._get_input_buffer_cursor_column(), # cursor_pos
481 self.input_buffer) # block
495 self.input_buffer) # block
482 pos = self._get_cursor().position()
496 pos = self._get_cursor().position()
483 info = self._CompletionRequest(msg_id, pos)
497 info = self._CompletionRequest(msg_id, pos)
484 self._request_info['complete'] = info
498 self._request_info['complete'] = info
485
499
486 def _get_banner(self):
500 def _get_banner(self):
487 """ Gets a banner to display at the beginning of a session.
501 """ Gets a banner to display at the beginning of a session.
488 """
502 """
489 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
503 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
490 '"license" for more information.'
504 '"license" for more information.'
491 return banner % (sys.version, sys.platform)
505 return banner % (sys.version, sys.platform)
492
506
493 def _get_context(self, cursor=None):
507 def _get_context(self, cursor=None):
494 """ Gets the context for the specified cursor (or the current cursor
508 """ Gets the context for the specified cursor (or the current cursor
495 if none is specified).
509 if none is specified).
496 """
510 """
497 if cursor is None:
511 if cursor is None:
498 cursor = self._get_cursor()
512 cursor = self._get_cursor()
499 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
513 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
500 QtGui.QTextCursor.KeepAnchor)
514 QtGui.QTextCursor.KeepAnchor)
501 text = unicode(cursor.selection().toPlainText())
515 text = unicode(cursor.selection().toPlainText())
502 return self._completion_lexer.get_context(text)
516 return self._completion_lexer.get_context(text)
503
517
504 def _process_execute_abort(self, msg):
518 def _process_execute_abort(self, msg):
505 """ Process a reply for an aborted execution request.
519 """ Process a reply for an aborted execution request.
506 """
520 """
507 self._append_plain_text("ERROR: execution aborted\n")
521 self._append_plain_text("ERROR: execution aborted\n")
508
522
509 def _process_execute_error(self, msg):
523 def _process_execute_error(self, msg):
510 """ 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.
511 """
525 """
512 content = msg['content']
526 content = msg['content']
513 traceback = ''.join(content['traceback'])
527 traceback = ''.join(content['traceback'])
514 self._append_plain_text(traceback)
528 self._append_plain_text(traceback)
515
529
516 def _process_execute_ok(self, msg):
530 def _process_execute_ok(self, msg):
517 """ Process a reply for a successful execution equest.
531 """ Process a reply for a successful execution equest.
518 """
532 """
519 payload = msg['content']['payload']
533 payload = msg['content']['payload']
520 for item in payload:
534 for item in payload:
521 if not self._process_execute_payload(item):
535 if not self._process_execute_payload(item):
522 warning = 'Warning: received unknown payload of type %s'
536 warning = 'Warning: received unknown payload of type %s'
523 print(warning % repr(item['source']))
537 print(warning % repr(item['source']))
524
538
525 def _process_execute_payload(self, item):
539 def _process_execute_payload(self, item):
526 """ 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
527 execution reply. Returns whether the payload was handled.
541 execution reply. Returns whether the payload was handled.
528 """
542 """
529 # The basic FrontendWidget doesn't handle payloads, as they are a
543 # The basic FrontendWidget doesn't handle payloads, as they are a
530 # mechanism for going beyond the standard Python interpreter model.
544 # mechanism for going beyond the standard Python interpreter model.
531 return False
545 return False
532
546
533 def _show_interpreter_prompt(self):
547 def _show_interpreter_prompt(self):
534 """ Shows a prompt for the interpreter.
548 """ Shows a prompt for the interpreter.
535 """
549 """
536 self._show_prompt('>>> ')
550 self._show_prompt('>>> ')
537
551
538 def _show_interpreter_prompt_for_reply(self, msg):
552 def _show_interpreter_prompt_for_reply(self, msg):
539 """ Shows a prompt for the interpreter given an 'execute_reply' message.
553 """ Shows a prompt for the interpreter given an 'execute_reply' message.
540 """
554 """
541 self._show_interpreter_prompt()
555 self._show_interpreter_prompt()
542
556
543 #------ Signal handlers ----------------------------------------------------
557 #------ Signal handlers ----------------------------------------------------
544
558
545 def _document_contents_change(self, position, removed, added):
559 def _document_contents_change(self, position, removed, added):
546 """ Called whenever the document's content changes. Display a call tip
560 """ Called whenever the document's content changes. Display a call tip
547 if appropriate.
561 if appropriate.
548 """
562 """
549 # Calculate where the cursor should be *after* the change:
563 # Calculate where the cursor should be *after* the change:
550 position += added
564 position += added
551
565
552 document = self._control.document()
566 document = self._control.document()
553 if position == self._get_cursor().position():
567 if position == self._get_cursor().position():
554 self._call_tip()
568 self._call_tip()
@@ -1,144 +1,158 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):
34 def __init__(self, app, frontend, existing=False):
35 """ Create a MainWindow for the specified FrontendWidget.
35 """ Create a MainWindow for the specified FrontendWidget.
36
37 The app is passed as an argument to allow for different
38 closing behavior depending on whether we are the Kernel's parent.
39
40 If existing is True, then this Window does not own the Kernel.
36 """
41 """
37 super(MainWindow, self).__init__()
42 super(MainWindow, self).__init__()
43 self._app = app
38 self._frontend = frontend
44 self._frontend = frontend
45 self._existing = existing
39 self._frontend.exit_requested.connect(self.close)
46 self._frontend.exit_requested.connect(self.close)
40 self.setCentralWidget(frontend)
47 self.setCentralWidget(frontend)
41
48
42 #---------------------------------------------------------------------------
49 #---------------------------------------------------------------------------
43 # QWidget interface
50 # QWidget interface
44 #---------------------------------------------------------------------------
51 #---------------------------------------------------------------------------
45
52
46 def closeEvent(self, event):
53 def closeEvent(self, event):
47 """ Reimplemented to prompt the user and close the kernel cleanly.
54 """ Reimplemented to prompt the user and close the kernel cleanly.
48 """
55 """
49 kernel_manager = self._frontend.kernel_manager
56 kernel_manager = self._frontend.kernel_manager
50 if kernel_manager and kernel_manager.channels_running:
57 if kernel_manager and kernel_manager.channels_running:
51 title = self.window().windowTitle()
58 title = self.window().windowTitle()
52 reply = QtGui.QMessageBox.question(self, title,
59 reply = QtGui.QMessageBox.question(self, title,
53 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
60 "Close just this console, or shutdown the kernel and close "+
54 if reply == QtGui.QMessageBox.Yes:
61 "all windows attached to it?",
62 'Cancel', 'Close Console', 'Close All')
63 if reply == 2: # close All
55 kernel_manager.shutdown_kernel()
64 kernel_manager.shutdown_kernel()
56 #kernel_manager.stop_channels()
65 #kernel_manager.stop_channels()
57 event.accept()
66 event.accept()
67 elif reply == 1: # close Console
68 if not self._existing:
69 # I have the kernel: don't quit, just close the window
70 self._app.setQuitOnLastWindowClosed(False)
71 event.accept()
58 else:
72 else:
59 event.ignore()
73 event.ignore()
60
74
61 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
62 # Main entry point
76 # Main entry point
63 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
64
78
65 def main():
79 def main():
66 """ Entry point for application.
80 """ Entry point for application.
67 """
81 """
68 # Parse command line arguments.
82 # Parse command line arguments.
69 parser = ArgumentParser()
83 parser = ArgumentParser()
70 kgroup = parser.add_argument_group('kernel options')
84 kgroup = parser.add_argument_group('kernel options')
71 kgroup.add_argument('-e', '--existing', action='store_true',
85 kgroup.add_argument('-e', '--existing', action='store_true',
72 help='connect to an existing kernel')
86 help='connect to an existing kernel')
73 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
87 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
74 help='set the kernel\'s IP address [default localhost]')
88 help='set the kernel\'s IP address [default localhost]')
75 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
89 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
76 help='set the XREQ channel port [default random]')
90 help='set the XREQ channel port [default random]')
77 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
91 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
78 help='set the SUB channel port [default random]')
92 help='set the SUB channel port [default random]')
79 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
93 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
80 help='set the REP channel port [default random]')
94 help='set the REP channel port [default random]')
81 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
95 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
82 help='set the heartbeat port [default: random]')
96 help='set the heartbeat port [default: random]')
83
97
84 egroup = kgroup.add_mutually_exclusive_group()
98 egroup = kgroup.add_mutually_exclusive_group()
85 egroup.add_argument('--pure', action='store_true', help = \
99 egroup.add_argument('--pure', action='store_true', help = \
86 'use a pure Python kernel instead of an IPython kernel')
100 'use a pure Python kernel instead of an IPython kernel')
87 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
101 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
88 const='auto', help = \
102 const='auto', help = \
89 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
103 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
90 given, the GUI backend is matplotlib's, otherwise use one of: \
104 given, the GUI backend is matplotlib's, otherwise use one of: \
91 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
105 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
92
106
93 wgroup = parser.add_argument_group('widget options')
107 wgroup = parser.add_argument_group('widget options')
94 wgroup.add_argument('--paging', type=str, default='inside',
108 wgroup.add_argument('--paging', type=str, default='inside',
95 choices = ['inside', 'hsplit', 'vsplit', 'none'],
109 choices = ['inside', 'hsplit', 'vsplit', 'none'],
96 help='set the paging style [default inside]')
110 help='set the paging style [default inside]')
97 wgroup.add_argument('--rich', action='store_true',
111 wgroup.add_argument('--rich', action='store_true',
98 help='enable rich text support')
112 help='enable rich text support')
99 wgroup.add_argument('--gui-completion', action='store_true',
113 wgroup.add_argument('--gui-completion', action='store_true',
100 help='use a GUI widget for tab completion')
114 help='use a GUI widget for tab completion')
101
115
102 args = parser.parse_args()
116 args = parser.parse_args()
103
117
104 # Don't let Qt or ZMQ swallow KeyboardInterupts.
118 # Don't let Qt or ZMQ swallow KeyboardInterupts.
105 import signal
119 import signal
106 signal.signal(signal.SIGINT, signal.SIG_DFL)
120 signal.signal(signal.SIGINT, signal.SIG_DFL)
107
121
108 # Create a KernelManager and start a kernel.
122 # Create a KernelManager and start a kernel.
109 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
123 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
110 sub_address=(args.ip, args.sub),
124 sub_address=(args.ip, args.sub),
111 rep_address=(args.ip, args.rep),
125 rep_address=(args.ip, args.rep),
112 hb_address=(args.ip, args.hb))
126 hb_address=(args.ip, args.hb))
113 if args.ip == LOCALHOST and not args.existing:
127 if args.ip == LOCALHOST and not args.existing:
114 if args.pure:
128 if args.pure:
115 kernel_manager.start_kernel(ipython=False)
129 kernel_manager.start_kernel(ipython=False)
116 elif args.pylab:
130 elif args.pylab:
117 kernel_manager.start_kernel(pylab=args.pylab)
131 kernel_manager.start_kernel(pylab=args.pylab)
118 else:
132 else:
119 kernel_manager.start_kernel()
133 kernel_manager.start_kernel()
120 kernel_manager.start_channels()
134 kernel_manager.start_channels()
121
135
122 # Create the widget.
136 # Create the widget.
123 app = QtGui.QApplication([])
137 app = QtGui.QApplication([])
124 if args.pure:
138 if args.pure:
125 kind = 'rich' if args.rich else 'plain'
139 kind = 'rich' if args.rich else 'plain'
126 widget = FrontendWidget(kind=kind, paging=args.paging)
140 widget = FrontendWidget(kind=kind, paging=args.paging)
127 elif args.rich or args.pylab:
141 elif args.rich or args.pylab:
128 widget = RichIPythonWidget(paging=args.paging)
142 widget = RichIPythonWidget(paging=args.paging)
129 else:
143 else:
130 widget = IPythonWidget(paging=args.paging)
144 widget = IPythonWidget(paging=args.paging)
131 widget.gui_completion = args.gui_completion
145 widget.gui_completion = args.gui_completion
132 widget.kernel_manager = kernel_manager
146 widget.kernel_manager = kernel_manager
133
147
134 # Create the main window.
148 # Create the main window.
135 window = MainWindow(widget)
149 window = MainWindow(app, widget, args.existing)
136 window.setWindowTitle('Python' if args.pure else 'IPython')
150 window.setWindowTitle('Python' if args.pure else 'IPython')
137 window.show()
151 window.show()
138
152
139 # Start the application main loop.
153 # Start the application main loop.
140 app.exec_()
154 app.exec_()
141
155
142
156
143 if __name__ == '__main__':
157 if __name__ == '__main__':
144 main()
158 main()
@@ -1,237 +1,240 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelManager that provides signals and slots.
2 """
2 """
3
3
4 # System library imports.
4 # System library imports.
5 from PyQt4 import QtCore
5 from PyQt4 import QtCore
6
6
7 # IPython imports.
7 # IPython imports.
8 from IPython.utils.traitlets import Type
8 from IPython.utils.traitlets import Type
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 XReqSocketChannel, RepSocketChannel, HBSocketChannel
10 XReqSocketChannel, RepSocketChannel, HBSocketChannel
11 from util import MetaQObjectHasTraits, SuperQObject
11 from util import MetaQObjectHasTraits, SuperQObject
12
12
13
13
14 class SocketChannelQObject(SuperQObject):
14 class SocketChannelQObject(SuperQObject):
15
15
16 # Emitted when the channel is started.
16 # Emitted when the channel is started.
17 started = QtCore.pyqtSignal()
17 started = QtCore.pyqtSignal()
18
18
19 # Emitted when the channel is stopped.
19 # Emitted when the channel is stopped.
20 stopped = QtCore.pyqtSignal()
20 stopped = QtCore.pyqtSignal()
21
21
22 #---------------------------------------------------------------------------
22 #---------------------------------------------------------------------------
23 # 'ZmqSocketChannel' interface
23 # 'ZmqSocketChannel' interface
24 #---------------------------------------------------------------------------
24 #---------------------------------------------------------------------------
25
25
26 def start(self):
26 def start(self):
27 """ Reimplemented to emit signal.
27 """ Reimplemented to emit signal.
28 """
28 """
29 super(SocketChannelQObject, self).start()
29 super(SocketChannelQObject, self).start()
30 self.started.emit()
30 self.started.emit()
31
31
32 def stop(self):
32 def stop(self):
33 """ Reimplemented to emit signal.
33 """ Reimplemented to emit signal.
34 """
34 """
35 super(SocketChannelQObject, self).stop()
35 super(SocketChannelQObject, self).stop()
36 self.stopped.emit()
36 self.stopped.emit()
37
37
38
38
39 class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
39 class QtXReqSocketChannel(SocketChannelQObject, XReqSocketChannel):
40
40
41 # Emitted when any message is received.
41 # Emitted when any message is received.
42 message_received = QtCore.pyqtSignal(object)
42 message_received = QtCore.pyqtSignal(object)
43
43
44 # Emitted when a reply has been received for the corresponding request
44 # Emitted when a reply has been received for the corresponding request
45 # type.
45 # type.
46 execute_reply = QtCore.pyqtSignal(object)
46 execute_reply = QtCore.pyqtSignal(object)
47 complete_reply = QtCore.pyqtSignal(object)
47 complete_reply = QtCore.pyqtSignal(object)
48 object_info_reply = QtCore.pyqtSignal(object)
48 object_info_reply = QtCore.pyqtSignal(object)
49
49
50 # Emitted when the first reply comes back.
50 # Emitted when the first reply comes back.
51 first_reply = QtCore.pyqtSignal()
51 first_reply = QtCore.pyqtSignal()
52
52
53 # Used by the first_reply signal logic to determine if a reply is the
53 # Used by the first_reply signal logic to determine if a reply is the
54 # first.
54 # first.
55 _handlers_called = False
55 _handlers_called = False
56
56
57 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
58 # 'XReqSocketChannel' interface
58 # 'XReqSocketChannel' interface
59 #---------------------------------------------------------------------------
59 #---------------------------------------------------------------------------
60
60
61 def call_handlers(self, msg):
61 def call_handlers(self, msg):
62 """ Reimplemented to emit signals instead of making callbacks.
62 """ Reimplemented to emit signals instead of making callbacks.
63 """
63 """
64 # Emit the generic signal.
64 # Emit the generic signal.
65 self.message_received.emit(msg)
65 self.message_received.emit(msg)
66
66
67 # Emit signals for specialized message types.
67 # Emit signals for specialized message types.
68 msg_type = msg['msg_type']
68 msg_type = msg['msg_type']
69 signal = getattr(self, msg_type, None)
69 signal = getattr(self, msg_type, None)
70 if signal:
70 if signal:
71 signal.emit(msg)
71 signal.emit(msg)
72
72
73 if not self._handlers_called:
73 if not self._handlers_called:
74 self.first_reply.emit()
74 self.first_reply.emit()
75 self._handlers_called = True
75 self._handlers_called = True
76
76
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78 # 'QtXReqSocketChannel' interface
78 # 'QtXReqSocketChannel' interface
79 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
80
80
81 def reset_first_reply(self):
81 def reset_first_reply(self):
82 """ Reset the first_reply signal to fire again on the next reply.
82 """ Reset the first_reply signal to fire again on the next reply.
83 """
83 """
84 self._handlers_called = False
84 self._handlers_called = False
85
85
86
86
87 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
87 class QtSubSocketChannel(SocketChannelQObject, SubSocketChannel):
88
88
89 # Emitted when any message is received.
89 # Emitted when any message is received.
90 message_received = QtCore.pyqtSignal(object)
90 message_received = QtCore.pyqtSignal(object)
91
91
92 # Emitted when a message of type 'stream' is received.
92 # Emitted when a message of type 'stream' is received.
93 stream_received = QtCore.pyqtSignal(object)
93 stream_received = QtCore.pyqtSignal(object)
94
94
95 # Emitted when a message of type 'pyin' is received.
95 # Emitted when a message of type 'pyin' is received.
96 pyin_received = QtCore.pyqtSignal(object)
96 pyin_received = QtCore.pyqtSignal(object)
97
97
98 # Emitted when a message of type 'pyout' is received.
98 # Emitted when a message of type 'pyout' is received.
99 pyout_received = QtCore.pyqtSignal(object)
99 pyout_received = QtCore.pyqtSignal(object)
100
100
101 # Emitted when a message of type 'pyerr' is received.
101 # Emitted when a message of type 'pyerr' is received.
102 pyerr_received = QtCore.pyqtSignal(object)
102 pyerr_received = QtCore.pyqtSignal(object)
103
103
104 # Emitted when a crash report message is received from the kernel's
104 # Emitted when a crash report message is received from the kernel's
105 # last-resort sys.excepthook.
105 # last-resort sys.excepthook.
106 crash_received = QtCore.pyqtSignal(object)
106 crash_received = QtCore.pyqtSignal(object)
107
107
108 # Emitted when a shutdown is noticed.
109 shutdown_reply_received = QtCore.pyqtSignal(object)
110
108 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
109 # 'SubSocketChannel' interface
112 # 'SubSocketChannel' interface
110 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
111
114
112 def call_handlers(self, msg):
115 def call_handlers(self, msg):
113 """ Reimplemented to emit signals instead of making callbacks.
116 """ Reimplemented to emit signals instead of making callbacks.
114 """
117 """
115 # Emit the generic signal.
118 # Emit the generic signal.
116 self.message_received.emit(msg)
119 self.message_received.emit(msg)
117
120
118 # Emit signals for specialized message types.
121 # Emit signals for specialized message types.
119 msg_type = msg['msg_type']
122 msg_type = msg['msg_type']
120 signal = getattr(self, msg_type + '_received', None)
123 signal = getattr(self, msg_type + '_received', None)
121 if signal:
124 if signal:
122 signal.emit(msg)
125 signal.emit(msg)
123 elif msg_type in ('stdout', 'stderr'):
126 elif msg_type in ('stdout', 'stderr'):
124 self.stream_received.emit(msg)
127 self.stream_received.emit(msg)
125
128
126 def flush(self):
129 def flush(self):
127 """ Reimplemented to ensure that signals are dispatched immediately.
130 """ Reimplemented to ensure that signals are dispatched immediately.
128 """
131 """
129 super(QtSubSocketChannel, self).flush()
132 super(QtSubSocketChannel, self).flush()
130 QtCore.QCoreApplication.instance().processEvents()
133 QtCore.QCoreApplication.instance().processEvents()
131
134
132
135
133 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
136 class QtRepSocketChannel(SocketChannelQObject, RepSocketChannel):
134
137
135 # Emitted when any message is received.
138 # Emitted when any message is received.
136 message_received = QtCore.pyqtSignal(object)
139 message_received = QtCore.pyqtSignal(object)
137
140
138 # Emitted when an input request is received.
141 # Emitted when an input request is received.
139 input_requested = QtCore.pyqtSignal(object)
142 input_requested = QtCore.pyqtSignal(object)
140
143
141 #---------------------------------------------------------------------------
144 #---------------------------------------------------------------------------
142 # 'RepSocketChannel' interface
145 # 'RepSocketChannel' interface
143 #---------------------------------------------------------------------------
146 #---------------------------------------------------------------------------
144
147
145 def call_handlers(self, msg):
148 def call_handlers(self, msg):
146 """ Reimplemented to emit signals instead of making callbacks.
149 """ Reimplemented to emit signals instead of making callbacks.
147 """
150 """
148 # Emit the generic signal.
151 # Emit the generic signal.
149 self.message_received.emit(msg)
152 self.message_received.emit(msg)
150
153
151 # Emit signals for specialized message types.
154 # Emit signals for specialized message types.
152 msg_type = msg['msg_type']
155 msg_type = msg['msg_type']
153 if msg_type == 'input_request':
156 if msg_type == 'input_request':
154 self.input_requested.emit(msg)
157 self.input_requested.emit(msg)
155
158
156
159
157 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
160 class QtHBSocketChannel(SocketChannelQObject, HBSocketChannel):
158
161
159 # Emitted when the kernel has died.
162 # Emitted when the kernel has died.
160 kernel_died = QtCore.pyqtSignal(object)
163 kernel_died = QtCore.pyqtSignal(object)
161
164
162 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
163 # 'HBSocketChannel' interface
166 # 'HBSocketChannel' interface
164 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
165
168
166 def call_handlers(self, since_last_heartbeat):
169 def call_handlers(self, since_last_heartbeat):
167 """ Reimplemented to emit signals instead of making callbacks.
170 """ Reimplemented to emit signals instead of making callbacks.
168 """
171 """
169 # Emit the generic signal.
172 # Emit the generic signal.
170 self.kernel_died.emit(since_last_heartbeat)
173 self.kernel_died.emit(since_last_heartbeat)
171
174
172
175
173 class QtKernelManager(KernelManager, SuperQObject):
176 class QtKernelManager(KernelManager, SuperQObject):
174 """ A KernelManager that provides signals and slots.
177 """ A KernelManager that provides signals and slots.
175 """
178 """
176
179
177 __metaclass__ = MetaQObjectHasTraits
180 __metaclass__ = MetaQObjectHasTraits
178
181
179 # Emitted when the kernel manager has started listening.
182 # Emitted when the kernel manager has started listening.
180 started_channels = QtCore.pyqtSignal()
183 started_channels = QtCore.pyqtSignal()
181
184
182 # Emitted when the kernel manager has stopped listening.
185 # Emitted when the kernel manager has stopped listening.
183 stopped_channels = QtCore.pyqtSignal()
186 stopped_channels = QtCore.pyqtSignal()
184
187
185 # Use Qt-specific channel classes that emit signals.
188 # Use Qt-specific channel classes that emit signals.
186 sub_channel_class = Type(QtSubSocketChannel)
189 sub_channel_class = Type(QtSubSocketChannel)
187 xreq_channel_class = Type(QtXReqSocketChannel)
190 xreq_channel_class = Type(QtXReqSocketChannel)
188 rep_channel_class = Type(QtRepSocketChannel)
191 rep_channel_class = Type(QtRepSocketChannel)
189 hb_channel_class = Type(QtHBSocketChannel)
192 hb_channel_class = Type(QtHBSocketChannel)
190
193
191 #---------------------------------------------------------------------------
194 #---------------------------------------------------------------------------
192 # 'KernelManager' interface
195 # 'KernelManager' interface
193 #---------------------------------------------------------------------------
196 #---------------------------------------------------------------------------
194
197
195 #------ Kernel process management ------------------------------------------
198 #------ Kernel process management ------------------------------------------
196
199
197 def start_kernel(self, *args, **kw):
200 def start_kernel(self, *args, **kw):
198 """ Reimplemented for proper heartbeat management.
201 """ Reimplemented for proper heartbeat management.
199 """
202 """
200 if self._xreq_channel is not None:
203 if self._xreq_channel is not None:
201 self._xreq_channel.reset_first_reply()
204 self._xreq_channel.reset_first_reply()
202 super(QtKernelManager, self).start_kernel(*args, **kw)
205 super(QtKernelManager, self).start_kernel(*args, **kw)
203
206
204 #------ Channel management -------------------------------------------------
207 #------ Channel management -------------------------------------------------
205
208
206 def start_channels(self, *args, **kw):
209 def start_channels(self, *args, **kw):
207 """ Reimplemented to emit signal.
210 """ Reimplemented to emit signal.
208 """
211 """
209 super(QtKernelManager, self).start_channels(*args, **kw)
212 super(QtKernelManager, self).start_channels(*args, **kw)
210 self.started_channels.emit()
213 self.started_channels.emit()
211
214
212 def stop_channels(self):
215 def stop_channels(self):
213 """ Reimplemented to emit signal.
216 """ Reimplemented to emit signal.
214 """
217 """
215 super(QtKernelManager, self).stop_channels()
218 super(QtKernelManager, self).stop_channels()
216 self.stopped_channels.emit()
219 self.stopped_channels.emit()
217
220
218 @property
221 @property
219 def xreq_channel(self):
222 def xreq_channel(self):
220 """ Reimplemented for proper heartbeat management.
223 """ Reimplemented for proper heartbeat management.
221 """
224 """
222 if self._xreq_channel is None:
225 if self._xreq_channel is None:
223 self._xreq_channel = super(QtKernelManager, self).xreq_channel
226 self._xreq_channel = super(QtKernelManager, self).xreq_channel
224 self._xreq_channel.first_reply.connect(self._first_reply)
227 self._xreq_channel.first_reply.connect(self._first_reply)
225 return self._xreq_channel
228 return self._xreq_channel
226
229
227 #---------------------------------------------------------------------------
230 #---------------------------------------------------------------------------
228 # Protected interface
231 # Protected interface
229 #---------------------------------------------------------------------------
232 #---------------------------------------------------------------------------
230
233
231 def _first_reply(self):
234 def _first_reply(self):
232 """ Unpauses the heartbeat channel when the first reply is received on
235 """ Unpauses the heartbeat channel when the first reply is received on
233 the execute channel. Note that this will *not* start the heartbeat
236 the execute channel. Note that this will *not* start the heartbeat
234 channel if it is not already running!
237 channel if it is not already running!
235 """
238 """
236 if self._hb_channel is not None:
239 if self._hb_channel is not None:
237 self._hb_channel.unpause()
240 self._hb_channel.unpause()
@@ -1,626 +1,627 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 import sys
22 import time
22 import time
23 import traceback
23 import traceback
24
24
25 # System library imports.
25 # System library imports.
26 import zmq
26 import zmq
27
27
28 # Local imports.
28 # Local imports.
29 from IPython.config.configurable import Configurable
29 from IPython.config.configurable import Configurable
30 from IPython.utils import io
30 from IPython.utils import io
31 from IPython.utils.jsonutil import json_clean
31 from IPython.utils.jsonutil import json_clean
32 from IPython.lib import pylabtools
32 from IPython.lib import pylabtools
33 from IPython.utils.traitlets import Instance, Float
33 from IPython.utils.traitlets import Instance, Float
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 start_kernel)
35 start_kernel)
36 from iostream import OutStream
36 from iostream import OutStream
37 from session import Session, Message
37 from session import Session, Message
38 from zmqshell import ZMQInteractiveShell
38 from zmqshell import ZMQInteractiveShell
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Main kernel class
41 # Main kernel class
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 class Kernel(Configurable):
44 class Kernel(Configurable):
45
45
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47 # Kernel interface
47 # Kernel interface
48 #---------------------------------------------------------------------------
48 #---------------------------------------------------------------------------
49
49
50 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
50 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
51 session = Instance(Session)
51 session = Instance(Session)
52 reply_socket = Instance('zmq.Socket')
52 reply_socket = Instance('zmq.Socket')
53 pub_socket = Instance('zmq.Socket')
53 pub_socket = Instance('zmq.Socket')
54 req_socket = Instance('zmq.Socket')
54 req_socket = Instance('zmq.Socket')
55
55
56 # Private interface
56 # Private interface
57
57
58 # Time to sleep after flushing the stdout/err buffers in each execute
58 # Time to sleep after flushing the stdout/err buffers in each execute
59 # cycle. While this introduces a hard limit on the minimal latency of the
59 # cycle. While this introduces a hard limit on the minimal latency of the
60 # execute cycle, it helps prevent output synchronization problems for
60 # execute cycle, it helps prevent output synchronization problems for
61 # clients.
61 # clients.
62 # Units are in seconds. The minimum zmq latency on local host is probably
62 # Units are in seconds. The minimum zmq latency on local host is probably
63 # ~150 microseconds, set this to 500us for now. We may need to increase it
63 # ~150 microseconds, set this to 500us for now. We may need to increase it
64 # a little if it's not enough after more interactive testing.
64 # a little if it's not enough after more interactive testing.
65 _execute_sleep = Float(0.0005, config=True)
65 _execute_sleep = Float(0.0005, config=True)
66
66
67 # Frequency of the kernel's event loop.
67 # Frequency of the kernel's event loop.
68 # Units are in seconds, kernel subclasses for GUI toolkits may need to
68 # Units are in seconds, kernel subclasses for GUI toolkits may need to
69 # adapt to milliseconds.
69 # adapt to milliseconds.
70 _poll_interval = Float(0.05, config=True)
70 _poll_interval = Float(0.05, config=True)
71
71
72 # If the shutdown was requested over the network, we leave here the
72 # If the shutdown was requested over the network, we leave here the
73 # necessary reply message so it can be sent by our registered atexit
73 # necessary reply message so it can be sent by our registered atexit
74 # handler. This ensures that the reply is only sent to clients truly at
74 # handler. This ensures that the reply is only sent to clients truly at
75 # the end of our shutdown process (which happens after the underlying
75 # the end of our shutdown process (which happens after the underlying
76 # IPython shell's own shutdown).
76 # IPython shell's own shutdown).
77 _shutdown_message = None
77 _shutdown_message = None
78
78
79 # This is a dict of port number that the kernel is listening on. It is set
79 # This is a dict of port number that the kernel is listening on. It is set
80 # by record_ports and used by connect_request.
80 # by record_ports and used by connect_request.
81 _recorded_ports = None
81 _recorded_ports = None
82
82
83 def __init__(self, **kwargs):
83 def __init__(self, **kwargs):
84 super(Kernel, self).__init__(**kwargs)
84 super(Kernel, self).__init__(**kwargs)
85
85
86 # Before we even start up the shell, register *first* our exit handlers
86 # Before we even start up the shell, register *first* our exit handlers
87 # so they come before the shell's
87 # so they come before the shell's
88 atexit.register(self._at_shutdown)
88 atexit.register(self._at_shutdown)
89
89
90 # Initialize the InteractiveShell subclass
90 # Initialize the InteractiveShell subclass
91 self.shell = ZMQInteractiveShell.instance()
91 self.shell = ZMQInteractiveShell.instance()
92 self.shell.displayhook.session = self.session
92 self.shell.displayhook.session = self.session
93 self.shell.displayhook.pub_socket = self.pub_socket
93 self.shell.displayhook.pub_socket = self.pub_socket
94
94
95 # TMP - hack while developing
95 # TMP - hack while developing
96 self.shell._reply_content = None
96 self.shell._reply_content = None
97
97
98 # Build dict of handlers for message types
98 # Build dict of handlers for message types
99 msg_types = [ 'execute_request', 'complete_request',
99 msg_types = [ 'execute_request', 'complete_request',
100 'object_info_request', 'history_request',
100 'object_info_request', 'history_request',
101 'connect_request', 'shutdown_request']
101 'connect_request', 'shutdown_request']
102 self.handlers = {}
102 self.handlers = {}
103 for msg_type in msg_types:
103 for msg_type in msg_types:
104 self.handlers[msg_type] = getattr(self, msg_type)
104 self.handlers[msg_type] = getattr(self, msg_type)
105
105
106 def do_one_iteration(self):
106 def do_one_iteration(self):
107 """Do one iteration of the kernel's evaluation loop.
107 """Do one iteration of the kernel's evaluation loop.
108 """
108 """
109 try:
109 try:
110 ident = self.reply_socket.recv(zmq.NOBLOCK)
110 ident = self.reply_socket.recv(zmq.NOBLOCK)
111 except zmq.ZMQError, e:
111 except zmq.ZMQError, e:
112 if e.errno == zmq.EAGAIN:
112 if e.errno == zmq.EAGAIN:
113 return
113 return
114 else:
114 else:
115 raise
115 raise
116 # FIXME: Bug in pyzmq/zmq?
116 # FIXME: Bug in pyzmq/zmq?
117 # assert self.reply_socket.rcvmore(), "Missing message part."
117 # assert self.reply_socket.rcvmore(), "Missing message part."
118 msg = self.reply_socket.recv_json()
118 msg = self.reply_socket.recv_json()
119
119
120 # Print some info about this message and leave a '--->' marker, so it's
120 # Print some info about this message and leave a '--->' marker, so it's
121 # easier to trace visually the message chain when debugging. Each
121 # easier to trace visually the message chain when debugging. Each
122 # handler prints its message at the end.
122 # handler prints its message at the end.
123 # Eventually we'll move these from stdout to a logger.
123 # Eventually we'll move these from stdout to a logger.
124 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
124 io.raw_print('\n*** MESSAGE TYPE:', msg['msg_type'], '***')
125 io.raw_print(' Content: ', msg['content'],
125 io.raw_print(' Content: ', msg['content'],
126 '\n --->\n ', sep='', end='')
126 '\n --->\n ', sep='', end='')
127
127
128 # Find and call actual handler for message
128 # Find and call actual handler for message
129 handler = self.handlers.get(msg['msg_type'], None)
129 handler = self.handlers.get(msg['msg_type'], None)
130 if handler is None:
130 if handler is None:
131 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
131 io.raw_print_err("UNKNOWN MESSAGE TYPE:", msg)
132 else:
132 else:
133 handler(ident, msg)
133 handler(ident, msg)
134
134
135 # Check whether we should exit, in case the incoming message set the
135 # Check whether we should exit, in case the incoming message set the
136 # exit flag on
136 # exit flag on
137 if self.shell.exit_now:
137 if self.shell.exit_now:
138 io.raw_print('\nExiting IPython kernel...')
138 io.raw_print('\nExiting IPython kernel...')
139 # We do a normal, clean exit, which allows any actions registered
139 # We do a normal, clean exit, which allows any actions registered
140 # via atexit (such as history saving) to take place.
140 # via atexit (such as history saving) to take place.
141 sys.exit(0)
141 sys.exit(0)
142
142
143
143
144 def start(self):
144 def start(self):
145 """ Start the kernel main loop.
145 """ Start the kernel main loop.
146 """
146 """
147 while True:
147 while True:
148 time.sleep(self._poll_interval)
148 time.sleep(self._poll_interval)
149 self.do_one_iteration()
149 self.do_one_iteration()
150
150
151 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
151 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
152 """Record the ports that this kernel is using.
152 """Record the ports that this kernel is using.
153
153
154 The creator of the Kernel instance must call this methods if they
154 The creator of the Kernel instance must call this methods if they
155 want the :meth:`connect_request` method to return the port numbers.
155 want the :meth:`connect_request` method to return the port numbers.
156 """
156 """
157 self._recorded_ports = {
157 self._recorded_ports = {
158 'xrep_port' : xrep_port,
158 'xrep_port' : xrep_port,
159 'pub_port' : pub_port,
159 'pub_port' : pub_port,
160 'req_port' : req_port,
160 'req_port' : req_port,
161 'hb_port' : hb_port
161 'hb_port' : hb_port
162 }
162 }
163
163
164 #---------------------------------------------------------------------------
164 #---------------------------------------------------------------------------
165 # Kernel request handlers
165 # Kernel request handlers
166 #---------------------------------------------------------------------------
166 #---------------------------------------------------------------------------
167
167
168 def _publish_pyin(self, code, parent):
168 def _publish_pyin(self, code, parent):
169 """Publish the code request on the pyin stream."""
169 """Publish the code request on the pyin stream."""
170
170
171 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
171 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
172 self.pub_socket.send_json(pyin_msg)
172 self.pub_socket.send_json(pyin_msg)
173
173
174 def execute_request(self, ident, parent):
174 def execute_request(self, ident, parent):
175
175
176 status_msg = self.session.msg(
176 status_msg = self.session.msg(
177 u'status',
177 u'status',
178 {u'execution_state':u'busy'},
178 {u'execution_state':u'busy'},
179 parent=parent
179 parent=parent
180 )
180 )
181 self.pub_socket.send_json(status_msg)
181 self.pub_socket.send_json(status_msg)
182
182
183 try:
183 try:
184 content = parent[u'content']
184 content = parent[u'content']
185 code = content[u'code']
185 code = content[u'code']
186 silent = content[u'silent']
186 silent = content[u'silent']
187 except:
187 except:
188 io.raw_print_err("Got bad msg: ")
188 io.raw_print_err("Got bad msg: ")
189 io.raw_print_err(Message(parent))
189 io.raw_print_err(Message(parent))
190 return
190 return
191
191
192 shell = self.shell # we'll need this a lot here
192 shell = self.shell # we'll need this a lot here
193
193
194 # Replace raw_input. Note that is not sufficient to replace
194 # Replace raw_input. Note that is not sufficient to replace
195 # raw_input in the user namespace.
195 # raw_input in the user namespace.
196 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
196 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
197 __builtin__.raw_input = raw_input
197 __builtin__.raw_input = raw_input
198
198
199 # Set the parent message of the display hook and out streams.
199 # Set the parent message of the display hook and out streams.
200 shell.displayhook.set_parent(parent)
200 shell.displayhook.set_parent(parent)
201 sys.stdout.set_parent(parent)
201 sys.stdout.set_parent(parent)
202 sys.stderr.set_parent(parent)
202 sys.stderr.set_parent(parent)
203
203
204 # Re-broadcast our input for the benefit of listening clients, and
204 # Re-broadcast our input for the benefit of listening clients, and
205 # start computing output
205 # start computing output
206 if not silent:
206 if not silent:
207 self._publish_pyin(code, parent)
207 self._publish_pyin(code, parent)
208
208
209 reply_content = {}
209 reply_content = {}
210 try:
210 try:
211 if silent:
211 if silent:
212 # runcode uses 'exec' mode, so no displayhook will fire, and it
212 # runcode uses 'exec' mode, so no displayhook will fire, and it
213 # doesn't call logging or history manipulations. Print
213 # doesn't call logging or history manipulations. Print
214 # statements in that code will obviously still execute.
214 # statements in that code will obviously still execute.
215 shell.runcode(code)
215 shell.runcode(code)
216 else:
216 else:
217 # FIXME: runlines calls the exception handler itself.
217 # FIXME: runlines calls the exception handler itself.
218 shell._reply_content = None
218 shell._reply_content = None
219
219
220 # For now leave this here until we're sure we can stop using it
220 # For now leave this here until we're sure we can stop using it
221 #shell.runlines(code)
221 #shell.runlines(code)
222
222
223 # Experimental: cell mode! Test more before turning into
223 # Experimental: cell mode! Test more before turning into
224 # default and removing the hacks around runlines.
224 # default and removing the hacks around runlines.
225 shell.run_cell(code)
225 shell.run_cell(code)
226 except:
226 except:
227 status = u'error'
227 status = u'error'
228 # FIXME: this code right now isn't being used yet by default,
228 # FIXME: this code right now isn't being used yet by default,
229 # because the runlines() call above directly fires off exception
229 # because the runlines() call above directly fires off exception
230 # reporting. This code, therefore, is only active in the scenario
230 # reporting. This code, therefore, is only active in the scenario
231 # where runlines itself has an unhandled exception. We need to
231 # where runlines itself has an unhandled exception. We need to
232 # uniformize this, for all exception construction to come from a
232 # uniformize this, for all exception construction to come from a
233 # single location in the codbase.
233 # single location in the codbase.
234 etype, evalue, tb = sys.exc_info()
234 etype, evalue, tb = sys.exc_info()
235 tb_list = traceback.format_exception(etype, evalue, tb)
235 tb_list = traceback.format_exception(etype, evalue, tb)
236 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
236 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
237 else:
237 else:
238 status = u'ok'
238 status = u'ok'
239
239
240 reply_content[u'status'] = status
240 reply_content[u'status'] = status
241 # Compute the execution counter so clients can display prompts
241 # Compute the execution counter so clients can display prompts
242 reply_content['execution_count'] = shell.displayhook.prompt_count
242 reply_content['execution_count'] = shell.displayhook.prompt_count
243
243
244 # FIXME - fish exception info out of shell, possibly left there by
244 # FIXME - fish exception info out of shell, possibly left there by
245 # runlines. We'll need to clean up this logic later.
245 # runlines. We'll need to clean up this logic later.
246 if shell._reply_content is not None:
246 if shell._reply_content is not None:
247 reply_content.update(shell._reply_content)
247 reply_content.update(shell._reply_content)
248
248
249 # At this point, we can tell whether the main code execution succeeded
249 # At this point, we can tell whether the main code execution succeeded
250 # or not. If it did, we proceed to evaluate user_variables/expressions
250 # or not. If it did, we proceed to evaluate user_variables/expressions
251 if reply_content['status'] == 'ok':
251 if reply_content['status'] == 'ok':
252 reply_content[u'user_variables'] = \
252 reply_content[u'user_variables'] = \
253 shell.user_variables(content[u'user_variables'])
253 shell.user_variables(content[u'user_variables'])
254 reply_content[u'user_expressions'] = \
254 reply_content[u'user_expressions'] = \
255 shell.user_expressions(content[u'user_expressions'])
255 shell.user_expressions(content[u'user_expressions'])
256 else:
256 else:
257 # If there was an error, don't even try to compute variables or
257 # If there was an error, don't even try to compute variables or
258 # expressions
258 # expressions
259 reply_content[u'user_variables'] = {}
259 reply_content[u'user_variables'] = {}
260 reply_content[u'user_expressions'] = {}
260 reply_content[u'user_expressions'] = {}
261
261
262 # Payloads should be retrieved regardless of outcome, so we can both
262 # Payloads should be retrieved regardless of outcome, so we can both
263 # recover partial output (that could have been generated early in a
263 # recover partial output (that could have been generated early in a
264 # block, before an error) and clear the payload system always.
264 # block, before an error) and clear the payload system always.
265 reply_content[u'payload'] = shell.payload_manager.read_payload()
265 reply_content[u'payload'] = shell.payload_manager.read_payload()
266 # Be agressive about clearing the payload because we don't want
266 # Be agressive about clearing the payload because we don't want
267 # it to sit in memory until the next execute_request comes in.
267 # it to sit in memory until the next execute_request comes in.
268 shell.payload_manager.clear_payload()
268 shell.payload_manager.clear_payload()
269
269
270 # Send the reply.
270 # Send the reply.
271 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
271 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
272 io.raw_print(reply_msg)
272 io.raw_print(reply_msg)
273
273
274 # Flush output before sending the reply.
274 # Flush output before sending the reply.
275 sys.stdout.flush()
275 sys.stdout.flush()
276 sys.stderr.flush()
276 sys.stderr.flush()
277 # FIXME: on rare occasions, the flush doesn't seem to make it to the
277 # FIXME: on rare occasions, the flush doesn't seem to make it to the
278 # clients... This seems to mitigate the problem, but we definitely need
278 # clients... This seems to mitigate the problem, but we definitely need
279 # to better understand what's going on.
279 # to better understand what's going on.
280 if self._execute_sleep:
280 if self._execute_sleep:
281 time.sleep(self._execute_sleep)
281 time.sleep(self._execute_sleep)
282
282
283 self.reply_socket.send(ident, zmq.SNDMORE)
283 self.reply_socket.send(ident, zmq.SNDMORE)
284 self.reply_socket.send_json(reply_msg)
284 self.reply_socket.send_json(reply_msg)
285 if reply_msg['content']['status'] == u'error':
285 if reply_msg['content']['status'] == u'error':
286 self._abort_queue()
286 self._abort_queue()
287
287
288 status_msg = self.session.msg(
288 status_msg = self.session.msg(
289 u'status',
289 u'status',
290 {u'execution_state':u'idle'},
290 {u'execution_state':u'idle'},
291 parent=parent
291 parent=parent
292 )
292 )
293 self.pub_socket.send_json(status_msg)
293 self.pub_socket.send_json(status_msg)
294
294
295 def complete_request(self, ident, parent):
295 def complete_request(self, ident, parent):
296 txt, matches = self._complete(parent)
296 txt, matches = self._complete(parent)
297 matches = {'matches' : matches,
297 matches = {'matches' : matches,
298 'matched_text' : txt,
298 'matched_text' : txt,
299 'status' : 'ok'}
299 'status' : 'ok'}
300 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
300 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
301 matches, parent, ident)
301 matches, parent, ident)
302 io.raw_print(completion_msg)
302 io.raw_print(completion_msg)
303
303
304 def object_info_request(self, ident, parent):
304 def object_info_request(self, ident, parent):
305 object_info = self.shell.object_inspect(parent['content']['oname'])
305 object_info = self.shell.object_inspect(parent['content']['oname'])
306 # Before we send this object over, we scrub it for JSON usage
306 # Before we send this object over, we scrub it for JSON usage
307 oinfo = json_clean(object_info)
307 oinfo = json_clean(object_info)
308 msg = self.session.send(self.reply_socket, 'object_info_reply',
308 msg = self.session.send(self.reply_socket, 'object_info_reply',
309 oinfo, parent, ident)
309 oinfo, parent, ident)
310 io.raw_print(msg)
310 io.raw_print(msg)
311
311
312 def history_request(self, ident, parent):
312 def history_request(self, ident, parent):
313 output = parent['content']['output']
313 output = parent['content']['output']
314 index = parent['content']['index']
314 index = parent['content']['index']
315 raw = parent['content']['raw']
315 raw = parent['content']['raw']
316 hist = self.shell.get_history(index=index, raw=raw, output=output)
316 hist = self.shell.get_history(index=index, raw=raw, output=output)
317 content = {'history' : hist}
317 content = {'history' : hist}
318 msg = self.session.send(self.reply_socket, 'history_reply',
318 msg = self.session.send(self.reply_socket, 'history_reply',
319 content, parent, ident)
319 content, parent, ident)
320 io.raw_print(msg)
320 io.raw_print(msg)
321
321
322 def connect_request(self, ident, parent):
322 def connect_request(self, ident, parent):
323 if self._recorded_ports is not None:
323 if self._recorded_ports is not None:
324 content = self._recorded_ports.copy()
324 content = self._recorded_ports.copy()
325 else:
325 else:
326 content = {}
326 content = {}
327 msg = self.session.send(self.reply_socket, 'connect_reply',
327 msg = self.session.send(self.reply_socket, 'connect_reply',
328 content, parent, ident)
328 content, parent, ident)
329 io.raw_print(msg)
329 io.raw_print(msg)
330
330
331 def shutdown_request(self, ident, parent):
331 def shutdown_request(self, ident, parent):
332 self.shell.exit_now = True
332 self.shell.exit_now = True
333 self._shutdown_message = self.session.msg(u'shutdown_reply', {}, parent)
333 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
334 sys.exit(0)
334 sys.exit(0)
335
335
336 #---------------------------------------------------------------------------
336 #---------------------------------------------------------------------------
337 # Protected interface
337 # Protected interface
338 #---------------------------------------------------------------------------
338 #---------------------------------------------------------------------------
339
339
340 def _abort_queue(self):
340 def _abort_queue(self):
341 while True:
341 while True:
342 try:
342 try:
343 ident = self.reply_socket.recv(zmq.NOBLOCK)
343 ident = self.reply_socket.recv(zmq.NOBLOCK)
344 except zmq.ZMQError, e:
344 except zmq.ZMQError, e:
345 if e.errno == zmq.EAGAIN:
345 if e.errno == zmq.EAGAIN:
346 break
346 break
347 else:
347 else:
348 assert self.reply_socket.rcvmore(), \
348 assert self.reply_socket.rcvmore(), \
349 "Unexpected missing message part."
349 "Unexpected missing message part."
350 msg = self.reply_socket.recv_json()
350 msg = self.reply_socket.recv_json()
351 io.raw_print("Aborting:\n", Message(msg))
351 io.raw_print("Aborting:\n", Message(msg))
352 msg_type = msg['msg_type']
352 msg_type = msg['msg_type']
353 reply_type = msg_type.split('_')[0] + '_reply'
353 reply_type = msg_type.split('_')[0] + '_reply'
354 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
354 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
355 io.raw_print(reply_msg)
355 io.raw_print(reply_msg)
356 self.reply_socket.send(ident,zmq.SNDMORE)
356 self.reply_socket.send(ident,zmq.SNDMORE)
357 self.reply_socket.send_json(reply_msg)
357 self.reply_socket.send_json(reply_msg)
358 # We need to wait a bit for requests to come in. This can probably
358 # We need to wait a bit for requests to come in. This can probably
359 # be set shorter for true asynchronous clients.
359 # be set shorter for true asynchronous clients.
360 time.sleep(0.1)
360 time.sleep(0.1)
361
361
362 def _raw_input(self, prompt, ident, parent):
362 def _raw_input(self, prompt, ident, parent):
363 # Flush output before making the request.
363 # Flush output before making the request.
364 sys.stderr.flush()
364 sys.stderr.flush()
365 sys.stdout.flush()
365 sys.stdout.flush()
366
366
367 # Send the input request.
367 # Send the input request.
368 content = dict(prompt=prompt)
368 content = dict(prompt=prompt)
369 msg = self.session.msg(u'input_request', content, parent)
369 msg = self.session.msg(u'input_request', content, parent)
370 self.req_socket.send_json(msg)
370 self.req_socket.send_json(msg)
371
371
372 # Await a response.
372 # Await a response.
373 reply = self.req_socket.recv_json()
373 reply = self.req_socket.recv_json()
374 try:
374 try:
375 value = reply['content']['value']
375 value = reply['content']['value']
376 except:
376 except:
377 io.raw_print_err("Got bad raw_input reply: ")
377 io.raw_print_err("Got bad raw_input reply: ")
378 io.raw_print_err(Message(parent))
378 io.raw_print_err(Message(parent))
379 value = ''
379 value = ''
380 return value
380 return value
381
381
382 def _complete(self, msg):
382 def _complete(self, msg):
383 c = msg['content']
383 c = msg['content']
384 try:
384 try:
385 cpos = int(c['cursor_pos'])
385 cpos = int(c['cursor_pos'])
386 except:
386 except:
387 # If we don't get something that we can convert to an integer, at
387 # If we don't get something that we can convert to an integer, at
388 # least attempt the completion guessing the cursor is at the end of
388 # least attempt the completion guessing the cursor is at the end of
389 # the text, if there's any, and otherwise of the line
389 # the text, if there's any, and otherwise of the line
390 cpos = len(c['text'])
390 cpos = len(c['text'])
391 if cpos==0:
391 if cpos==0:
392 cpos = len(c['line'])
392 cpos = len(c['line'])
393 return self.shell.complete(c['text'], c['line'], cpos)
393 return self.shell.complete(c['text'], c['line'], cpos)
394
394
395 def _object_info(self, context):
395 def _object_info(self, context):
396 symbol, leftover = self._symbol_from_context(context)
396 symbol, leftover = self._symbol_from_context(context)
397 if symbol is not None and not leftover:
397 if symbol is not None and not leftover:
398 doc = getattr(symbol, '__doc__', '')
398 doc = getattr(symbol, '__doc__', '')
399 else:
399 else:
400 doc = ''
400 doc = ''
401 object_info = dict(docstring = doc)
401 object_info = dict(docstring = doc)
402 return object_info
402 return object_info
403
403
404 def _symbol_from_context(self, context):
404 def _symbol_from_context(self, context):
405 if not context:
405 if not context:
406 return None, context
406 return None, context
407
407
408 base_symbol_string = context[0]
408 base_symbol_string = context[0]
409 symbol = self.shell.user_ns.get(base_symbol_string, None)
409 symbol = self.shell.user_ns.get(base_symbol_string, None)
410 if symbol is None:
410 if symbol is None:
411 symbol = __builtin__.__dict__.get(base_symbol_string, None)
411 symbol = __builtin__.__dict__.get(base_symbol_string, None)
412 if symbol is None:
412 if symbol is None:
413 return None, context
413 return None, context
414
414
415 context = context[1:]
415 context = context[1:]
416 for i, name in enumerate(context):
416 for i, name in enumerate(context):
417 new_symbol = getattr(symbol, name, None)
417 new_symbol = getattr(symbol, name, None)
418 if new_symbol is None:
418 if new_symbol is None:
419 return symbol, context[i:]
419 return symbol, context[i:]
420 else:
420 else:
421 symbol = new_symbol
421 symbol = new_symbol
422
422
423 return symbol, []
423 return symbol, []
424
424
425 def _at_shutdown(self):
425 def _at_shutdown(self):
426 """Actions taken at shutdown by the kernel, called by python's atexit.
426 """Actions taken at shutdown by the kernel, called by python's atexit.
427 """
427 """
428 # io.rprint("Kernel at_shutdown") # dbg
428 # io.rprint("Kernel at_shutdown") # dbg
429 if self._shutdown_message is not None:
429 if self._shutdown_message is not None:
430 self.reply_socket.send_json(self._shutdown_message)
430 self.reply_socket.send_json(self._shutdown_message)
431 self.pub_socket.send_json(self._shutdown_message)
431 io.raw_print(self._shutdown_message)
432 io.raw_print(self._shutdown_message)
432 # A very short sleep to give zmq time to flush its message buffers
433 # A very short sleep to give zmq time to flush its message buffers
433 # before Python truly shuts down.
434 # before Python truly shuts down.
434 time.sleep(0.01)
435 time.sleep(0.01)
435
436
436
437
437 class QtKernel(Kernel):
438 class QtKernel(Kernel):
438 """A Kernel subclass with Qt support."""
439 """A Kernel subclass with Qt support."""
439
440
440 def start(self):
441 def start(self):
441 """Start a kernel with QtPy4 event loop integration."""
442 """Start a kernel with QtPy4 event loop integration."""
442
443
443 from PyQt4 import QtCore
444 from PyQt4 import QtCore
444 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
445 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
445
446
446 self.app = get_app_qt4([" "])
447 self.app = get_app_qt4([" "])
447 self.app.setQuitOnLastWindowClosed(False)
448 self.app.setQuitOnLastWindowClosed(False)
448 self.timer = QtCore.QTimer()
449 self.timer = QtCore.QTimer()
449 self.timer.timeout.connect(self.do_one_iteration)
450 self.timer.timeout.connect(self.do_one_iteration)
450 # Units for the timer are in milliseconds
451 # Units for the timer are in milliseconds
451 self.timer.start(1000*self._poll_interval)
452 self.timer.start(1000*self._poll_interval)
452 start_event_loop_qt4(self.app)
453 start_event_loop_qt4(self.app)
453
454
454
455
455 class WxKernel(Kernel):
456 class WxKernel(Kernel):
456 """A Kernel subclass with Wx support."""
457 """A Kernel subclass with Wx support."""
457
458
458 def start(self):
459 def start(self):
459 """Start a kernel with wx event loop support."""
460 """Start a kernel with wx event loop support."""
460
461
461 import wx
462 import wx
462 from IPython.lib.guisupport import start_event_loop_wx
463 from IPython.lib.guisupport import start_event_loop_wx
463
464
464 doi = self.do_one_iteration
465 doi = self.do_one_iteration
465 # Wx uses milliseconds
466 # Wx uses milliseconds
466 poll_interval = int(1000*self._poll_interval)
467 poll_interval = int(1000*self._poll_interval)
467
468
468 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
469 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
469 # We make the Frame hidden when we create it in the main app below.
470 # We make the Frame hidden when we create it in the main app below.
470 class TimerFrame(wx.Frame):
471 class TimerFrame(wx.Frame):
471 def __init__(self, func):
472 def __init__(self, func):
472 wx.Frame.__init__(self, None, -1)
473 wx.Frame.__init__(self, None, -1)
473 self.timer = wx.Timer(self)
474 self.timer = wx.Timer(self)
474 # Units for the timer are in milliseconds
475 # Units for the timer are in milliseconds
475 self.timer.Start(poll_interval)
476 self.timer.Start(poll_interval)
476 self.Bind(wx.EVT_TIMER, self.on_timer)
477 self.Bind(wx.EVT_TIMER, self.on_timer)
477 self.func = func
478 self.func = func
478
479
479 def on_timer(self, event):
480 def on_timer(self, event):
480 self.func()
481 self.func()
481
482
482 # We need a custom wx.App to create our Frame subclass that has the
483 # We need a custom wx.App to create our Frame subclass that has the
483 # wx.Timer to drive the ZMQ event loop.
484 # wx.Timer to drive the ZMQ event loop.
484 class IPWxApp(wx.App):
485 class IPWxApp(wx.App):
485 def OnInit(self):
486 def OnInit(self):
486 self.frame = TimerFrame(doi)
487 self.frame = TimerFrame(doi)
487 self.frame.Show(False)
488 self.frame.Show(False)
488 return True
489 return True
489
490
490 # The redirect=False here makes sure that wx doesn't replace
491 # The redirect=False here makes sure that wx doesn't replace
491 # sys.stdout/stderr with its own classes.
492 # sys.stdout/stderr with its own classes.
492 self.app = IPWxApp(redirect=False)
493 self.app = IPWxApp(redirect=False)
493 start_event_loop_wx(self.app)
494 start_event_loop_wx(self.app)
494
495
495
496
496 class TkKernel(Kernel):
497 class TkKernel(Kernel):
497 """A Kernel subclass with Tk support."""
498 """A Kernel subclass with Tk support."""
498
499
499 def start(self):
500 def start(self):
500 """Start a Tk enabled event loop."""
501 """Start a Tk enabled event loop."""
501
502
502 import Tkinter
503 import Tkinter
503 doi = self.do_one_iteration
504 doi = self.do_one_iteration
504 # Tk uses milliseconds
505 # Tk uses milliseconds
505 poll_interval = int(1000*self._poll_interval)
506 poll_interval = int(1000*self._poll_interval)
506 # For Tkinter, we create a Tk object and call its withdraw method.
507 # For Tkinter, we create a Tk object and call its withdraw method.
507 class Timer(object):
508 class Timer(object):
508 def __init__(self, func):
509 def __init__(self, func):
509 self.app = Tkinter.Tk()
510 self.app = Tkinter.Tk()
510 self.app.withdraw()
511 self.app.withdraw()
511 self.func = func
512 self.func = func
512
513
513 def on_timer(self):
514 def on_timer(self):
514 self.func()
515 self.func()
515 self.app.after(poll_interval, self.on_timer)
516 self.app.after(poll_interval, self.on_timer)
516
517
517 def start(self):
518 def start(self):
518 self.on_timer() # Call it once to get things going.
519 self.on_timer() # Call it once to get things going.
519 self.app.mainloop()
520 self.app.mainloop()
520
521
521 self.timer = Timer(doi)
522 self.timer = Timer(doi)
522 self.timer.start()
523 self.timer.start()
523
524
524
525
525 class GTKKernel(Kernel):
526 class GTKKernel(Kernel):
526 """A Kernel subclass with GTK support."""
527 """A Kernel subclass with GTK support."""
527
528
528 def start(self):
529 def start(self):
529 """Start the kernel, coordinating with the GTK event loop"""
530 """Start the kernel, coordinating with the GTK event loop"""
530 from .gui.gtkembed import GTKEmbed
531 from .gui.gtkembed import GTKEmbed
531
532
532 gtk_kernel = GTKEmbed(self)
533 gtk_kernel = GTKEmbed(self)
533 gtk_kernel.start()
534 gtk_kernel.start()
534
535
535
536
536 #-----------------------------------------------------------------------------
537 #-----------------------------------------------------------------------------
537 # Kernel main and launch functions
538 # Kernel main and launch functions
538 #-----------------------------------------------------------------------------
539 #-----------------------------------------------------------------------------
539
540
540 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
541 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
541 independent=False, pylab=False):
542 independent=False, pylab=False):
542 """Launches a localhost kernel, binding to the specified ports.
543 """Launches a localhost kernel, binding to the specified ports.
543
544
544 Parameters
545 Parameters
545 ----------
546 ----------
546 xrep_port : int, optional
547 xrep_port : int, optional
547 The port to use for XREP channel.
548 The port to use for XREP channel.
548
549
549 pub_port : int, optional
550 pub_port : int, optional
550 The port to use for the SUB channel.
551 The port to use for the SUB channel.
551
552
552 req_port : int, optional
553 req_port : int, optional
553 The port to use for the REQ (raw input) channel.
554 The port to use for the REQ (raw input) channel.
554
555
555 hb_port : int, optional
556 hb_port : int, optional
556 The port to use for the hearbeat REP channel.
557 The port to use for the hearbeat REP channel.
557
558
558 independent : bool, optional (default False)
559 independent : bool, optional (default False)
559 If set, the kernel process is guaranteed to survive if this process
560 If set, the kernel process is guaranteed to survive if this process
560 dies. If not set, an effort is made to ensure that the kernel is killed
561 dies. If not set, an effort is made to ensure that the kernel is killed
561 when this process dies. Note that in this case it is still good practice
562 when this process dies. Note that in this case it is still good practice
562 to kill kernels manually before exiting.
563 to kill kernels manually before exiting.
563
564
564 pylab : bool or string, optional (default False)
565 pylab : bool or string, optional (default False)
565 If not False, the kernel will be launched with pylab enabled. If a
566 If not False, the kernel will be launched with pylab enabled. If a
566 string is passed, matplotlib will use the specified backend. Otherwise,
567 string is passed, matplotlib will use the specified backend. Otherwise,
567 matplotlib's default backend will be used.
568 matplotlib's default backend will be used.
568
569
569 Returns
570 Returns
570 -------
571 -------
571 A tuple of form:
572 A tuple of form:
572 (kernel_process, xrep_port, pub_port, req_port)
573 (kernel_process, xrep_port, pub_port, req_port)
573 where kernel_process is a Popen object and the ports are integers.
574 where kernel_process is a Popen object and the ports are integers.
574 """
575 """
575 extra_arguments = []
576 extra_arguments = []
576 if pylab:
577 if pylab:
577 extra_arguments.append('--pylab')
578 extra_arguments.append('--pylab')
578 if isinstance(pylab, basestring):
579 if isinstance(pylab, basestring):
579 extra_arguments.append(pylab)
580 extra_arguments.append(pylab)
580 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
581 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
581 xrep_port, pub_port, req_port, hb_port,
582 xrep_port, pub_port, req_port, hb_port,
582 independent, extra_arguments)
583 independent, extra_arguments)
583
584
584
585
585 def main():
586 def main():
586 """ The IPython kernel main entry point.
587 """ The IPython kernel main entry point.
587 """
588 """
588 parser = make_argument_parser()
589 parser = make_argument_parser()
589 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
590 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
590 const='auto', help = \
591 const='auto', help = \
591 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
592 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
592 given, the GUI backend is matplotlib's, otherwise use one of: \
593 given, the GUI backend is matplotlib's, otherwise use one of: \
593 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
594 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
594 namespace = parser.parse_args()
595 namespace = parser.parse_args()
595
596
596 kernel_class = Kernel
597 kernel_class = Kernel
597
598
598 kernel_classes = {
599 kernel_classes = {
599 'qt' : QtKernel,
600 'qt' : QtKernel,
600 'qt4': QtKernel,
601 'qt4': QtKernel,
601 'inline': Kernel,
602 'inline': Kernel,
602 'wx' : WxKernel,
603 'wx' : WxKernel,
603 'tk' : TkKernel,
604 'tk' : TkKernel,
604 'gtk': GTKKernel,
605 'gtk': GTKKernel,
605 }
606 }
606 if namespace.pylab:
607 if namespace.pylab:
607 if namespace.pylab == 'auto':
608 if namespace.pylab == 'auto':
608 gui, backend = pylabtools.find_gui_and_backend()
609 gui, backend = pylabtools.find_gui_and_backend()
609 else:
610 else:
610 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
611 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
611 kernel_class = kernel_classes.get(gui)
612 kernel_class = kernel_classes.get(gui)
612 if kernel_class is None:
613 if kernel_class is None:
613 raise ValueError('GUI is not supported: %r' % gui)
614 raise ValueError('GUI is not supported: %r' % gui)
614 pylabtools.activate_matplotlib(backend)
615 pylabtools.activate_matplotlib(backend)
615
616
616 kernel = make_kernel(namespace, kernel_class, OutStream)
617 kernel = make_kernel(namespace, kernel_class, OutStream)
617
618
618 if namespace.pylab:
619 if namespace.pylab:
619 pylabtools.import_pylab(kernel.shell.user_ns, backend,
620 pylabtools.import_pylab(kernel.shell.user_ns, backend,
620 shell=kernel.shell)
621 shell=kernel.shell)
621
622
622 start_kernel(namespace, kernel)
623 start_kernel(namespace, kernel)
623
624
624
625
625 if __name__ == '__main__':
626 if __name__ == '__main__':
626 main()
627 main()
@@ -1,905 +1,905 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 from Queue import Queue, Empty
20 from Queue import Queue, Empty
21 from subprocess import Popen
21 from subprocess import Popen
22 import signal
22 import signal
23 import sys
23 import sys
24 from threading import Thread
24 from threading import Thread
25 import time
25 import time
26
26
27 # System library imports.
27 # System library imports.
28 import zmq
28 import zmq
29 from zmq import POLLIN, POLLOUT, POLLERR
29 from zmq import POLLIN, POLLOUT, POLLERR
30 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop
31
31
32 # Local imports.
32 # Local imports.
33 from IPython.utils import io
33 from IPython.utils import io
34 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
34 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
35 from session import Session
35 from session import Session
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Constants and exceptions
38 # Constants and exceptions
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 LOCALHOST = '127.0.0.1'
41 LOCALHOST = '127.0.0.1'
42
42
43 class InvalidPortNumber(Exception):
43 class InvalidPortNumber(Exception):
44 pass
44 pass
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Utility functions
47 # Utility functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 # some utilities to validate message structure, these might get moved elsewhere
50 # some utilities to validate message structure, these might get moved elsewhere
51 # if they prove to have more generic utility
51 # if they prove to have more generic utility
52
52
53 def validate_string_list(lst):
53 def validate_string_list(lst):
54 """Validate that the input is a list of strings.
54 """Validate that the input is a list of strings.
55
55
56 Raises ValueError if not."""
56 Raises ValueError if not."""
57 if not isinstance(lst, list):
57 if not isinstance(lst, list):
58 raise ValueError('input %r must be a list' % lst)
58 raise ValueError('input %r must be a list' % lst)
59 for x in lst:
59 for x in lst:
60 if not isinstance(x, basestring):
60 if not isinstance(x, basestring):
61 raise ValueError('element %r in list must be a string' % x)
61 raise ValueError('element %r in list must be a string' % x)
62
62
63
63
64 def validate_string_dict(dct):
64 def validate_string_dict(dct):
65 """Validate that the input is a dict with string keys and values.
65 """Validate that the input is a dict with string keys and values.
66
66
67 Raises ValueError if not."""
67 Raises ValueError if not."""
68 for k,v in dct.iteritems():
68 for k,v in dct.iteritems():
69 if not isinstance(k, basestring):
69 if not isinstance(k, basestring):
70 raise ValueError('key %r in dict must be a string' % k)
70 raise ValueError('key %r in dict must be a string' % k)
71 if not isinstance(v, basestring):
71 if not isinstance(v, basestring):
72 raise ValueError('value %r in dict must be a string' % v)
72 raise ValueError('value %r in dict must be a string' % v)
73
73
74
74
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76 # ZMQ Socket Channel classes
76 # ZMQ Socket Channel classes
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78
78
79 class ZmqSocketChannel(Thread):
79 class ZmqSocketChannel(Thread):
80 """The base class for the channels that use ZMQ sockets.
80 """The base class for the channels that use ZMQ sockets.
81 """
81 """
82 context = None
82 context = None
83 session = None
83 session = None
84 socket = None
84 socket = None
85 ioloop = None
85 ioloop = None
86 iostate = None
86 iostate = None
87 _address = None
87 _address = None
88
88
89 def __init__(self, context, session, address):
89 def __init__(self, context, session, address):
90 """Create a channel
90 """Create a channel
91
91
92 Parameters
92 Parameters
93 ----------
93 ----------
94 context : :class:`zmq.Context`
94 context : :class:`zmq.Context`
95 The ZMQ context to use.
95 The ZMQ context to use.
96 session : :class:`session.Session`
96 session : :class:`session.Session`
97 The session to use.
97 The session to use.
98 address : tuple
98 address : tuple
99 Standard (ip, port) tuple that the kernel is listening on.
99 Standard (ip, port) tuple that the kernel is listening on.
100 """
100 """
101 super(ZmqSocketChannel, self).__init__()
101 super(ZmqSocketChannel, self).__init__()
102 self.daemon = True
102 self.daemon = True
103
103
104 self.context = context
104 self.context = context
105 self.session = session
105 self.session = session
106 if address[1] == 0:
106 if address[1] == 0:
107 message = 'The port number for a channel cannot be 0.'
107 message = 'The port number for a channel cannot be 0.'
108 raise InvalidPortNumber(message)
108 raise InvalidPortNumber(message)
109 self._address = address
109 self._address = address
110
110
111 def stop(self):
111 def stop(self):
112 """Stop the channel's activity.
112 """Stop the channel's activity.
113
113
114 This calls :method:`Thread.join` and returns when the thread
114 This calls :method:`Thread.join` and returns when the thread
115 terminates. :class:`RuntimeError` will be raised if
115 terminates. :class:`RuntimeError` will be raised if
116 :method:`self.start` is called again.
116 :method:`self.start` is called again.
117 """
117 """
118 self.join()
118 self.join()
119
119
120 @property
120 @property
121 def address(self):
121 def address(self):
122 """Get the channel's address as an (ip, port) tuple.
122 """Get the channel's address as an (ip, port) tuple.
123
123
124 By the default, the address is (localhost, 0), where 0 means a random
124 By the default, the address is (localhost, 0), where 0 means a random
125 port.
125 port.
126 """
126 """
127 return self._address
127 return self._address
128
128
129 def add_io_state(self, state):
129 def add_io_state(self, state):
130 """Add IO state to the eventloop.
130 """Add IO state to the eventloop.
131
131
132 Parameters
132 Parameters
133 ----------
133 ----------
134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
135 The IO state flag to set.
135 The IO state flag to set.
136
136
137 This is thread safe as it uses the thread safe IOLoop.add_callback.
137 This is thread safe as it uses the thread safe IOLoop.add_callback.
138 """
138 """
139 def add_io_state_callback():
139 def add_io_state_callback():
140 if not self.iostate & state:
140 if not self.iostate & state:
141 self.iostate = self.iostate | state
141 self.iostate = self.iostate | state
142 self.ioloop.update_handler(self.socket, self.iostate)
142 self.ioloop.update_handler(self.socket, self.iostate)
143 self.ioloop.add_callback(add_io_state_callback)
143 self.ioloop.add_callback(add_io_state_callback)
144
144
145 def drop_io_state(self, state):
145 def drop_io_state(self, state):
146 """Drop IO state from the eventloop.
146 """Drop IO state from the eventloop.
147
147
148 Parameters
148 Parameters
149 ----------
149 ----------
150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
151 The IO state flag to set.
151 The IO state flag to set.
152
152
153 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 This is thread safe as it uses the thread safe IOLoop.add_callback.
154 """
154 """
155 def drop_io_state_callback():
155 def drop_io_state_callback():
156 if self.iostate & state:
156 if self.iostate & state:
157 self.iostate = self.iostate & (~state)
157 self.iostate = self.iostate & (~state)
158 self.ioloop.update_handler(self.socket, self.iostate)
158 self.ioloop.update_handler(self.socket, self.iostate)
159 self.ioloop.add_callback(drop_io_state_callback)
159 self.ioloop.add_callback(drop_io_state_callback)
160
160
161
161
162 class XReqSocketChannel(ZmqSocketChannel):
162 class XReqSocketChannel(ZmqSocketChannel):
163 """The XREQ channel for issues request/replies to the kernel.
163 """The XREQ channel for issues request/replies to the kernel.
164 """
164 """
165
165
166 command_queue = None
166 command_queue = None
167
167
168 def __init__(self, context, session, address):
168 def __init__(self, context, session, address):
169 super(XReqSocketChannel, self).__init__(context, session, address)
169 super(XReqSocketChannel, self).__init__(context, session, address)
170 self.command_queue = Queue()
170 self.command_queue = Queue()
171 self.ioloop = ioloop.IOLoop()
171 self.ioloop = ioloop.IOLoop()
172
172
173 def run(self):
173 def run(self):
174 """The thread's main activity. Call start() instead."""
174 """The thread's main activity. Call start() instead."""
175 self.socket = self.context.socket(zmq.XREQ)
175 self.socket = self.context.socket(zmq.XREQ)
176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
177 self.socket.connect('tcp://%s:%i' % self.address)
177 self.socket.connect('tcp://%s:%i' % self.address)
178 self.iostate = POLLERR|POLLIN
178 self.iostate = POLLERR|POLLIN
179 self.ioloop.add_handler(self.socket, self._handle_events,
179 self.ioloop.add_handler(self.socket, self._handle_events,
180 self.iostate)
180 self.iostate)
181 self.ioloop.start()
181 self.ioloop.start()
182
182
183 def stop(self):
183 def stop(self):
184 self.ioloop.stop()
184 self.ioloop.stop()
185 super(XReqSocketChannel, self).stop()
185 super(XReqSocketChannel, self).stop()
186
186
187 def call_handlers(self, msg):
187 def call_handlers(self, msg):
188 """This method is called in the ioloop thread when a message arrives.
188 """This method is called in the ioloop thread when a message arrives.
189
189
190 Subclasses should override this method to handle incoming messages.
190 Subclasses should override this method to handle incoming messages.
191 It is important to remember that this method is called in the thread
191 It is important to remember that this method is called in the thread
192 so that some logic must be done to ensure that the application leve
192 so that some logic must be done to ensure that the application leve
193 handlers are called in the application thread.
193 handlers are called in the application thread.
194 """
194 """
195 raise NotImplementedError('call_handlers must be defined in a subclass.')
195 raise NotImplementedError('call_handlers must be defined in a subclass.')
196
196
197 def execute(self, code, silent=False,
197 def execute(self, code, silent=False,
198 user_variables=None, user_expressions=None):
198 user_variables=None, user_expressions=None):
199 """Execute code in the kernel.
199 """Execute code in the kernel.
200
200
201 Parameters
201 Parameters
202 ----------
202 ----------
203 code : str
203 code : str
204 A string of Python code.
204 A string of Python code.
205
205
206 silent : bool, optional (default False)
206 silent : bool, optional (default False)
207 If set, the kernel will execute the code as quietly possible.
207 If set, the kernel will execute the code as quietly possible.
208
208
209 user_variables : list, optional
209 user_variables : list, optional
210 A list of variable names to pull from the user's namespace. They
210 A list of variable names to pull from the user's namespace. They
211 will come back as a dict with these names as keys and their
211 will come back as a dict with these names as keys and their
212 :func:`repr` as values.
212 :func:`repr` as values.
213
213
214 user_expressions : dict, optional
214 user_expressions : dict, optional
215 A dict with string keys and to pull from the user's
215 A dict with string keys and to pull from the user's
216 namespace. They will come back as a dict with these names as keys
216 namespace. They will come back as a dict with these names as keys
217 and their :func:`repr` as values.
217 and their :func:`repr` as values.
218
218
219 Returns
219 Returns
220 -------
220 -------
221 The msg_id of the message sent.
221 The msg_id of the message sent.
222 """
222 """
223 if user_variables is None:
223 if user_variables is None:
224 user_variables = []
224 user_variables = []
225 if user_expressions is None:
225 if user_expressions is None:
226 user_expressions = {}
226 user_expressions = {}
227
227
228 # Don't waste network traffic if inputs are invalid
228 # Don't waste network traffic if inputs are invalid
229 if not isinstance(code, basestring):
229 if not isinstance(code, basestring):
230 raise ValueError('code %r must be a string' % code)
230 raise ValueError('code %r must be a string' % code)
231 validate_string_list(user_variables)
231 validate_string_list(user_variables)
232 validate_string_dict(user_expressions)
232 validate_string_dict(user_expressions)
233
233
234 # Create class for content/msg creation. Related to, but possibly
234 # Create class for content/msg creation. Related to, but possibly
235 # not in Session.
235 # not in Session.
236 content = dict(code=code, silent=silent,
236 content = dict(code=code, silent=silent,
237 user_variables=user_variables,
237 user_variables=user_variables,
238 user_expressions=user_expressions)
238 user_expressions=user_expressions)
239 msg = self.session.msg('execute_request', content)
239 msg = self.session.msg('execute_request', content)
240 self._queue_request(msg)
240 self._queue_request(msg)
241 return msg['header']['msg_id']
241 return msg['header']['msg_id']
242
242
243 def complete(self, text, line, cursor_pos, block=None):
243 def complete(self, text, line, cursor_pos, block=None):
244 """Tab complete text in the kernel's namespace.
244 """Tab complete text in the kernel's namespace.
245
245
246 Parameters
246 Parameters
247 ----------
247 ----------
248 text : str
248 text : str
249 The text to complete.
249 The text to complete.
250 line : str
250 line : str
251 The full line of text that is the surrounding context for the
251 The full line of text that is the surrounding context for the
252 text to complete.
252 text to complete.
253 cursor_pos : int
253 cursor_pos : int
254 The position of the cursor in the line where the completion was
254 The position of the cursor in the line where the completion was
255 requested.
255 requested.
256 block : str, optional
256 block : str, optional
257 The full block of code in which the completion is being requested.
257 The full block of code in which the completion is being requested.
258
258
259 Returns
259 Returns
260 -------
260 -------
261 The msg_id of the message sent.
261 The msg_id of the message sent.
262 """
262 """
263 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
263 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
264 msg = self.session.msg('complete_request', content)
264 msg = self.session.msg('complete_request', content)
265 self._queue_request(msg)
265 self._queue_request(msg)
266 return msg['header']['msg_id']
266 return msg['header']['msg_id']
267
267
268 def object_info(self, oname):
268 def object_info(self, oname):
269 """Get metadata information about an object.
269 """Get metadata information about an object.
270
270
271 Parameters
271 Parameters
272 ----------
272 ----------
273 oname : str
273 oname : str
274 A string specifying the object name.
274 A string specifying the object name.
275
275
276 Returns
276 Returns
277 -------
277 -------
278 The msg_id of the message sent.
278 The msg_id of the message sent.
279 """
279 """
280 content = dict(oname=oname)
280 content = dict(oname=oname)
281 msg = self.session.msg('object_info_request', content)
281 msg = self.session.msg('object_info_request', content)
282 self._queue_request(msg)
282 self._queue_request(msg)
283 return msg['header']['msg_id']
283 return msg['header']['msg_id']
284
284
285 def history(self, index=None, raw=False, output=True):
285 def history(self, index=None, raw=False, output=True):
286 """Get the history list.
286 """Get the history list.
287
287
288 Parameters
288 Parameters
289 ----------
289 ----------
290 index : n or (n1, n2) or None
290 index : n or (n1, n2) or None
291 If n, then the last entries. If a tuple, then all in
291 If n, then the last entries. If a tuple, then all in
292 range(n1, n2). If None, then all entries. Raises IndexError if
292 range(n1, n2). If None, then all entries. Raises IndexError if
293 the format of index is incorrect.
293 the format of index is incorrect.
294 raw : bool
294 raw : bool
295 If True, return the raw input.
295 If True, return the raw input.
296 output : bool
296 output : bool
297 If True, then return the output as well.
297 If True, then return the output as well.
298
298
299 Returns
299 Returns
300 -------
300 -------
301 The msg_id of the message sent.
301 The msg_id of the message sent.
302 """
302 """
303 content = dict(index=index, raw=raw, output=output)
303 content = dict(index=index, raw=raw, output=output)
304 msg = self.session.msg('history_request', content)
304 msg = self.session.msg('history_request', content)
305 self._queue_request(msg)
305 self._queue_request(msg)
306 return msg['header']['msg_id']
306 return msg['header']['msg_id']
307
307
308 def shutdown(self):
308 def shutdown(self, restart=False):
309 """Request an immediate kernel shutdown.
309 """Request an immediate kernel shutdown.
310
310
311 Upon receipt of the (empty) reply, client code can safely assume that
311 Upon receipt of the (empty) reply, client code can safely assume that
312 the kernel has shut down and it's safe to forcefully terminate it if
312 the kernel has shut down and it's safe to forcefully terminate it if
313 it's still alive.
313 it's still alive.
314
314
315 The kernel will send the reply via a function registered with Python's
315 The kernel will send the reply via a function registered with Python's
316 atexit module, ensuring it's truly done as the kernel is done with all
316 atexit module, ensuring it's truly done as the kernel is done with all
317 normal operation.
317 normal operation.
318 """
318 """
319 # Send quit message to kernel. Once we implement kernel-side setattr,
319 # Send quit message to kernel. Once we implement kernel-side setattr,
320 # this should probably be done that way, but for now this will do.
320 # this should probably be done that way, but for now this will do.
321 msg = self.session.msg('shutdown_request', {})
321 msg = self.session.msg('shutdown_request', {'restart':restart})
322 self._queue_request(msg)
322 self._queue_request(msg)
323 return msg['header']['msg_id']
323 return msg['header']['msg_id']
324
324
325 def _handle_events(self, socket, events):
325 def _handle_events(self, socket, events):
326 if events & POLLERR:
326 if events & POLLERR:
327 self._handle_err()
327 self._handle_err()
328 if events & POLLOUT:
328 if events & POLLOUT:
329 self._handle_send()
329 self._handle_send()
330 if events & POLLIN:
330 if events & POLLIN:
331 self._handle_recv()
331 self._handle_recv()
332
332
333 def _handle_recv(self):
333 def _handle_recv(self):
334 msg = self.socket.recv_json()
334 msg = self.socket.recv_json()
335 self.call_handlers(msg)
335 self.call_handlers(msg)
336
336
337 def _handle_send(self):
337 def _handle_send(self):
338 try:
338 try:
339 msg = self.command_queue.get(False)
339 msg = self.command_queue.get(False)
340 except Empty:
340 except Empty:
341 pass
341 pass
342 else:
342 else:
343 self.socket.send_json(msg)
343 self.socket.send_json(msg)
344 if self.command_queue.empty():
344 if self.command_queue.empty():
345 self.drop_io_state(POLLOUT)
345 self.drop_io_state(POLLOUT)
346
346
347 def _handle_err(self):
347 def _handle_err(self):
348 # We don't want to let this go silently, so eventually we should log.
348 # We don't want to let this go silently, so eventually we should log.
349 raise zmq.ZMQError()
349 raise zmq.ZMQError()
350
350
351 def _queue_request(self, msg):
351 def _queue_request(self, msg):
352 self.command_queue.put(msg)
352 self.command_queue.put(msg)
353 self.add_io_state(POLLOUT)
353 self.add_io_state(POLLOUT)
354
354
355
355
356 class SubSocketChannel(ZmqSocketChannel):
356 class SubSocketChannel(ZmqSocketChannel):
357 """The SUB channel which listens for messages that the kernel publishes.
357 """The SUB channel which listens for messages that the kernel publishes.
358 """
358 """
359
359
360 def __init__(self, context, session, address):
360 def __init__(self, context, session, address):
361 super(SubSocketChannel, self).__init__(context, session, address)
361 super(SubSocketChannel, self).__init__(context, session, address)
362 self.ioloop = ioloop.IOLoop()
362 self.ioloop = ioloop.IOLoop()
363
363
364 def run(self):
364 def run(self):
365 """The thread's main activity. Call start() instead."""
365 """The thread's main activity. Call start() instead."""
366 self.socket = self.context.socket(zmq.SUB)
366 self.socket = self.context.socket(zmq.SUB)
367 self.socket.setsockopt(zmq.SUBSCRIBE,'')
367 self.socket.setsockopt(zmq.SUBSCRIBE,'')
368 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
368 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
369 self.socket.connect('tcp://%s:%i' % self.address)
369 self.socket.connect('tcp://%s:%i' % self.address)
370 self.iostate = POLLIN|POLLERR
370 self.iostate = POLLIN|POLLERR
371 self.ioloop.add_handler(self.socket, self._handle_events,
371 self.ioloop.add_handler(self.socket, self._handle_events,
372 self.iostate)
372 self.iostate)
373 self.ioloop.start()
373 self.ioloop.start()
374
374
375 def stop(self):
375 def stop(self):
376 self.ioloop.stop()
376 self.ioloop.stop()
377 super(SubSocketChannel, self).stop()
377 super(SubSocketChannel, self).stop()
378
378
379 def call_handlers(self, msg):
379 def call_handlers(self, msg):
380 """This method is called in the ioloop thread when a message arrives.
380 """This method is called in the ioloop thread when a message arrives.
381
381
382 Subclasses should override this method to handle incoming messages.
382 Subclasses should override this method to handle incoming messages.
383 It is important to remember that this method is called in the thread
383 It is important to remember that this method is called in the thread
384 so that some logic must be done to ensure that the application leve
384 so that some logic must be done to ensure that the application leve
385 handlers are called in the application thread.
385 handlers are called in the application thread.
386 """
386 """
387 raise NotImplementedError('call_handlers must be defined in a subclass.')
387 raise NotImplementedError('call_handlers must be defined in a subclass.')
388
388
389 def flush(self, timeout=1.0):
389 def flush(self, timeout=1.0):
390 """Immediately processes all pending messages on the SUB channel.
390 """Immediately processes all pending messages on the SUB channel.
391
391
392 Callers should use this method to ensure that :method:`call_handlers`
392 Callers should use this method to ensure that :method:`call_handlers`
393 has been called for all messages that have been received on the
393 has been called for all messages that have been received on the
394 0MQ SUB socket of this channel.
394 0MQ SUB socket of this channel.
395
395
396 This method is thread safe.
396 This method is thread safe.
397
397
398 Parameters
398 Parameters
399 ----------
399 ----------
400 timeout : float, optional
400 timeout : float, optional
401 The maximum amount of time to spend flushing, in seconds. The
401 The maximum amount of time to spend flushing, in seconds. The
402 default is one second.
402 default is one second.
403 """
403 """
404 # We do the IOLoop callback process twice to ensure that the IOLoop
404 # We do the IOLoop callback process twice to ensure that the IOLoop
405 # gets to perform at least one full poll.
405 # gets to perform at least one full poll.
406 stop_time = time.time() + timeout
406 stop_time = time.time() + timeout
407 for i in xrange(2):
407 for i in xrange(2):
408 self._flushed = False
408 self._flushed = False
409 self.ioloop.add_callback(self._flush)
409 self.ioloop.add_callback(self._flush)
410 while not self._flushed and time.time() < stop_time:
410 while not self._flushed and time.time() < stop_time:
411 time.sleep(0.01)
411 time.sleep(0.01)
412
412
413 def _handle_events(self, socket, events):
413 def _handle_events(self, socket, events):
414 # Turn on and off POLLOUT depending on if we have made a request
414 # Turn on and off POLLOUT depending on if we have made a request
415 if events & POLLERR:
415 if events & POLLERR:
416 self._handle_err()
416 self._handle_err()
417 if events & POLLIN:
417 if events & POLLIN:
418 self._handle_recv()
418 self._handle_recv()
419
419
420 def _handle_err(self):
420 def _handle_err(self):
421 # We don't want to let this go silently, so eventually we should log.
421 # We don't want to let this go silently, so eventually we should log.
422 raise zmq.ZMQError()
422 raise zmq.ZMQError()
423
423
424 def _handle_recv(self):
424 def _handle_recv(self):
425 # Get all of the messages we can
425 # Get all of the messages we can
426 while True:
426 while True:
427 try:
427 try:
428 msg = self.socket.recv_json(zmq.NOBLOCK)
428 msg = self.socket.recv_json(zmq.NOBLOCK)
429 except zmq.ZMQError:
429 except zmq.ZMQError:
430 # Check the errno?
430 # Check the errno?
431 # Will this trigger POLLERR?
431 # Will this trigger POLLERR?
432 break
432 break
433 else:
433 else:
434 self.call_handlers(msg)
434 self.call_handlers(msg)
435
435
436 def _flush(self):
436 def _flush(self):
437 """Callback for :method:`self.flush`."""
437 """Callback for :method:`self.flush`."""
438 self._flushed = True
438 self._flushed = True
439
439
440
440
441 class RepSocketChannel(ZmqSocketChannel):
441 class RepSocketChannel(ZmqSocketChannel):
442 """A reply channel to handle raw_input requests that the kernel makes."""
442 """A reply channel to handle raw_input requests that the kernel makes."""
443
443
444 msg_queue = None
444 msg_queue = None
445
445
446 def __init__(self, context, session, address):
446 def __init__(self, context, session, address):
447 super(RepSocketChannel, self).__init__(context, session, address)
447 super(RepSocketChannel, self).__init__(context, session, address)
448 self.ioloop = ioloop.IOLoop()
448 self.ioloop = ioloop.IOLoop()
449 self.msg_queue = Queue()
449 self.msg_queue = Queue()
450
450
451 def run(self):
451 def run(self):
452 """The thread's main activity. Call start() instead."""
452 """The thread's main activity. Call start() instead."""
453 self.socket = self.context.socket(zmq.XREQ)
453 self.socket = self.context.socket(zmq.XREQ)
454 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
454 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
455 self.socket.connect('tcp://%s:%i' % self.address)
455 self.socket.connect('tcp://%s:%i' % self.address)
456 self.iostate = POLLERR|POLLIN
456 self.iostate = POLLERR|POLLIN
457 self.ioloop.add_handler(self.socket, self._handle_events,
457 self.ioloop.add_handler(self.socket, self._handle_events,
458 self.iostate)
458 self.iostate)
459 self.ioloop.start()
459 self.ioloop.start()
460
460
461 def stop(self):
461 def stop(self):
462 self.ioloop.stop()
462 self.ioloop.stop()
463 super(RepSocketChannel, self).stop()
463 super(RepSocketChannel, self).stop()
464
464
465 def call_handlers(self, msg):
465 def call_handlers(self, msg):
466 """This method is called in the ioloop thread when a message arrives.
466 """This method is called in the ioloop thread when a message arrives.
467
467
468 Subclasses should override this method to handle incoming messages.
468 Subclasses should override this method to handle incoming messages.
469 It is important to remember that this method is called in the thread
469 It is important to remember that this method is called in the thread
470 so that some logic must be done to ensure that the application leve
470 so that some logic must be done to ensure that the application leve
471 handlers are called in the application thread.
471 handlers are called in the application thread.
472 """
472 """
473 raise NotImplementedError('call_handlers must be defined in a subclass.')
473 raise NotImplementedError('call_handlers must be defined in a subclass.')
474
474
475 def input(self, string):
475 def input(self, string):
476 """Send a string of raw input to the kernel."""
476 """Send a string of raw input to the kernel."""
477 content = dict(value=string)
477 content = dict(value=string)
478 msg = self.session.msg('input_reply', content)
478 msg = self.session.msg('input_reply', content)
479 self._queue_reply(msg)
479 self._queue_reply(msg)
480
480
481 def _handle_events(self, socket, events):
481 def _handle_events(self, socket, events):
482 if events & POLLERR:
482 if events & POLLERR:
483 self._handle_err()
483 self._handle_err()
484 if events & POLLOUT:
484 if events & POLLOUT:
485 self._handle_send()
485 self._handle_send()
486 if events & POLLIN:
486 if events & POLLIN:
487 self._handle_recv()
487 self._handle_recv()
488
488
489 def _handle_recv(self):
489 def _handle_recv(self):
490 msg = self.socket.recv_json()
490 msg = self.socket.recv_json()
491 self.call_handlers(msg)
491 self.call_handlers(msg)
492
492
493 def _handle_send(self):
493 def _handle_send(self):
494 try:
494 try:
495 msg = self.msg_queue.get(False)
495 msg = self.msg_queue.get(False)
496 except Empty:
496 except Empty:
497 pass
497 pass
498 else:
498 else:
499 self.socket.send_json(msg)
499 self.socket.send_json(msg)
500 if self.msg_queue.empty():
500 if self.msg_queue.empty():
501 self.drop_io_state(POLLOUT)
501 self.drop_io_state(POLLOUT)
502
502
503 def _handle_err(self):
503 def _handle_err(self):
504 # We don't want to let this go silently, so eventually we should log.
504 # We don't want to let this go silently, so eventually we should log.
505 raise zmq.ZMQError()
505 raise zmq.ZMQError()
506
506
507 def _queue_reply(self, msg):
507 def _queue_reply(self, msg):
508 self.msg_queue.put(msg)
508 self.msg_queue.put(msg)
509 self.add_io_state(POLLOUT)
509 self.add_io_state(POLLOUT)
510
510
511
511
512 class HBSocketChannel(ZmqSocketChannel):
512 class HBSocketChannel(ZmqSocketChannel):
513 """The heartbeat channel which monitors the kernel heartbeat.
513 """The heartbeat channel which monitors the kernel heartbeat.
514
514
515 Note that the heartbeat channel is paused by default. As long as you start
515 Note that the heartbeat channel is paused by default. As long as you start
516 this channel, the kernel manager will ensure that it is paused and un-paused
516 this channel, the kernel manager will ensure that it is paused and un-paused
517 as appropriate.
517 as appropriate.
518 """
518 """
519
519
520 time_to_dead = 3.0
520 time_to_dead = 3.0
521 socket = None
521 socket = None
522 poller = None
522 poller = None
523 _running = None
523 _running = None
524 _pause = None
524 _pause = None
525
525
526 def __init__(self, context, session, address):
526 def __init__(self, context, session, address):
527 super(HBSocketChannel, self).__init__(context, session, address)
527 super(HBSocketChannel, self).__init__(context, session, address)
528 self._running = False
528 self._running = False
529 self._pause = True
529 self._pause = True
530
530
531 def _create_socket(self):
531 def _create_socket(self):
532 self.socket = self.context.socket(zmq.REQ)
532 self.socket = self.context.socket(zmq.REQ)
533 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
533 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
534 self.socket.connect('tcp://%s:%i' % self.address)
534 self.socket.connect('tcp://%s:%i' % self.address)
535 self.poller = zmq.Poller()
535 self.poller = zmq.Poller()
536 self.poller.register(self.socket, zmq.POLLIN)
536 self.poller.register(self.socket, zmq.POLLIN)
537
537
538 def run(self):
538 def run(self):
539 """The thread's main activity. Call start() instead."""
539 """The thread's main activity. Call start() instead."""
540 self._create_socket()
540 self._create_socket()
541 self._running = True
541 self._running = True
542 while self._running:
542 while self._running:
543 if self._pause:
543 if self._pause:
544 time.sleep(self.time_to_dead)
544 time.sleep(self.time_to_dead)
545 else:
545 else:
546 since_last_heartbeat = 0.0
546 since_last_heartbeat = 0.0
547 request_time = time.time()
547 request_time = time.time()
548 try:
548 try:
549 #io.rprint('Ping from HB channel') # dbg
549 #io.rprint('Ping from HB channel') # dbg
550 self.socket.send_json('ping')
550 self.socket.send_json('ping')
551 except zmq.ZMQError, e:
551 except zmq.ZMQError, e:
552 #io.rprint('*** HB Error:', e) # dbg
552 #io.rprint('*** HB Error:', e) # dbg
553 if e.errno == zmq.EFSM:
553 if e.errno == zmq.EFSM:
554 #io.rprint('sleep...', self.time_to_dead) # dbg
554 #io.rprint('sleep...', self.time_to_dead) # dbg
555 time.sleep(self.time_to_dead)
555 time.sleep(self.time_to_dead)
556 self._create_socket()
556 self._create_socket()
557 else:
557 else:
558 raise
558 raise
559 else:
559 else:
560 while True:
560 while True:
561 try:
561 try:
562 self.socket.recv_json(zmq.NOBLOCK)
562 self.socket.recv_json(zmq.NOBLOCK)
563 except zmq.ZMQError, e:
563 except zmq.ZMQError, e:
564 #io.rprint('*** HB Error 2:', e) # dbg
564 #io.rprint('*** HB Error 2:', e) # dbg
565 if e.errno == zmq.EAGAIN:
565 if e.errno == zmq.EAGAIN:
566 before_poll = time.time()
566 before_poll = time.time()
567 until_dead = self.time_to_dead - (before_poll -
567 until_dead = self.time_to_dead - (before_poll -
568 request_time)
568 request_time)
569
569
570 # When the return value of poll() is an empty
570 # When the return value of poll() is an empty
571 # list, that is when things have gone wrong
571 # list, that is when things have gone wrong
572 # (zeromq bug). As long as it is not an empty
572 # (zeromq bug). As long as it is not an empty
573 # list, poll is working correctly even if it
573 # list, poll is working correctly even if it
574 # returns quickly. Note: poll timeout is in
574 # returns quickly. Note: poll timeout is in
575 # milliseconds.
575 # milliseconds.
576 self.poller.poll(1000*until_dead)
576 self.poller.poll(1000*until_dead)
577
577
578 since_last_heartbeat = time.time()-request_time
578 since_last_heartbeat = time.time()-request_time
579 if since_last_heartbeat > self.time_to_dead:
579 if since_last_heartbeat > self.time_to_dead:
580 self.call_handlers(since_last_heartbeat)
580 self.call_handlers(since_last_heartbeat)
581 break
581 break
582 else:
582 else:
583 # FIXME: We should probably log this instead.
583 # FIXME: We should probably log this instead.
584 raise
584 raise
585 else:
585 else:
586 until_dead = self.time_to_dead - (time.time() -
586 until_dead = self.time_to_dead - (time.time() -
587 request_time)
587 request_time)
588 if until_dead > 0.0:
588 if until_dead > 0.0:
589 #io.rprint('sleep...', self.time_to_dead) # dbg
589 #io.rprint('sleep...', self.time_to_dead) # dbg
590 time.sleep(until_dead)
590 time.sleep(until_dead)
591 break
591 break
592
592
593 def pause(self):
593 def pause(self):
594 """Pause the heartbeat."""
594 """Pause the heartbeat."""
595 self._pause = True
595 self._pause = True
596
596
597 def unpause(self):
597 def unpause(self):
598 """Unpause the heartbeat."""
598 """Unpause the heartbeat."""
599 self._pause = False
599 self._pause = False
600
600
601 def is_beating(self):
601 def is_beating(self):
602 """Is the heartbeat running and not paused."""
602 """Is the heartbeat running and not paused."""
603 if self.is_alive() and not self._pause:
603 if self.is_alive() and not self._pause:
604 return True
604 return True
605 else:
605 else:
606 return False
606 return False
607
607
608 def stop(self):
608 def stop(self):
609 self._running = False
609 self._running = False
610 super(HBSocketChannel, self).stop()
610 super(HBSocketChannel, self).stop()
611
611
612 def call_handlers(self, since_last_heartbeat):
612 def call_handlers(self, since_last_heartbeat):
613 """This method is called in the ioloop thread when a message arrives.
613 """This method is called in the ioloop thread when a message arrives.
614
614
615 Subclasses should override this method to handle incoming messages.
615 Subclasses should override this method to handle incoming messages.
616 It is important to remember that this method is called in the thread
616 It is important to remember that this method is called in the thread
617 so that some logic must be done to ensure that the application leve
617 so that some logic must be done to ensure that the application leve
618 handlers are called in the application thread.
618 handlers are called in the application thread.
619 """
619 """
620 raise NotImplementedError('call_handlers must be defined in a subclass.')
620 raise NotImplementedError('call_handlers must be defined in a subclass.')
621
621
622
622
623 #-----------------------------------------------------------------------------
623 #-----------------------------------------------------------------------------
624 # Main kernel manager class
624 # Main kernel manager class
625 #-----------------------------------------------------------------------------
625 #-----------------------------------------------------------------------------
626
626
627 class KernelManager(HasTraits):
627 class KernelManager(HasTraits):
628 """ Manages a kernel for a frontend.
628 """ Manages a kernel for a frontend.
629
629
630 The SUB channel is for the frontend to receive messages published by the
630 The SUB channel is for the frontend to receive messages published by the
631 kernel.
631 kernel.
632
632
633 The REQ channel is for the frontend to make requests of the kernel.
633 The REQ channel is for the frontend to make requests of the kernel.
634
634
635 The REP channel is for the kernel to request stdin (raw_input) from the
635 The REP channel is for the kernel to request stdin (raw_input) from the
636 frontend.
636 frontend.
637 """
637 """
638 # The PyZMQ Context to use for communication with the kernel.
638 # The PyZMQ Context to use for communication with the kernel.
639 context = Instance(zmq.Context,(),{})
639 context = Instance(zmq.Context,(),{})
640
640
641 # The Session to use for communication with the kernel.
641 # The Session to use for communication with the kernel.
642 session = Instance(Session,(),{})
642 session = Instance(Session,(),{})
643
643
644 # The kernel process with which the KernelManager is communicating.
644 # The kernel process with which the KernelManager is communicating.
645 kernel = Instance(Popen)
645 kernel = Instance(Popen)
646
646
647 # The addresses for the communication channels.
647 # The addresses for the communication channels.
648 xreq_address = TCPAddress((LOCALHOST, 0))
648 xreq_address = TCPAddress((LOCALHOST, 0))
649 sub_address = TCPAddress((LOCALHOST, 0))
649 sub_address = TCPAddress((LOCALHOST, 0))
650 rep_address = TCPAddress((LOCALHOST, 0))
650 rep_address = TCPAddress((LOCALHOST, 0))
651 hb_address = TCPAddress((LOCALHOST, 0))
651 hb_address = TCPAddress((LOCALHOST, 0))
652
652
653 # The classes to use for the various channels.
653 # The classes to use for the various channels.
654 xreq_channel_class = Type(XReqSocketChannel)
654 xreq_channel_class = Type(XReqSocketChannel)
655 sub_channel_class = Type(SubSocketChannel)
655 sub_channel_class = Type(SubSocketChannel)
656 rep_channel_class = Type(RepSocketChannel)
656 rep_channel_class = Type(RepSocketChannel)
657 hb_channel_class = Type(HBSocketChannel)
657 hb_channel_class = Type(HBSocketChannel)
658
658
659 # Protected traits.
659 # Protected traits.
660 _launch_args = Any
660 _launch_args = Any
661 _xreq_channel = Any
661 _xreq_channel = Any
662 _sub_channel = Any
662 _sub_channel = Any
663 _rep_channel = Any
663 _rep_channel = Any
664 _hb_channel = Any
664 _hb_channel = Any
665
665
666 def __init__(self, **kwargs):
666 def __init__(self, **kwargs):
667 super(KernelManager, self).__init__(**kwargs)
667 super(KernelManager, self).__init__(**kwargs)
668 # Uncomment this to try closing the context.
668 # Uncomment this to try closing the context.
669 # atexit.register(self.context.close)
669 # atexit.register(self.context.close)
670
670
671 #--------------------------------------------------------------------------
671 #--------------------------------------------------------------------------
672 # Channel management methods:
672 # Channel management methods:
673 #--------------------------------------------------------------------------
673 #--------------------------------------------------------------------------
674
674
675 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
675 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
676 """Starts the channels for this kernel.
676 """Starts the channels for this kernel.
677
677
678 This will create the channels if they do not exist and then start
678 This will create the channels if they do not exist and then start
679 them. If port numbers of 0 are being used (random ports) then you
679 them. If port numbers of 0 are being used (random ports) then you
680 must first call :method:`start_kernel`. If the channels have been
680 must first call :method:`start_kernel`. If the channels have been
681 stopped and you call this, :class:`RuntimeError` will be raised.
681 stopped and you call this, :class:`RuntimeError` will be raised.
682 """
682 """
683 if xreq:
683 if xreq:
684 self.xreq_channel.start()
684 self.xreq_channel.start()
685 if sub:
685 if sub:
686 self.sub_channel.start()
686 self.sub_channel.start()
687 if rep:
687 if rep:
688 self.rep_channel.start()
688 self.rep_channel.start()
689 if hb:
689 if hb:
690 self.hb_channel.start()
690 self.hb_channel.start()
691
691
692 def stop_channels(self):
692 def stop_channels(self):
693 """Stops all the running channels for this kernel.
693 """Stops all the running channels for this kernel.
694 """
694 """
695 if self.xreq_channel.is_alive():
695 if self.xreq_channel.is_alive():
696 self.xreq_channel.stop()
696 self.xreq_channel.stop()
697 if self.sub_channel.is_alive():
697 if self.sub_channel.is_alive():
698 self.sub_channel.stop()
698 self.sub_channel.stop()
699 if self.rep_channel.is_alive():
699 if self.rep_channel.is_alive():
700 self.rep_channel.stop()
700 self.rep_channel.stop()
701 if self.hb_channel.is_alive():
701 if self.hb_channel.is_alive():
702 self.hb_channel.stop()
702 self.hb_channel.stop()
703
703
704 @property
704 @property
705 def channels_running(self):
705 def channels_running(self):
706 """Are any of the channels created and running?"""
706 """Are any of the channels created and running?"""
707 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
707 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
708 self.rep_channel.is_alive() or self.hb_channel.is_alive())
708 self.rep_channel.is_alive() or self.hb_channel.is_alive())
709
709
710 #--------------------------------------------------------------------------
710 #--------------------------------------------------------------------------
711 # Kernel process management methods:
711 # Kernel process management methods:
712 #--------------------------------------------------------------------------
712 #--------------------------------------------------------------------------
713
713
714 def start_kernel(self, **kw):
714 def start_kernel(self, **kw):
715 """Starts a kernel process and configures the manager to use it.
715 """Starts a kernel process and configures the manager to use it.
716
716
717 If random ports (port=0) are being used, this method must be called
717 If random ports (port=0) are being used, this method must be called
718 before the channels are created.
718 before the channels are created.
719
719
720 Parameters:
720 Parameters:
721 -----------
721 -----------
722 ipython : bool, optional (default True)
722 ipython : bool, optional (default True)
723 Whether to use an IPython kernel instead of a plain Python kernel.
723 Whether to use an IPython kernel instead of a plain Python kernel.
724 """
724 """
725 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
725 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
726 self.rep_address, self.hb_address
726 self.rep_address, self.hb_address
727 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
727 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
728 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
728 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
729 raise RuntimeError("Can only launch a kernel on localhost."
729 raise RuntimeError("Can only launch a kernel on localhost."
730 "Make sure that the '*_address' attributes are "
730 "Make sure that the '*_address' attributes are "
731 "configured properly.")
731 "configured properly.")
732
732
733 self._launch_args = kw.copy()
733 self._launch_args = kw.copy()
734 if kw.pop('ipython', True):
734 if kw.pop('ipython', True):
735 from ipkernel import launch_kernel
735 from ipkernel import launch_kernel
736 else:
736 else:
737 from pykernel import launch_kernel
737 from pykernel import launch_kernel
738 self.kernel, xrep, pub, req, hb = launch_kernel(
738 self.kernel, xrep, pub, req, hb = launch_kernel(
739 xrep_port=xreq[1], pub_port=sub[1],
739 xrep_port=xreq[1], pub_port=sub[1],
740 req_port=rep[1], hb_port=hb[1], **kw)
740 req_port=rep[1], hb_port=hb[1], **kw)
741 self.xreq_address = (LOCALHOST, xrep)
741 self.xreq_address = (LOCALHOST, xrep)
742 self.sub_address = (LOCALHOST, pub)
742 self.sub_address = (LOCALHOST, pub)
743 self.rep_address = (LOCALHOST, req)
743 self.rep_address = (LOCALHOST, req)
744 self.hb_address = (LOCALHOST, hb)
744 self.hb_address = (LOCALHOST, hb)
745
745
746 def shutdown_kernel(self):
746 def shutdown_kernel(self, restart=False):
747 """ Attempts to the stop the kernel process cleanly. If the kernel
747 """ Attempts to the stop the kernel process cleanly. If the kernel
748 cannot be stopped, it is killed, if possible.
748 cannot be stopped, it is killed, if possible.
749 """
749 """
750 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
750 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
751 if sys.platform == 'win32':
751 if sys.platform == 'win32':
752 self.kill_kernel()
752 self.kill_kernel()
753 return
753 return
754
754
755 # Pause the heart beat channel if it exists.
755 # Pause the heart beat channel if it exists.
756 if self._hb_channel is not None:
756 if self._hb_channel is not None:
757 self._hb_channel.pause()
757 self._hb_channel.pause()
758
758
759 # Don't send any additional kernel kill messages immediately, to give
759 # Don't send any additional kernel kill messages immediately, to give
760 # the kernel a chance to properly execute shutdown actions. Wait for at
760 # the kernel a chance to properly execute shutdown actions. Wait for at
761 # most 1s, checking every 0.1s.
761 # most 1s, checking every 0.1s.
762 self.xreq_channel.shutdown()
762 self.xreq_channel.shutdown(restart=restart)
763 for i in range(10):
763 for i in range(10):
764 if self.is_alive:
764 if self.is_alive:
765 time.sleep(0.1)
765 time.sleep(0.1)
766 else:
766 else:
767 break
767 break
768 else:
768 else:
769 # OK, we've waited long enough.
769 # OK, we've waited long enough.
770 if self.has_kernel:
770 if self.has_kernel:
771 self.kill_kernel()
771 self.kill_kernel()
772
772
773 def restart_kernel(self, now=False):
773 def restart_kernel(self, now=False):
774 """Restarts a kernel with the same arguments that were used to launch
774 """Restarts a kernel with the same arguments that were used to launch
775 it. If the old kernel was launched with random ports, the same ports
775 it. If the old kernel was launched with random ports, the same ports
776 will be used for the new kernel.
776 will be used for the new kernel.
777
777
778 Parameters
778 Parameters
779 ----------
779 ----------
780 now : bool, optional
780 now : bool, optional
781 If True, the kernel is forcefully restarted *immediately*, without
781 If True, the kernel is forcefully restarted *immediately*, without
782 having a chance to do any cleanup action. Otherwise the kernel is
782 having a chance to do any cleanup action. Otherwise the kernel is
783 given 1s to clean up before a forceful restart is issued.
783 given 1s to clean up before a forceful restart is issued.
784
784
785 In all cases the kernel is restarted, the only difference is whether
785 In all cases the kernel is restarted, the only difference is whether
786 it is given a chance to perform a clean shutdown or not.
786 it is given a chance to perform a clean shutdown or not.
787 """
787 """
788 if self._launch_args is None:
788 if self._launch_args is None:
789 raise RuntimeError("Cannot restart the kernel. "
789 raise RuntimeError("Cannot restart the kernel. "
790 "No previous call to 'start_kernel'.")
790 "No previous call to 'start_kernel'.")
791 else:
791 else:
792 if self.has_kernel:
792 if self.has_kernel:
793 if now:
793 if now:
794 self.kill_kernel()
794 self.kill_kernel()
795 else:
795 else:
796 self.shutdown_kernel()
796 self.shutdown_kernel(restart=True)
797 self.start_kernel(**self._launch_args)
797 self.start_kernel(**self._launch_args)
798
798
799 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
799 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
800 # unless there is some delay here.
800 # unless there is some delay here.
801 if sys.platform == 'win32':
801 if sys.platform == 'win32':
802 time.sleep(0.2)
802 time.sleep(0.2)
803
803
804 @property
804 @property
805 def has_kernel(self):
805 def has_kernel(self):
806 """Returns whether a kernel process has been specified for the kernel
806 """Returns whether a kernel process has been specified for the kernel
807 manager.
807 manager.
808 """
808 """
809 return self.kernel is not None
809 return self.kernel is not None
810
810
811 def kill_kernel(self):
811 def kill_kernel(self):
812 """ Kill the running kernel. """
812 """ Kill the running kernel. """
813 if self.has_kernel:
813 if self.has_kernel:
814 # Pause the heart beat channel if it exists.
814 # Pause the heart beat channel if it exists.
815 if self._hb_channel is not None:
815 if self._hb_channel is not None:
816 self._hb_channel.pause()
816 self._hb_channel.pause()
817
817
818 # Attempt to kill the kernel.
818 # Attempt to kill the kernel.
819 try:
819 try:
820 self.kernel.kill()
820 self.kernel.kill()
821 except OSError, e:
821 except OSError, e:
822 # In Windows, we will get an Access Denied error if the process
822 # In Windows, we will get an Access Denied error if the process
823 # has already terminated. Ignore it.
823 # has already terminated. Ignore it.
824 if not (sys.platform == 'win32' and e.winerror == 5):
824 if not (sys.platform == 'win32' and e.winerror == 5):
825 raise
825 raise
826 self.kernel = None
826 self.kernel = None
827 else:
827 else:
828 raise RuntimeError("Cannot kill kernel. No kernel is running!")
828 raise RuntimeError("Cannot kill kernel. No kernel is running!")
829
829
830 def interrupt_kernel(self):
830 def interrupt_kernel(self):
831 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
831 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
832 well supported on all platforms.
832 well supported on all platforms.
833 """
833 """
834 if self.has_kernel:
834 if self.has_kernel:
835 if sys.platform == 'win32':
835 if sys.platform == 'win32':
836 from parentpoller import ParentPollerWindows as Poller
836 from parentpoller import ParentPollerWindows as Poller
837 Poller.send_interrupt(self.kernel.win32_interrupt_event)
837 Poller.send_interrupt(self.kernel.win32_interrupt_event)
838 else:
838 else:
839 self.kernel.send_signal(signal.SIGINT)
839 self.kernel.send_signal(signal.SIGINT)
840 else:
840 else:
841 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
841 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
842
842
843 def signal_kernel(self, signum):
843 def signal_kernel(self, signum):
844 """ Sends a signal to the kernel. Note that since only SIGTERM is
844 """ Sends a signal to the kernel. Note that since only SIGTERM is
845 supported on Windows, this function is only useful on Unix systems.
845 supported on Windows, this function is only useful on Unix systems.
846 """
846 """
847 if self.has_kernel:
847 if self.has_kernel:
848 self.kernel.send_signal(signum)
848 self.kernel.send_signal(signum)
849 else:
849 else:
850 raise RuntimeError("Cannot signal kernel. No kernel is running!")
850 raise RuntimeError("Cannot signal kernel. No kernel is running!")
851
851
852 @property
852 @property
853 def is_alive(self):
853 def is_alive(self):
854 """Is the kernel process still running?"""
854 """Is the kernel process still running?"""
855 # FIXME: not using a heartbeat means this method is broken for any
855 # FIXME: not using a heartbeat means this method is broken for any
856 # remote kernel, it's only capable of handling local kernels.
856 # remote kernel, it's only capable of handling local kernels.
857 if self.has_kernel:
857 if self.has_kernel:
858 if self.kernel.poll() is None:
858 if self.kernel.poll() is None:
859 return True
859 return True
860 else:
860 else:
861 return False
861 return False
862 else:
862 else:
863 # We didn't start the kernel with this KernelManager so we don't
863 # We didn't start the kernel with this KernelManager so we don't
864 # know if it is running. We should use a heartbeat for this case.
864 # know if it is running. We should use a heartbeat for this case.
865 return True
865 return True
866
866
867 #--------------------------------------------------------------------------
867 #--------------------------------------------------------------------------
868 # Channels used for communication with the kernel:
868 # Channels used for communication with the kernel:
869 #--------------------------------------------------------------------------
869 #--------------------------------------------------------------------------
870
870
871 @property
871 @property
872 def xreq_channel(self):
872 def xreq_channel(self):
873 """Get the REQ socket channel object to make requests of the kernel."""
873 """Get the REQ socket channel object to make requests of the kernel."""
874 if self._xreq_channel is None:
874 if self._xreq_channel is None:
875 self._xreq_channel = self.xreq_channel_class(self.context,
875 self._xreq_channel = self.xreq_channel_class(self.context,
876 self.session,
876 self.session,
877 self.xreq_address)
877 self.xreq_address)
878 return self._xreq_channel
878 return self._xreq_channel
879
879
880 @property
880 @property
881 def sub_channel(self):
881 def sub_channel(self):
882 """Get the SUB socket channel object."""
882 """Get the SUB socket channel object."""
883 if self._sub_channel is None:
883 if self._sub_channel is None:
884 self._sub_channel = self.sub_channel_class(self.context,
884 self._sub_channel = self.sub_channel_class(self.context,
885 self.session,
885 self.session,
886 self.sub_address)
886 self.sub_address)
887 return self._sub_channel
887 return self._sub_channel
888
888
889 @property
889 @property
890 def rep_channel(self):
890 def rep_channel(self):
891 """Get the REP socket channel object to handle stdin (raw_input)."""
891 """Get the REP socket channel object to handle stdin (raw_input)."""
892 if self._rep_channel is None:
892 if self._rep_channel is None:
893 self._rep_channel = self.rep_channel_class(self.context,
893 self._rep_channel = self.rep_channel_class(self.context,
894 self.session,
894 self.session,
895 self.rep_address)
895 self.rep_address)
896 return self._rep_channel
896 return self._rep_channel
897
897
898 @property
898 @property
899 def hb_channel(self):
899 def hb_channel(self):
900 """Get the REP socket channel object to handle stdin (raw_input)."""
900 """Get the REP socket channel object to handle stdin (raw_input)."""
901 if self._hb_channel is None:
901 if self._hb_channel is None:
902 self._hb_channel = self.hb_channel_class(self.context,
902 self._hb_channel = self.hb_channel_class(self.context,
903 self.session,
903 self.session,
904 self.hb_address)
904 self.hb_address)
905 return self._hb_channel
905 return self._hb_channel
@@ -1,286 +1,296 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 # Standard library imports.
17 # Standard library imports.
18 import __builtin__
18 import __builtin__
19 from code import CommandCompiler
19 from code import CommandCompiler
20 import sys
20 import sys
21 import time
21 import time
22 import traceback
22 import traceback
23
23
24 # System library imports.
24 # System library imports.
25 import zmq
25 import zmq
26
26
27 # Local imports.
27 # Local imports.
28 from IPython.utils.traitlets import HasTraits, Instance
28 from IPython.utils.traitlets import HasTraits, Instance
29 from completer import KernelCompleter
29 from completer import KernelCompleter
30 from entry_point import base_launch_kernel, make_default_main
30 from entry_point import base_launch_kernel, make_default_main
31 from session import Session, Message
31 from session import Session, Message
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Main kernel class
34 # Main kernel class
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class Kernel(HasTraits):
37 class Kernel(HasTraits):
38
38
39 # Private interface
39 # Private interface
40
40
41 # This is a dict of port number that the kernel is listening on. It is set
41 # This is a dict of port number that the kernel is listening on. It is set
42 # by record_ports and used by connect_request.
42 # by record_ports and used by connect_request.
43 _recorded_ports = None
43 _recorded_ports = None
44
44
45 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
46 # Kernel interface
46 # Kernel interface
47 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
48
48
49 session = Instance(Session)
49 session = Instance(Session)
50 reply_socket = Instance('zmq.Socket')
50 reply_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
53
53
54 def __init__(self, **kwargs):
54 def __init__(self, **kwargs):
55 super(Kernel, self).__init__(**kwargs)
55 super(Kernel, self).__init__(**kwargs)
56 self.user_ns = {}
56 self.user_ns = {}
57 self.history = []
57 self.history = []
58 self.compiler = CommandCompiler()
58 self.compiler = CommandCompiler()
59 self.completer = KernelCompleter(self.user_ns)
59 self.completer = KernelCompleter(self.user_ns)
60
60
61 # Build dict of handlers for message types
61 # Build dict of handlers for message types
62 msg_types = [ 'execute_request', 'complete_request',
62 msg_types = [ 'execute_request', 'complete_request',
63 'object_info_request' ]
63 'object_info_request', 'shutdown_request' ]
64 self.handlers = {}
64 self.handlers = {}
65 for msg_type in msg_types:
65 for msg_type in msg_types:
66 self.handlers[msg_type] = getattr(self, msg_type)
66 self.handlers[msg_type] = getattr(self, msg_type)
67
67
68 def start(self):
68 def start(self):
69 """ Start the kernel main loop.
69 """ Start the kernel main loop.
70 """
70 """
71 while True:
71 while True:
72 ident = self.reply_socket.recv()
72 ident = self.reply_socket.recv()
73 assert self.reply_socket.rcvmore(), "Missing message part."
73 assert self.reply_socket.rcvmore(), "Missing message part."
74 msg = self.reply_socket.recv_json()
74 msg = self.reply_socket.recv_json()
75 omsg = Message(msg)
75 omsg = Message(msg)
76 print>>sys.__stdout__
76 print>>sys.__stdout__
77 print>>sys.__stdout__, omsg
77 print>>sys.__stdout__, omsg
78 handler = self.handlers.get(omsg.msg_type, None)
78 handler = self.handlers.get(omsg.msg_type, None)
79 if handler is None:
79 if handler is None:
80 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
80 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
81 else:
81 else:
82 handler(ident, omsg)
82 handler(ident, omsg)
83
83
84 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
84 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
85 """Record the ports that this kernel is using.
85 """Record the ports that this kernel is using.
86
86
87 The creator of the Kernel instance must call this methods if they
87 The creator of the Kernel instance must call this methods if they
88 want the :meth:`connect_request` method to return the port numbers.
88 want the :meth:`connect_request` method to return the port numbers.
89 """
89 """
90 self._recorded_ports = {
90 self._recorded_ports = {
91 'xrep_port' : xrep_port,
91 'xrep_port' : xrep_port,
92 'pub_port' : pub_port,
92 'pub_port' : pub_port,
93 'req_port' : req_port,
93 'req_port' : req_port,
94 'hb_port' : hb_port
94 'hb_port' : hb_port
95 }
95 }
96
96
97 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
98 # Kernel request handlers
98 # Kernel request handlers
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100
100
101 def execute_request(self, ident, parent):
101 def execute_request(self, ident, parent):
102 try:
102 try:
103 code = parent[u'content'][u'code']
103 code = parent[u'content'][u'code']
104 except:
104 except:
105 print>>sys.__stderr__, "Got bad msg: "
105 print>>sys.__stderr__, "Got bad msg: "
106 print>>sys.__stderr__, Message(parent)
106 print>>sys.__stderr__, Message(parent)
107 return
107 return
108 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
108 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
109 self.pub_socket.send_json(pyin_msg)
109 self.pub_socket.send_json(pyin_msg)
110
110
111 try:
111 try:
112 comp_code = self.compiler(code, '<zmq-kernel>')
112 comp_code = self.compiler(code, '<zmq-kernel>')
113
113
114 # Replace raw_input. Note that is not sufficient to replace
114 # Replace raw_input. Note that is not sufficient to replace
115 # raw_input in the user namespace.
115 # raw_input in the user namespace.
116 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
116 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
117 __builtin__.raw_input = raw_input
117 __builtin__.raw_input = raw_input
118
118
119 # Set the parent message of the display hook and out streams.
119 # Set the parent message of the display hook and out streams.
120 sys.displayhook.set_parent(parent)
120 sys.displayhook.set_parent(parent)
121 sys.stdout.set_parent(parent)
121 sys.stdout.set_parent(parent)
122 sys.stderr.set_parent(parent)
122 sys.stderr.set_parent(parent)
123
123
124 exec comp_code in self.user_ns, self.user_ns
124 exec comp_code in self.user_ns, self.user_ns
125 except:
125 except:
126 etype, evalue, tb = sys.exc_info()
126 etype, evalue, tb = sys.exc_info()
127 tb = traceback.format_exception(etype, evalue, tb)
127 tb = traceback.format_exception(etype, evalue, tb)
128 exc_content = {
128 exc_content = {
129 u'status' : u'error',
129 u'status' : u'error',
130 u'traceback' : tb,
130 u'traceback' : tb,
131 u'ename' : unicode(etype.__name__),
131 u'ename' : unicode(etype.__name__),
132 u'evalue' : unicode(evalue)
132 u'evalue' : unicode(evalue)
133 }
133 }
134 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
134 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
135 self.pub_socket.send_json(exc_msg)
135 self.pub_socket.send_json(exc_msg)
136 reply_content = exc_content
136 reply_content = exc_content
137 else:
137 else:
138 reply_content = { 'status' : 'ok', 'payload' : {} }
138 reply_content = { 'status' : 'ok', 'payload' : {} }
139
139
140 # Flush output before sending the reply.
140 # Flush output before sending the reply.
141 sys.stderr.flush()
141 sys.stderr.flush()
142 sys.stdout.flush()
142 sys.stdout.flush()
143
143
144 # Send the reply.
144 # Send the reply.
145 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
145 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
146 print>>sys.__stdout__, Message(reply_msg)
146 print>>sys.__stdout__, Message(reply_msg)
147 self.reply_socket.send(ident, zmq.SNDMORE)
147 self.reply_socket.send(ident, zmq.SNDMORE)
148 self.reply_socket.send_json(reply_msg)
148 self.reply_socket.send_json(reply_msg)
149 if reply_msg['content']['status'] == u'error':
149 if reply_msg['content']['status'] == u'error':
150 self._abort_queue()
150 self._abort_queue()
151
151
152 def complete_request(self, ident, parent):
152 def complete_request(self, ident, parent):
153 matches = {'matches' : self._complete(parent),
153 matches = {'matches' : self._complete(parent),
154 'status' : 'ok'}
154 'status' : 'ok'}
155 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
155 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
156 matches, parent, ident)
156 matches, parent, ident)
157 print >> sys.__stdout__, completion_msg
157 print >> sys.__stdout__, completion_msg
158
158
159 def object_info_request(self, ident, parent):
159 def object_info_request(self, ident, parent):
160 context = parent['content']['oname'].split('.')
160 context = parent['content']['oname'].split('.')
161 object_info = self._object_info(context)
161 object_info = self._object_info(context)
162 msg = self.session.send(self.reply_socket, 'object_info_reply',
162 msg = self.session.send(self.reply_socket, 'object_info_reply',
163 object_info, parent, ident)
163 object_info, parent, ident)
164 print >> sys.__stdout__, msg
164 print >> sys.__stdout__, msg
165
165
166 def shutdown_request(self, ident, parent):
167 content = dict(parent['content'])
168 msg = self.session.send(self.reply_socket, 'shutdown_reply',
169 content, parent, ident)
170 msg = self.session.send(self.pub_socket, 'shutdown_reply',
171 content, parent, ident)
172 print >> sys.__stdout__, msg
173 time.sleep(0.1)
174 sys.exit(0)
175
166 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
167 # Protected interface
177 # Protected interface
168 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
169
179
170 def _abort_queue(self):
180 def _abort_queue(self):
171 while True:
181 while True:
172 try:
182 try:
173 ident = self.reply_socket.recv(zmq.NOBLOCK)
183 ident = self.reply_socket.recv(zmq.NOBLOCK)
174 except zmq.ZMQError, e:
184 except zmq.ZMQError, e:
175 if e.errno == zmq.EAGAIN:
185 if e.errno == zmq.EAGAIN:
176 break
186 break
177 else:
187 else:
178 assert self.reply_socket.rcvmore(), "Missing message part."
188 assert self.reply_socket.rcvmore(), "Missing message part."
179 msg = self.reply_socket.recv_json()
189 msg = self.reply_socket.recv_json()
180 print>>sys.__stdout__, "Aborting:"
190 print>>sys.__stdout__, "Aborting:"
181 print>>sys.__stdout__, Message(msg)
191 print>>sys.__stdout__, Message(msg)
182 msg_type = msg['msg_type']
192 msg_type = msg['msg_type']
183 reply_type = msg_type.split('_')[0] + '_reply'
193 reply_type = msg_type.split('_')[0] + '_reply'
184 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
194 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
185 print>>sys.__stdout__, Message(reply_msg)
195 print>>sys.__stdout__, Message(reply_msg)
186 self.reply_socket.send(ident,zmq.SNDMORE)
196 self.reply_socket.send(ident,zmq.SNDMORE)
187 self.reply_socket.send_json(reply_msg)
197 self.reply_socket.send_json(reply_msg)
188 # We need to wait a bit for requests to come in. This can probably
198 # We need to wait a bit for requests to come in. This can probably
189 # be set shorter for true asynchronous clients.
199 # be set shorter for true asynchronous clients.
190 time.sleep(0.1)
200 time.sleep(0.1)
191
201
192 def _raw_input(self, prompt, ident, parent):
202 def _raw_input(self, prompt, ident, parent):
193 # Flush output before making the request.
203 # Flush output before making the request.
194 sys.stderr.flush()
204 sys.stderr.flush()
195 sys.stdout.flush()
205 sys.stdout.flush()
196
206
197 # Send the input request.
207 # Send the input request.
198 content = dict(prompt=prompt)
208 content = dict(prompt=prompt)
199 msg = self.session.msg(u'input_request', content, parent)
209 msg = self.session.msg(u'input_request', content, parent)
200 self.req_socket.send_json(msg)
210 self.req_socket.send_json(msg)
201
211
202 # Await a response.
212 # Await a response.
203 reply = self.req_socket.recv_json()
213 reply = self.req_socket.recv_json()
204 try:
214 try:
205 value = reply['content']['value']
215 value = reply['content']['value']
206 except:
216 except:
207 print>>sys.__stderr__, "Got bad raw_input reply: "
217 print>>sys.__stderr__, "Got bad raw_input reply: "
208 print>>sys.__stderr__, Message(parent)
218 print>>sys.__stderr__, Message(parent)
209 value = ''
219 value = ''
210 return value
220 return value
211
221
212 def _complete(self, msg):
222 def _complete(self, msg):
213 return self.completer.complete(msg.content.line, msg.content.text)
223 return self.completer.complete(msg.content.line, msg.content.text)
214
224
215 def _object_info(self, context):
225 def _object_info(self, context):
216 symbol, leftover = self._symbol_from_context(context)
226 symbol, leftover = self._symbol_from_context(context)
217 if symbol is not None and not leftover:
227 if symbol is not None and not leftover:
218 doc = getattr(symbol, '__doc__', '')
228 doc = getattr(symbol, '__doc__', '')
219 else:
229 else:
220 doc = ''
230 doc = ''
221 object_info = dict(docstring = doc)
231 object_info = dict(docstring = doc)
222 return object_info
232 return object_info
223
233
224 def _symbol_from_context(self, context):
234 def _symbol_from_context(self, context):
225 if not context:
235 if not context:
226 return None, context
236 return None, context
227
237
228 base_symbol_string = context[0]
238 base_symbol_string = context[0]
229 symbol = self.user_ns.get(base_symbol_string, None)
239 symbol = self.user_ns.get(base_symbol_string, None)
230 if symbol is None:
240 if symbol is None:
231 symbol = __builtin__.__dict__.get(base_symbol_string, None)
241 symbol = __builtin__.__dict__.get(base_symbol_string, None)
232 if symbol is None:
242 if symbol is None:
233 return None, context
243 return None, context
234
244
235 context = context[1:]
245 context = context[1:]
236 for i, name in enumerate(context):
246 for i, name in enumerate(context):
237 new_symbol = getattr(symbol, name, None)
247 new_symbol = getattr(symbol, name, None)
238 if new_symbol is None:
248 if new_symbol is None:
239 return symbol, context[i:]
249 return symbol, context[i:]
240 else:
250 else:
241 symbol = new_symbol
251 symbol = new_symbol
242
252
243 return symbol, []
253 return symbol, []
244
254
245 #-----------------------------------------------------------------------------
255 #-----------------------------------------------------------------------------
246 # Kernel main and launch functions
256 # Kernel main and launch functions
247 #-----------------------------------------------------------------------------
257 #-----------------------------------------------------------------------------
248
258
249 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
259 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
250 independent=False):
260 independent=False):
251 """ Launches a localhost kernel, binding to the specified ports.
261 """ Launches a localhost kernel, binding to the specified ports.
252
262
253 Parameters
263 Parameters
254 ----------
264 ----------
255 xrep_port : int, optional
265 xrep_port : int, optional
256 The port to use for XREP channel.
266 The port to use for XREP channel.
257
267
258 pub_port : int, optional
268 pub_port : int, optional
259 The port to use for the SUB channel.
269 The port to use for the SUB channel.
260
270
261 req_port : int, optional
271 req_port : int, optional
262 The port to use for the REQ (raw input) channel.
272 The port to use for the REQ (raw input) channel.
263
273
264 hb_port : int, optional
274 hb_port : int, optional
265 The port to use for the hearbeat REP channel.
275 The port to use for the hearbeat REP channel.
266
276
267 independent : bool, optional (default False)
277 independent : bool, optional (default False)
268 If set, the kernel process is guaranteed to survive if this process
278 If set, the kernel process is guaranteed to survive if this process
269 dies. If not set, an effort is made to ensure that the kernel is killed
279 dies. If not set, an effort is made to ensure that the kernel is killed
270 when this process dies. Note that in this case it is still good practice
280 when this process dies. Note that in this case it is still good practice
271 to kill kernels manually before exiting.
281 to kill kernels manually before exiting.
272
282
273 Returns
283 Returns
274 -------
284 -------
275 A tuple of form:
285 A tuple of form:
276 (kernel_process, xrep_port, pub_port, req_port)
286 (kernel_process, xrep_port, pub_port, req_port)
277 where kernel_process is a Popen object and the ports are integers.
287 where kernel_process is a Popen object and the ports are integers.
278 """
288 """
279 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
289 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
280 xrep_port, pub_port, req_port, hb_port,
290 xrep_port, pub_port, req_port, hb_port,
281 independent)
291 independent)
282
292
283 main = make_default_main(Kernel)
293 main = make_default_main(Kernel)
284
294
285 if __name__ == '__main__':
295 if __name__ == '__main__':
286 main()
296 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