##// END OF EJS Templates
Rename 'instant_death' to 'now' as per code review.
Fernando Perez -
Show More
@@ -1,545 +1,544 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
6
7 # System library imports
7 # System library imports
8 from pygments.lexers import PythonLexer
8 from pygments.lexers import PythonLexer
9 from PyQt4 import QtCore, QtGui
9 from PyQt4 import QtCore, QtGui
10
10
11 # Local imports
11 # Local imports
12 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
12 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
13 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
13 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
14 from IPython.utils.traitlets import Bool
14 from IPython.utils.traitlets import Bool
15 from bracket_matcher import BracketMatcher
15 from bracket_matcher import BracketMatcher
16 from call_tip_widget import CallTipWidget
16 from call_tip_widget import CallTipWidget
17 from completion_lexer import CompletionLexer
17 from completion_lexer import CompletionLexer
18 from history_console_widget import HistoryConsoleWidget
18 from history_console_widget import HistoryConsoleWidget
19 from pygments_highlighter import PygmentsHighlighter
19 from pygments_highlighter import PygmentsHighlighter
20
20
21
21
22 class FrontendHighlighter(PygmentsHighlighter):
22 class FrontendHighlighter(PygmentsHighlighter):
23 """ A PygmentsHighlighter that can be turned on and off and that ignores
23 """ A PygmentsHighlighter that can be turned on and off and that ignores
24 prompts.
24 prompts.
25 """
25 """
26
26
27 def __init__(self, frontend):
27 def __init__(self, frontend):
28 super(FrontendHighlighter, self).__init__(frontend._control.document())
28 super(FrontendHighlighter, self).__init__(frontend._control.document())
29 self._current_offset = 0
29 self._current_offset = 0
30 self._frontend = frontend
30 self._frontend = frontend
31 self.highlighting_on = False
31 self.highlighting_on = False
32
32
33 def highlightBlock(self, qstring):
33 def highlightBlock(self, qstring):
34 """ Highlight a block of text. Reimplemented to highlight selectively.
34 """ Highlight a block of text. Reimplemented to highlight selectively.
35 """
35 """
36 if not self.highlighting_on:
36 if not self.highlighting_on:
37 return
37 return
38
38
39 # The input to this function is unicode string that may contain
39 # The input to this function is unicode string that may contain
40 # paragraph break characters, non-breaking spaces, etc. Here we acquire
40 # paragraph break characters, non-breaking spaces, etc. Here we acquire
41 # the string as plain text so we can compare it.
41 # the string as plain text so we can compare it.
42 current_block = self.currentBlock()
42 current_block = self.currentBlock()
43 string = self._frontend._get_block_plain_text(current_block)
43 string = self._frontend._get_block_plain_text(current_block)
44
44
45 # Decide whether to check for the regular or continuation prompt.
45 # Decide whether to check for the regular or continuation prompt.
46 if current_block.contains(self._frontend._prompt_pos):
46 if current_block.contains(self._frontend._prompt_pos):
47 prompt = self._frontend._prompt
47 prompt = self._frontend._prompt
48 else:
48 else:
49 prompt = self._frontend._continuation_prompt
49 prompt = self._frontend._continuation_prompt
50
50
51 # Don't highlight the part of the string that contains the prompt.
51 # Don't highlight the part of the string that contains the prompt.
52 if string.startswith(prompt):
52 if string.startswith(prompt):
53 self._current_offset = len(prompt)
53 self._current_offset = len(prompt)
54 qstring.remove(0, len(prompt))
54 qstring.remove(0, len(prompt))
55 else:
55 else:
56 self._current_offset = 0
56 self._current_offset = 0
57
57
58 PygmentsHighlighter.highlightBlock(self, qstring)
58 PygmentsHighlighter.highlightBlock(self, qstring)
59
59
60 def rehighlightBlock(self, block):
60 def rehighlightBlock(self, block):
61 """ Reimplemented to temporarily enable highlighting if disabled.
61 """ Reimplemented to temporarily enable highlighting if disabled.
62 """
62 """
63 old = self.highlighting_on
63 old = self.highlighting_on
64 self.highlighting_on = True
64 self.highlighting_on = True
65 super(FrontendHighlighter, self).rehighlightBlock(block)
65 super(FrontendHighlighter, self).rehighlightBlock(block)
66 self.highlighting_on = old
66 self.highlighting_on = old
67
67
68 def setFormat(self, start, count, format):
68 def setFormat(self, start, count, format):
69 """ Reimplemented to highlight selectively.
69 """ Reimplemented to highlight selectively.
70 """
70 """
71 start += self._current_offset
71 start += self._current_offset
72 PygmentsHighlighter.setFormat(self, start, count, format)
72 PygmentsHighlighter.setFormat(self, start, count, format)
73
73
74
74
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 """ A Qt frontend for a generic Python kernel.
76 """ A Qt frontend for a generic Python kernel.
77 """
77 """
78
78
79 # An option and corresponding signal for overriding the default kernel
79 # An option and corresponding signal for overriding the default kernel
80 # interrupt behavior.
80 # interrupt behavior.
81 custom_interrupt = Bool(False)
81 custom_interrupt = Bool(False)
82 custom_interrupt_requested = QtCore.pyqtSignal()
82 custom_interrupt_requested = QtCore.pyqtSignal()
83
83
84 # An option and corresponding signals for overriding the default kernel
84 # An option and corresponding signals for overriding the default kernel
85 # restart behavior.
85 # restart behavior.
86 custom_restart = Bool(False)
86 custom_restart = Bool(False)
87 custom_restart_kernel_died = QtCore.pyqtSignal(float)
87 custom_restart_kernel_died = QtCore.pyqtSignal(float)
88 custom_restart_requested = QtCore.pyqtSignal()
88 custom_restart_requested = QtCore.pyqtSignal()
89
89
90 # Emitted when an 'execute_reply' has been received from the kernel and
90 # Emitted when an 'execute_reply' has been received from the kernel and
91 # processed by the FrontendWidget.
91 # processed by the FrontendWidget.
92 executed = QtCore.pyqtSignal(object)
92 executed = QtCore.pyqtSignal(object)
93
93
94 # Emitted when an exit request has been received from the kernel.
94 # Emitted when an exit request has been received from the kernel.
95 exit_requested = QtCore.pyqtSignal()
95 exit_requested = QtCore.pyqtSignal()
96
96
97 # Protected class variables.
97 # Protected class variables.
98 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
98 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
99 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
99 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
100 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
100 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
101 _input_splitter_class = InputSplitter
101 _input_splitter_class = InputSplitter
102
102
103 #---------------------------------------------------------------------------
103 #---------------------------------------------------------------------------
104 # 'object' interface
104 # 'object' interface
105 #---------------------------------------------------------------------------
105 #---------------------------------------------------------------------------
106
106
107 def __init__(self, *args, **kw):
107 def __init__(self, *args, **kw):
108 super(FrontendWidget, self).__init__(*args, **kw)
108 super(FrontendWidget, self).__init__(*args, **kw)
109
109
110 # FrontendWidget protected variables.
110 # FrontendWidget protected variables.
111 self._bracket_matcher = BracketMatcher(self._control)
111 self._bracket_matcher = BracketMatcher(self._control)
112 self._call_tip_widget = CallTipWidget(self._control)
112 self._call_tip_widget = CallTipWidget(self._control)
113 self._completion_lexer = CompletionLexer(PythonLexer())
113 self._completion_lexer = CompletionLexer(PythonLexer())
114 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
114 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
115 self._hidden = False
115 self._hidden = False
116 self._highlighter = FrontendHighlighter(self)
116 self._highlighter = FrontendHighlighter(self)
117 self._input_splitter = self._input_splitter_class(input_mode='cell')
117 self._input_splitter = self._input_splitter_class(input_mode='cell')
118 self._kernel_manager = None
118 self._kernel_manager = None
119 self._possible_kernel_restart = False
119 self._possible_kernel_restart = False
120 self._request_info = {}
120 self._request_info = {}
121
121
122 # Configure the ConsoleWidget.
122 # Configure the ConsoleWidget.
123 self.tab_width = 4
123 self.tab_width = 4
124 self._set_continuation_prompt('... ')
124 self._set_continuation_prompt('... ')
125
125
126 # Configure actions.
126 # Configure actions.
127 action = self._copy_raw_action
127 action = self._copy_raw_action
128 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
128 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
129 action.setEnabled(False)
129 action.setEnabled(False)
130 action.setShortcut(QtGui.QKeySequence(key))
130 action.setShortcut(QtGui.QKeySequence(key))
131 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
131 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
132 action.triggered.connect(self.copy_raw)
132 action.triggered.connect(self.copy_raw)
133 self.copy_available.connect(action.setEnabled)
133 self.copy_available.connect(action.setEnabled)
134 self.addAction(action)
134 self.addAction(action)
135
135
136 # Connect signal handlers.
136 # Connect signal handlers.
137 document = self._control.document()
137 document = self._control.document()
138 document.contentsChange.connect(self._document_contents_change)
138 document.contentsChange.connect(self._document_contents_change)
139
139
140 #---------------------------------------------------------------------------
140 #---------------------------------------------------------------------------
141 # 'ConsoleWidget' public interface
141 # 'ConsoleWidget' public interface
142 #---------------------------------------------------------------------------
142 #---------------------------------------------------------------------------
143
143
144 def copy(self):
144 def copy(self):
145 """ Copy the currently selected text to the clipboard, removing prompts.
145 """ Copy the currently selected text to the clipboard, removing prompts.
146 """
146 """
147 text = unicode(self._control.textCursor().selection().toPlainText())
147 text = unicode(self._control.textCursor().selection().toPlainText())
148 if text:
148 if text:
149 lines = map(transform_classic_prompt, text.splitlines())
149 lines = map(transform_classic_prompt, text.splitlines())
150 text = '\n'.join(lines)
150 text = '\n'.join(lines)
151 QtGui.QApplication.clipboard().setText(text)
151 QtGui.QApplication.clipboard().setText(text)
152
152
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154 # 'ConsoleWidget' abstract interface
154 # 'ConsoleWidget' abstract interface
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156
156
157 def _is_complete(self, source, interactive):
157 def _is_complete(self, source, interactive):
158 """ Returns whether 'source' can be completely processed and a new
158 """ Returns whether 'source' can be completely processed and a new
159 prompt created. When triggered by an Enter/Return key press,
159 prompt created. When triggered by an Enter/Return key press,
160 'interactive' is True; otherwise, it is False.
160 'interactive' is True; otherwise, it is False.
161 """
161 """
162 complete = self._input_splitter.push(source)
162 complete = self._input_splitter.push(source)
163 if interactive:
163 if interactive:
164 complete = not self._input_splitter.push_accepts_more()
164 complete = not self._input_splitter.push_accepts_more()
165 return complete
165 return complete
166
166
167 def _execute(self, source, hidden):
167 def _execute(self, source, hidden):
168 """ Execute 'source'. If 'hidden', do not show any output.
168 """ Execute 'source'. If 'hidden', do not show any output.
169
169
170 See parent class :meth:`execute` docstring for full details.
170 See parent class :meth:`execute` docstring for full details.
171 """
171 """
172 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
172 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
173 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
173 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
174 self._hidden = hidden
174 self._hidden = hidden
175
175
176 def _prompt_started_hook(self):
176 def _prompt_started_hook(self):
177 """ Called immediately after a new prompt is displayed.
177 """ Called immediately after a new prompt is displayed.
178 """
178 """
179 if not self._reading:
179 if not self._reading:
180 self._highlighter.highlighting_on = True
180 self._highlighter.highlighting_on = True
181
181
182 def _prompt_finished_hook(self):
182 def _prompt_finished_hook(self):
183 """ Called immediately after a prompt is finished, i.e. when some input
183 """ Called immediately after a prompt is finished, i.e. when some input
184 will be processed and a new prompt displayed.
184 will be processed and a new prompt displayed.
185 """
185 """
186 if not self._reading:
186 if not self._reading:
187 self._highlighter.highlighting_on = False
187 self._highlighter.highlighting_on = False
188
188
189 def _tab_pressed(self):
189 def _tab_pressed(self):
190 """ Called when the tab key is pressed. Returns whether to continue
190 """ Called when the tab key is pressed. Returns whether to continue
191 processing the event.
191 processing the event.
192 """
192 """
193 # Perform tab completion if:
193 # Perform tab completion if:
194 # 1) The cursor is in the input buffer.
194 # 1) The cursor is in the input buffer.
195 # 2) There is a non-whitespace character before the cursor.
195 # 2) There is a non-whitespace character before the cursor.
196 text = self._get_input_buffer_cursor_line()
196 text = self._get_input_buffer_cursor_line()
197 if text is None:
197 if text is None:
198 return False
198 return False
199 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
199 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
200 if complete:
200 if complete:
201 self._complete()
201 self._complete()
202 return not complete
202 return not complete
203
203
204 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
205 # 'ConsoleWidget' protected interface
205 # 'ConsoleWidget' protected interface
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207
207
208 def _context_menu_make(self, pos):
208 def _context_menu_make(self, pos):
209 """ Reimplemented to add an action for raw copy.
209 """ Reimplemented to add an action for raw copy.
210 """
210 """
211 menu = super(FrontendWidget, self)._context_menu_make(pos)
211 menu = super(FrontendWidget, self)._context_menu_make(pos)
212 for before_action in menu.actions():
212 for before_action in menu.actions():
213 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
213 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
214 QtGui.QKeySequence.ExactMatch:
214 QtGui.QKeySequence.ExactMatch:
215 menu.insertAction(before_action, self._copy_raw_action)
215 menu.insertAction(before_action, self._copy_raw_action)
216 break
216 break
217 return menu
217 return menu
218
218
219 def _event_filter_console_keypress(self, event):
219 def _event_filter_console_keypress(self, event):
220 """ Reimplemented for execution interruption and smart backspace.
220 """ Reimplemented for execution interruption and smart backspace.
221 """
221 """
222 key = event.key()
222 key = event.key()
223 if self._control_key_down(event.modifiers(), include_command=False):
223 if self._control_key_down(event.modifiers(), include_command=False):
224
224
225 if key == QtCore.Qt.Key_C and self._executing:
225 if key == QtCore.Qt.Key_C and self._executing:
226 self.interrupt_kernel()
226 self.interrupt_kernel()
227 return True
227 return True
228
228
229 elif key == QtCore.Qt.Key_Period:
229 elif key == QtCore.Qt.Key_Period:
230 message = 'Are you sure you want to restart the kernel?'
230 message = 'Are you sure you want to restart the kernel?'
231 self.restart_kernel(message, instant_death=False)
231 self.restart_kernel(message, now=False)
232 return True
232 return True
233
233
234 elif not event.modifiers() & QtCore.Qt.AltModifier:
234 elif not event.modifiers() & QtCore.Qt.AltModifier:
235
235
236 # Smart backspace: remove four characters in one backspace if:
236 # Smart backspace: remove four characters in one backspace if:
237 # 1) everything left of the cursor is whitespace
237 # 1) everything left of the cursor is whitespace
238 # 2) the four characters immediately left of the cursor are spaces
238 # 2) the four characters immediately left of the cursor are spaces
239 if key == QtCore.Qt.Key_Backspace:
239 if key == QtCore.Qt.Key_Backspace:
240 col = self._get_input_buffer_cursor_column()
240 col = self._get_input_buffer_cursor_column()
241 cursor = self._control.textCursor()
241 cursor = self._control.textCursor()
242 if col > 3 and not cursor.hasSelection():
242 if col > 3 and not cursor.hasSelection():
243 text = self._get_input_buffer_cursor_line()[:col]
243 text = self._get_input_buffer_cursor_line()[:col]
244 if text.endswith(' ') and not text.strip():
244 if text.endswith(' ') and not text.strip():
245 cursor.movePosition(QtGui.QTextCursor.Left,
245 cursor.movePosition(QtGui.QTextCursor.Left,
246 QtGui.QTextCursor.KeepAnchor, 4)
246 QtGui.QTextCursor.KeepAnchor, 4)
247 cursor.removeSelectedText()
247 cursor.removeSelectedText()
248 return True
248 return True
249
249
250 return super(FrontendWidget, self)._event_filter_console_keypress(event)
250 return super(FrontendWidget, self)._event_filter_console_keypress(event)
251
251
252 def _insert_continuation_prompt(self, cursor):
252 def _insert_continuation_prompt(self, cursor):
253 """ Reimplemented for auto-indentation.
253 """ Reimplemented for auto-indentation.
254 """
254 """
255 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
255 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
256 cursor.insertText(' ' * self._input_splitter.indent_spaces)
256 cursor.insertText(' ' * self._input_splitter.indent_spaces)
257
257
258 #---------------------------------------------------------------------------
258 #---------------------------------------------------------------------------
259 # 'BaseFrontendMixin' abstract interface
259 # 'BaseFrontendMixin' abstract interface
260 #---------------------------------------------------------------------------
260 #---------------------------------------------------------------------------
261
261
262 def _handle_complete_reply(self, rep):
262 def _handle_complete_reply(self, rep):
263 """ Handle replies for tab completion.
263 """ Handle replies for tab completion.
264 """
264 """
265 cursor = self._get_cursor()
265 cursor = self._get_cursor()
266 info = self._request_info.get('complete')
266 info = self._request_info.get('complete')
267 if info and info.id == rep['parent_header']['msg_id'] and \
267 if info and info.id == rep['parent_header']['msg_id'] and \
268 info.pos == cursor.position():
268 info.pos == cursor.position():
269 text = '.'.join(self._get_context())
269 text = '.'.join(self._get_context())
270 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
270 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
271 self._complete_with_items(cursor, rep['content']['matches'])
271 self._complete_with_items(cursor, rep['content']['matches'])
272
272
273 def _handle_execute_reply(self, msg):
273 def _handle_execute_reply(self, msg):
274 """ Handles replies for code execution.
274 """ Handles replies for code execution.
275 """
275 """
276 info = self._request_info.get('execute')
276 info = self._request_info.get('execute')
277 if info and info.id == msg['parent_header']['msg_id'] and \
277 if info and info.id == msg['parent_header']['msg_id'] and \
278 info.kind == 'user' and not self._hidden:
278 info.kind == 'user' and not self._hidden:
279 # Make sure that all output from the SUB channel has been processed
279 # Make sure that all output from the SUB channel has been processed
280 # before writing a new prompt.
280 # before writing a new prompt.
281 self.kernel_manager.sub_channel.flush()
281 self.kernel_manager.sub_channel.flush()
282
282
283 # Reset the ANSI style information to prevent bad text in stdout
283 # Reset the ANSI style information to prevent bad text in stdout
284 # from messing up our colors. We're not a true terminal so we're
284 # from messing up our colors. We're not a true terminal so we're
285 # allowed to do this.
285 # allowed to do this.
286 if self.ansi_codes:
286 if self.ansi_codes:
287 self._ansi_processor.reset_sgr()
287 self._ansi_processor.reset_sgr()
288
288
289 content = msg['content']
289 content = msg['content']
290 status = content['status']
290 status = content['status']
291 if status == 'ok':
291 if status == 'ok':
292 self._process_execute_ok(msg)
292 self._process_execute_ok(msg)
293 elif status == 'error':
293 elif status == 'error':
294 self._process_execute_error(msg)
294 self._process_execute_error(msg)
295 elif status == 'abort':
295 elif status == 'abort':
296 self._process_execute_abort(msg)
296 self._process_execute_abort(msg)
297
297
298 self._show_interpreter_prompt_for_reply(msg)
298 self._show_interpreter_prompt_for_reply(msg)
299 self.executed.emit(msg)
299 self.executed.emit(msg)
300
300
301 def _handle_input_request(self, msg):
301 def _handle_input_request(self, msg):
302 """ Handle requests for raw_input.
302 """ Handle requests for raw_input.
303 """
303 """
304 if self._hidden:
304 if self._hidden:
305 raise RuntimeError('Request for raw input during hidden execution.')
305 raise RuntimeError('Request for raw input during hidden execution.')
306
306
307 # Make sure that all output from the SUB channel has been processed
307 # Make sure that all output from the SUB channel has been processed
308 # before entering readline mode.
308 # before entering readline mode.
309 self.kernel_manager.sub_channel.flush()
309 self.kernel_manager.sub_channel.flush()
310
310
311 def callback(line):
311 def callback(line):
312 self.kernel_manager.rep_channel.input(line)
312 self.kernel_manager.rep_channel.input(line)
313 self._readline(msg['content']['prompt'], callback=callback)
313 self._readline(msg['content']['prompt'], callback=callback)
314
314
315 def _handle_kernel_died(self, since_last_heartbeat):
315 def _handle_kernel_died(self, since_last_heartbeat):
316 """ Handle the kernel's death by asking if the user wants to restart.
316 """ Handle the kernel's death by asking if the user wants to restart.
317 """
317 """
318 message = 'The kernel heartbeat has been inactive for %.2f ' \
318 message = 'The kernel heartbeat has been inactive for %.2f ' \
319 'seconds. Do you want to restart the kernel? You may ' \
319 'seconds. Do you want to restart the kernel? You may ' \
320 'first want to check the network connection.' % \
320 'first want to check the network connection.' % \
321 since_last_heartbeat
321 since_last_heartbeat
322 if self.custom_restart:
322 if self.custom_restart:
323 self.custom_restart_kernel_died.emit(since_last_heartbeat)
323 self.custom_restart_kernel_died.emit(since_last_heartbeat)
324 else:
324 else:
325 self.restart_kernel(message, instant_death=True)
325 self.restart_kernel(message, now=True)
326
326
327 def _handle_object_info_reply(self, rep):
327 def _handle_object_info_reply(self, rep):
328 """ Handle replies for call tips.
328 """ Handle replies for call tips.
329 """
329 """
330 cursor = self._get_cursor()
330 cursor = self._get_cursor()
331 info = self._request_info.get('call_tip')
331 info = self._request_info.get('call_tip')
332 if info and info.id == rep['parent_header']['msg_id'] and \
332 if info and info.id == rep['parent_header']['msg_id'] and \
333 info.pos == cursor.position():
333 info.pos == cursor.position():
334 doc = rep['content']['docstring']
334 doc = rep['content']['docstring']
335 if doc:
335 if doc:
336 self._call_tip_widget.show_docstring(doc)
336 self._call_tip_widget.show_docstring(doc)
337
337
338 def _handle_pyout(self, msg):
338 def _handle_pyout(self, msg):
339 """ Handle display hook output.
339 """ Handle display hook output.
340 """
340 """
341 if not self._hidden and self._is_from_this_session(msg):
341 if not self._hidden and self._is_from_this_session(msg):
342 self._append_plain_text(msg['content']['data'] + '\n')
342 self._append_plain_text(msg['content']['data'] + '\n')
343
343
344 def _handle_stream(self, msg):
344 def _handle_stream(self, msg):
345 """ Handle stdout, stderr, and stdin.
345 """ Handle stdout, stderr, and stdin.
346 """
346 """
347 if not self._hidden and self._is_from_this_session(msg):
347 if not self._hidden and self._is_from_this_session(msg):
348 # Most consoles treat tabs as being 8 space characters. Convert tabs
348 # Most consoles treat tabs as being 8 space characters. Convert tabs
349 # to spaces so that output looks as expected regardless of this
349 # to spaces so that output looks as expected regardless of this
350 # widget's tab width.
350 # widget's tab width.
351 text = msg['content']['data'].expandtabs(8)
351 text = msg['content']['data'].expandtabs(8)
352
352
353 self._append_plain_text(text)
353 self._append_plain_text(text)
354 self._control.moveCursor(QtGui.QTextCursor.End)
354 self._control.moveCursor(QtGui.QTextCursor.End)
355
355
356 def _started_channels(self):
356 def _started_channels(self):
357 """ Called when the KernelManager channels have started listening or
357 """ Called when the KernelManager channels have started listening or
358 when the frontend is assigned an already listening KernelManager.
358 when the frontend is assigned an already listening KernelManager.
359 """
359 """
360 self._control.clear()
360 self._control.clear()
361 self._append_plain_text(self._get_banner())
361 self._append_plain_text(self._get_banner())
362 self._show_interpreter_prompt()
362 self._show_interpreter_prompt()
363
363
364 def _stopped_channels(self):
364 def _stopped_channels(self):
365 """ Called when the KernelManager channels have stopped listening or
365 """ Called when the KernelManager channels have stopped listening or
366 when a listening KernelManager is removed from the frontend.
366 when a listening KernelManager is removed from the frontend.
367 """
367 """
368 self._executing = self._reading = False
368 self._executing = self._reading = False
369 self._highlighter.highlighting_on = False
369 self._highlighter.highlighting_on = False
370
370
371 #---------------------------------------------------------------------------
371 #---------------------------------------------------------------------------
372 # 'FrontendWidget' public interface
372 # 'FrontendWidget' public interface
373 #---------------------------------------------------------------------------
373 #---------------------------------------------------------------------------
374
374
375 def copy_raw(self):
375 def copy_raw(self):
376 """ Copy the currently selected text to the clipboard without attempting
376 """ Copy the currently selected text to the clipboard without attempting
377 to remove prompts or otherwise alter the text.
377 to remove prompts or otherwise alter the text.
378 """
378 """
379 self._control.copy()
379 self._control.copy()
380
380
381 def execute_file(self, path, hidden=False):
381 def execute_file(self, path, hidden=False):
382 """ Attempts to execute file with 'path'. If 'hidden', no output is
382 """ Attempts to execute file with 'path'. If 'hidden', no output is
383 shown.
383 shown.
384 """
384 """
385 self.execute('execfile("%s")' % path, hidden=hidden)
385 self.execute('execfile("%s")' % path, hidden=hidden)
386
386
387 def interrupt_kernel(self):
387 def interrupt_kernel(self):
388 """ Attempts to interrupt the running kernel.
388 """ Attempts to interrupt the running kernel.
389 """
389 """
390 if self.custom_interrupt:
390 if self.custom_interrupt:
391 self.custom_interrupt_requested.emit()
391 self.custom_interrupt_requested.emit()
392 elif self.kernel_manager.has_kernel:
392 elif self.kernel_manager.has_kernel:
393 self.kernel_manager.interrupt_kernel()
393 self.kernel_manager.interrupt_kernel()
394 else:
394 else:
395 self._append_plain_text('Kernel process is either remote or '
395 self._append_plain_text('Kernel process is either remote or '
396 'unspecified. Cannot interrupt.\n')
396 'unspecified. Cannot interrupt.\n')
397
397
398 def restart_kernel(self, message, instant_death=False):
398 def restart_kernel(self, message, now=False):
399 """ Attempts to restart the running kernel.
399 """ Attempts to restart the running kernel.
400 """
400 """
401 # FIXME: instant_death should be configurable via a checkbox in the
401 # FIXME: now should be configurable via a checkbox in the
402 # dialog. Right now at least the heartbeat path sets it to True and
402 # dialog. Right now at least the heartbeat path sets it to True and
403 # the manual restart to False. But those should just be the
403 # the manual restart to False. But those should just be the
404 # pre-selected states of a checkbox that the user could override if so
404 # pre-selected states of a checkbox that the user could override if so
405 # desired. But I don't know enough Qt to go implementing the checkbox
405 # desired. But I don't know enough Qt to go implementing the checkbox
406 # now.
406 # now.
407
407
408 # We want to make sure that if this dialog is already happening, that
408 # We want to make sure that if this dialog is already happening, that
409 # other signals don't trigger it again. This can happen when the
409 # other signals don't trigger it again. This can happen when the
410 # kernel_died heartbeat signal is emitted and the user is slow to
410 # kernel_died heartbeat signal is emitted and the user is slow to
411 # respond to the dialog.
411 # respond to the dialog.
412 if not self._possible_kernel_restart:
412 if not self._possible_kernel_restart:
413 if self.custom_restart:
413 if self.custom_restart:
414 self.custom_restart_requested.emit()
414 self.custom_restart_requested.emit()
415 elif self.kernel_manager.has_kernel:
415 elif self.kernel_manager.has_kernel:
416 # Setting this to True will prevent this logic from happening
416 # Setting this to True will prevent this logic from happening
417 # again until the current pass is completed.
417 # again until the current pass is completed.
418 self._possible_kernel_restart = True
418 self._possible_kernel_restart = True
419 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
419 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
420 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
420 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
421 message, buttons)
421 message, buttons)
422 if result == QtGui.QMessageBox.Yes:
422 if result == QtGui.QMessageBox.Yes:
423 try:
423 try:
424 self.kernel_manager.restart_kernel(
424 self.kernel_manager.restart_kernel(now=now)
425 instant_death=instant_death)
426 except RuntimeError:
425 except RuntimeError:
427 message = 'Kernel started externally. Cannot restart.\n'
426 message = 'Kernel started externally. Cannot restart.\n'
428 self._append_plain_text(message)
427 self._append_plain_text(message)
429 else:
428 else:
430 self._stopped_channels()
429 self._stopped_channels()
431 self._append_plain_text('Kernel restarting...\n')
430 self._append_plain_text('Kernel restarting...\n')
432 self._show_interpreter_prompt()
431 self._show_interpreter_prompt()
433 # This might need to be moved to another location?
432 # This might need to be moved to another location?
434 self._possible_kernel_restart = False
433 self._possible_kernel_restart = False
435 else:
434 else:
436 self._append_plain_text('Kernel process is either remote or '
435 self._append_plain_text('Kernel process is either remote or '
437 'unspecified. Cannot restart.\n')
436 'unspecified. Cannot restart.\n')
438
437
439 #---------------------------------------------------------------------------
438 #---------------------------------------------------------------------------
440 # 'FrontendWidget' protected interface
439 # 'FrontendWidget' protected interface
441 #---------------------------------------------------------------------------
440 #---------------------------------------------------------------------------
442
441
443 def _call_tip(self):
442 def _call_tip(self):
444 """ Shows a call tip, if appropriate, at the current cursor location.
443 """ Shows a call tip, if appropriate, at the current cursor location.
445 """
444 """
446 # Decide if it makes sense to show a call tip
445 # Decide if it makes sense to show a call tip
447 cursor = self._get_cursor()
446 cursor = self._get_cursor()
448 cursor.movePosition(QtGui.QTextCursor.Left)
447 cursor.movePosition(QtGui.QTextCursor.Left)
449 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
448 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
450 return False
449 return False
451 context = self._get_context(cursor)
450 context = self._get_context(cursor)
452 if not context:
451 if not context:
453 return False
452 return False
454
453
455 # Send the metadata request to the kernel
454 # Send the metadata request to the kernel
456 name = '.'.join(context)
455 name = '.'.join(context)
457 msg_id = self.kernel_manager.xreq_channel.object_info(name)
456 msg_id = self.kernel_manager.xreq_channel.object_info(name)
458 pos = self._get_cursor().position()
457 pos = self._get_cursor().position()
459 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
458 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
460 return True
459 return True
461
460
462 def _complete(self):
461 def _complete(self):
463 """ Performs completion at the current cursor location.
462 """ Performs completion at the current cursor location.
464 """
463 """
465 context = self._get_context()
464 context = self._get_context()
466 if context:
465 if context:
467 # Send the completion request to the kernel
466 # Send the completion request to the kernel
468 msg_id = self.kernel_manager.xreq_channel.complete(
467 msg_id = self.kernel_manager.xreq_channel.complete(
469 '.'.join(context), # text
468 '.'.join(context), # text
470 self._get_input_buffer_cursor_line(), # line
469 self._get_input_buffer_cursor_line(), # line
471 self._get_input_buffer_cursor_column(), # cursor_pos
470 self._get_input_buffer_cursor_column(), # cursor_pos
472 self.input_buffer) # block
471 self.input_buffer) # block
473 pos = self._get_cursor().position()
472 pos = self._get_cursor().position()
474 info = self._CompletionRequest(msg_id, pos)
473 info = self._CompletionRequest(msg_id, pos)
475 self._request_info['complete'] = info
474 self._request_info['complete'] = info
476
475
477 def _get_banner(self):
476 def _get_banner(self):
478 """ Gets a banner to display at the beginning of a session.
477 """ Gets a banner to display at the beginning of a session.
479 """
478 """
480 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
479 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
481 '"license" for more information.'
480 '"license" for more information.'
482 return banner % (sys.version, sys.platform)
481 return banner % (sys.version, sys.platform)
483
482
484 def _get_context(self, cursor=None):
483 def _get_context(self, cursor=None):
485 """ Gets the context for the specified cursor (or the current cursor
484 """ Gets the context for the specified cursor (or the current cursor
486 if none is specified).
485 if none is specified).
487 """
486 """
488 if cursor is None:
487 if cursor is None:
489 cursor = self._get_cursor()
488 cursor = self._get_cursor()
490 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
489 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
491 QtGui.QTextCursor.KeepAnchor)
490 QtGui.QTextCursor.KeepAnchor)
492 text = unicode(cursor.selection().toPlainText())
491 text = unicode(cursor.selection().toPlainText())
493 return self._completion_lexer.get_context(text)
492 return self._completion_lexer.get_context(text)
494
493
495 def _process_execute_abort(self, msg):
494 def _process_execute_abort(self, msg):
496 """ Process a reply for an aborted execution request.
495 """ Process a reply for an aborted execution request.
497 """
496 """
498 self._append_plain_text("ERROR: execution aborted\n")
497 self._append_plain_text("ERROR: execution aborted\n")
499
498
500 def _process_execute_error(self, msg):
499 def _process_execute_error(self, msg):
501 """ Process a reply for an execution request that resulted in an error.
500 """ Process a reply for an execution request that resulted in an error.
502 """
501 """
503 content = msg['content']
502 content = msg['content']
504 traceback = ''.join(content['traceback'])
503 traceback = ''.join(content['traceback'])
505 self._append_plain_text(traceback)
504 self._append_plain_text(traceback)
506
505
507 def _process_execute_ok(self, msg):
506 def _process_execute_ok(self, msg):
508 """ Process a reply for a successful execution equest.
507 """ Process a reply for a successful execution equest.
509 """
508 """
510 payload = msg['content']['payload']
509 payload = msg['content']['payload']
511 for item in payload:
510 for item in payload:
512 if not self._process_execute_payload(item):
511 if not self._process_execute_payload(item):
513 warning = 'Warning: received unknown payload of type %s'
512 warning = 'Warning: received unknown payload of type %s'
514 print(warning % repr(item['source']))
513 print(warning % repr(item['source']))
515
514
516 def _process_execute_payload(self, item):
515 def _process_execute_payload(self, item):
517 """ Process a single payload item from the list of payload items in an
516 """ Process a single payload item from the list of payload items in an
518 execution reply. Returns whether the payload was handled.
517 execution reply. Returns whether the payload was handled.
519 """
518 """
520 # The basic FrontendWidget doesn't handle payloads, as they are a
519 # The basic FrontendWidget doesn't handle payloads, as they are a
521 # mechanism for going beyond the standard Python interpreter model.
520 # mechanism for going beyond the standard Python interpreter model.
522 return False
521 return False
523
522
524 def _show_interpreter_prompt(self):
523 def _show_interpreter_prompt(self):
525 """ Shows a prompt for the interpreter.
524 """ Shows a prompt for the interpreter.
526 """
525 """
527 self._show_prompt('>>> ')
526 self._show_prompt('>>> ')
528
527
529 def _show_interpreter_prompt_for_reply(self, msg):
528 def _show_interpreter_prompt_for_reply(self, msg):
530 """ Shows a prompt for the interpreter given an 'execute_reply' message.
529 """ Shows a prompt for the interpreter given an 'execute_reply' message.
531 """
530 """
532 self._show_interpreter_prompt()
531 self._show_interpreter_prompt()
533
532
534 #------ Signal handlers ----------------------------------------------------
533 #------ Signal handlers ----------------------------------------------------
535
534
536 def _document_contents_change(self, position, removed, added):
535 def _document_contents_change(self, position, removed, added):
537 """ Called whenever the document's content changes. Display a call tip
536 """ Called whenever the document's content changes. Display a call tip
538 if appropriate.
537 if appropriate.
539 """
538 """
540 # Calculate where the cursor should be *after* the change:
539 # Calculate where the cursor should be *after* the change:
541 position += added
540 position += added
542
541
543 document = self._control.document()
542 document = self._control.document()
544 if position == self._get_cursor().position():
543 if position == self._get_cursor().position():
545 self._call_tip()
544 self._call_tip()
@@ -1,881 +1,881 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 from Queue import Queue, Empty
19 from Queue import Queue, Empty
20 from subprocess import Popen
20 from subprocess import Popen
21 import signal
21 import signal
22 import sys
22 import sys
23 from threading import Thread
23 from threading import Thread
24 import time
24 import time
25
25
26 # System library imports.
26 # System library imports.
27 import zmq
27 import zmq
28 from zmq import POLLIN, POLLOUT, POLLERR
28 from zmq import POLLIN, POLLOUT, POLLERR
29 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.utils import io
32 from IPython.utils import io
33 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
34 from session import Session
34 from session import Session
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Constants and exceptions
37 # Constants and exceptions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 LOCALHOST = '127.0.0.1'
40 LOCALHOST = '127.0.0.1'
41
41
42 class InvalidPortNumber(Exception):
42 class InvalidPortNumber(Exception):
43 pass
43 pass
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Utility functions
46 # Utility functions
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 # some utilities to validate message structure, these might get moved elsewhere
49 # some utilities to validate message structure, these might get moved elsewhere
50 # if they prove to have more generic utility
50 # if they prove to have more generic utility
51
51
52 def validate_string_list(lst):
52 def validate_string_list(lst):
53 """Validate that the input is a list of strings.
53 """Validate that the input is a list of strings.
54
54
55 Raises ValueError if not."""
55 Raises ValueError if not."""
56 if not isinstance(lst, list):
56 if not isinstance(lst, list):
57 raise ValueError('input %r must be a list' % lst)
57 raise ValueError('input %r must be a list' % lst)
58 for x in lst:
58 for x in lst:
59 if not isinstance(x, basestring):
59 if not isinstance(x, basestring):
60 raise ValueError('element %r in list must be a string' % x)
60 raise ValueError('element %r in list must be a string' % x)
61
61
62
62
63 def validate_string_dict(dct):
63 def validate_string_dict(dct):
64 """Validate that the input is a dict with string keys and values.
64 """Validate that the input is a dict with string keys and values.
65
65
66 Raises ValueError if not."""
66 Raises ValueError if not."""
67 for k,v in dct.iteritems():
67 for k,v in dct.iteritems():
68 if not isinstance(k, basestring):
68 if not isinstance(k, basestring):
69 raise ValueError('key %r in dict must be a string' % k)
69 raise ValueError('key %r in dict must be a string' % k)
70 if not isinstance(v, basestring):
70 if not isinstance(v, basestring):
71 raise ValueError('value %r in dict must be a string' % v)
71 raise ValueError('value %r in dict must be a string' % v)
72
72
73
73
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75 # ZMQ Socket Channel classes
75 # ZMQ Socket Channel classes
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77
77
78 class ZmqSocketChannel(Thread):
78 class ZmqSocketChannel(Thread):
79 """The base class for the channels that use ZMQ sockets.
79 """The base class for the channels that use ZMQ sockets.
80 """
80 """
81 context = None
81 context = None
82 session = None
82 session = None
83 socket = None
83 socket = None
84 ioloop = None
84 ioloop = None
85 iostate = None
85 iostate = None
86 _address = None
86 _address = None
87
87
88 def __init__(self, context, session, address):
88 def __init__(self, context, session, address):
89 """Create a channel
89 """Create a channel
90
90
91 Parameters
91 Parameters
92 ----------
92 ----------
93 context : :class:`zmq.Context`
93 context : :class:`zmq.Context`
94 The ZMQ context to use.
94 The ZMQ context to use.
95 session : :class:`session.Session`
95 session : :class:`session.Session`
96 The session to use.
96 The session to use.
97 address : tuple
97 address : tuple
98 Standard (ip, port) tuple that the kernel is listening on.
98 Standard (ip, port) tuple that the kernel is listening on.
99 """
99 """
100 super(ZmqSocketChannel, self).__init__()
100 super(ZmqSocketChannel, self).__init__()
101 self.daemon = True
101 self.daemon = True
102
102
103 self.context = context
103 self.context = context
104 self.session = session
104 self.session = session
105 if address[1] == 0:
105 if address[1] == 0:
106 message = 'The port number for a channel cannot be 0.'
106 message = 'The port number for a channel cannot be 0.'
107 raise InvalidPortNumber(message)
107 raise InvalidPortNumber(message)
108 self._address = address
108 self._address = address
109
109
110 def stop(self):
110 def stop(self):
111 """Stop the channel's activity.
111 """Stop the channel's activity.
112
112
113 This calls :method:`Thread.join` and returns when the thread
113 This calls :method:`Thread.join` and returns when the thread
114 terminates. :class:`RuntimeError` will be raised if
114 terminates. :class:`RuntimeError` will be raised if
115 :method:`self.start` is called again.
115 :method:`self.start` is called again.
116 """
116 """
117 self.join()
117 self.join()
118
118
119 @property
119 @property
120 def address(self):
120 def address(self):
121 """Get the channel's address as an (ip, port) tuple.
121 """Get the channel's address as an (ip, port) tuple.
122
122
123 By the default, the address is (localhost, 0), where 0 means a random
123 By the default, the address is (localhost, 0), where 0 means a random
124 port.
124 port.
125 """
125 """
126 return self._address
126 return self._address
127
127
128 def add_io_state(self, state):
128 def add_io_state(self, state):
129 """Add IO state to the eventloop.
129 """Add IO state to the eventloop.
130
130
131 Parameters
131 Parameters
132 ----------
132 ----------
133 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
133 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
134 The IO state flag to set.
134 The IO state flag to set.
135
135
136 This is thread safe as it uses the thread safe IOLoop.add_callback.
136 This is thread safe as it uses the thread safe IOLoop.add_callback.
137 """
137 """
138 def add_io_state_callback():
138 def add_io_state_callback():
139 if not self.iostate & state:
139 if not self.iostate & state:
140 self.iostate = self.iostate | state
140 self.iostate = self.iostate | state
141 self.ioloop.update_handler(self.socket, self.iostate)
141 self.ioloop.update_handler(self.socket, self.iostate)
142 self.ioloop.add_callback(add_io_state_callback)
142 self.ioloop.add_callback(add_io_state_callback)
143
143
144 def drop_io_state(self, state):
144 def drop_io_state(self, state):
145 """Drop IO state from the eventloop.
145 """Drop IO state from the eventloop.
146
146
147 Parameters
147 Parameters
148 ----------
148 ----------
149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 The IO state flag to set.
150 The IO state flag to set.
151
151
152 This is thread safe as it uses the thread safe IOLoop.add_callback.
152 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 """
153 """
154 def drop_io_state_callback():
154 def drop_io_state_callback():
155 if self.iostate & state:
155 if self.iostate & state:
156 self.iostate = self.iostate & (~state)
156 self.iostate = self.iostate & (~state)
157 self.ioloop.update_handler(self.socket, self.iostate)
157 self.ioloop.update_handler(self.socket, self.iostate)
158 self.ioloop.add_callback(drop_io_state_callback)
158 self.ioloop.add_callback(drop_io_state_callback)
159
159
160
160
161 class XReqSocketChannel(ZmqSocketChannel):
161 class XReqSocketChannel(ZmqSocketChannel):
162 """The XREQ channel for issues request/replies to the kernel.
162 """The XREQ channel for issues request/replies to the kernel.
163 """
163 """
164
164
165 command_queue = None
165 command_queue = None
166
166
167 def __init__(self, context, session, address):
167 def __init__(self, context, session, address):
168 super(XReqSocketChannel, self).__init__(context, session, address)
168 super(XReqSocketChannel, self).__init__(context, session, address)
169 self.command_queue = Queue()
169 self.command_queue = Queue()
170 self.ioloop = ioloop.IOLoop()
170 self.ioloop = ioloop.IOLoop()
171
171
172 def run(self):
172 def run(self):
173 """The thread's main activity. Call start() instead."""
173 """The thread's main activity. Call start() instead."""
174 self.socket = self.context.socket(zmq.XREQ)
174 self.socket = self.context.socket(zmq.XREQ)
175 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
175 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
176 self.socket.connect('tcp://%s:%i' % self.address)
176 self.socket.connect('tcp://%s:%i' % self.address)
177 self.iostate = POLLERR|POLLIN
177 self.iostate = POLLERR|POLLIN
178 self.ioloop.add_handler(self.socket, self._handle_events,
178 self.ioloop.add_handler(self.socket, self._handle_events,
179 self.iostate)
179 self.iostate)
180 self.ioloop.start()
180 self.ioloop.start()
181
181
182 def stop(self):
182 def stop(self):
183 self.ioloop.stop()
183 self.ioloop.stop()
184 super(XReqSocketChannel, self).stop()
184 super(XReqSocketChannel, self).stop()
185
185
186 def call_handlers(self, msg):
186 def call_handlers(self, msg):
187 """This method is called in the ioloop thread when a message arrives.
187 """This method is called in the ioloop thread when a message arrives.
188
188
189 Subclasses should override this method to handle incoming messages.
189 Subclasses should override this method to handle incoming messages.
190 It is important to remember that this method is called in the thread
190 It is important to remember that this method is called in the thread
191 so that some logic must be done to ensure that the application leve
191 so that some logic must be done to ensure that the application leve
192 handlers are called in the application thread.
192 handlers are called in the application thread.
193 """
193 """
194 raise NotImplementedError('call_handlers must be defined in a subclass.')
194 raise NotImplementedError('call_handlers must be defined in a subclass.')
195
195
196 def execute(self, code, silent=False,
196 def execute(self, code, silent=False,
197 user_variables=None, user_expressions=None):
197 user_variables=None, user_expressions=None):
198 """Execute code in the kernel.
198 """Execute code in the kernel.
199
199
200 Parameters
200 Parameters
201 ----------
201 ----------
202 code : str
202 code : str
203 A string of Python code.
203 A string of Python code.
204
204
205 silent : bool, optional (default False)
205 silent : bool, optional (default False)
206 If set, the kernel will execute the code as quietly possible.
206 If set, the kernel will execute the code as quietly possible.
207
207
208 user_variables : list, optional
208 user_variables : list, optional
209 A list of variable names to pull from the user's namespace. They
209 A list of variable names to pull from the user's namespace. They
210 will come back as a dict with these names as keys and their
210 will come back as a dict with these names as keys and their
211 :func:`repr` as values.
211 :func:`repr` as values.
212
212
213 user_expressions : dict, optional
213 user_expressions : dict, optional
214 A dict with string keys and to pull from the user's
214 A dict with string keys and to pull from the user's
215 namespace. They will come back as a dict with these names as keys
215 namespace. They will come back as a dict with these names as keys
216 and their :func:`repr` as values.
216 and their :func:`repr` as values.
217
217
218 Returns
218 Returns
219 -------
219 -------
220 The msg_id of the message sent.
220 The msg_id of the message sent.
221 """
221 """
222 if user_variables is None:
222 if user_variables is None:
223 user_variables = []
223 user_variables = []
224 if user_expressions is None:
224 if user_expressions is None:
225 user_expressions = {}
225 user_expressions = {}
226
226
227 # Don't waste network traffic if inputs are invalid
227 # Don't waste network traffic if inputs are invalid
228 if not isinstance(code, basestring):
228 if not isinstance(code, basestring):
229 raise ValueError('code %r must be a string' % code)
229 raise ValueError('code %r must be a string' % code)
230 validate_string_list(user_variables)
230 validate_string_list(user_variables)
231 validate_string_dict(user_expressions)
231 validate_string_dict(user_expressions)
232
232
233 # Create class for content/msg creation. Related to, but possibly
233 # Create class for content/msg creation. Related to, but possibly
234 # not in Session.
234 # not in Session.
235 content = dict(code=code, silent=silent,
235 content = dict(code=code, silent=silent,
236 user_variables=user_variables,
236 user_variables=user_variables,
237 user_expressions=user_expressions)
237 user_expressions=user_expressions)
238 msg = self.session.msg('execute_request', content)
238 msg = self.session.msg('execute_request', content)
239 self._queue_request(msg)
239 self._queue_request(msg)
240 return msg['header']['msg_id']
240 return msg['header']['msg_id']
241
241
242 def complete(self, text, line, cursor_pos, block=None):
242 def complete(self, text, line, cursor_pos, block=None):
243 """Tab complete text in the kernel's namespace.
243 """Tab complete text in the kernel's namespace.
244
244
245 Parameters
245 Parameters
246 ----------
246 ----------
247 text : str
247 text : str
248 The text to complete.
248 The text to complete.
249 line : str
249 line : str
250 The full line of text that is the surrounding context for the
250 The full line of text that is the surrounding context for the
251 text to complete.
251 text to complete.
252 cursor_pos : int
252 cursor_pos : int
253 The position of the cursor in the line where the completion was
253 The position of the cursor in the line where the completion was
254 requested.
254 requested.
255 block : str, optional
255 block : str, optional
256 The full block of code in which the completion is being requested.
256 The full block of code in which the completion is being requested.
257
257
258 Returns
258 Returns
259 -------
259 -------
260 The msg_id of the message sent.
260 The msg_id of the message sent.
261 """
261 """
262 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
262 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
263 msg = self.session.msg('complete_request', content)
263 msg = self.session.msg('complete_request', content)
264 self._queue_request(msg)
264 self._queue_request(msg)
265 return msg['header']['msg_id']
265 return msg['header']['msg_id']
266
266
267 def object_info(self, oname):
267 def object_info(self, oname):
268 """Get metadata information about an object.
268 """Get metadata information about an object.
269
269
270 Parameters
270 Parameters
271 ----------
271 ----------
272 oname : str
272 oname : str
273 A string specifying the object name.
273 A string specifying the object name.
274
274
275 Returns
275 Returns
276 -------
276 -------
277 The msg_id of the message sent.
277 The msg_id of the message sent.
278 """
278 """
279 content = dict(oname=oname)
279 content = dict(oname=oname)
280 msg = self.session.msg('object_info_request', content)
280 msg = self.session.msg('object_info_request', content)
281 self._queue_request(msg)
281 self._queue_request(msg)
282 return msg['header']['msg_id']
282 return msg['header']['msg_id']
283
283
284 def history(self, index=None, raw=False, output=True):
284 def history(self, index=None, raw=False, output=True):
285 """Get the history list.
285 """Get the history list.
286
286
287 Parameters
287 Parameters
288 ----------
288 ----------
289 index : n or (n1, n2) or None
289 index : n or (n1, n2) or None
290 If n, then the last entries. If a tuple, then all in
290 If n, then the last entries. If a tuple, then all in
291 range(n1, n2). If None, then all entries. Raises IndexError if
291 range(n1, n2). If None, then all entries. Raises IndexError if
292 the format of index is incorrect.
292 the format of index is incorrect.
293 raw : bool
293 raw : bool
294 If True, return the raw input.
294 If True, return the raw input.
295 output : bool
295 output : bool
296 If True, then return the output as well.
296 If True, then return the output as well.
297
297
298 Returns
298 Returns
299 -------
299 -------
300 The msg_id of the message sent.
300 The msg_id of the message sent.
301 """
301 """
302 content = dict(index=index, raw=raw, output=output)
302 content = dict(index=index, raw=raw, output=output)
303 msg = self.session.msg('history_request', content)
303 msg = self.session.msg('history_request', content)
304 self._queue_request(msg)
304 self._queue_request(msg)
305 return msg['header']['msg_id']
305 return msg['header']['msg_id']
306
306
307 def shutdown(self):
307 def shutdown(self):
308 """Request an immediate kernel shutdown.
308 """Request an immediate kernel shutdown.
309
309
310 Upon receipt of the (empty) reply, client code can safely assume that
310 Upon receipt of the (empty) reply, client code can safely assume that
311 the kernel has shut down and it's safe to forcefully terminate it if
311 the kernel has shut down and it's safe to forcefully terminate it if
312 it's still alive.
312 it's still alive.
313
313
314 The kernel will send the reply via a function registered with Python's
314 The kernel will send the reply via a function registered with Python's
315 atexit module, ensuring it's truly done as the kernel is done with all
315 atexit module, ensuring it's truly done as the kernel is done with all
316 normal operation.
316 normal operation.
317 """
317 """
318 # Send quit message to kernel. Once we implement kernel-side setattr,
318 # Send quit message to kernel. Once we implement kernel-side setattr,
319 # this should probably be done that way, but for now this will do.
319 # this should probably be done that way, but for now this will do.
320 msg = self.session.msg('shutdown_request', {})
320 msg = self.session.msg('shutdown_request', {})
321 self._queue_request(msg)
321 self._queue_request(msg)
322 return msg['header']['msg_id']
322 return msg['header']['msg_id']
323
323
324 def _handle_events(self, socket, events):
324 def _handle_events(self, socket, events):
325 if events & POLLERR:
325 if events & POLLERR:
326 self._handle_err()
326 self._handle_err()
327 if events & POLLOUT:
327 if events & POLLOUT:
328 self._handle_send()
328 self._handle_send()
329 if events & POLLIN:
329 if events & POLLIN:
330 self._handle_recv()
330 self._handle_recv()
331
331
332 def _handle_recv(self):
332 def _handle_recv(self):
333 msg = self.socket.recv_json()
333 msg = self.socket.recv_json()
334 self.call_handlers(msg)
334 self.call_handlers(msg)
335
335
336 def _handle_send(self):
336 def _handle_send(self):
337 try:
337 try:
338 msg = self.command_queue.get(False)
338 msg = self.command_queue.get(False)
339 except Empty:
339 except Empty:
340 pass
340 pass
341 else:
341 else:
342 self.socket.send_json(msg)
342 self.socket.send_json(msg)
343 if self.command_queue.empty():
343 if self.command_queue.empty():
344 self.drop_io_state(POLLOUT)
344 self.drop_io_state(POLLOUT)
345
345
346 def _handle_err(self):
346 def _handle_err(self):
347 # We don't want to let this go silently, so eventually we should log.
347 # We don't want to let this go silently, so eventually we should log.
348 raise zmq.ZMQError()
348 raise zmq.ZMQError()
349
349
350 def _queue_request(self, msg):
350 def _queue_request(self, msg):
351 self.command_queue.put(msg)
351 self.command_queue.put(msg)
352 self.add_io_state(POLLOUT)
352 self.add_io_state(POLLOUT)
353
353
354
354
355 class SubSocketChannel(ZmqSocketChannel):
355 class SubSocketChannel(ZmqSocketChannel):
356 """The SUB channel which listens for messages that the kernel publishes.
356 """The SUB channel which listens for messages that the kernel publishes.
357 """
357 """
358
358
359 def __init__(self, context, session, address):
359 def __init__(self, context, session, address):
360 super(SubSocketChannel, self).__init__(context, session, address)
360 super(SubSocketChannel, self).__init__(context, session, address)
361 self.ioloop = ioloop.IOLoop()
361 self.ioloop = ioloop.IOLoop()
362
362
363 def run(self):
363 def run(self):
364 """The thread's main activity. Call start() instead."""
364 """The thread's main activity. Call start() instead."""
365 self.socket = self.context.socket(zmq.SUB)
365 self.socket = self.context.socket(zmq.SUB)
366 self.socket.setsockopt(zmq.SUBSCRIBE,'')
366 self.socket.setsockopt(zmq.SUBSCRIBE,'')
367 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
367 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
368 self.socket.connect('tcp://%s:%i' % self.address)
368 self.socket.connect('tcp://%s:%i' % self.address)
369 self.iostate = POLLIN|POLLERR
369 self.iostate = POLLIN|POLLERR
370 self.ioloop.add_handler(self.socket, self._handle_events,
370 self.ioloop.add_handler(self.socket, self._handle_events,
371 self.iostate)
371 self.iostate)
372 self.ioloop.start()
372 self.ioloop.start()
373
373
374 def stop(self):
374 def stop(self):
375 self.ioloop.stop()
375 self.ioloop.stop()
376 super(SubSocketChannel, self).stop()
376 super(SubSocketChannel, self).stop()
377
377
378 def call_handlers(self, msg):
378 def call_handlers(self, msg):
379 """This method is called in the ioloop thread when a message arrives.
379 """This method is called in the ioloop thread when a message arrives.
380
380
381 Subclasses should override this method to handle incoming messages.
381 Subclasses should override this method to handle incoming messages.
382 It is important to remember that this method is called in the thread
382 It is important to remember that this method is called in the thread
383 so that some logic must be done to ensure that the application leve
383 so that some logic must be done to ensure that the application leve
384 handlers are called in the application thread.
384 handlers are called in the application thread.
385 """
385 """
386 raise NotImplementedError('call_handlers must be defined in a subclass.')
386 raise NotImplementedError('call_handlers must be defined in a subclass.')
387
387
388 def flush(self, timeout=1.0):
388 def flush(self, timeout=1.0):
389 """Immediately processes all pending messages on the SUB channel.
389 """Immediately processes all pending messages on the SUB channel.
390
390
391 Callers should use this method to ensure that :method:`call_handlers`
391 Callers should use this method to ensure that :method:`call_handlers`
392 has been called for all messages that have been received on the
392 has been called for all messages that have been received on the
393 0MQ SUB socket of this channel.
393 0MQ SUB socket of this channel.
394
394
395 This method is thread safe.
395 This method is thread safe.
396
396
397 Parameters
397 Parameters
398 ----------
398 ----------
399 timeout : float, optional
399 timeout : float, optional
400 The maximum amount of time to spend flushing, in seconds. The
400 The maximum amount of time to spend flushing, in seconds. The
401 default is one second.
401 default is one second.
402 """
402 """
403 # We do the IOLoop callback process twice to ensure that the IOLoop
403 # We do the IOLoop callback process twice to ensure that the IOLoop
404 # gets to perform at least one full poll.
404 # gets to perform at least one full poll.
405 stop_time = time.time() + timeout
405 stop_time = time.time() + timeout
406 for i in xrange(2):
406 for i in xrange(2):
407 self._flushed = False
407 self._flushed = False
408 self.ioloop.add_callback(self._flush)
408 self.ioloop.add_callback(self._flush)
409 while not self._flushed and time.time() < stop_time:
409 while not self._flushed and time.time() < stop_time:
410 time.sleep(0.01)
410 time.sleep(0.01)
411
411
412 def _handle_events(self, socket, events):
412 def _handle_events(self, socket, events):
413 # Turn on and off POLLOUT depending on if we have made a request
413 # Turn on and off POLLOUT depending on if we have made a request
414 if events & POLLERR:
414 if events & POLLERR:
415 self._handle_err()
415 self._handle_err()
416 if events & POLLIN:
416 if events & POLLIN:
417 self._handle_recv()
417 self._handle_recv()
418
418
419 def _handle_err(self):
419 def _handle_err(self):
420 # We don't want to let this go silently, so eventually we should log.
420 # We don't want to let this go silently, so eventually we should log.
421 raise zmq.ZMQError()
421 raise zmq.ZMQError()
422
422
423 def _handle_recv(self):
423 def _handle_recv(self):
424 # Get all of the messages we can
424 # Get all of the messages we can
425 while True:
425 while True:
426 try:
426 try:
427 msg = self.socket.recv_json(zmq.NOBLOCK)
427 msg = self.socket.recv_json(zmq.NOBLOCK)
428 except zmq.ZMQError:
428 except zmq.ZMQError:
429 # Check the errno?
429 # Check the errno?
430 # Will this trigger POLLERR?
430 # Will this trigger POLLERR?
431 break
431 break
432 else:
432 else:
433 self.call_handlers(msg)
433 self.call_handlers(msg)
434
434
435 def _flush(self):
435 def _flush(self):
436 """Callback for :method:`self.flush`."""
436 """Callback for :method:`self.flush`."""
437 self._flushed = True
437 self._flushed = True
438
438
439
439
440 class RepSocketChannel(ZmqSocketChannel):
440 class RepSocketChannel(ZmqSocketChannel):
441 """A reply channel to handle raw_input requests that the kernel makes."""
441 """A reply channel to handle raw_input requests that the kernel makes."""
442
442
443 msg_queue = None
443 msg_queue = None
444
444
445 def __init__(self, context, session, address):
445 def __init__(self, context, session, address):
446 super(RepSocketChannel, self).__init__(context, session, address)
446 super(RepSocketChannel, self).__init__(context, session, address)
447 self.ioloop = ioloop.IOLoop()
447 self.ioloop = ioloop.IOLoop()
448 self.msg_queue = Queue()
448 self.msg_queue = Queue()
449
449
450 def run(self):
450 def run(self):
451 """The thread's main activity. Call start() instead."""
451 """The thread's main activity. Call start() instead."""
452 self.socket = self.context.socket(zmq.XREQ)
452 self.socket = self.context.socket(zmq.XREQ)
453 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
453 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
454 self.socket.connect('tcp://%s:%i' % self.address)
454 self.socket.connect('tcp://%s:%i' % self.address)
455 self.iostate = POLLERR|POLLIN
455 self.iostate = POLLERR|POLLIN
456 self.ioloop.add_handler(self.socket, self._handle_events,
456 self.ioloop.add_handler(self.socket, self._handle_events,
457 self.iostate)
457 self.iostate)
458 self.ioloop.start()
458 self.ioloop.start()
459
459
460 def stop(self):
460 def stop(self):
461 self.ioloop.stop()
461 self.ioloop.stop()
462 super(RepSocketChannel, self).stop()
462 super(RepSocketChannel, self).stop()
463
463
464 def call_handlers(self, msg):
464 def call_handlers(self, msg):
465 """This method is called in the ioloop thread when a message arrives.
465 """This method is called in the ioloop thread when a message arrives.
466
466
467 Subclasses should override this method to handle incoming messages.
467 Subclasses should override this method to handle incoming messages.
468 It is important to remember that this method is called in the thread
468 It is important to remember that this method is called in the thread
469 so that some logic must be done to ensure that the application leve
469 so that some logic must be done to ensure that the application leve
470 handlers are called in the application thread.
470 handlers are called in the application thread.
471 """
471 """
472 raise NotImplementedError('call_handlers must be defined in a subclass.')
472 raise NotImplementedError('call_handlers must be defined in a subclass.')
473
473
474 def input(self, string):
474 def input(self, string):
475 """Send a string of raw input to the kernel."""
475 """Send a string of raw input to the kernel."""
476 content = dict(value=string)
476 content = dict(value=string)
477 msg = self.session.msg('input_reply', content)
477 msg = self.session.msg('input_reply', content)
478 self._queue_reply(msg)
478 self._queue_reply(msg)
479
479
480 def _handle_events(self, socket, events):
480 def _handle_events(self, socket, events):
481 if events & POLLERR:
481 if events & POLLERR:
482 self._handle_err()
482 self._handle_err()
483 if events & POLLOUT:
483 if events & POLLOUT:
484 self._handle_send()
484 self._handle_send()
485 if events & POLLIN:
485 if events & POLLIN:
486 self._handle_recv()
486 self._handle_recv()
487
487
488 def _handle_recv(self):
488 def _handle_recv(self):
489 msg = self.socket.recv_json()
489 msg = self.socket.recv_json()
490 self.call_handlers(msg)
490 self.call_handlers(msg)
491
491
492 def _handle_send(self):
492 def _handle_send(self):
493 try:
493 try:
494 msg = self.msg_queue.get(False)
494 msg = self.msg_queue.get(False)
495 except Empty:
495 except Empty:
496 pass
496 pass
497 else:
497 else:
498 self.socket.send_json(msg)
498 self.socket.send_json(msg)
499 if self.msg_queue.empty():
499 if self.msg_queue.empty():
500 self.drop_io_state(POLLOUT)
500 self.drop_io_state(POLLOUT)
501
501
502 def _handle_err(self):
502 def _handle_err(self):
503 # We don't want to let this go silently, so eventually we should log.
503 # We don't want to let this go silently, so eventually we should log.
504 raise zmq.ZMQError()
504 raise zmq.ZMQError()
505
505
506 def _queue_reply(self, msg):
506 def _queue_reply(self, msg):
507 self.msg_queue.put(msg)
507 self.msg_queue.put(msg)
508 self.add_io_state(POLLOUT)
508 self.add_io_state(POLLOUT)
509
509
510
510
511 class HBSocketChannel(ZmqSocketChannel):
511 class HBSocketChannel(ZmqSocketChannel):
512 """The heartbeat channel which monitors the kernel heartbeat."""
512 """The heartbeat channel which monitors the kernel heartbeat."""
513
513
514 time_to_dead = 3.0
514 time_to_dead = 3.0
515 socket = None
515 socket = None
516 poller = None
516 poller = None
517 _running = None
517 _running = None
518 _pause = None
518 _pause = None
519
519
520 def __init__(self, context, session, address):
520 def __init__(self, context, session, address):
521 super(HBSocketChannel, self).__init__(context, session, address)
521 super(HBSocketChannel, self).__init__(context, session, address)
522 self._running = False
522 self._running = False
523 self._pause = False
523 self._pause = False
524
524
525 def _create_socket(self):
525 def _create_socket(self):
526 self.socket = self.context.socket(zmq.REQ)
526 self.socket = self.context.socket(zmq.REQ)
527 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
527 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
528 self.socket.connect('tcp://%s:%i' % self.address)
528 self.socket.connect('tcp://%s:%i' % self.address)
529 self.poller = zmq.Poller()
529 self.poller = zmq.Poller()
530 self.poller.register(self.socket, zmq.POLLIN)
530 self.poller.register(self.socket, zmq.POLLIN)
531
531
532 def run(self):
532 def run(self):
533 """The thread's main activity. Call start() instead."""
533 """The thread's main activity. Call start() instead."""
534 self._create_socket()
534 self._create_socket()
535 self._running = True
535 self._running = True
536 # Wait 2 seconds for the kernel to come up and the sockets to auto
536 # Wait 2 seconds for the kernel to come up and the sockets to auto
537 # connect. If we don't we will see the kernel as dead. Also, before
537 # connect. If we don't we will see the kernel as dead. Also, before
538 # the sockets are connected, the poller.poll line below is returning
538 # the sockets are connected, the poller.poll line below is returning
539 # too fast. This avoids that because the polling doesn't start until
539 # too fast. This avoids that because the polling doesn't start until
540 # after the sockets are connected.
540 # after the sockets are connected.
541 time.sleep(2.0)
541 time.sleep(2.0)
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 list,
570 # When the return value of poll() is an empty list,
571 # that is when things have gone wrong (zeromq bug).
571 # that is when things have gone wrong (zeromq bug).
572 # As long as it is not an empty list, poll is
572 # As long as it is not an empty list, poll is
573 # working correctly even if it returns quickly.
573 # working correctly even if it returns quickly.
574 # Note: poll timeout is in milliseconds.
574 # Note: poll timeout is in milliseconds.
575 self.poller.poll(1000*until_dead)
575 self.poller.poll(1000*until_dead)
576
576
577 since_last_heartbeat = time.time() - request_time
577 since_last_heartbeat = time.time() - request_time
578 if since_last_heartbeat > self.time_to_dead:
578 if since_last_heartbeat > self.time_to_dead:
579 self.call_handlers(since_last_heartbeat)
579 self.call_handlers(since_last_heartbeat)
580 break
580 break
581 else:
581 else:
582 # FIXME: We should probably log this instead.
582 # FIXME: We should probably log this instead.
583 raise
583 raise
584 else:
584 else:
585 until_dead = self.time_to_dead - (time.time() -
585 until_dead = self.time_to_dead - (time.time() -
586 request_time)
586 request_time)
587 if until_dead > 0.0:
587 if until_dead > 0.0:
588 #io.rprint('sleep...', self.time_to_dead) # dbg
588 #io.rprint('sleep...', self.time_to_dead) # dbg
589 time.sleep(until_dead)
589 time.sleep(until_dead)
590 break
590 break
591
591
592 def pause(self):
592 def pause(self):
593 """Pause the heartbeat."""
593 """Pause the heartbeat."""
594 self._pause = True
594 self._pause = True
595
595
596 def unpause(self):
596 def unpause(self):
597 """Unpause the heartbeat."""
597 """Unpause the heartbeat."""
598 self._pause = False
598 self._pause = False
599
599
600 def is_beating(self):
600 def is_beating(self):
601 """Is the heartbeat running and not paused."""
601 """Is the heartbeat running and not paused."""
602 if self.is_alive() and not self._pause:
602 if self.is_alive() and not self._pause:
603 return True
603 return True
604 else:
604 else:
605 return False
605 return False
606
606
607 def stop(self):
607 def stop(self):
608 self._running = False
608 self._running = False
609 super(HBSocketChannel, self).stop()
609 super(HBSocketChannel, self).stop()
610
610
611 def call_handlers(self, since_last_heartbeat):
611 def call_handlers(self, since_last_heartbeat):
612 """This method is called in the ioloop thread when a message arrives.
612 """This method is called in the ioloop thread when a message arrives.
613
613
614 Subclasses should override this method to handle incoming messages.
614 Subclasses should override this method to handle incoming messages.
615 It is important to remember that this method is called in the thread
615 It is important to remember that this method is called in the thread
616 so that some logic must be done to ensure that the application leve
616 so that some logic must be done to ensure that the application leve
617 handlers are called in the application thread.
617 handlers are called in the application thread.
618 """
618 """
619 raise NotImplementedError('call_handlers must be defined in a subclass.')
619 raise NotImplementedError('call_handlers must be defined in a subclass.')
620
620
621
621
622 #-----------------------------------------------------------------------------
622 #-----------------------------------------------------------------------------
623 # Main kernel manager class
623 # Main kernel manager class
624 #-----------------------------------------------------------------------------
624 #-----------------------------------------------------------------------------
625
625
626 class KernelManager(HasTraits):
626 class KernelManager(HasTraits):
627 """ Manages a kernel for a frontend.
627 """ Manages a kernel for a frontend.
628
628
629 The SUB channel is for the frontend to receive messages published by the
629 The SUB channel is for the frontend to receive messages published by the
630 kernel.
630 kernel.
631
631
632 The REQ channel is for the frontend to make requests of the kernel.
632 The REQ channel is for the frontend to make requests of the kernel.
633
633
634 The REP channel is for the kernel to request stdin (raw_input) from the
634 The REP channel is for the kernel to request stdin (raw_input) from the
635 frontend.
635 frontend.
636 """
636 """
637 # The PyZMQ Context to use for communication with the kernel.
637 # The PyZMQ Context to use for communication with the kernel.
638 context = Instance(zmq.Context,(),{})
638 context = Instance(zmq.Context,(),{})
639
639
640 # The Session to use for communication with the kernel.
640 # The Session to use for communication with the kernel.
641 session = Instance(Session,(),{})
641 session = Instance(Session,(),{})
642
642
643 # The kernel process with which the KernelManager is communicating.
643 # The kernel process with which the KernelManager is communicating.
644 kernel = Instance(Popen)
644 kernel = Instance(Popen)
645
645
646 # The addresses for the communication channels.
646 # The addresses for the communication channels.
647 xreq_address = TCPAddress((LOCALHOST, 0))
647 xreq_address = TCPAddress((LOCALHOST, 0))
648 sub_address = TCPAddress((LOCALHOST, 0))
648 sub_address = TCPAddress((LOCALHOST, 0))
649 rep_address = TCPAddress((LOCALHOST, 0))
649 rep_address = TCPAddress((LOCALHOST, 0))
650 hb_address = TCPAddress((LOCALHOST, 0))
650 hb_address = TCPAddress((LOCALHOST, 0))
651
651
652 # The classes to use for the various channels.
652 # The classes to use for the various channels.
653 xreq_channel_class = Type(XReqSocketChannel)
653 xreq_channel_class = Type(XReqSocketChannel)
654 sub_channel_class = Type(SubSocketChannel)
654 sub_channel_class = Type(SubSocketChannel)
655 rep_channel_class = Type(RepSocketChannel)
655 rep_channel_class = Type(RepSocketChannel)
656 hb_channel_class = Type(HBSocketChannel)
656 hb_channel_class = Type(HBSocketChannel)
657
657
658 # Protected traits.
658 # Protected traits.
659 _launch_args = Any
659 _launch_args = Any
660 _xreq_channel = Any
660 _xreq_channel = Any
661 _sub_channel = Any
661 _sub_channel = Any
662 _rep_channel = Any
662 _rep_channel = Any
663 _hb_channel = Any
663 _hb_channel = Any
664
664
665 #--------------------------------------------------------------------------
665 #--------------------------------------------------------------------------
666 # Channel management methods:
666 # Channel management methods:
667 #--------------------------------------------------------------------------
667 #--------------------------------------------------------------------------
668
668
669 def start_channels(self, xreq=True, sub=True, rep=True):
669 def start_channels(self, xreq=True, sub=True, rep=True):
670 """Starts the channels for this kernel, but not the heartbeat.
670 """Starts the channels for this kernel, but not the heartbeat.
671
671
672 This will create the channels if they do not exist and then start
672 This will create the channels if they do not exist and then start
673 them. If port numbers of 0 are being used (random ports) then you
673 them. If port numbers of 0 are being used (random ports) then you
674 must first call :method:`start_kernel`. If the channels have been
674 must first call :method:`start_kernel`. If the channels have been
675 stopped and you call this, :class:`RuntimeError` will be raised.
675 stopped and you call this, :class:`RuntimeError` will be raised.
676 """
676 """
677 if xreq:
677 if xreq:
678 self.xreq_channel.start()
678 self.xreq_channel.start()
679 if sub:
679 if sub:
680 self.sub_channel.start()
680 self.sub_channel.start()
681 if rep:
681 if rep:
682 self.rep_channel.start()
682 self.rep_channel.start()
683
683
684 def stop_channels(self):
684 def stop_channels(self):
685 """Stops all the running channels for this kernel.
685 """Stops all the running channels for this kernel.
686 """
686 """
687 if self.xreq_channel.is_alive():
687 if self.xreq_channel.is_alive():
688 self.xreq_channel.stop()
688 self.xreq_channel.stop()
689 if self.sub_channel.is_alive():
689 if self.sub_channel.is_alive():
690 self.sub_channel.stop()
690 self.sub_channel.stop()
691 if self.rep_channel.is_alive():
691 if self.rep_channel.is_alive():
692 self.rep_channel.stop()
692 self.rep_channel.stop()
693
693
694 @property
694 @property
695 def channels_running(self):
695 def channels_running(self):
696 """Are any of the channels created and running?"""
696 """Are any of the channels created and running?"""
697 return self.xreq_channel.is_alive() \
697 return self.xreq_channel.is_alive() \
698 or self.sub_channel.is_alive() \
698 or self.sub_channel.is_alive() \
699 or self.rep_channel.is_alive()
699 or self.rep_channel.is_alive()
700
700
701 #--------------------------------------------------------------------------
701 #--------------------------------------------------------------------------
702 # Kernel process management methods:
702 # Kernel process management methods:
703 #--------------------------------------------------------------------------
703 #--------------------------------------------------------------------------
704
704
705 def start_kernel(self, **kw):
705 def start_kernel(self, **kw):
706 """Starts a kernel process and configures the manager to use it.
706 """Starts a kernel process and configures the manager to use it.
707
707
708 If random ports (port=0) are being used, this method must be called
708 If random ports (port=0) are being used, this method must be called
709 before the channels are created.
709 before the channels are created.
710
710
711 Parameters:
711 Parameters:
712 -----------
712 -----------
713 ipython : bool, optional (default True)
713 ipython : bool, optional (default True)
714 Whether to use an IPython kernel instead of a plain Python kernel.
714 Whether to use an IPython kernel instead of a plain Python kernel.
715 """
715 """
716 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
716 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
717 self.rep_address, self.hb_address
717 self.rep_address, self.hb_address
718 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
718 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or \
719 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
719 rep[0] != LOCALHOST or hb[0] != LOCALHOST:
720 raise RuntimeError("Can only launch a kernel on localhost."
720 raise RuntimeError("Can only launch a kernel on localhost."
721 "Make sure that the '*_address' attributes are "
721 "Make sure that the '*_address' attributes are "
722 "configured properly.")
722 "configured properly.")
723
723
724 self._launch_args = kw.copy()
724 self._launch_args = kw.copy()
725 if kw.pop('ipython', True):
725 if kw.pop('ipython', True):
726 from ipkernel import launch_kernel
726 from ipkernel import launch_kernel
727 else:
727 else:
728 from pykernel import launch_kernel
728 from pykernel import launch_kernel
729 self.kernel, xrep, pub, req, hb = launch_kernel(
729 self.kernel, xrep, pub, req, hb = launch_kernel(
730 xrep_port=xreq[1], pub_port=sub[1],
730 xrep_port=xreq[1], pub_port=sub[1],
731 req_port=rep[1], hb_port=hb[1], **kw)
731 req_port=rep[1], hb_port=hb[1], **kw)
732 self.xreq_address = (LOCALHOST, xrep)
732 self.xreq_address = (LOCALHOST, xrep)
733 self.sub_address = (LOCALHOST, pub)
733 self.sub_address = (LOCALHOST, pub)
734 self.rep_address = (LOCALHOST, req)
734 self.rep_address = (LOCALHOST, req)
735 self.hb_address = (LOCALHOST, hb)
735 self.hb_address = (LOCALHOST, hb)
736
736
737 def shutdown_kernel(self):
737 def shutdown_kernel(self):
738 """ Attempts to the stop the kernel process cleanly. If the kernel
738 """ Attempts to the stop the kernel process cleanly. If the kernel
739 cannot be stopped, it is killed, if possible.
739 cannot be stopped, it is killed, if possible.
740 """
740 """
741 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
741 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
742 if sys.platform == 'win32':
742 if sys.platform == 'win32':
743 self.kill_kernel()
743 self.kill_kernel()
744 return
744 return
745
745
746 self.xreq_channel.shutdown()
746 self.xreq_channel.shutdown()
747 # Don't send any additional kernel kill messages immediately, to give
747 # Don't send any additional kernel kill messages immediately, to give
748 # the kernel a chance to properly execute shutdown actions. Wait for at
748 # the kernel a chance to properly execute shutdown actions. Wait for at
749 # most 1s, checking every 0.1s.
749 # most 1s, checking every 0.1s.
750 for i in range(10):
750 for i in range(10):
751 if self.is_alive:
751 if self.is_alive:
752 time.sleep(0.1)
752 time.sleep(0.1)
753 else:
753 else:
754 break
754 break
755 else:
755 else:
756 # OK, we've waited long enough.
756 # OK, we've waited long enough.
757 if self.has_kernel:
757 if self.has_kernel:
758 self.kill_kernel()
758 self.kill_kernel()
759
759
760 def restart_kernel(self, instant_death=False):
760 def restart_kernel(self, now=False):
761 """Restarts a kernel with the same arguments that were used to launch
761 """Restarts a kernel with the same arguments that were used to launch
762 it. If the old kernel was launched with random ports, the same ports
762 it. If the old kernel was launched with random ports, the same ports
763 will be used for the new kernel.
763 will be used for the new kernel.
764
764
765 Parameters
765 Parameters
766 ----------
766 ----------
767 instant_death : bool, optional
767 now : bool, optional
768 If True, the kernel is forcefully restarted *immediately*, without
768 If True, the kernel is forcefully restarted *immediately*, without
769 having a chance to do any cleanup action. Otherwise the kernel is
769 having a chance to do any cleanup action. Otherwise the kernel is
770 given 1s to clean up before a forceful restart is issued.
770 given 1s to clean up before a forceful restart is issued.
771
771
772 In all cases the kernel is restarted, the only difference is whether
772 In all cases the kernel is restarted, the only difference is whether
773 it is given a chance to perform a clean shutdown or not.
773 it is given a chance to perform a clean shutdown or not.
774 """
774 """
775 if self._launch_args is None:
775 if self._launch_args is None:
776 raise RuntimeError("Cannot restart the kernel. "
776 raise RuntimeError("Cannot restart the kernel. "
777 "No previous call to 'start_kernel'.")
777 "No previous call to 'start_kernel'.")
778 else:
778 else:
779 if self.has_kernel:
779 if self.has_kernel:
780 if instant_death:
780 if now:
781 self.kill_kernel()
781 self.kill_kernel()
782 else:
782 else:
783 self.shutdown_kernel()
783 self.shutdown_kernel()
784 self.start_kernel(**self._launch_args)
784 self.start_kernel(**self._launch_args)
785
785
786 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
786 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
787 # unless there is some delay here.
787 # unless there is some delay here.
788 if sys.platform == 'win32':
788 if sys.platform == 'win32':
789 time.sleep(0.2)
789 time.sleep(0.2)
790
790
791 @property
791 @property
792 def has_kernel(self):
792 def has_kernel(self):
793 """Returns whether a kernel process has been specified for the kernel
793 """Returns whether a kernel process has been specified for the kernel
794 manager.
794 manager.
795 """
795 """
796 return self.kernel is not None
796 return self.kernel is not None
797
797
798 def kill_kernel(self):
798 def kill_kernel(self):
799 """ Kill the running kernel. """
799 """ Kill the running kernel. """
800 if self.has_kernel:
800 if self.has_kernel:
801 self.kernel.kill()
801 self.kernel.kill()
802 self.kernel = None
802 self.kernel = None
803 else:
803 else:
804 raise RuntimeError("Cannot kill kernel. No kernel is running!")
804 raise RuntimeError("Cannot kill kernel. No kernel is running!")
805
805
806 def interrupt_kernel(self):
806 def interrupt_kernel(self):
807 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
807 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
808 well supported on all platforms.
808 well supported on all platforms.
809 """
809 """
810 if self.has_kernel:
810 if self.has_kernel:
811 if sys.platform == 'win32':
811 if sys.platform == 'win32':
812 from parentpoller import ParentPollerWindows as Poller
812 from parentpoller import ParentPollerWindows as Poller
813 Poller.send_interrupt(self.kernel.win32_interrupt_event)
813 Poller.send_interrupt(self.kernel.win32_interrupt_event)
814 else:
814 else:
815 self.kernel.send_signal(signal.SIGINT)
815 self.kernel.send_signal(signal.SIGINT)
816 else:
816 else:
817 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
817 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
818
818
819 def signal_kernel(self, signum):
819 def signal_kernel(self, signum):
820 """ Sends a signal to the kernel. Note that since only SIGTERM is
820 """ Sends a signal to the kernel. Note that since only SIGTERM is
821 supported on Windows, this function is only useful on Unix systems.
821 supported on Windows, this function is only useful on Unix systems.
822 """
822 """
823 if self.has_kernel:
823 if self.has_kernel:
824 self.kernel.send_signal(signum)
824 self.kernel.send_signal(signum)
825 else:
825 else:
826 raise RuntimeError("Cannot signal kernel. No kernel is running!")
826 raise RuntimeError("Cannot signal kernel. No kernel is running!")
827
827
828 @property
828 @property
829 def is_alive(self):
829 def is_alive(self):
830 """Is the kernel process still running?"""
830 """Is the kernel process still running?"""
831 # FIXME: not using a heartbeat means this method is broken for any
831 # FIXME: not using a heartbeat means this method is broken for any
832 # remote kernel, it's only capable of handling local kernels.
832 # remote kernel, it's only capable of handling local kernels.
833 if self.has_kernel:
833 if self.has_kernel:
834 if self.kernel.poll() is None:
834 if self.kernel.poll() is None:
835 return True
835 return True
836 else:
836 else:
837 return False
837 return False
838 else:
838 else:
839 # We didn't start the kernel with this KernelManager so we don't
839 # We didn't start the kernel with this KernelManager so we don't
840 # know if it is running. We should use a heartbeat for this case.
840 # know if it is running. We should use a heartbeat for this case.
841 return True
841 return True
842
842
843 #--------------------------------------------------------------------------
843 #--------------------------------------------------------------------------
844 # Channels used for communication with the kernel:
844 # Channels used for communication with the kernel:
845 #--------------------------------------------------------------------------
845 #--------------------------------------------------------------------------
846
846
847 @property
847 @property
848 def xreq_channel(self):
848 def xreq_channel(self):
849 """Get the REQ socket channel object to make requests of the kernel."""
849 """Get the REQ socket channel object to make requests of the kernel."""
850 if self._xreq_channel is None:
850 if self._xreq_channel is None:
851 self._xreq_channel = self.xreq_channel_class(self.context,
851 self._xreq_channel = self.xreq_channel_class(self.context,
852 self.session,
852 self.session,
853 self.xreq_address)
853 self.xreq_address)
854 return self._xreq_channel
854 return self._xreq_channel
855
855
856 @property
856 @property
857 def sub_channel(self):
857 def sub_channel(self):
858 """Get the SUB socket channel object."""
858 """Get the SUB socket channel object."""
859 if self._sub_channel is None:
859 if self._sub_channel is None:
860 self._sub_channel = self.sub_channel_class(self.context,
860 self._sub_channel = self.sub_channel_class(self.context,
861 self.session,
861 self.session,
862 self.sub_address)
862 self.sub_address)
863 return self._sub_channel
863 return self._sub_channel
864
864
865 @property
865 @property
866 def rep_channel(self):
866 def rep_channel(self):
867 """Get the REP socket channel object to handle stdin (raw_input)."""
867 """Get the REP socket channel object to handle stdin (raw_input)."""
868 if self._rep_channel is None:
868 if self._rep_channel is None:
869 self._rep_channel = self.rep_channel_class(self.context,
869 self._rep_channel = self.rep_channel_class(self.context,
870 self.session,
870 self.session,
871 self.rep_address)
871 self.rep_address)
872 return self._rep_channel
872 return self._rep_channel
873
873
874 @property
874 @property
875 def hb_channel(self):
875 def hb_channel(self):
876 """Get the REP socket channel object to handle stdin (raw_input)."""
876 """Get the REP socket channel object to handle stdin (raw_input)."""
877 if self._hb_channel is None:
877 if self._hb_channel is None:
878 self._hb_channel = self.hb_channel_class(self.context,
878 self._hb_channel = self.hb_channel_class(self.context,
879 self.session,
879 self.session,
880 self.hb_address)
880 self.hb_address)
881 return self._hb_channel
881 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now