##// END OF EJS Templates
expose shell channel methods at the client level
MinRK -
Show More
@@ -1,772 +1,772 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7 import uuid
7 import uuid
8
8
9 # System library imports
9 # System library imports
10 from pygments.lexers import PythonLexer
10 from pygments.lexers import PythonLexer
11 from IPython.external import qt
11 from IPython.external import qt
12 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
13
13
14 # Local imports
14 # Local imports
15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
16 from IPython.core.inputtransformer import classic_prompt
16 from IPython.core.inputtransformer import classic_prompt
17 from IPython.core.oinspect import call_tip
17 from IPython.core.oinspect import call_tip
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
19 from IPython.utils.traitlets import Bool, Instance, Unicode
19 from IPython.utils.traitlets import Bool, Instance, Unicode
20 from bracket_matcher import BracketMatcher
20 from bracket_matcher import BracketMatcher
21 from call_tip_widget import CallTipWidget
21 from call_tip_widget import CallTipWidget
22 from completion_lexer import CompletionLexer
22 from completion_lexer import CompletionLexer
23 from history_console_widget import HistoryConsoleWidget
23 from history_console_widget import HistoryConsoleWidget
24 from pygments_highlighter import PygmentsHighlighter
24 from pygments_highlighter import PygmentsHighlighter
25
25
26
26
27 class FrontendHighlighter(PygmentsHighlighter):
27 class FrontendHighlighter(PygmentsHighlighter):
28 """ A PygmentsHighlighter that understands and ignores prompts.
28 """ A PygmentsHighlighter that understands and ignores prompts.
29 """
29 """
30
30
31 def __init__(self, frontend):
31 def __init__(self, frontend):
32 super(FrontendHighlighter, self).__init__(frontend._control.document())
32 super(FrontendHighlighter, self).__init__(frontend._control.document())
33 self._current_offset = 0
33 self._current_offset = 0
34 self._frontend = frontend
34 self._frontend = frontend
35 self.highlighting_on = False
35 self.highlighting_on = False
36
36
37 def highlightBlock(self, string):
37 def highlightBlock(self, string):
38 """ Highlight a block of text. Reimplemented to highlight selectively.
38 """ Highlight a block of text. Reimplemented to highlight selectively.
39 """
39 """
40 if not self.highlighting_on:
40 if not self.highlighting_on:
41 return
41 return
42
42
43 # The input to this function is a unicode string that may contain
43 # The input to this function is a unicode string that may contain
44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 # the string as plain text so we can compare it.
45 # the string as plain text so we can compare it.
46 current_block = self.currentBlock()
46 current_block = self.currentBlock()
47 string = self._frontend._get_block_plain_text(current_block)
47 string = self._frontend._get_block_plain_text(current_block)
48
48
49 # Decide whether to check for the regular or continuation prompt.
49 # Decide whether to check for the regular or continuation prompt.
50 if current_block.contains(self._frontend._prompt_pos):
50 if current_block.contains(self._frontend._prompt_pos):
51 prompt = self._frontend._prompt
51 prompt = self._frontend._prompt
52 else:
52 else:
53 prompt = self._frontend._continuation_prompt
53 prompt = self._frontend._continuation_prompt
54
54
55 # Only highlight if we can identify a prompt, but make sure not to
55 # Only highlight if we can identify a prompt, but make sure not to
56 # highlight the prompt.
56 # highlight the prompt.
57 if string.startswith(prompt):
57 if string.startswith(prompt):
58 self._current_offset = len(prompt)
58 self._current_offset = len(prompt)
59 string = string[len(prompt):]
59 string = string[len(prompt):]
60 super(FrontendHighlighter, self).highlightBlock(string)
60 super(FrontendHighlighter, self).highlightBlock(string)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 super(FrontendHighlighter, self).setFormat(start, count, format)
74 super(FrontendHighlighter, self).setFormat(start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # The text to show when the kernel is (re)started.
81 # The text to show when the kernel is (re)started.
82 banner = Unicode()
82 banner = Unicode()
83
83
84 # An option and corresponding signal for overriding the default kernel
84 # An option and corresponding signal for overriding the default kernel
85 # interrupt behavior.
85 # interrupt behavior.
86 custom_interrupt = Bool(False)
86 custom_interrupt = Bool(False)
87 custom_interrupt_requested = QtCore.Signal()
87 custom_interrupt_requested = QtCore.Signal()
88
88
89 # An option and corresponding signals for overriding the default kernel
89 # An option and corresponding signals for overriding the default kernel
90 # restart behavior.
90 # restart behavior.
91 custom_restart = Bool(False)
91 custom_restart = Bool(False)
92 custom_restart_kernel_died = QtCore.Signal(float)
92 custom_restart_kernel_died = QtCore.Signal(float)
93 custom_restart_requested = QtCore.Signal()
93 custom_restart_requested = QtCore.Signal()
94
94
95 # Whether to automatically show calltips on open-parentheses.
95 # Whether to automatically show calltips on open-parentheses.
96 enable_calltips = Bool(True, config=True,
96 enable_calltips = Bool(True, config=True,
97 help="Whether to draw information calltips on open-parentheses.")
97 help="Whether to draw information calltips on open-parentheses.")
98
98
99 clear_on_kernel_restart = Bool(True, config=True,
99 clear_on_kernel_restart = Bool(True, config=True,
100 help="Whether to clear the console when the kernel is restarted")
100 help="Whether to clear the console when the kernel is restarted")
101
101
102 confirm_restart = Bool(True, config=True,
102 confirm_restart = Bool(True, config=True,
103 help="Whether to ask for user confirmation when restarting kernel")
103 help="Whether to ask for user confirmation when restarting kernel")
104
104
105 # Emitted when a user visible 'execute_request' has been submitted to the
105 # Emitted when a user visible 'execute_request' has been submitted to the
106 # kernel from the FrontendWidget. Contains the code to be executed.
106 # kernel from the FrontendWidget. Contains the code to be executed.
107 executing = QtCore.Signal(object)
107 executing = QtCore.Signal(object)
108
108
109 # Emitted when a user-visible 'execute_reply' has been received from the
109 # Emitted when a user-visible 'execute_reply' has been received from the
110 # kernel and processed by the FrontendWidget. Contains the response message.
110 # kernel and processed by the FrontendWidget. Contains the response message.
111 executed = QtCore.Signal(object)
111 executed = QtCore.Signal(object)
112
112
113 # Emitted when an exit request has been received from the kernel.
113 # Emitted when an exit request has been received from the kernel.
114 exit_requested = QtCore.Signal(object)
114 exit_requested = QtCore.Signal(object)
115
115
116 # Protected class variables.
116 # Protected class variables.
117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
118 logical_line_transforms=[],
118 logical_line_transforms=[],
119 python_line_transforms=[],
119 python_line_transforms=[],
120 )
120 )
121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
124 _input_splitter_class = InputSplitter
124 _input_splitter_class = InputSplitter
125 _local_kernel = False
125 _local_kernel = False
126 _highlighter = Instance(FrontendHighlighter)
126 _highlighter = Instance(FrontendHighlighter)
127
127
128 #---------------------------------------------------------------------------
128 #---------------------------------------------------------------------------
129 # 'object' interface
129 # 'object' interface
130 #---------------------------------------------------------------------------
130 #---------------------------------------------------------------------------
131
131
132 def __init__(self, *args, **kw):
132 def __init__(self, *args, **kw):
133 super(FrontendWidget, self).__init__(*args, **kw)
133 super(FrontendWidget, self).__init__(*args, **kw)
134 # FIXME: remove this when PySide min version is updated past 1.0.7
134 # FIXME: remove this when PySide min version is updated past 1.0.7
135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
136 if qt.QT_API == qt.QT_API_PYSIDE:
136 if qt.QT_API == qt.QT_API_PYSIDE:
137 import PySide
137 import PySide
138 if PySide.__version_info__ < (1,0,7):
138 if PySide.__version_info__ < (1,0,7):
139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
140 self.enable_calltips = False
140 self.enable_calltips = False
141
141
142 # FrontendWidget protected variables.
142 # FrontendWidget protected variables.
143 self._bracket_matcher = BracketMatcher(self._control)
143 self._bracket_matcher = BracketMatcher(self._control)
144 self._call_tip_widget = CallTipWidget(self._control)
144 self._call_tip_widget = CallTipWidget(self._control)
145 self._completion_lexer = CompletionLexer(PythonLexer())
145 self._completion_lexer = CompletionLexer(PythonLexer())
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 self._hidden = False
147 self._hidden = False
148 self._highlighter = FrontendHighlighter(self)
148 self._highlighter = FrontendHighlighter(self)
149 self._input_splitter = self._input_splitter_class()
149 self._input_splitter = self._input_splitter_class()
150 self._kernel_manager = None
150 self._kernel_manager = None
151 self._kernel_client = None
151 self._kernel_client = None
152 self._request_info = {}
152 self._request_info = {}
153 self._request_info['execute'] = {};
153 self._request_info['execute'] = {};
154 self._callback_dict = {}
154 self._callback_dict = {}
155
155
156 # Configure the ConsoleWidget.
156 # Configure the ConsoleWidget.
157 self.tab_width = 4
157 self.tab_width = 4
158 self._set_continuation_prompt('... ')
158 self._set_continuation_prompt('... ')
159
159
160 # Configure the CallTipWidget.
160 # Configure the CallTipWidget.
161 self._call_tip_widget.setFont(self.font)
161 self._call_tip_widget.setFont(self.font)
162 self.font_changed.connect(self._call_tip_widget.setFont)
162 self.font_changed.connect(self._call_tip_widget.setFont)
163
163
164 # Configure actions.
164 # Configure actions.
165 action = self._copy_raw_action
165 action = self._copy_raw_action
166 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
166 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
167 action.setEnabled(False)
167 action.setEnabled(False)
168 action.setShortcut(QtGui.QKeySequence(key))
168 action.setShortcut(QtGui.QKeySequence(key))
169 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
169 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
170 action.triggered.connect(self.copy_raw)
170 action.triggered.connect(self.copy_raw)
171 self.copy_available.connect(action.setEnabled)
171 self.copy_available.connect(action.setEnabled)
172 self.addAction(action)
172 self.addAction(action)
173
173
174 # Connect signal handlers.
174 # Connect signal handlers.
175 document = self._control.document()
175 document = self._control.document()
176 document.contentsChange.connect(self._document_contents_change)
176 document.contentsChange.connect(self._document_contents_change)
177
177
178 # Set flag for whether we are connected via localhost.
178 # Set flag for whether we are connected via localhost.
179 self._local_kernel = kw.get('local_kernel',
179 self._local_kernel = kw.get('local_kernel',
180 FrontendWidget._local_kernel)
180 FrontendWidget._local_kernel)
181
181
182 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
183 # 'ConsoleWidget' public interface
183 # 'ConsoleWidget' public interface
184 #---------------------------------------------------------------------------
184 #---------------------------------------------------------------------------
185
185
186 def copy(self):
186 def copy(self):
187 """ Copy the currently selected text to the clipboard, removing prompts.
187 """ Copy the currently selected text to the clipboard, removing prompts.
188 """
188 """
189 if self._page_control is not None and self._page_control.hasFocus():
189 if self._page_control is not None and self._page_control.hasFocus():
190 self._page_control.copy()
190 self._page_control.copy()
191 elif self._control.hasFocus():
191 elif self._control.hasFocus():
192 text = self._control.textCursor().selection().toPlainText()
192 text = self._control.textCursor().selection().toPlainText()
193 if text:
193 if text:
194 text = self._prompt_transformer.transform_cell(text)
194 text = self._prompt_transformer.transform_cell(text)
195 QtGui.QApplication.clipboard().setText(text)
195 QtGui.QApplication.clipboard().setText(text)
196 else:
196 else:
197 self.log.debug("frontend widget : unknown copy target")
197 self.log.debug("frontend widget : unknown copy target")
198
198
199 #---------------------------------------------------------------------------
199 #---------------------------------------------------------------------------
200 # 'ConsoleWidget' abstract interface
200 # 'ConsoleWidget' abstract interface
201 #---------------------------------------------------------------------------
201 #---------------------------------------------------------------------------
202
202
203 def _is_complete(self, source, interactive):
203 def _is_complete(self, source, interactive):
204 """ Returns whether 'source' can be completely processed and a new
204 """ Returns whether 'source' can be completely processed and a new
205 prompt created. When triggered by an Enter/Return key press,
205 prompt created. When triggered by an Enter/Return key press,
206 'interactive' is True; otherwise, it is False.
206 'interactive' is True; otherwise, it is False.
207 """
207 """
208 self._input_splitter.reset()
208 self._input_splitter.reset()
209 complete = self._input_splitter.push(source)
209 complete = self._input_splitter.push(source)
210 if interactive:
210 if interactive:
211 complete = not self._input_splitter.push_accepts_more()
211 complete = not self._input_splitter.push_accepts_more()
212 return complete
212 return complete
213
213
214 def _execute(self, source, hidden):
214 def _execute(self, source, hidden):
215 """ Execute 'source'. If 'hidden', do not show any output.
215 """ Execute 'source'. If 'hidden', do not show any output.
216
216
217 See parent class :meth:`execute` docstring for full details.
217 See parent class :meth:`execute` docstring for full details.
218 """
218 """
219 msg_id = self.kernel_client.shell_channel.execute(source, hidden)
219 msg_id = self.kernel_client.execute(source, hidden)
220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
221 self._hidden = hidden
221 self._hidden = hidden
222 if not hidden:
222 if not hidden:
223 self.executing.emit(source)
223 self.executing.emit(source)
224
224
225 def _prompt_started_hook(self):
225 def _prompt_started_hook(self):
226 """ Called immediately after a new prompt is displayed.
226 """ Called immediately after a new prompt is displayed.
227 """
227 """
228 if not self._reading:
228 if not self._reading:
229 self._highlighter.highlighting_on = True
229 self._highlighter.highlighting_on = True
230
230
231 def _prompt_finished_hook(self):
231 def _prompt_finished_hook(self):
232 """ Called immediately after a prompt is finished, i.e. when some input
232 """ Called immediately after a prompt is finished, i.e. when some input
233 will be processed and a new prompt displayed.
233 will be processed and a new prompt displayed.
234 """
234 """
235 # Flush all state from the input splitter so the next round of
235 # Flush all state from the input splitter so the next round of
236 # reading input starts with a clean buffer.
236 # reading input starts with a clean buffer.
237 self._input_splitter.reset()
237 self._input_splitter.reset()
238
238
239 if not self._reading:
239 if not self._reading:
240 self._highlighter.highlighting_on = False
240 self._highlighter.highlighting_on = False
241
241
242 def _tab_pressed(self):
242 def _tab_pressed(self):
243 """ Called when the tab key is pressed. Returns whether to continue
243 """ Called when the tab key is pressed. Returns whether to continue
244 processing the event.
244 processing the event.
245 """
245 """
246 # Perform tab completion if:
246 # Perform tab completion if:
247 # 1) The cursor is in the input buffer.
247 # 1) The cursor is in the input buffer.
248 # 2) There is a non-whitespace character before the cursor.
248 # 2) There is a non-whitespace character before the cursor.
249 text = self._get_input_buffer_cursor_line()
249 text = self._get_input_buffer_cursor_line()
250 if text is None:
250 if text is None:
251 return False
251 return False
252 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
252 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
253 if complete:
253 if complete:
254 self._complete()
254 self._complete()
255 return not complete
255 return not complete
256
256
257 #---------------------------------------------------------------------------
257 #---------------------------------------------------------------------------
258 # 'ConsoleWidget' protected interface
258 # 'ConsoleWidget' protected interface
259 #---------------------------------------------------------------------------
259 #---------------------------------------------------------------------------
260
260
261 def _context_menu_make(self, pos):
261 def _context_menu_make(self, pos):
262 """ Reimplemented to add an action for raw copy.
262 """ Reimplemented to add an action for raw copy.
263 """
263 """
264 menu = super(FrontendWidget, self)._context_menu_make(pos)
264 menu = super(FrontendWidget, self)._context_menu_make(pos)
265 for before_action in menu.actions():
265 for before_action in menu.actions():
266 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
266 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
267 QtGui.QKeySequence.ExactMatch:
267 QtGui.QKeySequence.ExactMatch:
268 menu.insertAction(before_action, self._copy_raw_action)
268 menu.insertAction(before_action, self._copy_raw_action)
269 break
269 break
270 return menu
270 return menu
271
271
272 def request_interrupt_kernel(self):
272 def request_interrupt_kernel(self):
273 if self._executing:
273 if self._executing:
274 self.interrupt_kernel()
274 self.interrupt_kernel()
275
275
276 def request_restart_kernel(self):
276 def request_restart_kernel(self):
277 message = 'Are you sure you want to restart the kernel?'
277 message = 'Are you sure you want to restart the kernel?'
278 self.restart_kernel(message, now=False)
278 self.restart_kernel(message, now=False)
279
279
280 def _event_filter_console_keypress(self, event):
280 def _event_filter_console_keypress(self, event):
281 """ Reimplemented for execution interruption and smart backspace.
281 """ Reimplemented for execution interruption and smart backspace.
282 """
282 """
283 key = event.key()
283 key = event.key()
284 if self._control_key_down(event.modifiers(), include_command=False):
284 if self._control_key_down(event.modifiers(), include_command=False):
285
285
286 if key == QtCore.Qt.Key_C and self._executing:
286 if key == QtCore.Qt.Key_C and self._executing:
287 self.request_interrupt_kernel()
287 self.request_interrupt_kernel()
288 return True
288 return True
289
289
290 elif key == QtCore.Qt.Key_Period:
290 elif key == QtCore.Qt.Key_Period:
291 self.request_restart_kernel()
291 self.request_restart_kernel()
292 return True
292 return True
293
293
294 elif not event.modifiers() & QtCore.Qt.AltModifier:
294 elif not event.modifiers() & QtCore.Qt.AltModifier:
295
295
296 # Smart backspace: remove four characters in one backspace if:
296 # Smart backspace: remove four characters in one backspace if:
297 # 1) everything left of the cursor is whitespace
297 # 1) everything left of the cursor is whitespace
298 # 2) the four characters immediately left of the cursor are spaces
298 # 2) the four characters immediately left of the cursor are spaces
299 if key == QtCore.Qt.Key_Backspace:
299 if key == QtCore.Qt.Key_Backspace:
300 col = self._get_input_buffer_cursor_column()
300 col = self._get_input_buffer_cursor_column()
301 cursor = self._control.textCursor()
301 cursor = self._control.textCursor()
302 if col > 3 and not cursor.hasSelection():
302 if col > 3 and not cursor.hasSelection():
303 text = self._get_input_buffer_cursor_line()[:col]
303 text = self._get_input_buffer_cursor_line()[:col]
304 if text.endswith(' ') and not text.strip():
304 if text.endswith(' ') and not text.strip():
305 cursor.movePosition(QtGui.QTextCursor.Left,
305 cursor.movePosition(QtGui.QTextCursor.Left,
306 QtGui.QTextCursor.KeepAnchor, 4)
306 QtGui.QTextCursor.KeepAnchor, 4)
307 cursor.removeSelectedText()
307 cursor.removeSelectedText()
308 return True
308 return True
309
309
310 return super(FrontendWidget, self)._event_filter_console_keypress(event)
310 return super(FrontendWidget, self)._event_filter_console_keypress(event)
311
311
312 def _insert_continuation_prompt(self, cursor):
312 def _insert_continuation_prompt(self, cursor):
313 """ Reimplemented for auto-indentation.
313 """ Reimplemented for auto-indentation.
314 """
314 """
315 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
315 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
316 cursor.insertText(' ' * self._input_splitter.indent_spaces)
316 cursor.insertText(' ' * self._input_splitter.indent_spaces)
317
317
318 #---------------------------------------------------------------------------
318 #---------------------------------------------------------------------------
319 # 'BaseFrontendMixin' abstract interface
319 # 'BaseFrontendMixin' abstract interface
320 #---------------------------------------------------------------------------
320 #---------------------------------------------------------------------------
321
321
322 def _handle_complete_reply(self, rep):
322 def _handle_complete_reply(self, rep):
323 """ Handle replies for tab completion.
323 """ Handle replies for tab completion.
324 """
324 """
325 self.log.debug("complete: %s", rep.get('content', ''))
325 self.log.debug("complete: %s", rep.get('content', ''))
326 cursor = self._get_cursor()
326 cursor = self._get_cursor()
327 info = self._request_info.get('complete')
327 info = self._request_info.get('complete')
328 if info and info.id == rep['parent_header']['msg_id'] and \
328 if info and info.id == rep['parent_header']['msg_id'] and \
329 info.pos == cursor.position():
329 info.pos == cursor.position():
330 text = '.'.join(self._get_context())
330 text = '.'.join(self._get_context())
331 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
331 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
332 self._complete_with_items(cursor, rep['content']['matches'])
332 self._complete_with_items(cursor, rep['content']['matches'])
333
333
334 def _silent_exec_callback(self, expr, callback):
334 def _silent_exec_callback(self, expr, callback):
335 """Silently execute `expr` in the kernel and call `callback` with reply
335 """Silently execute `expr` in the kernel and call `callback` with reply
336
336
337 the `expr` is evaluated silently in the kernel (without) output in
337 the `expr` is evaluated silently in the kernel (without) output in
338 the frontend. Call `callback` with the
338 the frontend. Call `callback` with the
339 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
339 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
340
340
341 Parameters
341 Parameters
342 ----------
342 ----------
343 expr : string
343 expr : string
344 valid string to be executed by the kernel.
344 valid string to be executed by the kernel.
345 callback : function
345 callback : function
346 function accepting one argument, as a string. The string will be
346 function accepting one argument, as a string. The string will be
347 the `repr` of the result of evaluating `expr`
347 the `repr` of the result of evaluating `expr`
348
348
349 The `callback` is called with the `repr()` of the result of `expr` as
349 The `callback` is called with the `repr()` of the result of `expr` as
350 first argument. To get the object, do `eval()` on the passed value.
350 first argument. To get the object, do `eval()` on the passed value.
351
351
352 See Also
352 See Also
353 --------
353 --------
354 _handle_exec_callback : private method, deal with calling callback with reply
354 _handle_exec_callback : private method, deal with calling callback with reply
355
355
356 """
356 """
357
357
358 # generate uuid, which would be used as an indication of whether or
358 # generate uuid, which would be used as an indication of whether or
359 # not the unique request originated from here (can use msg id ?)
359 # not the unique request originated from here (can use msg id ?)
360 local_uuid = str(uuid.uuid1())
360 local_uuid = str(uuid.uuid1())
361 msg_id = self.kernel_client.shell_channel.execute('',
361 msg_id = self.kernel_client.execute('',
362 silent=True, user_expressions={ local_uuid:expr })
362 silent=True, user_expressions={ local_uuid:expr })
363 self._callback_dict[local_uuid] = callback
363 self._callback_dict[local_uuid] = callback
364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
365
365
366 def _handle_exec_callback(self, msg):
366 def _handle_exec_callback(self, msg):
367 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
367 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
368
368
369 Parameters
369 Parameters
370 ----------
370 ----------
371 msg : raw message send by the kernel containing an `user_expressions`
371 msg : raw message send by the kernel containing an `user_expressions`
372 and having a 'silent_exec_callback' kind.
372 and having a 'silent_exec_callback' kind.
373
373
374 Notes
374 Notes
375 -----
375 -----
376 This function will look for a `callback` associated with the
376 This function will look for a `callback` associated with the
377 corresponding message id. Association has been made by
377 corresponding message id. Association has been made by
378 `_silent_exec_callback`. `callback` is then called with the `repr()`
378 `_silent_exec_callback`. `callback` is then called with the `repr()`
379 of the value of corresponding `user_expressions` as argument.
379 of the value of corresponding `user_expressions` as argument.
380 `callback` is then removed from the known list so that any message
380 `callback` is then removed from the known list so that any message
381 coming again with the same id won't trigger it.
381 coming again with the same id won't trigger it.
382
382
383 """
383 """
384
384
385 user_exp = msg['content'].get('user_expressions')
385 user_exp = msg['content'].get('user_expressions')
386 if not user_exp:
386 if not user_exp:
387 return
387 return
388 for expression in user_exp:
388 for expression in user_exp:
389 if expression in self._callback_dict:
389 if expression in self._callback_dict:
390 self._callback_dict.pop(expression)(user_exp[expression])
390 self._callback_dict.pop(expression)(user_exp[expression])
391
391
392 def _handle_execute_reply(self, msg):
392 def _handle_execute_reply(self, msg):
393 """ Handles replies for code execution.
393 """ Handles replies for code execution.
394 """
394 """
395 self.log.debug("execute: %s", msg.get('content', ''))
395 self.log.debug("execute: %s", msg.get('content', ''))
396 msg_id = msg['parent_header']['msg_id']
396 msg_id = msg['parent_header']['msg_id']
397 info = self._request_info['execute'].get(msg_id)
397 info = self._request_info['execute'].get(msg_id)
398 # unset reading flag, because if execute finished, raw_input can't
398 # unset reading flag, because if execute finished, raw_input can't
399 # still be pending.
399 # still be pending.
400 self._reading = False
400 self._reading = False
401 if info and info.kind == 'user' and not self._hidden:
401 if info and info.kind == 'user' and not self._hidden:
402 # Make sure that all output from the SUB channel has been processed
402 # Make sure that all output from the SUB channel has been processed
403 # before writing a new prompt.
403 # before writing a new prompt.
404 self.kernel_client.iopub_channel.flush()
404 self.kernel_client.iopub_channel.flush()
405
405
406 # Reset the ANSI style information to prevent bad text in stdout
406 # Reset the ANSI style information to prevent bad text in stdout
407 # from messing up our colors. We're not a true terminal so we're
407 # from messing up our colors. We're not a true terminal so we're
408 # allowed to do this.
408 # allowed to do this.
409 if self.ansi_codes:
409 if self.ansi_codes:
410 self._ansi_processor.reset_sgr()
410 self._ansi_processor.reset_sgr()
411
411
412 content = msg['content']
412 content = msg['content']
413 status = content['status']
413 status = content['status']
414 if status == 'ok':
414 if status == 'ok':
415 self._process_execute_ok(msg)
415 self._process_execute_ok(msg)
416 elif status == 'error':
416 elif status == 'error':
417 self._process_execute_error(msg)
417 self._process_execute_error(msg)
418 elif status == 'aborted':
418 elif status == 'aborted':
419 self._process_execute_abort(msg)
419 self._process_execute_abort(msg)
420
420
421 self._show_interpreter_prompt_for_reply(msg)
421 self._show_interpreter_prompt_for_reply(msg)
422 self.executed.emit(msg)
422 self.executed.emit(msg)
423 self._request_info['execute'].pop(msg_id)
423 self._request_info['execute'].pop(msg_id)
424 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
424 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
425 self._handle_exec_callback(msg)
425 self._handle_exec_callback(msg)
426 self._request_info['execute'].pop(msg_id)
426 self._request_info['execute'].pop(msg_id)
427 else:
427 else:
428 super(FrontendWidget, self)._handle_execute_reply(msg)
428 super(FrontendWidget, self)._handle_execute_reply(msg)
429
429
430 def _handle_input_request(self, msg):
430 def _handle_input_request(self, msg):
431 """ Handle requests for raw_input.
431 """ Handle requests for raw_input.
432 """
432 """
433 self.log.debug("input: %s", msg.get('content', ''))
433 self.log.debug("input: %s", msg.get('content', ''))
434 if self._hidden:
434 if self._hidden:
435 raise RuntimeError('Request for raw input during hidden execution.')
435 raise RuntimeError('Request for raw input during hidden execution.')
436
436
437 # Make sure that all output from the SUB channel has been processed
437 # Make sure that all output from the SUB channel has been processed
438 # before entering readline mode.
438 # before entering readline mode.
439 self.kernel_client.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
440
440
441 def callback(line):
441 def callback(line):
442 self.kernel_client.stdin_channel.input(line)
442 self.kernel_client.stdin_channel.input(line)
443 if self._reading:
443 if self._reading:
444 self.log.debug("Got second input request, assuming first was interrupted.")
444 self.log.debug("Got second input request, assuming first was interrupted.")
445 self._reading = False
445 self._reading = False
446 self._readline(msg['content']['prompt'], callback=callback)
446 self._readline(msg['content']['prompt'], callback=callback)
447
447
448 def _handle_kernel_died(self, since_last_heartbeat):
448 def _handle_kernel_died(self, since_last_heartbeat):
449 """ Handle the kernel's death by asking if the user wants to restart.
449 """ Handle the kernel's death by asking if the user wants to restart.
450 """
450 """
451 self.log.debug("kernel died: %s", since_last_heartbeat)
451 self.log.debug("kernel died: %s", since_last_heartbeat)
452 if self.custom_restart:
452 if self.custom_restart:
453 self.custom_restart_kernel_died.emit(since_last_heartbeat)
453 self.custom_restart_kernel_died.emit(since_last_heartbeat)
454 else:
454 else:
455 message = 'The kernel heartbeat has been inactive for %.2f ' \
455 message = 'The kernel heartbeat has been inactive for %.2f ' \
456 'seconds. Do you want to restart the kernel? You may ' \
456 'seconds. Do you want to restart the kernel? You may ' \
457 'first want to check the network connection.' % \
457 'first want to check the network connection.' % \
458 since_last_heartbeat
458 since_last_heartbeat
459 self.restart_kernel(message, now=True)
459 self.restart_kernel(message, now=True)
460
460
461 def _handle_object_info_reply(self, rep):
461 def _handle_object_info_reply(self, rep):
462 """ Handle replies for call tips.
462 """ Handle replies for call tips.
463 """
463 """
464 self.log.debug("oinfo: %s", rep.get('content', ''))
464 self.log.debug("oinfo: %s", rep.get('content', ''))
465 cursor = self._get_cursor()
465 cursor = self._get_cursor()
466 info = self._request_info.get('call_tip')
466 info = self._request_info.get('call_tip')
467 if info and info.id == rep['parent_header']['msg_id'] and \
467 if info and info.id == rep['parent_header']['msg_id'] and \
468 info.pos == cursor.position():
468 info.pos == cursor.position():
469 # Get the information for a call tip. For now we format the call
469 # Get the information for a call tip. For now we format the call
470 # line as string, later we can pass False to format_call and
470 # line as string, later we can pass False to format_call and
471 # syntax-highlight it ourselves for nicer formatting in the
471 # syntax-highlight it ourselves for nicer formatting in the
472 # calltip.
472 # calltip.
473 content = rep['content']
473 content = rep['content']
474 # if this is from pykernel, 'docstring' will be the only key
474 # if this is from pykernel, 'docstring' will be the only key
475 if content.get('ismagic', False):
475 if content.get('ismagic', False):
476 # Don't generate a call-tip for magics. Ideally, we should
476 # Don't generate a call-tip for magics. Ideally, we should
477 # generate a tooltip, but not on ( like we do for actual
477 # generate a tooltip, but not on ( like we do for actual
478 # callables.
478 # callables.
479 call_info, doc = None, None
479 call_info, doc = None, None
480 else:
480 else:
481 call_info, doc = call_tip(content, format_call=True)
481 call_info, doc = call_tip(content, format_call=True)
482 if call_info or doc:
482 if call_info or doc:
483 self._call_tip_widget.show_call_info(call_info, doc)
483 self._call_tip_widget.show_call_info(call_info, doc)
484
484
485 def _handle_pyout(self, msg):
485 def _handle_pyout(self, msg):
486 """ Handle display hook output.
486 """ Handle display hook output.
487 """
487 """
488 self.log.debug("pyout: %s", msg.get('content', ''))
488 self.log.debug("pyout: %s", msg.get('content', ''))
489 if not self._hidden and self._is_from_this_session(msg):
489 if not self._hidden and self._is_from_this_session(msg):
490 text = msg['content']['data']
490 text = msg['content']['data']
491 self._append_plain_text(text + '\n', before_prompt=True)
491 self._append_plain_text(text + '\n', before_prompt=True)
492
492
493 def _handle_stream(self, msg):
493 def _handle_stream(self, msg):
494 """ Handle stdout, stderr, and stdin.
494 """ Handle stdout, stderr, and stdin.
495 """
495 """
496 self.log.debug("stream: %s", msg.get('content', ''))
496 self.log.debug("stream: %s", msg.get('content', ''))
497 if not self._hidden and self._is_from_this_session(msg):
497 if not self._hidden and self._is_from_this_session(msg):
498 # Most consoles treat tabs as being 8 space characters. Convert tabs
498 # Most consoles treat tabs as being 8 space characters. Convert tabs
499 # to spaces so that output looks as expected regardless of this
499 # to spaces so that output looks as expected regardless of this
500 # widget's tab width.
500 # widget's tab width.
501 text = msg['content']['data'].expandtabs(8)
501 text = msg['content']['data'].expandtabs(8)
502
502
503 self._append_plain_text(text, before_prompt=True)
503 self._append_plain_text(text, before_prompt=True)
504 self._control.moveCursor(QtGui.QTextCursor.End)
504 self._control.moveCursor(QtGui.QTextCursor.End)
505
505
506 def _handle_shutdown_reply(self, msg):
506 def _handle_shutdown_reply(self, msg):
507 """ Handle shutdown signal, only if from other console.
507 """ Handle shutdown signal, only if from other console.
508 """
508 """
509 self.log.debug("shutdown: %s", msg.get('content', ''))
509 self.log.debug("shutdown: %s", msg.get('content', ''))
510 if not self._hidden and not self._is_from_this_session(msg):
510 if not self._hidden and not self._is_from_this_session(msg):
511 if self._local_kernel:
511 if self._local_kernel:
512 if not msg['content']['restart']:
512 if not msg['content']['restart']:
513 self.exit_requested.emit(self)
513 self.exit_requested.emit(self)
514 else:
514 else:
515 # we just got notified of a restart!
515 # we just got notified of a restart!
516 time.sleep(0.25) # wait 1/4 sec to reset
516 time.sleep(0.25) # wait 1/4 sec to reset
517 # lest the request for a new prompt
517 # lest the request for a new prompt
518 # goes to the old kernel
518 # goes to the old kernel
519 self.reset()
519 self.reset()
520 else: # remote kernel, prompt on Kernel shutdown/reset
520 else: # remote kernel, prompt on Kernel shutdown/reset
521 title = self.window().windowTitle()
521 title = self.window().windowTitle()
522 if not msg['content']['restart']:
522 if not msg['content']['restart']:
523 reply = QtGui.QMessageBox.question(self, title,
523 reply = QtGui.QMessageBox.question(self, title,
524 "Kernel has been shutdown permanently. "
524 "Kernel has been shutdown permanently. "
525 "Close the Console?",
525 "Close the Console?",
526 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
526 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
527 if reply == QtGui.QMessageBox.Yes:
527 if reply == QtGui.QMessageBox.Yes:
528 self.exit_requested.emit(self)
528 self.exit_requested.emit(self)
529 else:
529 else:
530 # XXX: remove message box in favor of using the
530 # XXX: remove message box in favor of using the
531 # clear_on_kernel_restart setting?
531 # clear_on_kernel_restart setting?
532 reply = QtGui.QMessageBox.question(self, title,
532 reply = QtGui.QMessageBox.question(self, title,
533 "Kernel has been reset. Clear the Console?",
533 "Kernel has been reset. Clear the Console?",
534 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
534 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
535 if reply == QtGui.QMessageBox.Yes:
535 if reply == QtGui.QMessageBox.Yes:
536 time.sleep(0.25) # wait 1/4 sec to reset
536 time.sleep(0.25) # wait 1/4 sec to reset
537 # lest the request for a new prompt
537 # lest the request for a new prompt
538 # goes to the old kernel
538 # goes to the old kernel
539 self.reset()
539 self.reset()
540
540
541 def _started_channels(self):
541 def _started_channels(self):
542 """ Called when the KernelManager channels have started listening or
542 """ Called when the KernelManager channels have started listening or
543 when the frontend is assigned an already listening KernelManager.
543 when the frontend is assigned an already listening KernelManager.
544 """
544 """
545 self.reset(clear=True)
545 self.reset(clear=True)
546
546
547 #---------------------------------------------------------------------------
547 #---------------------------------------------------------------------------
548 # 'FrontendWidget' public interface
548 # 'FrontendWidget' public interface
549 #---------------------------------------------------------------------------
549 #---------------------------------------------------------------------------
550
550
551 def copy_raw(self):
551 def copy_raw(self):
552 """ Copy the currently selected text to the clipboard without attempting
552 """ Copy the currently selected text to the clipboard without attempting
553 to remove prompts or otherwise alter the text.
553 to remove prompts or otherwise alter the text.
554 """
554 """
555 self._control.copy()
555 self._control.copy()
556
556
557 def execute_file(self, path, hidden=False):
557 def execute_file(self, path, hidden=False):
558 """ Attempts to execute file with 'path'. If 'hidden', no output is
558 """ Attempts to execute file with 'path'. If 'hidden', no output is
559 shown.
559 shown.
560 """
560 """
561 self.execute('execfile(%r)' % path, hidden=hidden)
561 self.execute('execfile(%r)' % path, hidden=hidden)
562
562
563 def interrupt_kernel(self):
563 def interrupt_kernel(self):
564 """ Attempts to interrupt the running kernel.
564 """ Attempts to interrupt the running kernel.
565
565
566 Also unsets _reading flag, to avoid runtime errors
566 Also unsets _reading flag, to avoid runtime errors
567 if raw_input is called again.
567 if raw_input is called again.
568 """
568 """
569 if self.custom_interrupt:
569 if self.custom_interrupt:
570 self._reading = False
570 self._reading = False
571 self.custom_interrupt_requested.emit()
571 self.custom_interrupt_requested.emit()
572 elif self.kernel_manager:
572 elif self.kernel_manager:
573 self._reading = False
573 self._reading = False
574 self.kernel_manager.interrupt_kernel()
574 self.kernel_manager.interrupt_kernel()
575 else:
575 else:
576 self._append_plain_text('Kernel process is either remote or '
576 self._append_plain_text('Kernel process is either remote or '
577 'unspecified. Cannot interrupt.\n')
577 'unspecified. Cannot interrupt.\n')
578
578
579 def reset(self, clear=False):
579 def reset(self, clear=False):
580 """ Resets the widget to its initial state if ``clear`` parameter or
580 """ Resets the widget to its initial state if ``clear`` parameter or
581 ``clear_on_kernel_restart`` configuration setting is True, otherwise
581 ``clear_on_kernel_restart`` configuration setting is True, otherwise
582 prints a visual indication of the fact that the kernel restarted, but
582 prints a visual indication of the fact that the kernel restarted, but
583 does not clear the traces from previous usage of the kernel before it
583 does not clear the traces from previous usage of the kernel before it
584 was restarted. With ``clear=True``, it is similar to ``%clear``, but
584 was restarted. With ``clear=True``, it is similar to ``%clear``, but
585 also re-writes the banner and aborts execution if necessary.
585 also re-writes the banner and aborts execution if necessary.
586 """
586 """
587 if self._executing:
587 if self._executing:
588 self._executing = False
588 self._executing = False
589 self._request_info['execute'] = {}
589 self._request_info['execute'] = {}
590 self._reading = False
590 self._reading = False
591 self._highlighter.highlighting_on = False
591 self._highlighter.highlighting_on = False
592
592
593 if self.clear_on_kernel_restart or clear:
593 if self.clear_on_kernel_restart or clear:
594 self._control.clear()
594 self._control.clear()
595 self._append_plain_text(self.banner)
595 self._append_plain_text(self.banner)
596 else:
596 else:
597 self._append_plain_text("# restarting kernel...")
597 self._append_plain_text("# restarting kernel...")
598 self._append_html("<hr><br>")
598 self._append_html("<hr><br>")
599 # XXX: Reprinting the full banner may be too much, but once #1680 is
599 # XXX: Reprinting the full banner may be too much, but once #1680 is
600 # addressed, that will mitigate it.
600 # addressed, that will mitigate it.
601 #self._append_plain_text(self.banner)
601 #self._append_plain_text(self.banner)
602 # update output marker for stdout/stderr, so that startup
602 # update output marker for stdout/stderr, so that startup
603 # messages appear after banner:
603 # messages appear after banner:
604 self._append_before_prompt_pos = self._get_cursor().position()
604 self._append_before_prompt_pos = self._get_cursor().position()
605 self._show_interpreter_prompt()
605 self._show_interpreter_prompt()
606
606
607 def restart_kernel(self, message, now=False):
607 def restart_kernel(self, message, now=False):
608 """ Attempts to restart the running kernel.
608 """ Attempts to restart the running kernel.
609 """
609 """
610 # FIXME: now should be configurable via a checkbox in the dialog. Right
610 # FIXME: now should be configurable via a checkbox in the dialog. Right
611 # now at least the heartbeat path sets it to True and the manual restart
611 # now at least the heartbeat path sets it to True and the manual restart
612 # to False. But those should just be the pre-selected states of a
612 # to False. But those should just be the pre-selected states of a
613 # checkbox that the user could override if so desired. But I don't know
613 # checkbox that the user could override if so desired. But I don't know
614 # enough Qt to go implementing the checkbox now.
614 # enough Qt to go implementing the checkbox now.
615
615
616 if self.custom_restart:
616 if self.custom_restart:
617 self.custom_restart_requested.emit()
617 self.custom_restart_requested.emit()
618
618
619 elif self.kernel_manager:
619 elif self.kernel_manager:
620 # Pause the heart beat channel to prevent further warnings.
620 # Pause the heart beat channel to prevent further warnings.
621 self.kernel_client.hb_channel.pause()
621 self.kernel_client.hb_channel.pause()
622
622
623 # Prompt the user to restart the kernel. Un-pause the heartbeat if
623 # Prompt the user to restart the kernel. Un-pause the heartbeat if
624 # they decline. (If they accept, the heartbeat will be un-paused
624 # they decline. (If they accept, the heartbeat will be un-paused
625 # automatically when the kernel is restarted.)
625 # automatically when the kernel is restarted.)
626 if self.confirm_restart:
626 if self.confirm_restart:
627 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
627 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
628 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
628 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
629 message, buttons)
629 message, buttons)
630 do_restart = result == QtGui.QMessageBox.Yes
630 do_restart = result == QtGui.QMessageBox.Yes
631 else:
631 else:
632 # confirm_restart is False, so we don't need to ask user
632 # confirm_restart is False, so we don't need to ask user
633 # anything, just do the restart
633 # anything, just do the restart
634 do_restart = True
634 do_restart = True
635 if do_restart:
635 if do_restart:
636 try:
636 try:
637 self.kernel_manager.restart_kernel(now=now)
637 self.kernel_manager.restart_kernel(now=now)
638 except RuntimeError:
638 except RuntimeError:
639 self._append_plain_text('Kernel started externally. '
639 self._append_plain_text('Kernel started externally. '
640 'Cannot restart.\n',
640 'Cannot restart.\n',
641 before_prompt=True
641 before_prompt=True
642 )
642 )
643 else:
643 else:
644 self.reset()
644 self.reset()
645 else:
645 else:
646 self.kernel_client.hb_channel.unpause()
646 self.kernel_client.hb_channel.unpause()
647
647
648 else:
648 else:
649 self._append_plain_text('Kernel process is either remote or '
649 self._append_plain_text('Kernel process is either remote or '
650 'unspecified. Cannot restart.\n',
650 'unspecified. Cannot restart.\n',
651 before_prompt=True
651 before_prompt=True
652 )
652 )
653
653
654 #---------------------------------------------------------------------------
654 #---------------------------------------------------------------------------
655 # 'FrontendWidget' protected interface
655 # 'FrontendWidget' protected interface
656 #---------------------------------------------------------------------------
656 #---------------------------------------------------------------------------
657
657
658 def _call_tip(self):
658 def _call_tip(self):
659 """ Shows a call tip, if appropriate, at the current cursor location.
659 """ Shows a call tip, if appropriate, at the current cursor location.
660 """
660 """
661 # Decide if it makes sense to show a call tip
661 # Decide if it makes sense to show a call tip
662 if not self.enable_calltips:
662 if not self.enable_calltips:
663 return False
663 return False
664 cursor = self._get_cursor()
664 cursor = self._get_cursor()
665 cursor.movePosition(QtGui.QTextCursor.Left)
665 cursor.movePosition(QtGui.QTextCursor.Left)
666 if cursor.document().characterAt(cursor.position()) != '(':
666 if cursor.document().characterAt(cursor.position()) != '(':
667 return False
667 return False
668 context = self._get_context(cursor)
668 context = self._get_context(cursor)
669 if not context:
669 if not context:
670 return False
670 return False
671
671
672 # Send the metadata request to the kernel
672 # Send the metadata request to the kernel
673 name = '.'.join(context)
673 name = '.'.join(context)
674 msg_id = self.kernel_client.shell_channel.object_info(name)
674 msg_id = self.kernel_client.object_info(name)
675 pos = self._get_cursor().position()
675 pos = self._get_cursor().position()
676 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
676 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
677 return True
677 return True
678
678
679 def _complete(self):
679 def _complete(self):
680 """ Performs completion at the current cursor location.
680 """ Performs completion at the current cursor location.
681 """
681 """
682 context = self._get_context()
682 context = self._get_context()
683 if context:
683 if context:
684 # Send the completion request to the kernel
684 # Send the completion request to the kernel
685 msg_id = self.kernel_client.shell_channel.complete(
685 msg_id = self.kernel_client.complete(
686 '.'.join(context), # text
686 '.'.join(context), # text
687 self._get_input_buffer_cursor_line(), # line
687 self._get_input_buffer_cursor_line(), # line
688 self._get_input_buffer_cursor_column(), # cursor_pos
688 self._get_input_buffer_cursor_column(), # cursor_pos
689 self.input_buffer) # block
689 self.input_buffer) # block
690 pos = self._get_cursor().position()
690 pos = self._get_cursor().position()
691 info = self._CompletionRequest(msg_id, pos)
691 info = self._CompletionRequest(msg_id, pos)
692 self._request_info['complete'] = info
692 self._request_info['complete'] = info
693
693
694 def _get_context(self, cursor=None):
694 def _get_context(self, cursor=None):
695 """ Gets the context for the specified cursor (or the current cursor
695 """ Gets the context for the specified cursor (or the current cursor
696 if none is specified).
696 if none is specified).
697 """
697 """
698 if cursor is None:
698 if cursor is None:
699 cursor = self._get_cursor()
699 cursor = self._get_cursor()
700 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
700 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
701 QtGui.QTextCursor.KeepAnchor)
701 QtGui.QTextCursor.KeepAnchor)
702 text = cursor.selection().toPlainText()
702 text = cursor.selection().toPlainText()
703 return self._completion_lexer.get_context(text)
703 return self._completion_lexer.get_context(text)
704
704
705 def _process_execute_abort(self, msg):
705 def _process_execute_abort(self, msg):
706 """ Process a reply for an aborted execution request.
706 """ Process a reply for an aborted execution request.
707 """
707 """
708 self._append_plain_text("ERROR: execution aborted\n")
708 self._append_plain_text("ERROR: execution aborted\n")
709
709
710 def _process_execute_error(self, msg):
710 def _process_execute_error(self, msg):
711 """ Process a reply for an execution request that resulted in an error.
711 """ Process a reply for an execution request that resulted in an error.
712 """
712 """
713 content = msg['content']
713 content = msg['content']
714 # If a SystemExit is passed along, this means exit() was called - also
714 # If a SystemExit is passed along, this means exit() was called - also
715 # all the ipython %exit magic syntax of '-k' to be used to keep
715 # all the ipython %exit magic syntax of '-k' to be used to keep
716 # the kernel running
716 # the kernel running
717 if content['ename']=='SystemExit':
717 if content['ename']=='SystemExit':
718 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
718 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
719 self._keep_kernel_on_exit = keepkernel
719 self._keep_kernel_on_exit = keepkernel
720 self.exit_requested.emit(self)
720 self.exit_requested.emit(self)
721 else:
721 else:
722 traceback = ''.join(content['traceback'])
722 traceback = ''.join(content['traceback'])
723 self._append_plain_text(traceback)
723 self._append_plain_text(traceback)
724
724
725 def _process_execute_ok(self, msg):
725 def _process_execute_ok(self, msg):
726 """ Process a reply for a successful execution request.
726 """ Process a reply for a successful execution request.
727 """
727 """
728 payload = msg['content']['payload']
728 payload = msg['content']['payload']
729 for item in payload:
729 for item in payload:
730 if not self._process_execute_payload(item):
730 if not self._process_execute_payload(item):
731 warning = 'Warning: received unknown payload of type %s'
731 warning = 'Warning: received unknown payload of type %s'
732 print(warning % repr(item['source']))
732 print(warning % repr(item['source']))
733
733
734 def _process_execute_payload(self, item):
734 def _process_execute_payload(self, item):
735 """ Process a single payload item from the list of payload items in an
735 """ Process a single payload item from the list of payload items in an
736 execution reply. Returns whether the payload was handled.
736 execution reply. Returns whether the payload was handled.
737 """
737 """
738 # The basic FrontendWidget doesn't handle payloads, as they are a
738 # The basic FrontendWidget doesn't handle payloads, as they are a
739 # mechanism for going beyond the standard Python interpreter model.
739 # mechanism for going beyond the standard Python interpreter model.
740 return False
740 return False
741
741
742 def _show_interpreter_prompt(self):
742 def _show_interpreter_prompt(self):
743 """ Shows a prompt for the interpreter.
743 """ Shows a prompt for the interpreter.
744 """
744 """
745 self._show_prompt('>>> ')
745 self._show_prompt('>>> ')
746
746
747 def _show_interpreter_prompt_for_reply(self, msg):
747 def _show_interpreter_prompt_for_reply(self, msg):
748 """ Shows a prompt for the interpreter given an 'execute_reply' message.
748 """ Shows a prompt for the interpreter given an 'execute_reply' message.
749 """
749 """
750 self._show_interpreter_prompt()
750 self._show_interpreter_prompt()
751
751
752 #------ Signal handlers ----------------------------------------------------
752 #------ Signal handlers ----------------------------------------------------
753
753
754 def _document_contents_change(self, position, removed, added):
754 def _document_contents_change(self, position, removed, added):
755 """ Called whenever the document's content changes. Display a call tip
755 """ Called whenever the document's content changes. Display a call tip
756 if appropriate.
756 if appropriate.
757 """
757 """
758 # Calculate where the cursor should be *after* the change:
758 # Calculate where the cursor should be *after* the change:
759 position += added
759 position += added
760
760
761 document = self._control.document()
761 document = self._control.document()
762 if position == self._get_cursor().position():
762 if position == self._get_cursor().position():
763 self._call_tip()
763 self._call_tip()
764
764
765 #------ Trait default initializers -----------------------------------------
765 #------ Trait default initializers -----------------------------------------
766
766
767 def _banner_default(self):
767 def _banner_default(self):
768 """ Returns the standard Python banner.
768 """ Returns the standard Python banner.
769 """
769 """
770 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
770 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
771 '"license" for more information.'
771 '"license" for more information.'
772 return banner % (sys.version, sys.platform)
772 return banner % (sys.version, sys.platform)
@@ -1,638 +1,648 b''
1 """Base classes to manage a Client's interaction with a running kernel
1 """Base classes to manage a Client's interaction with a running kernel
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import absolute_import
15 from __future__ import absolute_import
16
16
17 # Standard library imports
17 # Standard library imports
18 import atexit
18 import atexit
19 import errno
19 import errno
20 from threading import Thread
20 from threading import Thread
21 import time
21 import time
22
22
23 import zmq
23 import zmq
24 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
24 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
25 # during garbage collection of threads at exit:
25 # during garbage collection of threads at exit:
26 from zmq import ZMQError
26 from zmq import ZMQError
27 from zmq.eventloop import ioloop, zmqstream
27 from zmq.eventloop import ioloop, zmqstream
28
28
29 # Local imports
29 # Local imports
30 from .channelabc import (
30 from .channelabc import (
31 ShellChannelABC, IOPubChannelABC,
31 ShellChannelABC, IOPubChannelABC,
32 HBChannelABC, StdInChannelABC,
32 HBChannelABC, StdInChannelABC,
33 )
33 )
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Constants and exceptions
36 # Constants and exceptions
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class InvalidPortNumber(Exception):
39 class InvalidPortNumber(Exception):
40 pass
40 pass
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Utility functions
43 # Utility functions
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 # some utilities to validate message structure, these might get moved elsewhere
46 # some utilities to validate message structure, these might get moved elsewhere
47 # if they prove to have more generic utility
47 # if they prove to have more generic utility
48
48
49 def validate_string_list(lst):
49 def validate_string_list(lst):
50 """Validate that the input is a list of strings.
50 """Validate that the input is a list of strings.
51
51
52 Raises ValueError if not."""
52 Raises ValueError if not."""
53 if not isinstance(lst, list):
53 if not isinstance(lst, list):
54 raise ValueError('input %r must be a list' % lst)
54 raise ValueError('input %r must be a list' % lst)
55 for x in lst:
55 for x in lst:
56 if not isinstance(x, basestring):
56 if not isinstance(x, basestring):
57 raise ValueError('element %r in list must be a string' % x)
57 raise ValueError('element %r in list must be a string' % x)
58
58
59
59
60 def validate_string_dict(dct):
60 def validate_string_dict(dct):
61 """Validate that the input is a dict with string keys and values.
61 """Validate that the input is a dict with string keys and values.
62
62
63 Raises ValueError if not."""
63 Raises ValueError if not."""
64 for k,v in dct.iteritems():
64 for k,v in dct.iteritems():
65 if not isinstance(k, basestring):
65 if not isinstance(k, basestring):
66 raise ValueError('key %r in dict must be a string' % k)
66 raise ValueError('key %r in dict must be a string' % k)
67 if not isinstance(v, basestring):
67 if not isinstance(v, basestring):
68 raise ValueError('value %r in dict must be a string' % v)
68 raise ValueError('value %r in dict must be a string' % v)
69
69
70
70
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 # ZMQ Socket Channel classes
72 # ZMQ Socket Channel classes
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74
74
75 class ZMQSocketChannel(Thread):
75 class ZMQSocketChannel(Thread):
76 """The base class for the channels that use ZMQ sockets."""
76 """The base class for the channels that use ZMQ sockets."""
77 context = None
77 context = None
78 session = None
78 session = None
79 socket = None
79 socket = None
80 ioloop = None
80 ioloop = None
81 stream = None
81 stream = None
82 _address = None
82 _address = None
83 _exiting = False
83 _exiting = False
84 proxy_methods = []
84
85
85 def __init__(self, context, session, address):
86 def __init__(self, context, session, address):
86 """Create a channel.
87 """Create a channel.
87
88
88 Parameters
89 Parameters
89 ----------
90 ----------
90 context : :class:`zmq.Context`
91 context : :class:`zmq.Context`
91 The ZMQ context to use.
92 The ZMQ context to use.
92 session : :class:`session.Session`
93 session : :class:`session.Session`
93 The session to use.
94 The session to use.
94 address : zmq url
95 address : zmq url
95 Standard (ip, port) tuple that the kernel is listening on.
96 Standard (ip, port) tuple that the kernel is listening on.
96 """
97 """
97 super(ZMQSocketChannel, self).__init__()
98 super(ZMQSocketChannel, self).__init__()
98 self.daemon = True
99 self.daemon = True
99
100
100 self.context = context
101 self.context = context
101 self.session = session
102 self.session = session
102 if isinstance(address, tuple):
103 if isinstance(address, tuple):
103 if address[1] == 0:
104 if address[1] == 0:
104 message = 'The port number for a channel cannot be 0.'
105 message = 'The port number for a channel cannot be 0.'
105 raise InvalidPortNumber(message)
106 raise InvalidPortNumber(message)
106 address = "tcp://%s:%i" % address
107 address = "tcp://%s:%i" % address
107 self._address = address
108 self._address = address
108 atexit.register(self._notice_exit)
109 atexit.register(self._notice_exit)
109
110
110 def _notice_exit(self):
111 def _notice_exit(self):
111 self._exiting = True
112 self._exiting = True
112
113
113 def _run_loop(self):
114 def _run_loop(self):
114 """Run my loop, ignoring EINTR events in the poller"""
115 """Run my loop, ignoring EINTR events in the poller"""
115 while True:
116 while True:
116 try:
117 try:
117 self.ioloop.start()
118 self.ioloop.start()
118 except ZMQError as e:
119 except ZMQError as e:
119 if e.errno == errno.EINTR:
120 if e.errno == errno.EINTR:
120 continue
121 continue
121 else:
122 else:
122 raise
123 raise
123 except Exception:
124 except Exception:
124 if self._exiting:
125 if self._exiting:
125 break
126 break
126 else:
127 else:
127 raise
128 raise
128 else:
129 else:
129 break
130 break
130
131
131 def stop(self):
132 def stop(self):
132 """Stop the channel's event loop and join its thread.
133 """Stop the channel's event loop and join its thread.
133
134
134 This calls :method:`Thread.join` and returns when the thread
135 This calls :method:`Thread.join` and returns when the thread
135 terminates. :class:`RuntimeError` will be raised if
136 terminates. :class:`RuntimeError` will be raised if
136 :method:`self.start` is called again.
137 :method:`self.start` is called again.
137 """
138 """
138 self.join()
139 self.join()
139
140
140 @property
141 @property
141 def address(self):
142 def address(self):
142 """Get the channel's address as a zmq url string.
143 """Get the channel's address as a zmq url string.
143
144
144 These URLS have the form: 'tcp://127.0.0.1:5555'.
145 These URLS have the form: 'tcp://127.0.0.1:5555'.
145 """
146 """
146 return self._address
147 return self._address
147
148
148 def _queue_send(self, msg):
149 def _queue_send(self, msg):
149 """Queue a message to be sent from the IOLoop's thread.
150 """Queue a message to be sent from the IOLoop's thread.
150
151
151 Parameters
152 Parameters
152 ----------
153 ----------
153 msg : message to send
154 msg : message to send
154
155
155 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
156 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
156 thread control of the action.
157 thread control of the action.
157 """
158 """
158 def thread_send():
159 def thread_send():
159 self.session.send(self.stream, msg)
160 self.session.send(self.stream, msg)
160 self.ioloop.add_callback(thread_send)
161 self.ioloop.add_callback(thread_send)
161
162
162 def _handle_recv(self, msg):
163 def _handle_recv(self, msg):
163 """Callback for stream.on_recv.
164 """Callback for stream.on_recv.
164
165
165 Unpacks message, and calls handlers with it.
166 Unpacks message, and calls handlers with it.
166 """
167 """
167 ident,smsg = self.session.feed_identities(msg)
168 ident,smsg = self.session.feed_identities(msg)
168 self.call_handlers(self.session.unserialize(smsg))
169 self.call_handlers(self.session.unserialize(smsg))
169
170
170
171
171
172
172 class ShellChannel(ZMQSocketChannel):
173 class ShellChannel(ZMQSocketChannel):
173 """The shell channel for issuing request/replies to the kernel."""
174 """The shell channel for issuing request/replies to the kernel."""
174
175
175 command_queue = None
176 command_queue = None
176 # flag for whether execute requests should be allowed to call raw_input:
177 # flag for whether execute requests should be allowed to call raw_input:
177 allow_stdin = True
178 allow_stdin = True
179 proxy_methods = [
180 'execute',
181 'complete',
182 'object_info',
183 'history',
184 'kernel_info',
185 'shutdown',
186 ]
178
187
179 def __init__(self, context, session, address):
188 def __init__(self, context, session, address):
180 super(ShellChannel, self).__init__(context, session, address)
189 super(ShellChannel, self).__init__(context, session, address)
181 self.ioloop = ioloop.IOLoop()
190 self.ioloop = ioloop.IOLoop()
182
191
183 def run(self):
192 def run(self):
184 """The thread's main activity. Call start() instead."""
193 """The thread's main activity. Call start() instead."""
185 self.socket = self.context.socket(zmq.DEALER)
194 self.socket = self.context.socket(zmq.DEALER)
186 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
195 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
187 self.socket.connect(self.address)
196 self.socket.connect(self.address)
188 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
197 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
189 self.stream.on_recv(self._handle_recv)
198 self.stream.on_recv(self._handle_recv)
190 self._run_loop()
199 self._run_loop()
191 try:
200 try:
192 self.socket.close()
201 self.socket.close()
193 except:
202 except:
194 pass
203 pass
195
204
196 def stop(self):
205 def stop(self):
197 """Stop the channel's event loop and join its thread."""
206 """Stop the channel's event loop and join its thread."""
198 self.ioloop.stop()
207 self.ioloop.stop()
199 super(ShellChannel, self).stop()
208 super(ShellChannel, self).stop()
200
209
201 def call_handlers(self, msg):
210 def call_handlers(self, msg):
202 """This method is called in the ioloop thread when a message arrives.
211 """This method is called in the ioloop thread when a message arrives.
203
212
204 Subclasses should override this method to handle incoming messages.
213 Subclasses should override this method to handle incoming messages.
205 It is important to remember that this method is called in the thread
214 It is important to remember that this method is called in the thread
206 so that some logic must be done to ensure that the application level
215 so that some logic must be done to ensure that the application level
207 handlers are called in the application thread.
216 handlers are called in the application thread.
208 """
217 """
209 raise NotImplementedError('call_handlers must be defined in a subclass.')
218 raise NotImplementedError('call_handlers must be defined in a subclass.')
210
219
211 def execute(self, code, silent=False, store_history=True,
220 def execute(self, code, silent=False, store_history=True,
212 user_variables=None, user_expressions=None, allow_stdin=None):
221 user_variables=None, user_expressions=None, allow_stdin=None):
213 """Execute code in the kernel.
222 """Execute code in the kernel.
214
223
215 Parameters
224 Parameters
216 ----------
225 ----------
217 code : str
226 code : str
218 A string of Python code.
227 A string of Python code.
219
228
220 silent : bool, optional (default False)
229 silent : bool, optional (default False)
221 If set, the kernel will execute the code as quietly possible, and
230 If set, the kernel will execute the code as quietly possible, and
222 will force store_history to be False.
231 will force store_history to be False.
223
232
224 store_history : bool, optional (default True)
233 store_history : bool, optional (default True)
225 If set, the kernel will store command history. This is forced
234 If set, the kernel will store command history. This is forced
226 to be False if silent is True.
235 to be False if silent is True.
227
236
228 user_variables : list, optional
237 user_variables : list, optional
229 A list of variable names to pull from the user's namespace. They
238 A list of variable names to pull from the user's namespace. They
230 will come back as a dict with these names as keys and their
239 will come back as a dict with these names as keys and their
231 :func:`repr` as values.
240 :func:`repr` as values.
232
241
233 user_expressions : dict, optional
242 user_expressions : dict, optional
234 A dict mapping names to expressions to be evaluated in the user's
243 A dict mapping names to expressions to be evaluated in the user's
235 dict. The expression values are returned as strings formatted using
244 dict. The expression values are returned as strings formatted using
236 :func:`repr`.
245 :func:`repr`.
237
246
238 allow_stdin : bool, optional (default self.allow_stdin)
247 allow_stdin : bool, optional (default self.allow_stdin)
239 Flag for whether the kernel can send stdin requests to frontends.
248 Flag for whether the kernel can send stdin requests to frontends.
240
249
241 Some frontends (e.g. the Notebook) do not support stdin requests.
250 Some frontends (e.g. the Notebook) do not support stdin requests.
242 If raw_input is called from code executed from such a frontend, a
251 If raw_input is called from code executed from such a frontend, a
243 StdinNotImplementedError will be raised.
252 StdinNotImplementedError will be raised.
244
253
245 Returns
254 Returns
246 -------
255 -------
247 The msg_id of the message sent.
256 The msg_id of the message sent.
248 """
257 """
249 if user_variables is None:
258 if user_variables is None:
250 user_variables = []
259 user_variables = []
251 if user_expressions is None:
260 if user_expressions is None:
252 user_expressions = {}
261 user_expressions = {}
253 if allow_stdin is None:
262 if allow_stdin is None:
254 allow_stdin = self.allow_stdin
263 allow_stdin = self.allow_stdin
255
264
256
265
257 # Don't waste network traffic if inputs are invalid
266 # Don't waste network traffic if inputs are invalid
258 if not isinstance(code, basestring):
267 if not isinstance(code, basestring):
259 raise ValueError('code %r must be a string' % code)
268 raise ValueError('code %r must be a string' % code)
260 validate_string_list(user_variables)
269 validate_string_list(user_variables)
261 validate_string_dict(user_expressions)
270 validate_string_dict(user_expressions)
262
271
263 # Create class for content/msg creation. Related to, but possibly
272 # Create class for content/msg creation. Related to, but possibly
264 # not in Session.
273 # not in Session.
265 content = dict(code=code, silent=silent, store_history=store_history,
274 content = dict(code=code, silent=silent, store_history=store_history,
266 user_variables=user_variables,
275 user_variables=user_variables,
267 user_expressions=user_expressions,
276 user_expressions=user_expressions,
268 allow_stdin=allow_stdin,
277 allow_stdin=allow_stdin,
269 )
278 )
270 msg = self.session.msg('execute_request', content)
279 msg = self.session.msg('execute_request', content)
271 self._queue_send(msg)
280 self._queue_send(msg)
272 return msg['header']['msg_id']
281 return msg['header']['msg_id']
273
282
274 def complete(self, text, line, cursor_pos, block=None):
283 def complete(self, text, line, cursor_pos, block=None):
275 """Tab complete text in the kernel's namespace.
284 """Tab complete text in the kernel's namespace.
276
285
277 Parameters
286 Parameters
278 ----------
287 ----------
279 text : str
288 text : str
280 The text to complete.
289 The text to complete.
281 line : str
290 line : str
282 The full line of text that is the surrounding context for the
291 The full line of text that is the surrounding context for the
283 text to complete.
292 text to complete.
284 cursor_pos : int
293 cursor_pos : int
285 The position of the cursor in the line where the completion was
294 The position of the cursor in the line where the completion was
286 requested.
295 requested.
287 block : str, optional
296 block : str, optional
288 The full block of code in which the completion is being requested.
297 The full block of code in which the completion is being requested.
289
298
290 Returns
299 Returns
291 -------
300 -------
292 The msg_id of the message sent.
301 The msg_id of the message sent.
293 """
302 """
294 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
303 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
295 msg = self.session.msg('complete_request', content)
304 msg = self.session.msg('complete_request', content)
296 self._queue_send(msg)
305 self._queue_send(msg)
297 return msg['header']['msg_id']
306 return msg['header']['msg_id']
298
307
299 def object_info(self, oname, detail_level=0):
308 def object_info(self, oname, detail_level=0):
300 """Get metadata information about an object in the kernel's namespace.
309 """Get metadata information about an object in the kernel's namespace.
301
310
302 Parameters
311 Parameters
303 ----------
312 ----------
304 oname : str
313 oname : str
305 A string specifying the object name.
314 A string specifying the object name.
306 detail_level : int, optional
315 detail_level : int, optional
307 The level of detail for the introspection (0-2)
316 The level of detail for the introspection (0-2)
308
317
309 Returns
318 Returns
310 -------
319 -------
311 The msg_id of the message sent.
320 The msg_id of the message sent.
312 """
321 """
313 content = dict(oname=oname, detail_level=detail_level)
322 content = dict(oname=oname, detail_level=detail_level)
314 msg = self.session.msg('object_info_request', content)
323 msg = self.session.msg('object_info_request', content)
315 self._queue_send(msg)
324 self._queue_send(msg)
316 return msg['header']['msg_id']
325 return msg['header']['msg_id']
317
326
318 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
327 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
319 """Get entries from the kernel's history list.
328 """Get entries from the kernel's history list.
320
329
321 Parameters
330 Parameters
322 ----------
331 ----------
323 raw : bool
332 raw : bool
324 If True, return the raw input.
333 If True, return the raw input.
325 output : bool
334 output : bool
326 If True, then return the output as well.
335 If True, then return the output as well.
327 hist_access_type : str
336 hist_access_type : str
328 'range' (fill in session, start and stop params), 'tail' (fill in n)
337 'range' (fill in session, start and stop params), 'tail' (fill in n)
329 or 'search' (fill in pattern param).
338 or 'search' (fill in pattern param).
330
339
331 session : int
340 session : int
332 For a range request, the session from which to get lines. Session
341 For a range request, the session from which to get lines. Session
333 numbers are positive integers; negative ones count back from the
342 numbers are positive integers; negative ones count back from the
334 current session.
343 current session.
335 start : int
344 start : int
336 The first line number of a history range.
345 The first line number of a history range.
337 stop : int
346 stop : int
338 The final (excluded) line number of a history range.
347 The final (excluded) line number of a history range.
339
348
340 n : int
349 n : int
341 The number of lines of history to get for a tail request.
350 The number of lines of history to get for a tail request.
342
351
343 pattern : str
352 pattern : str
344 The glob-syntax pattern for a search request.
353 The glob-syntax pattern for a search request.
345
354
346 Returns
355 Returns
347 -------
356 -------
348 The msg_id of the message sent.
357 The msg_id of the message sent.
349 """
358 """
350 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
359 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
351 **kwargs)
360 **kwargs)
352 msg = self.session.msg('history_request', content)
361 msg = self.session.msg('history_request', content)
353 self._queue_send(msg)
362 self._queue_send(msg)
354 return msg['header']['msg_id']
363 return msg['header']['msg_id']
355
364
356 def kernel_info(self):
365 def kernel_info(self):
357 """Request kernel info."""
366 """Request kernel info."""
358 msg = self.session.msg('kernel_info_request')
367 msg = self.session.msg('kernel_info_request')
359 self._queue_send(msg)
368 self._queue_send(msg)
360 return msg['header']['msg_id']
369 return msg['header']['msg_id']
361
370
362 def shutdown(self, restart=False):
371 def shutdown(self, restart=False):
363 """Request an immediate kernel shutdown.
372 """Request an immediate kernel shutdown.
364
373
365 Upon receipt of the (empty) reply, client code can safely assume that
374 Upon receipt of the (empty) reply, client code can safely assume that
366 the kernel has shut down and it's safe to forcefully terminate it if
375 the kernel has shut down and it's safe to forcefully terminate it if
367 it's still alive.
376 it's still alive.
368
377
369 The kernel will send the reply via a function registered with Python's
378 The kernel will send the reply via a function registered with Python's
370 atexit module, ensuring it's truly done as the kernel is done with all
379 atexit module, ensuring it's truly done as the kernel is done with all
371 normal operation.
380 normal operation.
372 """
381 """
373 # Send quit message to kernel. Once we implement kernel-side setattr,
382 # Send quit message to kernel. Once we implement kernel-side setattr,
374 # this should probably be done that way, but for now this will do.
383 # this should probably be done that way, but for now this will do.
375 msg = self.session.msg('shutdown_request', {'restart':restart})
384 msg = self.session.msg('shutdown_request', {'restart':restart})
376 self._queue_send(msg)
385 self._queue_send(msg)
377 return msg['header']['msg_id']
386 return msg['header']['msg_id']
378
387
379
388
380
389
381 class IOPubChannel(ZMQSocketChannel):
390 class IOPubChannel(ZMQSocketChannel):
382 """The iopub channel which listens for messages that the kernel publishes.
391 """The iopub channel which listens for messages that the kernel publishes.
383
392
384 This channel is where all output is published to frontends.
393 This channel is where all output is published to frontends.
385 """
394 """
386
395
387 def __init__(self, context, session, address):
396 def __init__(self, context, session, address):
388 super(IOPubChannel, self).__init__(context, session, address)
397 super(IOPubChannel, self).__init__(context, session, address)
389 self.ioloop = ioloop.IOLoop()
398 self.ioloop = ioloop.IOLoop()
390
399
391 def run(self):
400 def run(self):
392 """The thread's main activity. Call start() instead."""
401 """The thread's main activity. Call start() instead."""
393 self.socket = self.context.socket(zmq.SUB)
402 self.socket = self.context.socket(zmq.SUB)
394 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
403 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
395 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
404 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
396 self.socket.connect(self.address)
405 self.socket.connect(self.address)
397 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
406 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
398 self.stream.on_recv(self._handle_recv)
407 self.stream.on_recv(self._handle_recv)
399 self._run_loop()
408 self._run_loop()
400 try:
409 try:
401 self.socket.close()
410 self.socket.close()
402 except:
411 except:
403 pass
412 pass
404
413
405 def stop(self):
414 def stop(self):
406 """Stop the channel's event loop and join its thread."""
415 """Stop the channel's event loop and join its thread."""
407 self.ioloop.stop()
416 self.ioloop.stop()
408 super(IOPubChannel, self).stop()
417 super(IOPubChannel, self).stop()
409
418
410 def call_handlers(self, msg):
419 def call_handlers(self, msg):
411 """This method is called in the ioloop thread when a message arrives.
420 """This method is called in the ioloop thread when a message arrives.
412
421
413 Subclasses should override this method to handle incoming messages.
422 Subclasses should override this method to handle incoming messages.
414 It is important to remember that this method is called in the thread
423 It is important to remember that this method is called in the thread
415 so that some logic must be done to ensure that the application leve
424 so that some logic must be done to ensure that the application leve
416 handlers are called in the application thread.
425 handlers are called in the application thread.
417 """
426 """
418 raise NotImplementedError('call_handlers must be defined in a subclass.')
427 raise NotImplementedError('call_handlers must be defined in a subclass.')
419
428
420 def flush(self, timeout=1.0):
429 def flush(self, timeout=1.0):
421 """Immediately processes all pending messages on the iopub channel.
430 """Immediately processes all pending messages on the iopub channel.
422
431
423 Callers should use this method to ensure that :method:`call_handlers`
432 Callers should use this method to ensure that :method:`call_handlers`
424 has been called for all messages that have been received on the
433 has been called for all messages that have been received on the
425 0MQ SUB socket of this channel.
434 0MQ SUB socket of this channel.
426
435
427 This method is thread safe.
436 This method is thread safe.
428
437
429 Parameters
438 Parameters
430 ----------
439 ----------
431 timeout : float, optional
440 timeout : float, optional
432 The maximum amount of time to spend flushing, in seconds. The
441 The maximum amount of time to spend flushing, in seconds. The
433 default is one second.
442 default is one second.
434 """
443 """
435 # We do the IOLoop callback process twice to ensure that the IOLoop
444 # We do the IOLoop callback process twice to ensure that the IOLoop
436 # gets to perform at least one full poll.
445 # gets to perform at least one full poll.
437 stop_time = time.time() + timeout
446 stop_time = time.time() + timeout
438 for i in xrange(2):
447 for i in xrange(2):
439 self._flushed = False
448 self._flushed = False
440 self.ioloop.add_callback(self._flush)
449 self.ioloop.add_callback(self._flush)
441 while not self._flushed and time.time() < stop_time:
450 while not self._flushed and time.time() < stop_time:
442 time.sleep(0.01)
451 time.sleep(0.01)
443
452
444 def _flush(self):
453 def _flush(self):
445 """Callback for :method:`self.flush`."""
454 """Callback for :method:`self.flush`."""
446 self.stream.flush()
455 self.stream.flush()
447 self._flushed = True
456 self._flushed = True
448
457
449
458
450 class StdInChannel(ZMQSocketChannel):
459 class StdInChannel(ZMQSocketChannel):
451 """The stdin channel to handle raw_input requests that the kernel makes."""
460 """The stdin channel to handle raw_input requests that the kernel makes."""
452
461
453 msg_queue = None
462 msg_queue = None
463 proxy_methods = ['input']
454
464
455 def __init__(self, context, session, address):
465 def __init__(self, context, session, address):
456 super(StdInChannel, self).__init__(context, session, address)
466 super(StdInChannel, self).__init__(context, session, address)
457 self.ioloop = ioloop.IOLoop()
467 self.ioloop = ioloop.IOLoop()
458
468
459 def run(self):
469 def run(self):
460 """The thread's main activity. Call start() instead."""
470 """The thread's main activity. Call start() instead."""
461 self.socket = self.context.socket(zmq.DEALER)
471 self.socket = self.context.socket(zmq.DEALER)
462 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
472 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
463 self.socket.connect(self.address)
473 self.socket.connect(self.address)
464 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
474 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
465 self.stream.on_recv(self._handle_recv)
475 self.stream.on_recv(self._handle_recv)
466 self._run_loop()
476 self._run_loop()
467 try:
477 try:
468 self.socket.close()
478 self.socket.close()
469 except:
479 except:
470 pass
480 pass
471
481
472 def stop(self):
482 def stop(self):
473 """Stop the channel's event loop and join its thread."""
483 """Stop the channel's event loop and join its thread."""
474 self.ioloop.stop()
484 self.ioloop.stop()
475 super(StdInChannel, self).stop()
485 super(StdInChannel, self).stop()
476
486
477 def call_handlers(self, msg):
487 def call_handlers(self, msg):
478 """This method is called in the ioloop thread when a message arrives.
488 """This method is called in the ioloop thread when a message arrives.
479
489
480 Subclasses should override this method to handle incoming messages.
490 Subclasses should override this method to handle incoming messages.
481 It is important to remember that this method is called in the thread
491 It is important to remember that this method is called in the thread
482 so that some logic must be done to ensure that the application leve
492 so that some logic must be done to ensure that the application leve
483 handlers are called in the application thread.
493 handlers are called in the application thread.
484 """
494 """
485 raise NotImplementedError('call_handlers must be defined in a subclass.')
495 raise NotImplementedError('call_handlers must be defined in a subclass.')
486
496
487 def input(self, string):
497 def input(self, string):
488 """Send a string of raw input to the kernel."""
498 """Send a string of raw input to the kernel."""
489 content = dict(value=string)
499 content = dict(value=string)
490 msg = self.session.msg('input_reply', content)
500 msg = self.session.msg('input_reply', content)
491 self._queue_send(msg)
501 self._queue_send(msg)
492
502
493
503
494 class HBChannel(ZMQSocketChannel):
504 class HBChannel(ZMQSocketChannel):
495 """The heartbeat channel which monitors the kernel heartbeat.
505 """The heartbeat channel which monitors the kernel heartbeat.
496
506
497 Note that the heartbeat channel is paused by default. As long as you start
507 Note that the heartbeat channel is paused by default. As long as you start
498 this channel, the kernel manager will ensure that it is paused and un-paused
508 this channel, the kernel manager will ensure that it is paused and un-paused
499 as appropriate.
509 as appropriate.
500 """
510 """
501
511
502 time_to_dead = 3.0
512 time_to_dead = 3.0
503 socket = None
513 socket = None
504 poller = None
514 poller = None
505 _running = None
515 _running = None
506 _pause = None
516 _pause = None
507 _beating = None
517 _beating = None
508
518
509 def __init__(self, context, session, address):
519 def __init__(self, context, session, address):
510 super(HBChannel, self).__init__(context, session, address)
520 super(HBChannel, self).__init__(context, session, address)
511 self._running = False
521 self._running = False
512 self._pause =True
522 self._pause =True
513 self.poller = zmq.Poller()
523 self.poller = zmq.Poller()
514
524
515 def _create_socket(self):
525 def _create_socket(self):
516 if self.socket is not None:
526 if self.socket is not None:
517 # close previous socket, before opening a new one
527 # close previous socket, before opening a new one
518 self.poller.unregister(self.socket)
528 self.poller.unregister(self.socket)
519 self.socket.close()
529 self.socket.close()
520 self.socket = self.context.socket(zmq.REQ)
530 self.socket = self.context.socket(zmq.REQ)
521 self.socket.setsockopt(zmq.LINGER, 0)
531 self.socket.setsockopt(zmq.LINGER, 0)
522 self.socket.connect(self.address)
532 self.socket.connect(self.address)
523
533
524 self.poller.register(self.socket, zmq.POLLIN)
534 self.poller.register(self.socket, zmq.POLLIN)
525
535
526 def _poll(self, start_time):
536 def _poll(self, start_time):
527 """poll for heartbeat replies until we reach self.time_to_dead.
537 """poll for heartbeat replies until we reach self.time_to_dead.
528
538
529 Ignores interrupts, and returns the result of poll(), which
539 Ignores interrupts, and returns the result of poll(), which
530 will be an empty list if no messages arrived before the timeout,
540 will be an empty list if no messages arrived before the timeout,
531 or the event tuple if there is a message to receive.
541 or the event tuple if there is a message to receive.
532 """
542 """
533
543
534 until_dead = self.time_to_dead - (time.time() - start_time)
544 until_dead = self.time_to_dead - (time.time() - start_time)
535 # ensure poll at least once
545 # ensure poll at least once
536 until_dead = max(until_dead, 1e-3)
546 until_dead = max(until_dead, 1e-3)
537 events = []
547 events = []
538 while True:
548 while True:
539 try:
549 try:
540 events = self.poller.poll(1000 * until_dead)
550 events = self.poller.poll(1000 * until_dead)
541 except ZMQError as e:
551 except ZMQError as e:
542 if e.errno == errno.EINTR:
552 if e.errno == errno.EINTR:
543 # ignore interrupts during heartbeat
553 # ignore interrupts during heartbeat
544 # this may never actually happen
554 # this may never actually happen
545 until_dead = self.time_to_dead - (time.time() - start_time)
555 until_dead = self.time_to_dead - (time.time() - start_time)
546 until_dead = max(until_dead, 1e-3)
556 until_dead = max(until_dead, 1e-3)
547 pass
557 pass
548 else:
558 else:
549 raise
559 raise
550 except Exception:
560 except Exception:
551 if self._exiting:
561 if self._exiting:
552 break
562 break
553 else:
563 else:
554 raise
564 raise
555 else:
565 else:
556 break
566 break
557 return events
567 return events
558
568
559 def run(self):
569 def run(self):
560 """The thread's main activity. Call start() instead."""
570 """The thread's main activity. Call start() instead."""
561 self._create_socket()
571 self._create_socket()
562 self._running = True
572 self._running = True
563 self._beating = True
573 self._beating = True
564
574
565 while self._running:
575 while self._running:
566 if self._pause:
576 if self._pause:
567 # just sleep, and skip the rest of the loop
577 # just sleep, and skip the rest of the loop
568 time.sleep(self.time_to_dead)
578 time.sleep(self.time_to_dead)
569 continue
579 continue
570
580
571 since_last_heartbeat = 0.0
581 since_last_heartbeat = 0.0
572 # io.rprint('Ping from HB channel') # dbg
582 # io.rprint('Ping from HB channel') # dbg
573 # no need to catch EFSM here, because the previous event was
583 # no need to catch EFSM here, because the previous event was
574 # either a recv or connect, which cannot be followed by EFSM
584 # either a recv or connect, which cannot be followed by EFSM
575 self.socket.send(b'ping')
585 self.socket.send(b'ping')
576 request_time = time.time()
586 request_time = time.time()
577 ready = self._poll(request_time)
587 ready = self._poll(request_time)
578 if ready:
588 if ready:
579 self._beating = True
589 self._beating = True
580 # the poll above guarantees we have something to recv
590 # the poll above guarantees we have something to recv
581 self.socket.recv()
591 self.socket.recv()
582 # sleep the remainder of the cycle
592 # sleep the remainder of the cycle
583 remainder = self.time_to_dead - (time.time() - request_time)
593 remainder = self.time_to_dead - (time.time() - request_time)
584 if remainder > 0:
594 if remainder > 0:
585 time.sleep(remainder)
595 time.sleep(remainder)
586 continue
596 continue
587 else:
597 else:
588 # nothing was received within the time limit, signal heart failure
598 # nothing was received within the time limit, signal heart failure
589 self._beating = False
599 self._beating = False
590 since_last_heartbeat = time.time() - request_time
600 since_last_heartbeat = time.time() - request_time
591 self.call_handlers(since_last_heartbeat)
601 self.call_handlers(since_last_heartbeat)
592 # and close/reopen the socket, because the REQ/REP cycle has been broken
602 # and close/reopen the socket, because the REQ/REP cycle has been broken
593 self._create_socket()
603 self._create_socket()
594 continue
604 continue
595 try:
605 try:
596 self.socket.close()
606 self.socket.close()
597 except:
607 except:
598 pass
608 pass
599
609
600 def pause(self):
610 def pause(self):
601 """Pause the heartbeat."""
611 """Pause the heartbeat."""
602 self._pause = True
612 self._pause = True
603
613
604 def unpause(self):
614 def unpause(self):
605 """Unpause the heartbeat."""
615 """Unpause the heartbeat."""
606 self._pause = False
616 self._pause = False
607
617
608 def is_beating(self):
618 def is_beating(self):
609 """Is the heartbeat running and responsive (and not paused)."""
619 """Is the heartbeat running and responsive (and not paused)."""
610 if self.is_alive() and not self._pause and self._beating:
620 if self.is_alive() and not self._pause and self._beating:
611 return True
621 return True
612 else:
622 else:
613 return False
623 return False
614
624
615 def stop(self):
625 def stop(self):
616 """Stop the channel's event loop and join its thread."""
626 """Stop the channel's event loop and join its thread."""
617 self._running = False
627 self._running = False
618 super(HBChannel, self).stop()
628 super(HBChannel, self).stop()
619
629
620 def call_handlers(self, since_last_heartbeat):
630 def call_handlers(self, since_last_heartbeat):
621 """This method is called in the ioloop thread when a message arrives.
631 """This method is called in the ioloop thread when a message arrives.
622
632
623 Subclasses should override this method to handle incoming messages.
633 Subclasses should override this method to handle incoming messages.
624 It is important to remember that this method is called in the thread
634 It is important to remember that this method is called in the thread
625 so that some logic must be done to ensure that the application level
635 so that some logic must be done to ensure that the application level
626 handlers are called in the application thread.
636 handlers are called in the application thread.
627 """
637 """
628 raise NotImplementedError('call_handlers must be defined in a subclass.')
638 raise NotImplementedError('call_handlers must be defined in a subclass.')
629
639
630
640
631 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
641 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
632 # ABC Registration
642 # ABC Registration
633 #-----------------------------------------------------------------------------
643 #-----------------------------------------------------------------------------
634
644
635 ShellChannelABC.register(ShellChannel)
645 ShellChannelABC.register(ShellChannel)
636 IOPubChannelABC.register(IOPubChannel)
646 IOPubChannelABC.register(IOPubChannel)
637 HBChannelABC.register(HBChannel)
647 HBChannelABC.register(HBChannel)
638 StdInChannelABC.register(StdInChannel)
648 StdInChannelABC.register(StdInChannel)
@@ -1,182 +1,216 b''
1 """Base class to manage the interaction with a running kernel
1 """Base class to manage the interaction with a running kernel
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import absolute_import
15 from __future__ import absolute_import
16
16
17 import zmq
17 import zmq
18
18
19 # Local imports
19 # Local imports
20 from IPython.config.configurable import LoggingConfigurable
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
21 from IPython.utils.traitlets import (
22 Any, Instance, Type,
22 Any, Instance, Type,
23 )
23 )
24
24
25 from .zmq.session import Session
25 from .zmq.session import Session
26 from .channels import (
26 from .channels import (
27 ShellChannel, IOPubChannel,
27 ShellChannel, IOPubChannel,
28 HBChannel, StdInChannel,
28 HBChannel, StdInChannel,
29 )
29 )
30 from .clientabc import KernelClientABC
30 from .clientabc import KernelClientABC
31 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
32
32
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Main kernel client class
35 # Main kernel client class
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
39 """Communicates with a single kernel on any host via zmq channels.
39 """Communicates with a single kernel on any host via zmq channels.
40
40
41 There are four channels associated with each kernel:
41 There are four channels associated with each kernel:
42
42
43 * shell: for request/reply calls to the kernel.
43 * shell: for request/reply calls to the kernel.
44 * iopub: for the kernel to publish results to frontends.
44 * iopub: for the kernel to publish results to frontends.
45 * hb: for monitoring the kernel's heartbeat.
45 * hb: for monitoring the kernel's heartbeat.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
47
47
48 """
48 """
49
49
50 # The PyZMQ Context to use for communication with the kernel.
50 # The PyZMQ Context to use for communication with the kernel.
51 context = Instance(zmq.Context)
51 context = Instance(zmq.Context)
52 def _context_default(self):
52 def _context_default(self):
53 return zmq.Context.instance()
53 return zmq.Context.instance()
54
54
55 # The Session to use for communication with the kernel.
55 # The Session to use for communication with the kernel.
56 session = Instance(Session)
56 session = Instance(Session)
57 def _session_default(self):
57 def _session_default(self):
58 return Session(config=self.config)
58 return Session(config=self.config)
59
59
60 # The classes to use for the various channels
60 # The classes to use for the various channels
61 shell_channel_class = Type(ShellChannel)
61 shell_channel_class = Type(ShellChannel)
62 iopub_channel_class = Type(IOPubChannel)
62 iopub_channel_class = Type(IOPubChannel)
63 stdin_channel_class = Type(StdInChannel)
63 stdin_channel_class = Type(StdInChannel)
64 hb_channel_class = Type(HBChannel)
64 hb_channel_class = Type(HBChannel)
65
65
66 # Protected traits
66 # Protected traits
67 _shell_channel = Any
67 _shell_channel = Any
68 _iopub_channel = Any
68 _iopub_channel = Any
69 _stdin_channel = Any
69 _stdin_channel = Any
70 _hb_channel = Any
70 _hb_channel = Any
71
71
72 # def __init__(self, *args, **kwargs):
73 # super(KernelClient, self).__init__(*args, **kwargs)
74 # # setup channel proxy methods, e.g.
75 # # Client.execute => shell_channel.execute
76 # for channel in ['shell', 'iopub', 'stdin', 'hb']:
77 # cls = getattr(self, '%s_channel_class' % channel)
78 # for method in cls.proxy_methods:
79 # setattr(self, method, self._proxy_method(channel, method))
80 #
81 #--------------------------------------------------------------------------
82 # Channel proxy methods
83 #--------------------------------------------------------------------------
84
85 def _get_msg(channel, *args, **kwargs):
86 return channel.get_msg(*args, **kwargs)
87
88 def get_shell_msg(self, *args, **kwargs):
89 """Get a message from the shell channel"""
90 return self.shell_channel.get_msg(*args, **kwargs)
91
92 def get_iopub_msg(self, *args, **kwargs):
93 """Get a message from the iopub channel"""
94 return self.iopub_channel.get_msg(*args, **kwargs)
95
96 def get_stdin_msg(self, *args, **kwargs):
97 """Get a message from the stdin channel"""
98 return self.stdin_channel.get_msg(*args, **kwargs)
99
72 #--------------------------------------------------------------------------
100 #--------------------------------------------------------------------------
73 # Channel management methods
101 # Channel management methods
74 #--------------------------------------------------------------------------
102 #--------------------------------------------------------------------------
75
103
76 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
104 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
77 """Starts the channels for this kernel.
105 """Starts the channels for this kernel.
78
106
79 This will create the channels if they do not exist and then start
107 This will create the channels if they do not exist and then start
80 them (their activity runs in a thread). If port numbers of 0 are
108 them (their activity runs in a thread). If port numbers of 0 are
81 being used (random ports) then you must first call
109 being used (random ports) then you must first call
82 :method:`start_kernel`. If the channels have been stopped and you
110 :method:`start_kernel`. If the channels have been stopped and you
83 call this, :class:`RuntimeError` will be raised.
111 call this, :class:`RuntimeError` will be raised.
84 """
112 """
85 if shell:
113 if shell:
86 self.shell_channel.start()
114 self.shell_channel.start()
115 for method in self.shell_channel.proxy_methods:
116 setattr(self, method, getattr(self.shell_channel, method))
87 if iopub:
117 if iopub:
88 self.iopub_channel.start()
118 self.iopub_channel.start()
119 for method in self.iopub_channel.proxy_methods:
120 setattr(self, method, getattr(self.iopub_channel, method))
89 if stdin:
121 if stdin:
90 self.stdin_channel.start()
122 self.stdin_channel.start()
123 for method in self.stdin_channel.proxy_methods:
124 setattr(self, method, getattr(self.stdin_channel, method))
91 self.shell_channel.allow_stdin = True
125 self.shell_channel.allow_stdin = True
92 else:
126 else:
93 self.shell_channel.allow_stdin = False
127 self.shell_channel.allow_stdin = False
94 if hb:
128 if hb:
95 self.hb_channel.start()
129 self.hb_channel.start()
96
130
97 def stop_channels(self):
131 def stop_channels(self):
98 """Stops all the running channels for this kernel.
132 """Stops all the running channels for this kernel.
99
133
100 This stops their event loops and joins their threads.
134 This stops their event loops and joins their threads.
101 """
135 """
102 if self.shell_channel.is_alive():
136 if self.shell_channel.is_alive():
103 self.shell_channel.stop()
137 self.shell_channel.stop()
104 if self.iopub_channel.is_alive():
138 if self.iopub_channel.is_alive():
105 self.iopub_channel.stop()
139 self.iopub_channel.stop()
106 if self.stdin_channel.is_alive():
140 if self.stdin_channel.is_alive():
107 self.stdin_channel.stop()
141 self.stdin_channel.stop()
108 if self.hb_channel.is_alive():
142 if self.hb_channel.is_alive():
109 self.hb_channel.stop()
143 self.hb_channel.stop()
110
144
111 @property
145 @property
112 def channels_running(self):
146 def channels_running(self):
113 """Are any of the channels created and running?"""
147 """Are any of the channels created and running?"""
114 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
148 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
115 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
149 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
116
150
117 def _make_url(self, port):
151 def _make_url(self, port):
118 """Make a zmq url with a port.
152 """Make a zmq url with a port.
119
153
120 There are two cases that this handles:
154 There are two cases that this handles:
121
155
122 * tcp: tcp://ip:port
156 * tcp: tcp://ip:port
123 * ipc: ipc://ip-port
157 * ipc: ipc://ip-port
124 """
158 """
125 if self.transport == 'tcp':
159 if self.transport == 'tcp':
126 return "tcp://%s:%i" % (self.ip, port)
160 return "tcp://%s:%i" % (self.ip, port)
127 else:
161 else:
128 return "%s://%s-%s" % (self.transport, self.ip, port)
162 return "%s://%s-%s" % (self.transport, self.ip, port)
129
163
130 @property
164 @property
131 def shell_channel(self):
165 def shell_channel(self):
132 """Get the shell channel object for this kernel."""
166 """Get the shell channel object for this kernel."""
133 if self._shell_channel is None:
167 if self._shell_channel is None:
134 self._shell_channel = self.shell_channel_class(
168 self._shell_channel = self.shell_channel_class(
135 self.context, self.session, self._make_url(self.shell_port)
169 self.context, self.session, self._make_url(self.shell_port)
136 )
170 )
137 return self._shell_channel
171 return self._shell_channel
138
172
139 @property
173 @property
140 def iopub_channel(self):
174 def iopub_channel(self):
141 """Get the iopub channel object for this kernel."""
175 """Get the iopub channel object for this kernel."""
142 if self._iopub_channel is None:
176 if self._iopub_channel is None:
143 self._iopub_channel = self.iopub_channel_class(
177 self._iopub_channel = self.iopub_channel_class(
144 self.context, self.session, self._make_url(self.iopub_port)
178 self.context, self.session, self._make_url(self.iopub_port)
145 )
179 )
146 return self._iopub_channel
180 return self._iopub_channel
147
181
148 @property
182 @property
149 def stdin_channel(self):
183 def stdin_channel(self):
150 """Get the stdin channel object for this kernel."""
184 """Get the stdin channel object for this kernel."""
151 if self._stdin_channel is None:
185 if self._stdin_channel is None:
152 self._stdin_channel = self.stdin_channel_class(
186 self._stdin_channel = self.stdin_channel_class(
153 self.context, self.session, self._make_url(self.stdin_port)
187 self.context, self.session, self._make_url(self.stdin_port)
154 )
188 )
155 return self._stdin_channel
189 return self._stdin_channel
156
190
157 @property
191 @property
158 def hb_channel(self):
192 def hb_channel(self):
159 """Get the hb channel object for this kernel."""
193 """Get the hb channel object for this kernel."""
160 if self._hb_channel is None:
194 if self._hb_channel is None:
161 self._hb_channel = self.hb_channel_class(
195 self._hb_channel = self.hb_channel_class(
162 self.context, self.session, self._make_url(self.hb_port)
196 self.context, self.session, self._make_url(self.hb_port)
163 )
197 )
164 return self._hb_channel
198 return self._hb_channel
165
199
166 def is_alive(self):
200 def is_alive(self):
167 """Is the kernel process still running?"""
201 """Is the kernel process still running?"""
168 if self._hb_channel is not None:
202 if self._hb_channel is not None:
169 # We didn't start the kernel with this KernelManager so we
203 # We didn't start the kernel with this KernelManager so we
170 # use the heartbeat.
204 # use the heartbeat.
171 return self._hb_channel.is_beating()
205 return self._hb_channel.is_beating()
172 else:
206 else:
173 # no heartbeat and not local, we can't tell if it's running,
207 # no heartbeat and not local, we can't tell if it's running,
174 # so naively return True
208 # so naively return True
175 return True
209 return True
176
210
177
211
178 #-----------------------------------------------------------------------------
212 #-----------------------------------------------------------------------------
179 # ABC Registration
213 # ABC Registration
180 #-----------------------------------------------------------------------------
214 #-----------------------------------------------------------------------------
181
215
182 KernelClientABC.register(KernelClient)
216 KernelClientABC.register(KernelClient)
@@ -1,502 +1,484 b''
1 """Test suite for our zeromq-based messaging specification.
1 """Test suite for our zeromq-based messaging specification.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010-2011 The IPython Development Team
4 # Copyright (C) 2010-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import re
10 import re
11 import sys
11 import sys
12 import time
12 import time
13 from subprocess import PIPE
13 from subprocess import PIPE
14 from Queue import Empty
14 from Queue import Empty
15
15
16 import nose.tools as nt
16 import nose.tools as nt
17
17
18 from IPython.kernel import KernelManager, BlockingKernelClient
18 from IPython.kernel import KernelManager, BlockingKernelClient
19
19
20
20
21 from IPython.testing import decorators as dec
21 from IPython.testing import decorators as dec
22 from IPython.utils import io
22 from IPython.utils import io
23 from IPython.utils.traitlets import (
23 from IPython.utils.traitlets import (
24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
25 )
25 )
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Global setup and utilities
28 # Global setup and utilities
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 def setup():
31 def setup():
32 global KM, KC
32 global KM, KC
33 KM = KernelManager()
33 KM = KernelManager()
34 KM.start_kernel(stdout=PIPE, stderr=PIPE)
34 KM.start_kernel(stdout=PIPE, stderr=PIPE)
35 KC = BlockingKernelClient(connection_file=KM.connection_file)
35 KC = BlockingKernelClient(connection_file=KM.connection_file)
36 KC.load_connection_file()
36 KC.load_connection_file()
37 KC.start_channels()
37 KC.start_channels()
38
38
39 # wait for kernel to be ready
39 # wait for kernel to be ready
40 KC.shell_channel.execute("pass")
40 KC.shell_channel.execute("pass")
41 KC.shell_channel.get_msg(block=True, timeout=5)
41 KC.shell_channel.get_msg(block=True, timeout=5)
42 flush_channels()
42 flush_channels()
43
43
44
44
45 def teardown():
45 def teardown():
46 KC.stop_channels()
46 KC.stop_channels()
47 KM.shutdown_kernel()
47 KM.shutdown_kernel()
48
48
49
49
50 def flush_channels(kc=None):
50 def flush_channels(kc=None):
51 """flush any messages waiting on the queue"""
51 """flush any messages waiting on the queue"""
52 if kc is None:
52 if kc is None:
53 kc = KC
53 kc = KC
54 for channel in (kc.shell_channel, kc.iopub_channel):
54 for channel in (kc.shell_channel, kc.iopub_channel):
55 while True:
55 while True:
56 try:
56 try:
57 msg = channel.get_msg(block=True, timeout=0.1)
57 msg = channel.get_msg(block=True, timeout=0.1)
58 except Empty:
58 except Empty:
59 break
59 break
60 else:
60 else:
61 list(validate_message(msg))
61 list(validate_message(msg))
62
62
63
63
64 def execute(code='', kc=None, **kwargs):
64 def execute(code='', kc=None, **kwargs):
65 """wrapper for doing common steps for validating an execution request"""
65 """wrapper for doing common steps for validating an execution request"""
66 if kc is None:
66 msg_id = KC.execute(code=code, **kwargs)
67 kc = KC
67 reply = KC.get_shell_msg(timeout=2)
68 shell = kc.shell_channel
69 sub = kc.iopub_channel
70
71 msg_id = shell.execute(code=code, **kwargs)
72 reply = shell.get_msg(timeout=2)
73 list(validate_message(reply, 'execute_reply', msg_id))
68 list(validate_message(reply, 'execute_reply', msg_id))
74 busy = sub.get_msg(timeout=2)
69 busy = KC.get_iopub_msg(timeout=2)
75 list(validate_message(busy, 'status', msg_id))
70 list(validate_message(busy, 'status', msg_id))
76 nt.assert_equal(busy['content']['execution_state'], 'busy')
71 nt.assert_equal(busy['content']['execution_state'], 'busy')
77
72
78 if not kwargs.get('silent'):
73 if not kwargs.get('silent'):
79 pyin = sub.get_msg(timeout=2)
74 pyin = KC.get_iopub_msg(timeout=2)
80 list(validate_message(pyin, 'pyin', msg_id))
75 list(validate_message(pyin, 'pyin', msg_id))
81 nt.assert_equal(pyin['content']['code'], code)
76 nt.assert_equal(pyin['content']['code'], code)
82
77
83 return msg_id, reply['content']
78 return msg_id, reply['content']
84
79
85 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
86 # MSG Spec References
81 # MSG Spec References
87 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
88
83
89
84
90 class Reference(HasTraits):
85 class Reference(HasTraits):
91
86
92 """
87 """
93 Base class for message spec specification testing.
88 Base class for message spec specification testing.
94
89
95 This class is the core of the message specification test. The
90 This class is the core of the message specification test. The
96 idea is that child classes implement trait attributes for each
91 idea is that child classes implement trait attributes for each
97 message keys, so that message keys can be tested against these
92 message keys, so that message keys can be tested against these
98 traits using :meth:`check` method.
93 traits using :meth:`check` method.
99
94
100 """
95 """
101
96
102 def check(self, d):
97 def check(self, d):
103 """validate a dict against our traits"""
98 """validate a dict against our traits"""
104 for key in self.trait_names():
99 for key in self.trait_names():
105 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
100 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
106 # FIXME: always allow None, probably not a good idea
101 # FIXME: always allow None, probably not a good idea
107 if d[key] is None:
102 if d[key] is None:
108 continue
103 continue
109 try:
104 try:
110 setattr(self, key, d[key])
105 setattr(self, key, d[key])
111 except TraitError as e:
106 except TraitError as e:
112 yield nt.assert_true(False, str(e))
107 yield nt.assert_true(False, str(e))
113
108
114
109
115 class RMessage(Reference):
110 class RMessage(Reference):
116 msg_id = Unicode()
111 msg_id = Unicode()
117 msg_type = Unicode()
112 msg_type = Unicode()
118 header = Dict()
113 header = Dict()
119 parent_header = Dict()
114 parent_header = Dict()
120 content = Dict()
115 content = Dict()
121
116
122 class RHeader(Reference):
117 class RHeader(Reference):
123 msg_id = Unicode()
118 msg_id = Unicode()
124 msg_type = Unicode()
119 msg_type = Unicode()
125 session = Unicode()
120 session = Unicode()
126 username = Unicode()
121 username = Unicode()
127
122
128 class RContent(Reference):
123 class RContent(Reference):
129 status = Enum((u'ok', u'error'))
124 status = Enum((u'ok', u'error'))
130
125
131
126
132 class ExecuteReply(Reference):
127 class ExecuteReply(Reference):
133 execution_count = Integer()
128 execution_count = Integer()
134 status = Enum((u'ok', u'error'))
129 status = Enum((u'ok', u'error'))
135
130
136 def check(self, d):
131 def check(self, d):
137 for tst in Reference.check(self, d):
132 for tst in Reference.check(self, d):
138 yield tst
133 yield tst
139 if d['status'] == 'ok':
134 if d['status'] == 'ok':
140 for tst in ExecuteReplyOkay().check(d):
135 for tst in ExecuteReplyOkay().check(d):
141 yield tst
136 yield tst
142 elif d['status'] == 'error':
137 elif d['status'] == 'error':
143 for tst in ExecuteReplyError().check(d):
138 for tst in ExecuteReplyError().check(d):
144 yield tst
139 yield tst
145
140
146
141
147 class ExecuteReplyOkay(Reference):
142 class ExecuteReplyOkay(Reference):
148 payload = List(Dict)
143 payload = List(Dict)
149 user_variables = Dict()
144 user_variables = Dict()
150 user_expressions = Dict()
145 user_expressions = Dict()
151
146
152
147
153 class ExecuteReplyError(Reference):
148 class ExecuteReplyError(Reference):
154 ename = Unicode()
149 ename = Unicode()
155 evalue = Unicode()
150 evalue = Unicode()
156 traceback = List(Unicode)
151 traceback = List(Unicode)
157
152
158
153
159 class OInfoReply(Reference):
154 class OInfoReply(Reference):
160 name = Unicode()
155 name = Unicode()
161 found = Bool()
156 found = Bool()
162 ismagic = Bool()
157 ismagic = Bool()
163 isalias = Bool()
158 isalias = Bool()
164 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
159 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
165 type_name = Unicode()
160 type_name = Unicode()
166 string_form = Unicode()
161 string_form = Unicode()
167 base_class = Unicode()
162 base_class = Unicode()
168 length = Integer()
163 length = Integer()
169 file = Unicode()
164 file = Unicode()
170 definition = Unicode()
165 definition = Unicode()
171 argspec = Dict()
166 argspec = Dict()
172 init_definition = Unicode()
167 init_definition = Unicode()
173 docstring = Unicode()
168 docstring = Unicode()
174 init_docstring = Unicode()
169 init_docstring = Unicode()
175 class_docstring = Unicode()
170 class_docstring = Unicode()
176 call_def = Unicode()
171 call_def = Unicode()
177 call_docstring = Unicode()
172 call_docstring = Unicode()
178 source = Unicode()
173 source = Unicode()
179
174
180 def check(self, d):
175 def check(self, d):
181 for tst in Reference.check(self, d):
176 for tst in Reference.check(self, d):
182 yield tst
177 yield tst
183 if d['argspec'] is not None:
178 if d['argspec'] is not None:
184 for tst in ArgSpec().check(d['argspec']):
179 for tst in ArgSpec().check(d['argspec']):
185 yield tst
180 yield tst
186
181
187
182
188 class ArgSpec(Reference):
183 class ArgSpec(Reference):
189 args = List(Unicode)
184 args = List(Unicode)
190 varargs = Unicode()
185 varargs = Unicode()
191 varkw = Unicode()
186 varkw = Unicode()
192 defaults = List()
187 defaults = List()
193
188
194
189
195 class Status(Reference):
190 class Status(Reference):
196 execution_state = Enum((u'busy', u'idle'))
191 execution_state = Enum((u'busy', u'idle'))
197
192
198
193
199 class CompleteReply(Reference):
194 class CompleteReply(Reference):
200 matches = List(Unicode)
195 matches = List(Unicode)
201
196
202
197
203 def Version(num, trait=Integer):
198 def Version(num, trait=Integer):
204 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
199 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
205
200
206
201
207 class KernelInfoReply(Reference):
202 class KernelInfoReply(Reference):
208
203
209 protocol_version = Version(2)
204 protocol_version = Version(2)
210 ipython_version = Version(4, Any)
205 ipython_version = Version(4, Any)
211 language_version = Version(3)
206 language_version = Version(3)
212 language = Unicode()
207 language = Unicode()
213
208
214 def _ipython_version_changed(self, name, old, new):
209 def _ipython_version_changed(self, name, old, new):
215 for v in new:
210 for v in new:
216 nt.assert_true(
211 nt.assert_true(
217 isinstance(v, int) or isinstance(v, basestring),
212 isinstance(v, int) or isinstance(v, basestring),
218 'expected int or string as version component, got {0!r}'
213 'expected int or string as version component, got {0!r}'
219 .format(v))
214 .format(v))
220
215
221
216
222 # IOPub messages
217 # IOPub messages
223
218
224 class PyIn(Reference):
219 class PyIn(Reference):
225 code = Unicode()
220 code = Unicode()
226 execution_count = Integer()
221 execution_count = Integer()
227
222
228
223
229 PyErr = ExecuteReplyError
224 PyErr = ExecuteReplyError
230
225
231
226
232 class Stream(Reference):
227 class Stream(Reference):
233 name = Enum((u'stdout', u'stderr'))
228 name = Enum((u'stdout', u'stderr'))
234 data = Unicode()
229 data = Unicode()
235
230
236
231
237 mime_pat = re.compile(r'\w+/\w+')
232 mime_pat = re.compile(r'\w+/\w+')
238
233
239 class DisplayData(Reference):
234 class DisplayData(Reference):
240 source = Unicode()
235 source = Unicode()
241 metadata = Dict()
236 metadata = Dict()
242 data = Dict()
237 data = Dict()
243 def _data_changed(self, name, old, new):
238 def _data_changed(self, name, old, new):
244 for k,v in new.iteritems():
239 for k,v in new.iteritems():
245 nt.assert_true(mime_pat.match(k))
240 nt.assert_true(mime_pat.match(k))
246 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
241 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
247
242
248
243
249 class PyOut(Reference):
244 class PyOut(Reference):
250 execution_count = Integer()
245 execution_count = Integer()
251 data = Dict()
246 data = Dict()
252 def _data_changed(self, name, old, new):
247 def _data_changed(self, name, old, new):
253 for k,v in new.iteritems():
248 for k,v in new.iteritems():
254 nt.assert_true(mime_pat.match(k))
249 nt.assert_true(mime_pat.match(k))
255 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
250 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
256
251
257
252
258 references = {
253 references = {
259 'execute_reply' : ExecuteReply(),
254 'execute_reply' : ExecuteReply(),
260 'object_info_reply' : OInfoReply(),
255 'object_info_reply' : OInfoReply(),
261 'status' : Status(),
256 'status' : Status(),
262 'complete_reply' : CompleteReply(),
257 'complete_reply' : CompleteReply(),
263 'kernel_info_reply': KernelInfoReply(),
258 'kernel_info_reply': KernelInfoReply(),
264 'pyin' : PyIn(),
259 'pyin' : PyIn(),
265 'pyout' : PyOut(),
260 'pyout' : PyOut(),
266 'pyerr' : PyErr(),
261 'pyerr' : PyErr(),
267 'stream' : Stream(),
262 'stream' : Stream(),
268 'display_data' : DisplayData(),
263 'display_data' : DisplayData(),
269 }
264 }
270 """
265 """
271 Specifications of `content` part of the reply messages.
266 Specifications of `content` part of the reply messages.
272 """
267 """
273
268
274
269
275 def validate_message(msg, msg_type=None, parent=None):
270 def validate_message(msg, msg_type=None, parent=None):
276 """validate a message
271 """validate a message
277
272
278 This is a generator, and must be iterated through to actually
273 This is a generator, and must be iterated through to actually
279 trigger each test.
274 trigger each test.
280
275
281 If msg_type and/or parent are given, the msg_type and/or parent msg_id
276 If msg_type and/or parent are given, the msg_type and/or parent msg_id
282 are compared with the given values.
277 are compared with the given values.
283 """
278 """
284 RMessage().check(msg)
279 RMessage().check(msg)
285 if msg_type:
280 if msg_type:
286 yield nt.assert_equal(msg['msg_type'], msg_type)
281 yield nt.assert_equal(msg['msg_type'], msg_type)
287 if parent:
282 if parent:
288 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
283 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
289 content = msg['content']
284 content = msg['content']
290 ref = references[msg['msg_type']]
285 ref = references[msg['msg_type']]
291 for tst in ref.check(content):
286 for tst in ref.check(content):
292 yield tst
287 yield tst
293
288
294
289
295 #-----------------------------------------------------------------------------
290 #-----------------------------------------------------------------------------
296 # Tests
291 # Tests
297 #-----------------------------------------------------------------------------
292 #-----------------------------------------------------------------------------
298
293
299 # Shell channel
294 # Shell channel
300
295
301 @dec.parametric
296 @dec.parametric
302 def test_execute():
297 def test_execute():
303 flush_channels()
298 flush_channels()
304
299
305 shell = KC.shell_channel
300 msg_id = KC.execute(code='x=1')
306 msg_id = shell.execute(code='x=1')
301 reply = KC.get_shell_msg(timeout=2)
307 reply = shell.get_msg(timeout=2)
308 for tst in validate_message(reply, 'execute_reply', msg_id):
302 for tst in validate_message(reply, 'execute_reply', msg_id):
309 yield tst
303 yield tst
310
304
311
305
312 @dec.parametric
306 @dec.parametric
313 def test_execute_silent():
307 def test_execute_silent():
314 flush_channels()
308 flush_channels()
315 msg_id, reply = execute(code='x=1', silent=True)
309 msg_id, reply = execute(code='x=1', silent=True)
316
310
317 # flush status=idle
311 # flush status=idle
318 status = KC.iopub_channel.get_msg(timeout=2)
312 status = KC.iopub_channel.get_msg(timeout=2)
319 for tst in validate_message(status, 'status', msg_id):
313 for tst in validate_message(status, 'status', msg_id):
320 yield tst
314 yield tst
321 nt.assert_equal(status['content']['execution_state'], 'idle')
315 nt.assert_equal(status['content']['execution_state'], 'idle')
322
316
323 yield nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
317 yield nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
324 count = reply['execution_count']
318 count = reply['execution_count']
325
319
326 msg_id, reply = execute(code='x=2', silent=True)
320 msg_id, reply = execute(code='x=2', silent=True)
327
321
328 # flush status=idle
322 # flush status=idle
329 status = KC.iopub_channel.get_msg(timeout=2)
323 status = KC.iopub_channel.get_msg(timeout=2)
330 for tst in validate_message(status, 'status', msg_id):
324 for tst in validate_message(status, 'status', msg_id):
331 yield tst
325 yield tst
332 yield nt.assert_equal(status['content']['execution_state'], 'idle')
326 yield nt.assert_equal(status['content']['execution_state'], 'idle')
333
327
334 yield nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
328 yield nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
335 count_2 = reply['execution_count']
329 count_2 = reply['execution_count']
336 yield nt.assert_equal(count_2, count)
330 yield nt.assert_equal(count_2, count)
337
331
338
332
339 @dec.parametric
333 @dec.parametric
340 def test_execute_error():
334 def test_execute_error():
341 flush_channels()
335 flush_channels()
342
336
343 msg_id, reply = execute(code='1/0')
337 msg_id, reply = execute(code='1/0')
344 yield nt.assert_equal(reply['status'], 'error')
338 yield nt.assert_equal(reply['status'], 'error')
345 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
339 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
346
340
347 pyerr = KC.iopub_channel.get_msg(timeout=2)
341 pyerr = KC.iopub_channel.get_msg(timeout=2)
348 for tst in validate_message(pyerr, 'pyerr', msg_id):
342 for tst in validate_message(pyerr, 'pyerr', msg_id):
349 yield tst
343 yield tst
350
344
351
345
352 def test_execute_inc():
346 def test_execute_inc():
353 """execute request should increment execution_count"""
347 """execute request should increment execution_count"""
354 flush_channels()
348 flush_channels()
355
349
356 msg_id, reply = execute(code='x=1')
350 msg_id, reply = execute(code='x=1')
357 count = reply['execution_count']
351 count = reply['execution_count']
358
352
359 flush_channels()
353 flush_channels()
360
354
361 msg_id, reply = execute(code='x=2')
355 msg_id, reply = execute(code='x=2')
362 count_2 = reply['execution_count']
356 count_2 = reply['execution_count']
363 nt.assert_equal(count_2, count+1)
357 nt.assert_equal(count_2, count+1)
364
358
365
359
366 def test_user_variables():
360 def test_user_variables():
367 flush_channels()
361 flush_channels()
368
362
369 msg_id, reply = execute(code='x=1', user_variables=['x'])
363 msg_id, reply = execute(code='x=1', user_variables=['x'])
370 user_variables = reply['user_variables']
364 user_variables = reply['user_variables']
371 nt.assert_equal(user_variables, {u'x' : u'1'})
365 nt.assert_equal(user_variables, {u'x' : u'1'})
372
366
373
367
374 def test_user_expressions():
368 def test_user_expressions():
375 flush_channels()
369 flush_channels()
376
370
377 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
371 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
378 user_expressions = reply['user_expressions']
372 user_expressions = reply['user_expressions']
379 nt.assert_equal(user_expressions, {u'foo' : u'2'})
373 nt.assert_equal(user_expressions, {u'foo' : u'2'})
380
374
381
375
382 @dec.parametric
376 @dec.parametric
383 def test_oinfo():
377 def test_oinfo():
384 flush_channels()
378 flush_channels()
385
379
386 shell = KC.shell_channel
380 msg_id = KC.object_info('a')
387
381 reply = KC.get_shell_msg(timeout=2)
388 msg_id = shell.object_info('a')
389 reply = shell.get_msg(timeout=2)
390 for tst in validate_message(reply, 'object_info_reply', msg_id):
382 for tst in validate_message(reply, 'object_info_reply', msg_id):
391 yield tst
383 yield tst
392
384
393
385
394 @dec.parametric
386 @dec.parametric
395 def test_oinfo_found():
387 def test_oinfo_found():
396 flush_channels()
388 flush_channels()
397
389
398 shell = KC.shell_channel
399
400 msg_id, reply = execute(code='a=5')
390 msg_id, reply = execute(code='a=5')
401
391
402 msg_id = shell.object_info('a')
392 msg_id = KC.object_info('a')
403 reply = shell.get_msg(timeout=2)
393 reply = KC.get_shell_msg(timeout=2)
404 for tst in validate_message(reply, 'object_info_reply', msg_id):
394 for tst in validate_message(reply, 'object_info_reply', msg_id):
405 yield tst
395 yield tst
406 content = reply['content']
396 content = reply['content']
407 yield nt.assert_true(content['found'])
397 yield nt.assert_true(content['found'])
408 argspec = content['argspec']
398 argspec = content['argspec']
409 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
399 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
410
400
411
401
412 @dec.parametric
402 @dec.parametric
413 def test_oinfo_detail():
403 def test_oinfo_detail():
414 flush_channels()
404 flush_channels()
415
405
416 shell = KC.shell_channel
417
418 msg_id, reply = execute(code='ip=get_ipython()')
406 msg_id, reply = execute(code='ip=get_ipython()')
419
407
420 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
408 msg_id = KC.object_info('ip.object_inspect', detail_level=2)
421 reply = shell.get_msg(timeout=2)
409 reply = KC.get_shell_msg(timeout=2)
422 for tst in validate_message(reply, 'object_info_reply', msg_id):
410 for tst in validate_message(reply, 'object_info_reply', msg_id):
423 yield tst
411 yield tst
424 content = reply['content']
412 content = reply['content']
425 yield nt.assert_true(content['found'])
413 yield nt.assert_true(content['found'])
426 argspec = content['argspec']
414 argspec = content['argspec']
427 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
415 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
428 yield nt.assert_equal(argspec['defaults'], [0])
416 yield nt.assert_equal(argspec['defaults'], [0])
429
417
430
418
431 @dec.parametric
419 @dec.parametric
432 def test_oinfo_not_found():
420 def test_oinfo_not_found():
433 flush_channels()
421 flush_channels()
434
422
435 shell = KC.shell_channel
423 msg_id = KC.object_info('dne')
436
424 reply = KC.get_shell_msg(timeout=2)
437 msg_id = shell.object_info('dne')
438 reply = shell.get_msg(timeout=2)
439 for tst in validate_message(reply, 'object_info_reply', msg_id):
425 for tst in validate_message(reply, 'object_info_reply', msg_id):
440 yield tst
426 yield tst
441 content = reply['content']
427 content = reply['content']
442 yield nt.assert_false(content['found'])
428 yield nt.assert_false(content['found'])
443
429
444
430
445 @dec.parametric
431 @dec.parametric
446 def test_complete():
432 def test_complete():
447 flush_channels()
433 flush_channels()
448
434
449 shell = KC.shell_channel
450
451 msg_id, reply = execute(code="alpha = albert = 5")
435 msg_id, reply = execute(code="alpha = albert = 5")
452
436
453 msg_id = shell.complete('al', 'al', 2)
437 msg_id = KC.complete('al', 'al', 2)
454 reply = shell.get_msg(timeout=2)
438 reply = KC.get_shell_msg(timeout=2)
455 for tst in validate_message(reply, 'complete_reply', msg_id):
439 for tst in validate_message(reply, 'complete_reply', msg_id):
456 yield tst
440 yield tst
457 matches = reply['content']['matches']
441 matches = reply['content']['matches']
458 for name in ('alpha', 'albert'):
442 for name in ('alpha', 'albert'):
459 yield nt.assert_true(name in matches, "Missing match: %r" % name)
443 yield nt.assert_true(name in matches, "Missing match: %r" % name)
460
444
461
445
462 @dec.parametric
446 @dec.parametric
463 def test_kernel_info_request():
447 def test_kernel_info_request():
464 flush_channels()
448 flush_channels()
465
449
466 shell = KC.shell_channel
450 msg_id = KC.kernel_info()
467
451 reply = KC.get_shell_msg(timeout=2)
468 msg_id = shell.kernel_info()
469 reply = shell.get_msg(timeout=2)
470 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
452 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
471 yield tst
453 yield tst
472
454
473
455
474 # IOPub channel
456 # IOPub channel
475
457
476
458
477 @dec.parametric
459 @dec.parametric
478 def test_stream():
460 def test_stream():
479 flush_channels()
461 flush_channels()
480
462
481 msg_id, reply = execute("print('hi')")
463 msg_id, reply = execute("print('hi')")
482
464
483 stdout = KC.iopub_channel.get_msg(timeout=2)
465 stdout = KC.iopub_channel.get_msg(timeout=2)
484 for tst in validate_message(stdout, 'stream', msg_id):
466 for tst in validate_message(stdout, 'stream', msg_id):
485 yield tst
467 yield tst
486 content = stdout['content']
468 content = stdout['content']
487 yield nt.assert_equal(content['name'], u'stdout')
469 yield nt.assert_equal(content['name'], u'stdout')
488 yield nt.assert_equal(content['data'], u'hi\n')
470 yield nt.assert_equal(content['data'], u'hi\n')
489
471
490
472
491 @dec.parametric
473 @dec.parametric
492 def test_display_data():
474 def test_display_data():
493 flush_channels()
475 flush_channels()
494
476
495 msg_id, reply = execute("from IPython.core.display import display; display(1)")
477 msg_id, reply = execute("from IPython.core.display import display; display(1)")
496
478
497 display = KC.iopub_channel.get_msg(timeout=2)
479 display = KC.iopub_channel.get_msg(timeout=2)
498 for tst in validate_message(display, 'display_data', parent=msg_id):
480 for tst in validate_message(display, 'display_data', parent=msg_id):
499 yield tst
481 yield tst
500 data = display['content']['data']
482 data = display['content']['data']
501 yield nt.assert_equal(data['text/plain'], u'1')
483 yield nt.assert_equal(data['text/plain'], u'1')
502
484
@@ -1,193 +1,188 b''
1 """test IPython.embed_kernel()"""
1 """test IPython.embed_kernel()"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import shutil
15 import shutil
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18 import time
18 import time
19
19
20 from contextlib import contextmanager
20 from contextlib import contextmanager
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22
22
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 from IPython.kernel import BlockingKernelClient
25 from IPython.kernel import BlockingKernelClient
26 from IPython.utils import path, py3compat
26 from IPython.utils import path, py3compat
27
27
28 #-------------------------------------------------------------------------------
28 #-------------------------------------------------------------------------------
29 # Tests
29 # Tests
30 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
31
31
32 def setup():
32 def setup():
33 """setup temporary IPYTHONDIR for tests"""
33 """setup temporary IPYTHONDIR for tests"""
34 global IPYTHONDIR
34 global IPYTHONDIR
35 global env
35 global env
36 global save_get_ipython_dir
36 global save_get_ipython_dir
37
37
38 IPYTHONDIR = tempfile.mkdtemp()
38 IPYTHONDIR = tempfile.mkdtemp()
39
39
40 env = os.environ.copy()
40 env = os.environ.copy()
41 env["IPYTHONDIR"] = IPYTHONDIR
41 env["IPYTHONDIR"] = IPYTHONDIR
42
42
43 save_get_ipython_dir = path.get_ipython_dir
43 save_get_ipython_dir = path.get_ipython_dir
44 path.get_ipython_dir = lambda : IPYTHONDIR
44 path.get_ipython_dir = lambda : IPYTHONDIR
45
45
46
46
47 def teardown():
47 def teardown():
48 path.get_ipython_dir = save_get_ipython_dir
48 path.get_ipython_dir = save_get_ipython_dir
49
49
50 try:
50 try:
51 shutil.rmtree(IPYTHONDIR)
51 shutil.rmtree(IPYTHONDIR)
52 except (OSError, IOError):
52 except (OSError, IOError):
53 # no such file
53 # no such file
54 pass
54 pass
55
55
56
56
57 @contextmanager
57 @contextmanager
58 def setup_kernel(cmd):
58 def setup_kernel(cmd):
59 """start an embedded kernel in a subprocess, and wait for it to be ready
59 """start an embedded kernel in a subprocess, and wait for it to be ready
60
60
61 Returns
61 Returns
62 -------
62 -------
63 kernel_manager: connected KernelManager instance
63 kernel_manager: connected KernelManager instance
64 """
64 """
65 kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, env=env)
65 kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, env=env)
66 connection_file = os.path.join(IPYTHONDIR,
66 connection_file = os.path.join(IPYTHONDIR,
67 'profile_default',
67 'profile_default',
68 'security',
68 'security',
69 'kernel-%i.json' % kernel.pid
69 'kernel-%i.json' % kernel.pid
70 )
70 )
71 # wait for connection file to exist, timeout after 5s
71 # wait for connection file to exist, timeout after 5s
72 tic = time.time()
72 tic = time.time()
73 while not os.path.exists(connection_file) and kernel.poll() is None and time.time() < tic + 10:
73 while not os.path.exists(connection_file) and kernel.poll() is None and time.time() < tic + 10:
74 time.sleep(0.1)
74 time.sleep(0.1)
75
75
76 if kernel.poll() is not None:
76 if kernel.poll() is not None:
77 o,e = kernel.communicate()
77 o,e = kernel.communicate()
78 e = py3compat.cast_unicode(e)
78 e = py3compat.cast_unicode(e)
79 raise IOError("Kernel failed to start:\n%s" % e)
79 raise IOError("Kernel failed to start:\n%s" % e)
80
80
81 if not os.path.exists(connection_file):
81 if not os.path.exists(connection_file):
82 if kernel.poll() is None:
82 if kernel.poll() is None:
83 kernel.terminate()
83 kernel.terminate()
84 raise IOError("Connection file %r never arrived" % connection_file)
84 raise IOError("Connection file %r never arrived" % connection_file)
85
85
86 client = BlockingKernelClient(connection_file=connection_file)
86 client = BlockingKernelClient(connection_file=connection_file)
87 client.load_connection_file()
87 client.load_connection_file()
88 client.start_channels()
88 client.start_channels()
89
89
90 try:
90 try:
91 yield client
91 yield client
92 finally:
92 finally:
93 client.stop_channels()
93 client.stop_channels()
94 kernel.terminate()
94 kernel.terminate()
95
95
96 def test_embed_kernel_basic():
96 def test_embed_kernel_basic():
97 """IPython.embed_kernel() is basically functional"""
97 """IPython.embed_kernel() is basically functional"""
98 cmd = '\n'.join([
98 cmd = '\n'.join([
99 'from IPython import embed_kernel',
99 'from IPython import embed_kernel',
100 'def go():',
100 'def go():',
101 ' a=5',
101 ' a=5',
102 ' b="hi there"',
102 ' b="hi there"',
103 ' embed_kernel()',
103 ' embed_kernel()',
104 'go()',
104 'go()',
105 '',
105 '',
106 ])
106 ])
107
107
108 with setup_kernel(cmd) as client:
108 with setup_kernel(cmd) as client:
109 shell = client.shell_channel
110
111 # oinfo a (int)
109 # oinfo a (int)
112 msg_id = shell.object_info('a')
110 msg_id = client.object_info('a')
113 msg = shell.get_msg(block=True, timeout=2)
111 msg = client.get_shell_msg(block=True, timeout=2)
114 content = msg['content']
112 content = msg['content']
115 nt.assert_true(content['found'])
113 nt.assert_true(content['found'])
116
114
117 msg_id = shell.execute("c=a*2")
115 msg_id = client.execute("c=a*2")
118 msg = shell.get_msg(block=True, timeout=2)
116 msg = client.get_shell_msg(block=True, timeout=2)
119 content = msg['content']
117 content = msg['content']
120 nt.assert_equal(content['status'], u'ok')
118 nt.assert_equal(content['status'], u'ok')
121
119
122 # oinfo c (should be 10)
120 # oinfo c (should be 10)
123 msg_id = shell.object_info('c')
121 msg_id = client.object_info('c')
124 msg = shell.get_msg(block=True, timeout=2)
122 msg = client.get_shell_msg(block=True, timeout=2)
125 content = msg['content']
123 content = msg['content']
126 nt.assert_true(content['found'])
124 nt.assert_true(content['found'])
127 nt.assert_equal(content['string_form'], u'10')
125 nt.assert_equal(content['string_form'], u'10')
128
126
129 def test_embed_kernel_namespace():
127 def test_embed_kernel_namespace():
130 """IPython.embed_kernel() inherits calling namespace"""
128 """IPython.embed_kernel() inherits calling namespace"""
131 cmd = '\n'.join([
129 cmd = '\n'.join([
132 'from IPython import embed_kernel',
130 'from IPython import embed_kernel',
133 'def go():',
131 'def go():',
134 ' a=5',
132 ' a=5',
135 ' b="hi there"',
133 ' b="hi there"',
136 ' embed_kernel()',
134 ' embed_kernel()',
137 'go()',
135 'go()',
138 '',
136 '',
139 ])
137 ])
140
138
141 with setup_kernel(cmd) as client:
139 with setup_kernel(cmd) as client:
142 shell = client.shell_channel
143
144 # oinfo a (int)
140 # oinfo a (int)
145 msg_id = shell.object_info('a')
141 msg_id = client.object_info('a')
146 msg = shell.get_msg(block=True, timeout=2)
142 msg = client.get_shell_msg(block=True, timeout=2)
147 content = msg['content']
143 content = msg['content']
148 nt.assert_true(content['found'])
144 nt.assert_true(content['found'])
149 nt.assert_equal(content['string_form'], u'5')
145 nt.assert_equal(content['string_form'], u'5')
150
146
151 # oinfo b (str)
147 # oinfo b (str)
152 msg_id = shell.object_info('b')
148 msg_id = client.object_info('b')
153 msg = shell.get_msg(block=True, timeout=2)
149 msg = client.get_shell_msg(block=True, timeout=2)
154 content = msg['content']
150 content = msg['content']
155 nt.assert_true(content['found'])
151 nt.assert_true(content['found'])
156 nt.assert_equal(content['string_form'], u'hi there')
152 nt.assert_equal(content['string_form'], u'hi there')
157
153
158 # oinfo c (undefined)
154 # oinfo c (undefined)
159 msg_id = shell.object_info('c')
155 msg_id = client.object_info('c')
160 msg = shell.get_msg(block=True, timeout=2)
156 msg = client.get_shell_msg(block=True, timeout=2)
161 content = msg['content']
157 content = msg['content']
162 nt.assert_false(content['found'])
158 nt.assert_false(content['found'])
163
159
164 def test_embed_kernel_reentrant():
160 def test_embed_kernel_reentrant():
165 """IPython.embed_kernel() can be called multiple times"""
161 """IPython.embed_kernel() can be called multiple times"""
166 cmd = '\n'.join([
162 cmd = '\n'.join([
167 'from IPython import embed_kernel',
163 'from IPython import embed_kernel',
168 'count = 0',
164 'count = 0',
169 'def go():',
165 'def go():',
170 ' global count',
166 ' global count',
171 ' embed_kernel()',
167 ' embed_kernel()',
172 ' count = count + 1',
168 ' count = count + 1',
173 '',
169 '',
174 'while True:'
170 'while True:'
175 ' go()',
171 ' go()',
176 '',
172 '',
177 ])
173 ])
178
174
179 with setup_kernel(cmd) as client:
175 with setup_kernel(cmd) as client:
180 shell = client.shell_channel
181 for i in range(5):
176 for i in range(5):
182 msg_id = shell.object_info('count')
177 msg_id = client.object_info('count')
183 msg = shell.get_msg(block=True, timeout=2)
178 msg = client.get_shell_msg(block=True, timeout=2)
184 content = msg['content']
179 content = msg['content']
185 nt.assert_true(content['found'])
180 nt.assert_true(content['found'])
186 nt.assert_equal(content['string_form'], unicode(i))
181 nt.assert_equal(content['string_form'], unicode(i))
187
182
188 # exit from embed_kernel
183 # exit from embed_kernel
189 shell.execute("get_ipython().exit_now = True")
184 client.execute("get_ipython().exit_now = True")
190 msg = shell.get_msg(block=True, timeout=2)
185 msg = client.get_shell_msg(block=True, timeout=2)
191 time.sleep(0.2)
186 time.sleep(0.2)
192
187
193
188
General Comments 0
You need to be logged in to leave comments. Login now