##// END OF EJS Templates
Put the whole history interface into kernelmanager.
Thomas Kluyver -
Show More
@@ -1,496 +1,496 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 from textwrap import dedent
15 from textwrap import dedent
16
16
17 # System library imports
17 # System library imports
18 from IPython.external.qt import QtCore, QtGui
18 from IPython.external.qt import QtCore, QtGui
19
19
20 # Local imports
20 # Local imports
21 from IPython.core.inputsplitter import IPythonInputSplitter, \
21 from IPython.core.inputsplitter import IPythonInputSplitter, \
22 transform_ipy_prompt
22 transform_ipy_prompt
23 from IPython.core.usage import default_gui_banner
23 from IPython.core.usage import default_gui_banner
24 from IPython.utils.traitlets import Bool, Str, Unicode
24 from IPython.utils.traitlets import Bool, Str, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 from styles import (default_light_style_sheet, default_light_syntax_style,
26 from styles import (default_light_style_sheet, default_light_syntax_style,
27 default_dark_style_sheet, default_dark_syntax_style,
27 default_dark_style_sheet, default_dark_syntax_style,
28 default_bw_style_sheet, default_bw_syntax_style)
28 default_bw_style_sheet, default_bw_syntax_style)
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Constants
31 # Constants
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 # Default strings to build and display input and output prompts (and separators
34 # Default strings to build and display input and output prompts (and separators
35 # in between)
35 # in between)
36 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
36 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
37 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
37 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
38 default_input_sep = '\n'
38 default_input_sep = '\n'
39 default_output_sep = ''
39 default_output_sep = ''
40 default_output_sep2 = ''
40 default_output_sep2 = ''
41
41
42 # Base path for most payload sources.
42 # Base path for most payload sources.
43 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
43 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # IPythonWidget class
46 # IPythonWidget class
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 class IPythonWidget(FrontendWidget):
49 class IPythonWidget(FrontendWidget):
50 """ A FrontendWidget for an IPython kernel.
50 """ A FrontendWidget for an IPython kernel.
51 """
51 """
52
52
53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 # settings.
55 # settings.
56 custom_edit = Bool(False)
56 custom_edit = Bool(False)
57 custom_edit_requested = QtCore.Signal(object, object)
57 custom_edit_requested = QtCore.Signal(object, object)
58
58
59 # A command for invoking a system text editor. If the string contains a
59 # A command for invoking a system text editor. If the string contains a
60 # {filename} format specifier, it will be used. Otherwise, the filename will
60 # {filename} format specifier, it will be used. Otherwise, the filename will
61 # be appended to the end the command.
61 # be appended to the end the command.
62 editor = Unicode('default', config=True)
62 editor = Unicode('default', config=True)
63
63
64 # The editor command to use when a specific line number is requested. The
64 # The editor command to use when a specific line number is requested. The
65 # string should contain two format specifiers: {line} and {filename}. If
65 # string should contain two format specifiers: {line} and {filename}. If
66 # this parameter is not specified, the line number option to the %edit magic
66 # this parameter is not specified, the line number option to the %edit magic
67 # will be ignored.
67 # will be ignored.
68 editor_line = Unicode(config=True)
68 editor_line = Unicode(config=True)
69
69
70 # A CSS stylesheet. The stylesheet can contain classes for:
70 # A CSS stylesheet. The stylesheet can contain classes for:
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
74 style_sheet = Unicode(config=True)
74 style_sheet = Unicode(config=True)
75
75
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
77 # the style sheet is queried for Pygments style information.
77 # the style sheet is queried for Pygments style information.
78 syntax_style = Str(config=True)
78 syntax_style = Str(config=True)
79
79
80 # Prompts.
80 # Prompts.
81 in_prompt = Str(default_in_prompt, config=True)
81 in_prompt = Str(default_in_prompt, config=True)
82 out_prompt = Str(default_out_prompt, config=True)
82 out_prompt = Str(default_out_prompt, config=True)
83 input_sep = Str(default_input_sep, config=True)
83 input_sep = Str(default_input_sep, config=True)
84 output_sep = Str(default_output_sep, config=True)
84 output_sep = Str(default_output_sep, config=True)
85 output_sep2 = Str(default_output_sep2, config=True)
85 output_sep2 = Str(default_output_sep2, config=True)
86
86
87 # FrontendWidget protected class variables.
87 # FrontendWidget protected class variables.
88 _input_splitter_class = IPythonInputSplitter
88 _input_splitter_class = IPythonInputSplitter
89
89
90 # IPythonWidget protected class variables.
90 # IPythonWidget protected class variables.
91 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
91 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
92 _payload_source_edit = zmq_shell_source + '.edit_magic'
92 _payload_source_edit = zmq_shell_source + '.edit_magic'
93 _payload_source_exit = zmq_shell_source + '.ask_exit'
93 _payload_source_exit = zmq_shell_source + '.ask_exit'
94 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
94 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
95 _payload_source_page = 'IPython.zmq.page.page'
95 _payload_source_page = 'IPython.zmq.page.page'
96
96
97 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
98 # 'object' interface
98 # 'object' interface
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100
100
101 def __init__(self, *args, **kw):
101 def __init__(self, *args, **kw):
102 super(IPythonWidget, self).__init__(*args, **kw)
102 super(IPythonWidget, self).__init__(*args, **kw)
103
103
104 # IPythonWidget protected variables.
104 # IPythonWidget protected variables.
105 self._code_to_load = None
105 self._code_to_load = None
106 self._payload_handlers = {
106 self._payload_handlers = {
107 self._payload_source_edit : self._handle_payload_edit,
107 self._payload_source_edit : self._handle_payload_edit,
108 self._payload_source_exit : self._handle_payload_exit,
108 self._payload_source_exit : self._handle_payload_exit,
109 self._payload_source_page : self._handle_payload_page,
109 self._payload_source_page : self._handle_payload_page,
110 self._payload_source_loadpy : self._handle_payload_loadpy }
110 self._payload_source_loadpy : self._handle_payload_loadpy }
111 self._previous_prompt_obj = None
111 self._previous_prompt_obj = None
112 self._keep_kernel_on_exit = None
112 self._keep_kernel_on_exit = None
113
113
114 # Initialize widget styling.
114 # Initialize widget styling.
115 if self.style_sheet:
115 if self.style_sheet:
116 self._style_sheet_changed()
116 self._style_sheet_changed()
117 self._syntax_style_changed()
117 self._syntax_style_changed()
118 else:
118 else:
119 self.set_default_style()
119 self.set_default_style()
120
120
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122 # 'BaseFrontendMixin' abstract interface
122 # 'BaseFrontendMixin' abstract interface
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124
124
125 def _handle_complete_reply(self, rep):
125 def _handle_complete_reply(self, rep):
126 """ Reimplemented to support IPython's improved completion machinery.
126 """ Reimplemented to support IPython's improved completion machinery.
127 """
127 """
128 cursor = self._get_cursor()
128 cursor = self._get_cursor()
129 info = self._request_info.get('complete')
129 info = self._request_info.get('complete')
130 if info and info.id == rep['parent_header']['msg_id'] and \
130 if info and info.id == rep['parent_header']['msg_id'] and \
131 info.pos == cursor.position():
131 info.pos == cursor.position():
132 matches = rep['content']['matches']
132 matches = rep['content']['matches']
133 text = rep['content']['matched_text']
133 text = rep['content']['matched_text']
134 offset = len(text)
134 offset = len(text)
135
135
136 # Clean up matches with period and path separators if the matched
136 # Clean up matches with period and path separators if the matched
137 # text has not been transformed. This is done by truncating all
137 # text has not been transformed. This is done by truncating all
138 # but the last component and then suitably decreasing the offset
138 # but the last component and then suitably decreasing the offset
139 # between the current cursor position and the start of completion.
139 # between the current cursor position and the start of completion.
140 if len(matches) > 1 and matches[0][:offset] == text:
140 if len(matches) > 1 and matches[0][:offset] == text:
141 parts = re.split(r'[./\\]', text)
141 parts = re.split(r'[./\\]', text)
142 sep_count = len(parts) - 1
142 sep_count = len(parts) - 1
143 if sep_count:
143 if sep_count:
144 chop_length = sum(map(len, parts[:sep_count])) + sep_count
144 chop_length = sum(map(len, parts[:sep_count])) + sep_count
145 matches = [ match[chop_length:] for match in matches ]
145 matches = [ match[chop_length:] for match in matches ]
146 offset -= chop_length
146 offset -= chop_length
147
147
148 # Move the cursor to the start of the match and complete.
148 # Move the cursor to the start of the match and complete.
149 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
149 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
150 self._complete_with_items(cursor, matches)
150 self._complete_with_items(cursor, matches)
151
151
152 def _handle_execute_reply(self, msg):
152 def _handle_execute_reply(self, msg):
153 """ Reimplemented to support prompt requests.
153 """ Reimplemented to support prompt requests.
154 """
154 """
155 info = self._request_info.get('execute')
155 info = self._request_info.get('execute')
156 if info and info.id == msg['parent_header']['msg_id']:
156 if info and info.id == msg['parent_header']['msg_id']:
157 if info.kind == 'prompt':
157 if info.kind == 'prompt':
158 number = msg['content']['execution_count'] + 1
158 number = msg['content']['execution_count'] + 1
159 self._show_interpreter_prompt(number)
159 self._show_interpreter_prompt(number)
160 else:
160 else:
161 super(IPythonWidget, self)._handle_execute_reply(msg)
161 super(IPythonWidget, self)._handle_execute_reply(msg)
162
162
163 def _handle_history_tail_reply(self, msg):
163 def _handle_history_tail_reply(self, msg):
164 """ Implemented to handle history tail replies, which are only supported
164 """ Implemented to handle history tail replies, which are only supported
165 by the IPython kernel.
165 by the IPython kernel.
166 """
166 """
167 history_items = msg['content']['history']
167 history_items = msg['content']['history']
168 items = [ line.rstrip() for _, _, line in history_items ]
168 items = [ line.rstrip() for _, _, line in history_items ]
169 self._set_history(items)
169 self._set_history(items)
170
170
171 def _handle_pyout(self, msg):
171 def _handle_pyout(self, msg):
172 """ Reimplemented for IPython-style "display hook".
172 """ Reimplemented for IPython-style "display hook".
173 """
173 """
174 if not self._hidden and self._is_from_this_session(msg):
174 if not self._hidden and self._is_from_this_session(msg):
175 content = msg['content']
175 content = msg['content']
176 prompt_number = content['execution_count']
176 prompt_number = content['execution_count']
177 data = content['data']
177 data = content['data']
178 if data.has_key('text/html'):
178 if data.has_key('text/html'):
179 self._append_plain_text(self.output_sep)
179 self._append_plain_text(self.output_sep)
180 self._append_html(self._make_out_prompt(prompt_number))
180 self._append_html(self._make_out_prompt(prompt_number))
181 html = data['text/html']
181 html = data['text/html']
182 self._append_plain_text('\n')
182 self._append_plain_text('\n')
183 self._append_html(html + self.output_sep2)
183 self._append_html(html + self.output_sep2)
184 elif data.has_key('text/plain'):
184 elif data.has_key('text/plain'):
185 self._append_plain_text(self.output_sep)
185 self._append_plain_text(self.output_sep)
186 self._append_html(self._make_out_prompt(prompt_number))
186 self._append_html(self._make_out_prompt(prompt_number))
187 text = data['text/plain']
187 text = data['text/plain']
188 self._append_plain_text(text + self.output_sep2)
188 self._append_plain_text(text + self.output_sep2)
189
189
190 def _handle_display_data(self, msg):
190 def _handle_display_data(self, msg):
191 """ The base handler for the ``display_data`` message.
191 """ The base handler for the ``display_data`` message.
192 """
192 """
193 # For now, we don't display data from other frontends, but we
193 # For now, we don't display data from other frontends, but we
194 # eventually will as this allows all frontends to monitor the display
194 # eventually will as this allows all frontends to monitor the display
195 # data. But we need to figure out how to handle this in the GUI.
195 # data. But we need to figure out how to handle this in the GUI.
196 if not self._hidden and self._is_from_this_session(msg):
196 if not self._hidden and self._is_from_this_session(msg):
197 source = msg['content']['source']
197 source = msg['content']['source']
198 data = msg['content']['data']
198 data = msg['content']['data']
199 metadata = msg['content']['metadata']
199 metadata = msg['content']['metadata']
200 # In the regular IPythonWidget, we simply print the plain text
200 # In the regular IPythonWidget, we simply print the plain text
201 # representation.
201 # representation.
202 if data.has_key('text/html'):
202 if data.has_key('text/html'):
203 html = data['text/html']
203 html = data['text/html']
204 self._append_html(html)
204 self._append_html(html)
205 elif data.has_key('text/plain'):
205 elif data.has_key('text/plain'):
206 text = data['text/plain']
206 text = data['text/plain']
207 self._append_plain_text(text)
207 self._append_plain_text(text)
208 # This newline seems to be needed for text and html output.
208 # This newline seems to be needed for text and html output.
209 self._append_plain_text(u'\n')
209 self._append_plain_text(u'\n')
210
210
211 def _started_channels(self):
211 def _started_channels(self):
212 """ Reimplemented to make a history request.
212 """ Reimplemented to make a history request.
213 """
213 """
214 super(IPythonWidget, self)._started_channels()
214 super(IPythonWidget, self)._started_channels()
215 self.kernel_manager.xreq_channel.history_tail(1000)
215 self.kernel_manager.xreq_channel.history(hist_access_type='tail', n=1000)
216
216
217 #---------------------------------------------------------------------------
217 #---------------------------------------------------------------------------
218 # 'ConsoleWidget' public interface
218 # 'ConsoleWidget' public interface
219 #---------------------------------------------------------------------------
219 #---------------------------------------------------------------------------
220
220
221 def copy(self):
221 def copy(self):
222 """ Copy the currently selected text to the clipboard, removing prompts
222 """ Copy the currently selected text to the clipboard, removing prompts
223 if possible.
223 if possible.
224 """
224 """
225 text = self._control.textCursor().selection().toPlainText()
225 text = self._control.textCursor().selection().toPlainText()
226 if text:
226 if text:
227 lines = map(transform_ipy_prompt, text.splitlines())
227 lines = map(transform_ipy_prompt, text.splitlines())
228 text = '\n'.join(lines)
228 text = '\n'.join(lines)
229 QtGui.QApplication.clipboard().setText(text)
229 QtGui.QApplication.clipboard().setText(text)
230
230
231 #---------------------------------------------------------------------------
231 #---------------------------------------------------------------------------
232 # 'FrontendWidget' public interface
232 # 'FrontendWidget' public interface
233 #---------------------------------------------------------------------------
233 #---------------------------------------------------------------------------
234
234
235 def execute_file(self, path, hidden=False):
235 def execute_file(self, path, hidden=False):
236 """ Reimplemented to use the 'run' magic.
236 """ Reimplemented to use the 'run' magic.
237 """
237 """
238 # Use forward slashes on Windows to avoid escaping each separator.
238 # Use forward slashes on Windows to avoid escaping each separator.
239 if sys.platform == 'win32':
239 if sys.platform == 'win32':
240 path = os.path.normpath(path).replace('\\', '/')
240 path = os.path.normpath(path).replace('\\', '/')
241
241
242 self.execute('%%run %s' % path, hidden=hidden)
242 self.execute('%%run %s' % path, hidden=hidden)
243
243
244 #---------------------------------------------------------------------------
244 #---------------------------------------------------------------------------
245 # 'FrontendWidget' protected interface
245 # 'FrontendWidget' protected interface
246 #---------------------------------------------------------------------------
246 #---------------------------------------------------------------------------
247
247
248 def _complete(self):
248 def _complete(self):
249 """ Reimplemented to support IPython's improved completion machinery.
249 """ Reimplemented to support IPython's improved completion machinery.
250 """
250 """
251 # We let the kernel split the input line, so we *always* send an empty
251 # We let the kernel split the input line, so we *always* send an empty
252 # text field. Readline-based frontends do get a real text field which
252 # text field. Readline-based frontends do get a real text field which
253 # they can use.
253 # they can use.
254 text = ''
254 text = ''
255
255
256 # Send the completion request to the kernel
256 # Send the completion request to the kernel
257 msg_id = self.kernel_manager.xreq_channel.complete(
257 msg_id = self.kernel_manager.xreq_channel.complete(
258 text, # text
258 text, # text
259 self._get_input_buffer_cursor_line(), # line
259 self._get_input_buffer_cursor_line(), # line
260 self._get_input_buffer_cursor_column(), # cursor_pos
260 self._get_input_buffer_cursor_column(), # cursor_pos
261 self.input_buffer) # block
261 self.input_buffer) # block
262 pos = self._get_cursor().position()
262 pos = self._get_cursor().position()
263 info = self._CompletionRequest(msg_id, pos)
263 info = self._CompletionRequest(msg_id, pos)
264 self._request_info['complete'] = info
264 self._request_info['complete'] = info
265
265
266 def _get_banner(self):
266 def _get_banner(self):
267 """ Reimplemented to return IPython's default banner.
267 """ Reimplemented to return IPython's default banner.
268 """
268 """
269 return default_gui_banner
269 return default_gui_banner
270
270
271 def _process_execute_error(self, msg):
271 def _process_execute_error(self, msg):
272 """ Reimplemented for IPython-style traceback formatting.
272 """ Reimplemented for IPython-style traceback formatting.
273 """
273 """
274 content = msg['content']
274 content = msg['content']
275 traceback = '\n'.join(content['traceback']) + '\n'
275 traceback = '\n'.join(content['traceback']) + '\n'
276 if False:
276 if False:
277 # FIXME: For now, tracebacks come as plain text, so we can't use
277 # FIXME: For now, tracebacks come as plain text, so we can't use
278 # the html renderer yet. Once we refactor ultratb to produce
278 # the html renderer yet. Once we refactor ultratb to produce
279 # properly styled tracebacks, this branch should be the default
279 # properly styled tracebacks, this branch should be the default
280 traceback = traceback.replace(' ', '&nbsp;')
280 traceback = traceback.replace(' ', '&nbsp;')
281 traceback = traceback.replace('\n', '<br/>')
281 traceback = traceback.replace('\n', '<br/>')
282
282
283 ename = content['ename']
283 ename = content['ename']
284 ename_styled = '<span class="error">%s</span>' % ename
284 ename_styled = '<span class="error">%s</span>' % ename
285 traceback = traceback.replace(ename, ename_styled)
285 traceback = traceback.replace(ename, ename_styled)
286
286
287 self._append_html(traceback)
287 self._append_html(traceback)
288 else:
288 else:
289 # This is the fallback for now, using plain text with ansi escapes
289 # This is the fallback for now, using plain text with ansi escapes
290 self._append_plain_text(traceback)
290 self._append_plain_text(traceback)
291
291
292 def _process_execute_payload(self, item):
292 def _process_execute_payload(self, item):
293 """ Reimplemented to dispatch payloads to handler methods.
293 """ Reimplemented to dispatch payloads to handler methods.
294 """
294 """
295 handler = self._payload_handlers.get(item['source'])
295 handler = self._payload_handlers.get(item['source'])
296 if handler is None:
296 if handler is None:
297 # We have no handler for this type of payload, simply ignore it
297 # We have no handler for this type of payload, simply ignore it
298 return False
298 return False
299 else:
299 else:
300 handler(item)
300 handler(item)
301 return True
301 return True
302
302
303 def _show_interpreter_prompt(self, number=None):
303 def _show_interpreter_prompt(self, number=None):
304 """ Reimplemented for IPython-style prompts.
304 """ Reimplemented for IPython-style prompts.
305 """
305 """
306 # If a number was not specified, make a prompt number request.
306 # If a number was not specified, make a prompt number request.
307 if number is None:
307 if number is None:
308 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
308 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
309 info = self._ExecutionRequest(msg_id, 'prompt')
309 info = self._ExecutionRequest(msg_id, 'prompt')
310 self._request_info['execute'] = info
310 self._request_info['execute'] = info
311 return
311 return
312
312
313 # Show a new prompt and save information about it so that it can be
313 # Show a new prompt and save information about it so that it can be
314 # updated later if the prompt number turns out to be wrong.
314 # updated later if the prompt number turns out to be wrong.
315 self._prompt_sep = self.input_sep
315 self._prompt_sep = self.input_sep
316 self._show_prompt(self._make_in_prompt(number), html=True)
316 self._show_prompt(self._make_in_prompt(number), html=True)
317 block = self._control.document().lastBlock()
317 block = self._control.document().lastBlock()
318 length = len(self._prompt)
318 length = len(self._prompt)
319 self._previous_prompt_obj = self._PromptBlock(block, length, number)
319 self._previous_prompt_obj = self._PromptBlock(block, length, number)
320
320
321 # Update continuation prompt to reflect (possibly) new prompt length.
321 # Update continuation prompt to reflect (possibly) new prompt length.
322 self._set_continuation_prompt(
322 self._set_continuation_prompt(
323 self._make_continuation_prompt(self._prompt), html=True)
323 self._make_continuation_prompt(self._prompt), html=True)
324
324
325 # Load code from the %loadpy magic, if necessary.
325 # Load code from the %loadpy magic, if necessary.
326 if self._code_to_load is not None:
326 if self._code_to_load is not None:
327 self.input_buffer = dedent(self._code_to_load.rstrip())
327 self.input_buffer = dedent(self._code_to_load.rstrip())
328 self._code_to_load = None
328 self._code_to_load = None
329
329
330 def _show_interpreter_prompt_for_reply(self, msg):
330 def _show_interpreter_prompt_for_reply(self, msg):
331 """ Reimplemented for IPython-style prompts.
331 """ Reimplemented for IPython-style prompts.
332 """
332 """
333 # Update the old prompt number if necessary.
333 # Update the old prompt number if necessary.
334 content = msg['content']
334 content = msg['content']
335 previous_prompt_number = content['execution_count']
335 previous_prompt_number = content['execution_count']
336 if self._previous_prompt_obj and \
336 if self._previous_prompt_obj and \
337 self._previous_prompt_obj.number != previous_prompt_number:
337 self._previous_prompt_obj.number != previous_prompt_number:
338 block = self._previous_prompt_obj.block
338 block = self._previous_prompt_obj.block
339
339
340 # Make sure the prompt block has not been erased.
340 # Make sure the prompt block has not been erased.
341 if block.isValid() and block.text():
341 if block.isValid() and block.text():
342
342
343 # Remove the old prompt and insert a new prompt.
343 # Remove the old prompt and insert a new prompt.
344 cursor = QtGui.QTextCursor(block)
344 cursor = QtGui.QTextCursor(block)
345 cursor.movePosition(QtGui.QTextCursor.Right,
345 cursor.movePosition(QtGui.QTextCursor.Right,
346 QtGui.QTextCursor.KeepAnchor,
346 QtGui.QTextCursor.KeepAnchor,
347 self._previous_prompt_obj.length)
347 self._previous_prompt_obj.length)
348 prompt = self._make_in_prompt(previous_prompt_number)
348 prompt = self._make_in_prompt(previous_prompt_number)
349 self._prompt = self._insert_html_fetching_plain_text(
349 self._prompt = self._insert_html_fetching_plain_text(
350 cursor, prompt)
350 cursor, prompt)
351
351
352 # When the HTML is inserted, Qt blows away the syntax
352 # When the HTML is inserted, Qt blows away the syntax
353 # highlighting for the line, so we need to rehighlight it.
353 # highlighting for the line, so we need to rehighlight it.
354 self._highlighter.rehighlightBlock(cursor.block())
354 self._highlighter.rehighlightBlock(cursor.block())
355
355
356 self._previous_prompt_obj = None
356 self._previous_prompt_obj = None
357
357
358 # Show a new prompt with the kernel's estimated prompt number.
358 # Show a new prompt with the kernel's estimated prompt number.
359 self._show_interpreter_prompt(previous_prompt_number + 1)
359 self._show_interpreter_prompt(previous_prompt_number + 1)
360
360
361 #---------------------------------------------------------------------------
361 #---------------------------------------------------------------------------
362 # 'IPythonWidget' interface
362 # 'IPythonWidget' interface
363 #---------------------------------------------------------------------------
363 #---------------------------------------------------------------------------
364
364
365 def set_default_style(self, colors='lightbg'):
365 def set_default_style(self, colors='lightbg'):
366 """ Sets the widget style to the class defaults.
366 """ Sets the widget style to the class defaults.
367
367
368 Parameters:
368 Parameters:
369 -----------
369 -----------
370 colors : str, optional (default lightbg)
370 colors : str, optional (default lightbg)
371 Whether to use the default IPython light background or dark
371 Whether to use the default IPython light background or dark
372 background or B&W style.
372 background or B&W style.
373 """
373 """
374 colors = colors.lower()
374 colors = colors.lower()
375 if colors=='lightbg':
375 if colors=='lightbg':
376 self.style_sheet = default_light_style_sheet
376 self.style_sheet = default_light_style_sheet
377 self.syntax_style = default_light_syntax_style
377 self.syntax_style = default_light_syntax_style
378 elif colors=='linux':
378 elif colors=='linux':
379 self.style_sheet = default_dark_style_sheet
379 self.style_sheet = default_dark_style_sheet
380 self.syntax_style = default_dark_syntax_style
380 self.syntax_style = default_dark_syntax_style
381 elif colors=='nocolor':
381 elif colors=='nocolor':
382 self.style_sheet = default_bw_style_sheet
382 self.style_sheet = default_bw_style_sheet
383 self.syntax_style = default_bw_syntax_style
383 self.syntax_style = default_bw_syntax_style
384 else:
384 else:
385 raise KeyError("No such color scheme: %s"%colors)
385 raise KeyError("No such color scheme: %s"%colors)
386
386
387 #---------------------------------------------------------------------------
387 #---------------------------------------------------------------------------
388 # 'IPythonWidget' protected interface
388 # 'IPythonWidget' protected interface
389 #---------------------------------------------------------------------------
389 #---------------------------------------------------------------------------
390
390
391 def _edit(self, filename, line=None):
391 def _edit(self, filename, line=None):
392 """ Opens a Python script for editing.
392 """ Opens a Python script for editing.
393
393
394 Parameters:
394 Parameters:
395 -----------
395 -----------
396 filename : str
396 filename : str
397 A path to a local system file.
397 A path to a local system file.
398
398
399 line : int, optional
399 line : int, optional
400 A line of interest in the file.
400 A line of interest in the file.
401 """
401 """
402 if self.custom_edit:
402 if self.custom_edit:
403 self.custom_edit_requested.emit(filename, line)
403 self.custom_edit_requested.emit(filename, line)
404 elif self.editor == 'default':
404 elif self.editor == 'default':
405 self._append_plain_text('No default editor available.\n')
405 self._append_plain_text('No default editor available.\n')
406 else:
406 else:
407 try:
407 try:
408 filename = '"%s"' % filename
408 filename = '"%s"' % filename
409 if line and self.editor_line:
409 if line and self.editor_line:
410 command = self.editor_line.format(filename=filename,
410 command = self.editor_line.format(filename=filename,
411 line=line)
411 line=line)
412 else:
412 else:
413 try:
413 try:
414 command = self.editor.format()
414 command = self.editor.format()
415 except KeyError:
415 except KeyError:
416 command = self.editor.format(filename=filename)
416 command = self.editor.format(filename=filename)
417 else:
417 else:
418 command += ' ' + filename
418 command += ' ' + filename
419 except KeyError:
419 except KeyError:
420 self._append_plain_text('Invalid editor command.\n')
420 self._append_plain_text('Invalid editor command.\n')
421 else:
421 else:
422 try:
422 try:
423 Popen(command, shell=True)
423 Popen(command, shell=True)
424 except OSError:
424 except OSError:
425 msg = 'Opening editor with command "%s" failed.\n'
425 msg = 'Opening editor with command "%s" failed.\n'
426 self._append_plain_text(msg % command)
426 self._append_plain_text(msg % command)
427
427
428 def _make_in_prompt(self, number):
428 def _make_in_prompt(self, number):
429 """ Given a prompt number, returns an HTML In prompt.
429 """ Given a prompt number, returns an HTML In prompt.
430 """
430 """
431 body = self.in_prompt % number
431 body = self.in_prompt % number
432 return '<span class="in-prompt">%s</span>' % body
432 return '<span class="in-prompt">%s</span>' % body
433
433
434 def _make_continuation_prompt(self, prompt):
434 def _make_continuation_prompt(self, prompt):
435 """ Given a plain text version of an In prompt, returns an HTML
435 """ Given a plain text version of an In prompt, returns an HTML
436 continuation prompt.
436 continuation prompt.
437 """
437 """
438 end_chars = '...: '
438 end_chars = '...: '
439 space_count = len(prompt.lstrip('\n')) - len(end_chars)
439 space_count = len(prompt.lstrip('\n')) - len(end_chars)
440 body = '&nbsp;' * space_count + end_chars
440 body = '&nbsp;' * space_count + end_chars
441 return '<span class="in-prompt">%s</span>' % body
441 return '<span class="in-prompt">%s</span>' % body
442
442
443 def _make_out_prompt(self, number):
443 def _make_out_prompt(self, number):
444 """ Given a prompt number, returns an HTML Out prompt.
444 """ Given a prompt number, returns an HTML Out prompt.
445 """
445 """
446 body = self.out_prompt % number
446 body = self.out_prompt % number
447 return '<span class="out-prompt">%s</span>' % body
447 return '<span class="out-prompt">%s</span>' % body
448
448
449 #------ Payload handlers --------------------------------------------------
449 #------ Payload handlers --------------------------------------------------
450
450
451 # Payload handlers with a generic interface: each takes the opaque payload
451 # Payload handlers with a generic interface: each takes the opaque payload
452 # dict, unpacks it and calls the underlying functions with the necessary
452 # dict, unpacks it and calls the underlying functions with the necessary
453 # arguments.
453 # arguments.
454
454
455 def _handle_payload_edit(self, item):
455 def _handle_payload_edit(self, item):
456 self._edit(item['filename'], item['line_number'])
456 self._edit(item['filename'], item['line_number'])
457
457
458 def _handle_payload_exit(self, item):
458 def _handle_payload_exit(self, item):
459 self._keep_kernel_on_exit = item['keepkernel']
459 self._keep_kernel_on_exit = item['keepkernel']
460 self.exit_requested.emit()
460 self.exit_requested.emit()
461
461
462 def _handle_payload_loadpy(self, item):
462 def _handle_payload_loadpy(self, item):
463 # Simple save the text of the .py file for later. The text is written
463 # Simple save the text of the .py file for later. The text is written
464 # to the buffer when _prompt_started_hook is called.
464 # to the buffer when _prompt_started_hook is called.
465 self._code_to_load = item['text']
465 self._code_to_load = item['text']
466
466
467 def _handle_payload_page(self, item):
467 def _handle_payload_page(self, item):
468 # Since the plain text widget supports only a very small subset of HTML
468 # Since the plain text widget supports only a very small subset of HTML
469 # and we have no control over the HTML source, we only page HTML
469 # and we have no control over the HTML source, we only page HTML
470 # payloads in the rich text widget.
470 # payloads in the rich text widget.
471 if item['html'] and self.kind == 'rich':
471 if item['html'] and self.kind == 'rich':
472 self._page(item['html'], html=True)
472 self._page(item['html'], html=True)
473 else:
473 else:
474 self._page(item['text'], html=False)
474 self._page(item['text'], html=False)
475
475
476 #------ Trait change handlers --------------------------------------------
476 #------ Trait change handlers --------------------------------------------
477
477
478 def _style_sheet_changed(self):
478 def _style_sheet_changed(self):
479 """ Set the style sheets of the underlying widgets.
479 """ Set the style sheets of the underlying widgets.
480 """
480 """
481 self.setStyleSheet(self.style_sheet)
481 self.setStyleSheet(self.style_sheet)
482 self._control.document().setDefaultStyleSheet(self.style_sheet)
482 self._control.document().setDefaultStyleSheet(self.style_sheet)
483 if self._page_control:
483 if self._page_control:
484 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
484 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
485
485
486 bg_color = self._control.palette().window().color()
486 bg_color = self._control.palette().window().color()
487 self._ansi_processor.set_background_color(bg_color)
487 self._ansi_processor.set_background_color(bg_color)
488
488
489 def _syntax_style_changed(self):
489 def _syntax_style_changed(self):
490 """ Set the style for the syntax highlighter.
490 """ Set the style for the syntax highlighter.
491 """
491 """
492 if self.syntax_style:
492 if self.syntax_style:
493 self._highlighter.set_style(self.syntax_style)
493 self._highlighter.set_style(self.syntax_style)
494 else:
494 else:
495 self._highlighter.set_style_sheet(self.style_sheet)
495 self._highlighter.set_style_sheet(self.style_sheet)
496
496
@@ -1,920 +1,937 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 from Queue import Queue, Empty
20 from Queue import Queue, Empty
21 from subprocess import Popen
21 from subprocess import Popen
22 import signal
22 import signal
23 import sys
23 import sys
24 from threading import Thread
24 from threading import Thread
25 import time
25 import time
26 import logging
26 import logging
27
27
28 # System library imports.
28 # System library imports.
29 import zmq
29 import zmq
30 from zmq import POLLIN, POLLOUT, POLLERR
30 from zmq import POLLIN, POLLOUT, POLLERR
31 from zmq.eventloop import ioloop
31 from zmq.eventloop import ioloop
32
32
33 # Local imports.
33 # Local imports.
34 from IPython.utils import io
34 from IPython.utils import io
35 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
35 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
36 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
36 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
37 from session import Session, Message
37 from session import Session, Message
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Constants and exceptions
40 # Constants and exceptions
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 class InvalidPortNumber(Exception):
43 class InvalidPortNumber(Exception):
44 pass
44 pass
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Utility functions
47 # Utility functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 # some utilities to validate message structure, these might get moved elsewhere
50 # some utilities to validate message structure, these might get moved elsewhere
51 # if they prove to have more generic utility
51 # if they prove to have more generic utility
52
52
53 def validate_string_list(lst):
53 def validate_string_list(lst):
54 """Validate that the input is a list of strings.
54 """Validate that the input is a list of strings.
55
55
56 Raises ValueError if not."""
56 Raises ValueError if not."""
57 if not isinstance(lst, list):
57 if not isinstance(lst, list):
58 raise ValueError('input %r must be a list' % lst)
58 raise ValueError('input %r must be a list' % lst)
59 for x in lst:
59 for x in lst:
60 if not isinstance(x, basestring):
60 if not isinstance(x, basestring):
61 raise ValueError('element %r in list must be a string' % x)
61 raise ValueError('element %r in list must be a string' % x)
62
62
63
63
64 def validate_string_dict(dct):
64 def validate_string_dict(dct):
65 """Validate that the input is a dict with string keys and values.
65 """Validate that the input is a dict with string keys and values.
66
66
67 Raises ValueError if not."""
67 Raises ValueError if not."""
68 for k,v in dct.iteritems():
68 for k,v in dct.iteritems():
69 if not isinstance(k, basestring):
69 if not isinstance(k, basestring):
70 raise ValueError('key %r in dict must be a string' % k)
70 raise ValueError('key %r in dict must be a string' % k)
71 if not isinstance(v, basestring):
71 if not isinstance(v, basestring):
72 raise ValueError('value %r in dict must be a string' % v)
72 raise ValueError('value %r in dict must be a string' % v)
73
73
74
74
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76 # ZMQ Socket Channel classes
76 # ZMQ Socket Channel classes
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78
78
79 class ZmqSocketChannel(Thread):
79 class ZmqSocketChannel(Thread):
80 """The base class for the channels that use ZMQ sockets.
80 """The base class for the channels that use ZMQ sockets.
81 """
81 """
82 context = None
82 context = None
83 session = None
83 session = None
84 socket = None
84 socket = None
85 ioloop = None
85 ioloop = None
86 iostate = None
86 iostate = None
87 _address = None
87 _address = None
88
88
89 def __init__(self, context, session, address):
89 def __init__(self, context, session, address):
90 """Create a channel
90 """Create a channel
91
91
92 Parameters
92 Parameters
93 ----------
93 ----------
94 context : :class:`zmq.Context`
94 context : :class:`zmq.Context`
95 The ZMQ context to use.
95 The ZMQ context to use.
96 session : :class:`session.Session`
96 session : :class:`session.Session`
97 The session to use.
97 The session to use.
98 address : tuple
98 address : tuple
99 Standard (ip, port) tuple that the kernel is listening on.
99 Standard (ip, port) tuple that the kernel is listening on.
100 """
100 """
101 super(ZmqSocketChannel, self).__init__()
101 super(ZmqSocketChannel, self).__init__()
102 self.daemon = True
102 self.daemon = True
103
103
104 self.context = context
104 self.context = context
105 self.session = session
105 self.session = session
106 if address[1] == 0:
106 if address[1] == 0:
107 message = 'The port number for a channel cannot be 0.'
107 message = 'The port number for a channel cannot be 0.'
108 raise InvalidPortNumber(message)
108 raise InvalidPortNumber(message)
109 self._address = address
109 self._address = address
110
110
111 def stop(self):
111 def stop(self):
112 """Stop the channel's activity.
112 """Stop the channel's activity.
113
113
114 This calls :method:`Thread.join` and returns when the thread
114 This calls :method:`Thread.join` and returns when the thread
115 terminates. :class:`RuntimeError` will be raised if
115 terminates. :class:`RuntimeError` will be raised if
116 :method:`self.start` is called again.
116 :method:`self.start` is called again.
117 """
117 """
118 self.join()
118 self.join()
119
119
120 @property
120 @property
121 def address(self):
121 def address(self):
122 """Get the channel's address as an (ip, port) tuple.
122 """Get the channel's address as an (ip, port) tuple.
123
123
124 By the default, the address is (localhost, 0), where 0 means a random
124 By the default, the address is (localhost, 0), where 0 means a random
125 port.
125 port.
126 """
126 """
127 return self._address
127 return self._address
128
128
129 def add_io_state(self, state):
129 def add_io_state(self, state):
130 """Add IO state to the eventloop.
130 """Add IO state to the eventloop.
131
131
132 Parameters
132 Parameters
133 ----------
133 ----------
134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
134 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
135 The IO state flag to set.
135 The IO state flag to set.
136
136
137 This is thread safe as it uses the thread safe IOLoop.add_callback.
137 This is thread safe as it uses the thread safe IOLoop.add_callback.
138 """
138 """
139 def add_io_state_callback():
139 def add_io_state_callback():
140 if not self.iostate & state:
140 if not self.iostate & state:
141 self.iostate = self.iostate | state
141 self.iostate = self.iostate | state
142 self.ioloop.update_handler(self.socket, self.iostate)
142 self.ioloop.update_handler(self.socket, self.iostate)
143 self.ioloop.add_callback(add_io_state_callback)
143 self.ioloop.add_callback(add_io_state_callback)
144
144
145 def drop_io_state(self, state):
145 def drop_io_state(self, state):
146 """Drop IO state from the eventloop.
146 """Drop IO state from the eventloop.
147
147
148 Parameters
148 Parameters
149 ----------
149 ----------
150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
151 The IO state flag to set.
151 The IO state flag to set.
152
152
153 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 This is thread safe as it uses the thread safe IOLoop.add_callback.
154 """
154 """
155 def drop_io_state_callback():
155 def drop_io_state_callback():
156 if self.iostate & state:
156 if self.iostate & state:
157 self.iostate = self.iostate & (~state)
157 self.iostate = self.iostate & (~state)
158 self.ioloop.update_handler(self.socket, self.iostate)
158 self.ioloop.update_handler(self.socket, self.iostate)
159 self.ioloop.add_callback(drop_io_state_callback)
159 self.ioloop.add_callback(drop_io_state_callback)
160
160
161
161
162 class XReqSocketChannel(ZmqSocketChannel):
162 class XReqSocketChannel(ZmqSocketChannel):
163 """The XREQ channel for issues request/replies to the kernel.
163 """The XREQ channel for issues request/replies to the kernel.
164 """
164 """
165
165
166 command_queue = None
166 command_queue = None
167
167
168 def __init__(self, context, session, address):
168 def __init__(self, context, session, address):
169 super(XReqSocketChannel, self).__init__(context, session, address)
169 super(XReqSocketChannel, self).__init__(context, session, address)
170 self.command_queue = Queue()
170 self.command_queue = Queue()
171 self.ioloop = ioloop.IOLoop()
171 self.ioloop = ioloop.IOLoop()
172
172
173 def run(self):
173 def run(self):
174 """The thread's main activity. Call start() instead."""
174 """The thread's main activity. Call start() instead."""
175 self.socket = self.context.socket(zmq.XREQ)
175 self.socket = self.context.socket(zmq.XREQ)
176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
176 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
177 self.socket.connect('tcp://%s:%i' % self.address)
177 self.socket.connect('tcp://%s:%i' % self.address)
178 self.iostate = POLLERR|POLLIN
178 self.iostate = POLLERR|POLLIN
179 self.ioloop.add_handler(self.socket, self._handle_events,
179 self.ioloop.add_handler(self.socket, self._handle_events,
180 self.iostate)
180 self.iostate)
181 self.ioloop.start()
181 self.ioloop.start()
182
182
183 def stop(self):
183 def stop(self):
184 self.ioloop.stop()
184 self.ioloop.stop()
185 super(XReqSocketChannel, self).stop()
185 super(XReqSocketChannel, self).stop()
186
186
187 def call_handlers(self, msg):
187 def call_handlers(self, msg):
188 """This method is called in the ioloop thread when a message arrives.
188 """This method is called in the ioloop thread when a message arrives.
189
189
190 Subclasses should override this method to handle incoming messages.
190 Subclasses should override this method to handle incoming messages.
191 It is important to remember that this method is called in the thread
191 It is important to remember that this method is called in the thread
192 so that some logic must be done to ensure that the application leve
192 so that some logic must be done to ensure that the application leve
193 handlers are called in the application thread.
193 handlers are called in the application thread.
194 """
194 """
195 raise NotImplementedError('call_handlers must be defined in a subclass.')
195 raise NotImplementedError('call_handlers must be defined in a subclass.')
196
196
197 def execute(self, code, silent=False,
197 def execute(self, code, silent=False,
198 user_variables=None, user_expressions=None):
198 user_variables=None, user_expressions=None):
199 """Execute code in the kernel.
199 """Execute code in the kernel.
200
200
201 Parameters
201 Parameters
202 ----------
202 ----------
203 code : str
203 code : str
204 A string of Python code.
204 A string of Python code.
205
205
206 silent : bool, optional (default False)
206 silent : bool, optional (default False)
207 If set, the kernel will execute the code as quietly possible.
207 If set, the kernel will execute the code as quietly possible.
208
208
209 user_variables : list, optional
209 user_variables : list, optional
210 A list of variable names to pull from the user's namespace. They
210 A list of variable names to pull from the user's namespace. They
211 will come back as a dict with these names as keys and their
211 will come back as a dict with these names as keys and their
212 :func:`repr` as values.
212 :func:`repr` as values.
213
213
214 user_expressions : dict, optional
214 user_expressions : dict, optional
215 A dict with string keys and to pull from the user's
215 A dict with string keys and to pull from the user's
216 namespace. They will come back as a dict with these names as keys
216 namespace. They will come back as a dict with these names as keys
217 and their :func:`repr` as values.
217 and their :func:`repr` as values.
218
218
219 Returns
219 Returns
220 -------
220 -------
221 The msg_id of the message sent.
221 The msg_id of the message sent.
222 """
222 """
223 if user_variables is None:
223 if user_variables is None:
224 user_variables = []
224 user_variables = []
225 if user_expressions is None:
225 if user_expressions is None:
226 user_expressions = {}
226 user_expressions = {}
227
227
228 # Don't waste network traffic if inputs are invalid
228 # Don't waste network traffic if inputs are invalid
229 if not isinstance(code, basestring):
229 if not isinstance(code, basestring):
230 raise ValueError('code %r must be a string' % code)
230 raise ValueError('code %r must be a string' % code)
231 validate_string_list(user_variables)
231 validate_string_list(user_variables)
232 validate_string_dict(user_expressions)
232 validate_string_dict(user_expressions)
233
233
234 # Create class for content/msg creation. Related to, but possibly
234 # Create class for content/msg creation. Related to, but possibly
235 # not in Session.
235 # not in Session.
236 content = dict(code=code, silent=silent,
236 content = dict(code=code, silent=silent,
237 user_variables=user_variables,
237 user_variables=user_variables,
238 user_expressions=user_expressions)
238 user_expressions=user_expressions)
239 msg = self.session.msg('execute_request', content)
239 msg = self.session.msg('execute_request', content)
240 self._queue_request(msg)
240 self._queue_request(msg)
241 return msg['header']['msg_id']
241 return msg['header']['msg_id']
242
242
243 def complete(self, text, line, cursor_pos, block=None):
243 def complete(self, text, line, cursor_pos, block=None):
244 """Tab complete text in the kernel's namespace.
244 """Tab complete text in the kernel's namespace.
245
245
246 Parameters
246 Parameters
247 ----------
247 ----------
248 text : str
248 text : str
249 The text to complete.
249 The text to complete.
250 line : str
250 line : str
251 The full line of text that is the surrounding context for the
251 The full line of text that is the surrounding context for the
252 text to complete.
252 text to complete.
253 cursor_pos : int
253 cursor_pos : int
254 The position of the cursor in the line where the completion was
254 The position of the cursor in the line where the completion was
255 requested.
255 requested.
256 block : str, optional
256 block : str, optional
257 The full block of code in which the completion is being requested.
257 The full block of code in which the completion is being requested.
258
258
259 Returns
259 Returns
260 -------
260 -------
261 The msg_id of the message sent.
261 The msg_id of the message sent.
262 """
262 """
263 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
263 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
264 msg = self.session.msg('complete_request', content)
264 msg = self.session.msg('complete_request', content)
265 self._queue_request(msg)
265 self._queue_request(msg)
266 return msg['header']['msg_id']
266 return msg['header']['msg_id']
267
267
268 def object_info(self, oname):
268 def object_info(self, oname):
269 """Get metadata information about an object.
269 """Get metadata information about an object.
270
270
271 Parameters
271 Parameters
272 ----------
272 ----------
273 oname : str
273 oname : str
274 A string specifying the object name.
274 A string specifying the object name.
275
275
276 Returns
276 Returns
277 -------
277 -------
278 The msg_id of the message sent.
278 The msg_id of the message sent.
279 """
279 """
280 content = dict(oname=oname)
280 content = dict(oname=oname)
281 msg = self.session.msg('object_info_request', content)
281 msg = self.session.msg('object_info_request', content)
282 self._queue_request(msg)
282 self._queue_request(msg)
283 return msg['header']['msg_id']
283 return msg['header']['msg_id']
284
284
285 def history_tail(self, n=10, raw=True, output=False):
285 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
286 """Get the history list.
286 """Get entries from the history list.
287
287
288 Parameters
288 Parameters
289 ----------
289 ----------
290 n : int
291 The number of lines of history to get.
292 raw : bool
290 raw : bool
293 If True, return the raw input.
291 If True, return the raw input.
294 output : bool
292 output : bool
295 If True, then return the output as well.
293 If True, then return the output as well.
294 hist_access_type : str
295 'range' (fill in session, start and stop params), 'tail' (fill in n)
296 or 'search' (fill in pattern param).
297
298 session : int
299 For a range request, the session from which to get lines. Session
300 numbers are positive integers; negative ones count back from the
301 current session.
302 start : int
303 The first line number of a history range.
304 stop : int
305 The final (excluded) line number of a history range.
306
307 n : int
308 The number of lines of history to get for a tail request.
309
310 pattern : str
311 The glob-syntax pattern for a search request.
296
312
297 Returns
313 Returns
298 -------
314 -------
299 The msg_id of the message sent.
315 The msg_id of the message sent.
300 """
316 """
301 content = dict(n=n, raw=raw, output=output, hist_access_type='tail')
317 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
318 **kwargs)
302 msg = self.session.msg('history_request', content)
319 msg = self.session.msg('history_request', content)
303 self._queue_request(msg)
320 self._queue_request(msg)
304 return msg['header']['msg_id']
321 return msg['header']['msg_id']
305
322
306 def shutdown(self, restart=False):
323 def shutdown(self, restart=False):
307 """Request an immediate kernel shutdown.
324 """Request an immediate kernel shutdown.
308
325
309 Upon receipt of the (empty) reply, client code can safely assume that
326 Upon receipt of the (empty) reply, client code can safely assume that
310 the kernel has shut down and it's safe to forcefully terminate it if
327 the kernel has shut down and it's safe to forcefully terminate it if
311 it's still alive.
328 it's still alive.
312
329
313 The kernel will send the reply via a function registered with Python's
330 The kernel will send the reply via a function registered with Python's
314 atexit module, ensuring it's truly done as the kernel is done with all
331 atexit module, ensuring it's truly done as the kernel is done with all
315 normal operation.
332 normal operation.
316 """
333 """
317 # Send quit message to kernel. Once we implement kernel-side setattr,
334 # Send quit message to kernel. Once we implement kernel-side setattr,
318 # this should probably be done that way, but for now this will do.
335 # this should probably be done that way, but for now this will do.
319 msg = self.session.msg('shutdown_request', {'restart':restart})
336 msg = self.session.msg('shutdown_request', {'restart':restart})
320 self._queue_request(msg)
337 self._queue_request(msg)
321 return msg['header']['msg_id']
338 return msg['header']['msg_id']
322
339
323 def _handle_events(self, socket, events):
340 def _handle_events(self, socket, events):
324 if events & POLLERR:
341 if events & POLLERR:
325 self._handle_err()
342 self._handle_err()
326 if events & POLLOUT:
343 if events & POLLOUT:
327 self._handle_send()
344 self._handle_send()
328 if events & POLLIN:
345 if events & POLLIN:
329 self._handle_recv()
346 self._handle_recv()
330
347
331 def _handle_recv(self):
348 def _handle_recv(self):
332 ident,msg = self.session.recv(self.socket, 0)
349 ident,msg = self.session.recv(self.socket, 0)
333 self.call_handlers(msg)
350 self.call_handlers(msg)
334
351
335 def _handle_send(self):
352 def _handle_send(self):
336 try:
353 try:
337 msg = self.command_queue.get(False)
354 msg = self.command_queue.get(False)
338 except Empty:
355 except Empty:
339 pass
356 pass
340 else:
357 else:
341 self.session.send(self.socket,msg)
358 self.session.send(self.socket,msg)
342 if self.command_queue.empty():
359 if self.command_queue.empty():
343 self.drop_io_state(POLLOUT)
360 self.drop_io_state(POLLOUT)
344
361
345 def _handle_err(self):
362 def _handle_err(self):
346 # We don't want to let this go silently, so eventually we should log.
363 # We don't want to let this go silently, so eventually we should log.
347 raise zmq.ZMQError()
364 raise zmq.ZMQError()
348
365
349 def _queue_request(self, msg):
366 def _queue_request(self, msg):
350 self.command_queue.put(msg)
367 self.command_queue.put(msg)
351 self.add_io_state(POLLOUT)
368 self.add_io_state(POLLOUT)
352
369
353
370
354 class SubSocketChannel(ZmqSocketChannel):
371 class SubSocketChannel(ZmqSocketChannel):
355 """The SUB channel which listens for messages that the kernel publishes.
372 """The SUB channel which listens for messages that the kernel publishes.
356 """
373 """
357
374
358 def __init__(self, context, session, address):
375 def __init__(self, context, session, address):
359 super(SubSocketChannel, self).__init__(context, session, address)
376 super(SubSocketChannel, self).__init__(context, session, address)
360 self.ioloop = ioloop.IOLoop()
377 self.ioloop = ioloop.IOLoop()
361
378
362 def run(self):
379 def run(self):
363 """The thread's main activity. Call start() instead."""
380 """The thread's main activity. Call start() instead."""
364 self.socket = self.context.socket(zmq.SUB)
381 self.socket = self.context.socket(zmq.SUB)
365 self.socket.setsockopt(zmq.SUBSCRIBE,'')
382 self.socket.setsockopt(zmq.SUBSCRIBE,'')
366 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
383 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
367 self.socket.connect('tcp://%s:%i' % self.address)
384 self.socket.connect('tcp://%s:%i' % self.address)
368 self.iostate = POLLIN|POLLERR
385 self.iostate = POLLIN|POLLERR
369 self.ioloop.add_handler(self.socket, self._handle_events,
386 self.ioloop.add_handler(self.socket, self._handle_events,
370 self.iostate)
387 self.iostate)
371 self.ioloop.start()
388 self.ioloop.start()
372
389
373 def stop(self):
390 def stop(self):
374 self.ioloop.stop()
391 self.ioloop.stop()
375 super(SubSocketChannel, self).stop()
392 super(SubSocketChannel, self).stop()
376
393
377 def call_handlers(self, msg):
394 def call_handlers(self, msg):
378 """This method is called in the ioloop thread when a message arrives.
395 """This method is called in the ioloop thread when a message arrives.
379
396
380 Subclasses should override this method to handle incoming messages.
397 Subclasses should override this method to handle incoming messages.
381 It is important to remember that this method is called in the thread
398 It is important to remember that this method is called in the thread
382 so that some logic must be done to ensure that the application leve
399 so that some logic must be done to ensure that the application leve
383 handlers are called in the application thread.
400 handlers are called in the application thread.
384 """
401 """
385 raise NotImplementedError('call_handlers must be defined in a subclass.')
402 raise NotImplementedError('call_handlers must be defined in a subclass.')
386
403
387 def flush(self, timeout=1.0):
404 def flush(self, timeout=1.0):
388 """Immediately processes all pending messages on the SUB channel.
405 """Immediately processes all pending messages on the SUB channel.
389
406
390 Callers should use this method to ensure that :method:`call_handlers`
407 Callers should use this method to ensure that :method:`call_handlers`
391 has been called for all messages that have been received on the
408 has been called for all messages that have been received on the
392 0MQ SUB socket of this channel.
409 0MQ SUB socket of this channel.
393
410
394 This method is thread safe.
411 This method is thread safe.
395
412
396 Parameters
413 Parameters
397 ----------
414 ----------
398 timeout : float, optional
415 timeout : float, optional
399 The maximum amount of time to spend flushing, in seconds. The
416 The maximum amount of time to spend flushing, in seconds. The
400 default is one second.
417 default is one second.
401 """
418 """
402 # We do the IOLoop callback process twice to ensure that the IOLoop
419 # We do the IOLoop callback process twice to ensure that the IOLoop
403 # gets to perform at least one full poll.
420 # gets to perform at least one full poll.
404 stop_time = time.time() + timeout
421 stop_time = time.time() + timeout
405 for i in xrange(2):
422 for i in xrange(2):
406 self._flushed = False
423 self._flushed = False
407 self.ioloop.add_callback(self._flush)
424 self.ioloop.add_callback(self._flush)
408 while not self._flushed and time.time() < stop_time:
425 while not self._flushed and time.time() < stop_time:
409 time.sleep(0.01)
426 time.sleep(0.01)
410
427
411 def _handle_events(self, socket, events):
428 def _handle_events(self, socket, events):
412 # Turn on and off POLLOUT depending on if we have made a request
429 # Turn on and off POLLOUT depending on if we have made a request
413 if events & POLLERR:
430 if events & POLLERR:
414 self._handle_err()
431 self._handle_err()
415 if events & POLLIN:
432 if events & POLLIN:
416 self._handle_recv()
433 self._handle_recv()
417
434
418 def _handle_err(self):
435 def _handle_err(self):
419 # We don't want to let this go silently, so eventually we should log.
436 # We don't want to let this go silently, so eventually we should log.
420 raise zmq.ZMQError()
437 raise zmq.ZMQError()
421
438
422 def _handle_recv(self):
439 def _handle_recv(self):
423 # Get all of the messages we can
440 # Get all of the messages we can
424 while True:
441 while True:
425 try:
442 try:
426 ident,msg = self.session.recv(self.socket)
443 ident,msg = self.session.recv(self.socket)
427 except zmq.ZMQError:
444 except zmq.ZMQError:
428 # Check the errno?
445 # Check the errno?
429 # Will this trigger POLLERR?
446 # Will this trigger POLLERR?
430 break
447 break
431 else:
448 else:
432 if msg is None:
449 if msg is None:
433 break
450 break
434 self.call_handlers(msg)
451 self.call_handlers(msg)
435
452
436 def _flush(self):
453 def _flush(self):
437 """Callback for :method:`self.flush`."""
454 """Callback for :method:`self.flush`."""
438 self._flushed = True
455 self._flushed = True
439
456
440
457
441 class RepSocketChannel(ZmqSocketChannel):
458 class RepSocketChannel(ZmqSocketChannel):
442 """A reply channel to handle raw_input requests that the kernel makes."""
459 """A reply channel to handle raw_input requests that the kernel makes."""
443
460
444 msg_queue = None
461 msg_queue = None
445
462
446 def __init__(self, context, session, address):
463 def __init__(self, context, session, address):
447 super(RepSocketChannel, self).__init__(context, session, address)
464 super(RepSocketChannel, self).__init__(context, session, address)
448 self.ioloop = ioloop.IOLoop()
465 self.ioloop = ioloop.IOLoop()
449 self.msg_queue = Queue()
466 self.msg_queue = Queue()
450
467
451 def run(self):
468 def run(self):
452 """The thread's main activity. Call start() instead."""
469 """The thread's main activity. Call start() instead."""
453 self.socket = self.context.socket(zmq.XREQ)
470 self.socket = self.context.socket(zmq.XREQ)
454 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
471 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
455 self.socket.connect('tcp://%s:%i' % self.address)
472 self.socket.connect('tcp://%s:%i' % self.address)
456 self.iostate = POLLERR|POLLIN
473 self.iostate = POLLERR|POLLIN
457 self.ioloop.add_handler(self.socket, self._handle_events,
474 self.ioloop.add_handler(self.socket, self._handle_events,
458 self.iostate)
475 self.iostate)
459 self.ioloop.start()
476 self.ioloop.start()
460
477
461 def stop(self):
478 def stop(self):
462 self.ioloop.stop()
479 self.ioloop.stop()
463 super(RepSocketChannel, self).stop()
480 super(RepSocketChannel, self).stop()
464
481
465 def call_handlers(self, msg):
482 def call_handlers(self, msg):
466 """This method is called in the ioloop thread when a message arrives.
483 """This method is called in the ioloop thread when a message arrives.
467
484
468 Subclasses should override this method to handle incoming messages.
485 Subclasses should override this method to handle incoming messages.
469 It is important to remember that this method is called in the thread
486 It is important to remember that this method is called in the thread
470 so that some logic must be done to ensure that the application leve
487 so that some logic must be done to ensure that the application leve
471 handlers are called in the application thread.
488 handlers are called in the application thread.
472 """
489 """
473 raise NotImplementedError('call_handlers must be defined in a subclass.')
490 raise NotImplementedError('call_handlers must be defined in a subclass.')
474
491
475 def input(self, string):
492 def input(self, string):
476 """Send a string of raw input to the kernel."""
493 """Send a string of raw input to the kernel."""
477 content = dict(value=string)
494 content = dict(value=string)
478 msg = self.session.msg('input_reply', content)
495 msg = self.session.msg('input_reply', content)
479 self._queue_reply(msg)
496 self._queue_reply(msg)
480
497
481 def _handle_events(self, socket, events):
498 def _handle_events(self, socket, events):
482 if events & POLLERR:
499 if events & POLLERR:
483 self._handle_err()
500 self._handle_err()
484 if events & POLLOUT:
501 if events & POLLOUT:
485 self._handle_send()
502 self._handle_send()
486 if events & POLLIN:
503 if events & POLLIN:
487 self._handle_recv()
504 self._handle_recv()
488
505
489 def _handle_recv(self):
506 def _handle_recv(self):
490 ident,msg = self.session.recv(self.socket, 0)
507 ident,msg = self.session.recv(self.socket, 0)
491 self.call_handlers(msg)
508 self.call_handlers(msg)
492
509
493 def _handle_send(self):
510 def _handle_send(self):
494 try:
511 try:
495 msg = self.msg_queue.get(False)
512 msg = self.msg_queue.get(False)
496 except Empty:
513 except Empty:
497 pass
514 pass
498 else:
515 else:
499 self.session.send(self.socket,msg)
516 self.session.send(self.socket,msg)
500 if self.msg_queue.empty():
517 if self.msg_queue.empty():
501 self.drop_io_state(POLLOUT)
518 self.drop_io_state(POLLOUT)
502
519
503 def _handle_err(self):
520 def _handle_err(self):
504 # We don't want to let this go silently, so eventually we should log.
521 # We don't want to let this go silently, so eventually we should log.
505 raise zmq.ZMQError()
522 raise zmq.ZMQError()
506
523
507 def _queue_reply(self, msg):
524 def _queue_reply(self, msg):
508 self.msg_queue.put(msg)
525 self.msg_queue.put(msg)
509 self.add_io_state(POLLOUT)
526 self.add_io_state(POLLOUT)
510
527
511
528
512 class HBSocketChannel(ZmqSocketChannel):
529 class HBSocketChannel(ZmqSocketChannel):
513 """The heartbeat channel which monitors the kernel heartbeat.
530 """The heartbeat channel which monitors the kernel heartbeat.
514
531
515 Note that the heartbeat channel is paused by default. As long as you start
532 Note that the heartbeat channel is paused by default. As long as you start
516 this channel, the kernel manager will ensure that it is paused and un-paused
533 this channel, the kernel manager will ensure that it is paused and un-paused
517 as appropriate.
534 as appropriate.
518 """
535 """
519
536
520 time_to_dead = 3.0
537 time_to_dead = 3.0
521 socket = None
538 socket = None
522 poller = None
539 poller = None
523 _running = None
540 _running = None
524 _pause = None
541 _pause = None
525
542
526 def __init__(self, context, session, address):
543 def __init__(self, context, session, address):
527 super(HBSocketChannel, self).__init__(context, session, address)
544 super(HBSocketChannel, self).__init__(context, session, address)
528 self._running = False
545 self._running = False
529 self._pause = True
546 self._pause = True
530
547
531 def _create_socket(self):
548 def _create_socket(self):
532 self.socket = self.context.socket(zmq.REQ)
549 self.socket = self.context.socket(zmq.REQ)
533 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
550 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
534 self.socket.connect('tcp://%s:%i' % self.address)
551 self.socket.connect('tcp://%s:%i' % self.address)
535 self.poller = zmq.Poller()
552 self.poller = zmq.Poller()
536 self.poller.register(self.socket, zmq.POLLIN)
553 self.poller.register(self.socket, zmq.POLLIN)
537
554
538 def run(self):
555 def run(self):
539 """The thread's main activity. Call start() instead."""
556 """The thread's main activity. Call start() instead."""
540 self._create_socket()
557 self._create_socket()
541 self._running = True
558 self._running = True
542 while self._running:
559 while self._running:
543 if self._pause:
560 if self._pause:
544 time.sleep(self.time_to_dead)
561 time.sleep(self.time_to_dead)
545 else:
562 else:
546 since_last_heartbeat = 0.0
563 since_last_heartbeat = 0.0
547 request_time = time.time()
564 request_time = time.time()
548 try:
565 try:
549 #io.rprint('Ping from HB channel') # dbg
566 #io.rprint('Ping from HB channel') # dbg
550 self.socket.send(b'ping')
567 self.socket.send(b'ping')
551 except zmq.ZMQError, e:
568 except zmq.ZMQError, e:
552 #io.rprint('*** HB Error:', e) # dbg
569 #io.rprint('*** HB Error:', e) # dbg
553 if e.errno == zmq.EFSM:
570 if e.errno == zmq.EFSM:
554 #io.rprint('sleep...', self.time_to_dead) # dbg
571 #io.rprint('sleep...', self.time_to_dead) # dbg
555 time.sleep(self.time_to_dead)
572 time.sleep(self.time_to_dead)
556 self._create_socket()
573 self._create_socket()
557 else:
574 else:
558 raise
575 raise
559 else:
576 else:
560 while True:
577 while True:
561 try:
578 try:
562 self.socket.recv(zmq.NOBLOCK)
579 self.socket.recv(zmq.NOBLOCK)
563 except zmq.ZMQError, e:
580 except zmq.ZMQError, e:
564 #io.rprint('*** HB Error 2:', e) # dbg
581 #io.rprint('*** HB Error 2:', e) # dbg
565 if e.errno == zmq.EAGAIN:
582 if e.errno == zmq.EAGAIN:
566 before_poll = time.time()
583 before_poll = time.time()
567 until_dead = self.time_to_dead - (before_poll -
584 until_dead = self.time_to_dead - (before_poll -
568 request_time)
585 request_time)
569
586
570 # When the return value of poll() is an empty
587 # When the return value of poll() is an empty
571 # list, that is when things have gone wrong
588 # list, that is when things have gone wrong
572 # (zeromq bug). As long as it is not an empty
589 # (zeromq bug). As long as it is not an empty
573 # list, poll is working correctly even if it
590 # list, poll is working correctly even if it
574 # returns quickly. Note: poll timeout is in
591 # returns quickly. Note: poll timeout is in
575 # milliseconds.
592 # milliseconds.
576 self.poller.poll(1000*until_dead)
593 self.poller.poll(1000*until_dead)
577
594
578 since_last_heartbeat = time.time()-request_time
595 since_last_heartbeat = time.time()-request_time
579 if since_last_heartbeat > self.time_to_dead:
596 if since_last_heartbeat > self.time_to_dead:
580 self.call_handlers(since_last_heartbeat)
597 self.call_handlers(since_last_heartbeat)
581 break
598 break
582 else:
599 else:
583 # FIXME: We should probably log this instead.
600 # FIXME: We should probably log this instead.
584 raise
601 raise
585 else:
602 else:
586 until_dead = self.time_to_dead - (time.time() -
603 until_dead = self.time_to_dead - (time.time() -
587 request_time)
604 request_time)
588 if until_dead > 0.0:
605 if until_dead > 0.0:
589 #io.rprint('sleep...', self.time_to_dead) # dbg
606 #io.rprint('sleep...', self.time_to_dead) # dbg
590 time.sleep(until_dead)
607 time.sleep(until_dead)
591 break
608 break
592
609
593 def pause(self):
610 def pause(self):
594 """Pause the heartbeat."""
611 """Pause the heartbeat."""
595 self._pause = True
612 self._pause = True
596
613
597 def unpause(self):
614 def unpause(self):
598 """Unpause the heartbeat."""
615 """Unpause the heartbeat."""
599 self._pause = False
616 self._pause = False
600
617
601 def is_beating(self):
618 def is_beating(self):
602 """Is the heartbeat running and not paused."""
619 """Is the heartbeat running and not paused."""
603 if self.is_alive() and not self._pause:
620 if self.is_alive() and not self._pause:
604 return True
621 return True
605 else:
622 else:
606 return False
623 return False
607
624
608 def stop(self):
625 def stop(self):
609 self._running = False
626 self._running = False
610 super(HBSocketChannel, self).stop()
627 super(HBSocketChannel, self).stop()
611
628
612 def call_handlers(self, since_last_heartbeat):
629 def call_handlers(self, since_last_heartbeat):
613 """This method is called in the ioloop thread when a message arrives.
630 """This method is called in the ioloop thread when a message arrives.
614
631
615 Subclasses should override this method to handle incoming messages.
632 Subclasses should override this method to handle incoming messages.
616 It is important to remember that this method is called in the thread
633 It is important to remember that this method is called in the thread
617 so that some logic must be done to ensure that the application leve
634 so that some logic must be done to ensure that the application leve
618 handlers are called in the application thread.
635 handlers are called in the application thread.
619 """
636 """
620 raise NotImplementedError('call_handlers must be defined in a subclass.')
637 raise NotImplementedError('call_handlers must be defined in a subclass.')
621
638
622
639
623 #-----------------------------------------------------------------------------
640 #-----------------------------------------------------------------------------
624 # Main kernel manager class
641 # Main kernel manager class
625 #-----------------------------------------------------------------------------
642 #-----------------------------------------------------------------------------
626
643
627 class KernelManager(HasTraits):
644 class KernelManager(HasTraits):
628 """ Manages a kernel for a frontend.
645 """ Manages a kernel for a frontend.
629
646
630 The SUB channel is for the frontend to receive messages published by the
647 The SUB channel is for the frontend to receive messages published by the
631 kernel.
648 kernel.
632
649
633 The REQ channel is for the frontend to make requests of the kernel.
650 The REQ channel is for the frontend to make requests of the kernel.
634
651
635 The REP channel is for the kernel to request stdin (raw_input) from the
652 The REP channel is for the kernel to request stdin (raw_input) from the
636 frontend.
653 frontend.
637 """
654 """
638 # The PyZMQ Context to use for communication with the kernel.
655 # The PyZMQ Context to use for communication with the kernel.
639 context = Instance(zmq.Context,(),{})
656 context = Instance(zmq.Context,(),{})
640
657
641 # The Session to use for communication with the kernel.
658 # The Session to use for communication with the kernel.
642 session = Instance(Session,(),{})
659 session = Instance(Session,(),{})
643
660
644 # The kernel process with which the KernelManager is communicating.
661 # The kernel process with which the KernelManager is communicating.
645 kernel = Instance(Popen)
662 kernel = Instance(Popen)
646
663
647 # The addresses for the communication channels.
664 # The addresses for the communication channels.
648 xreq_address = TCPAddress((LOCALHOST, 0))
665 xreq_address = TCPAddress((LOCALHOST, 0))
649 sub_address = TCPAddress((LOCALHOST, 0))
666 sub_address = TCPAddress((LOCALHOST, 0))
650 rep_address = TCPAddress((LOCALHOST, 0))
667 rep_address = TCPAddress((LOCALHOST, 0))
651 hb_address = TCPAddress((LOCALHOST, 0))
668 hb_address = TCPAddress((LOCALHOST, 0))
652
669
653 # The classes to use for the various channels.
670 # The classes to use for the various channels.
654 xreq_channel_class = Type(XReqSocketChannel)
671 xreq_channel_class = Type(XReqSocketChannel)
655 sub_channel_class = Type(SubSocketChannel)
672 sub_channel_class = Type(SubSocketChannel)
656 rep_channel_class = Type(RepSocketChannel)
673 rep_channel_class = Type(RepSocketChannel)
657 hb_channel_class = Type(HBSocketChannel)
674 hb_channel_class = Type(HBSocketChannel)
658
675
659 # Protected traits.
676 # Protected traits.
660 _launch_args = Any
677 _launch_args = Any
661 _xreq_channel = Any
678 _xreq_channel = Any
662 _sub_channel = Any
679 _sub_channel = Any
663 _rep_channel = Any
680 _rep_channel = Any
664 _hb_channel = Any
681 _hb_channel = Any
665
682
666 def __init__(self, **kwargs):
683 def __init__(self, **kwargs):
667 super(KernelManager, self).__init__(**kwargs)
684 super(KernelManager, self).__init__(**kwargs)
668 # Uncomment this to try closing the context.
685 # Uncomment this to try closing the context.
669 # atexit.register(self.context.close)
686 # atexit.register(self.context.close)
670
687
671 #--------------------------------------------------------------------------
688 #--------------------------------------------------------------------------
672 # Channel management methods:
689 # Channel management methods:
673 #--------------------------------------------------------------------------
690 #--------------------------------------------------------------------------
674
691
675 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
692 def start_channels(self, xreq=True, sub=True, rep=True, hb=True):
676 """Starts the channels for this kernel.
693 """Starts the channels for this kernel.
677
694
678 This will create the channels if they do not exist and then start
695 This will create the channels if they do not exist and then start
679 them. If port numbers of 0 are being used (random ports) then you
696 them. If port numbers of 0 are being used (random ports) then you
680 must first call :method:`start_kernel`. If the channels have been
697 must first call :method:`start_kernel`. If the channels have been
681 stopped and you call this, :class:`RuntimeError` will be raised.
698 stopped and you call this, :class:`RuntimeError` will be raised.
682 """
699 """
683 if xreq:
700 if xreq:
684 self.xreq_channel.start()
701 self.xreq_channel.start()
685 if sub:
702 if sub:
686 self.sub_channel.start()
703 self.sub_channel.start()
687 if rep:
704 if rep:
688 self.rep_channel.start()
705 self.rep_channel.start()
689 if hb:
706 if hb:
690 self.hb_channel.start()
707 self.hb_channel.start()
691
708
692 def stop_channels(self):
709 def stop_channels(self):
693 """Stops all the running channels for this kernel.
710 """Stops all the running channels for this kernel.
694 """
711 """
695 if self.xreq_channel.is_alive():
712 if self.xreq_channel.is_alive():
696 self.xreq_channel.stop()
713 self.xreq_channel.stop()
697 if self.sub_channel.is_alive():
714 if self.sub_channel.is_alive():
698 self.sub_channel.stop()
715 self.sub_channel.stop()
699 if self.rep_channel.is_alive():
716 if self.rep_channel.is_alive():
700 self.rep_channel.stop()
717 self.rep_channel.stop()
701 if self.hb_channel.is_alive():
718 if self.hb_channel.is_alive():
702 self.hb_channel.stop()
719 self.hb_channel.stop()
703
720
704 @property
721 @property
705 def channels_running(self):
722 def channels_running(self):
706 """Are any of the channels created and running?"""
723 """Are any of the channels created and running?"""
707 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
724 return (self.xreq_channel.is_alive() or self.sub_channel.is_alive() or
708 self.rep_channel.is_alive() or self.hb_channel.is_alive())
725 self.rep_channel.is_alive() or self.hb_channel.is_alive())
709
726
710 #--------------------------------------------------------------------------
727 #--------------------------------------------------------------------------
711 # Kernel process management methods:
728 # Kernel process management methods:
712 #--------------------------------------------------------------------------
729 #--------------------------------------------------------------------------
713
730
714 def start_kernel(self, **kw):
731 def start_kernel(self, **kw):
715 """Starts a kernel process and configures the manager to use it.
732 """Starts a kernel process and configures the manager to use it.
716
733
717 If random ports (port=0) are being used, this method must be called
734 If random ports (port=0) are being used, this method must be called
718 before the channels are created.
735 before the channels are created.
719
736
720 Parameters:
737 Parameters:
721 -----------
738 -----------
722 ipython : bool, optional (default True)
739 ipython : bool, optional (default True)
723 Whether to use an IPython kernel instead of a plain Python kernel.
740 Whether to use an IPython kernel instead of a plain Python kernel.
724
741
725 **kw : optional
742 **kw : optional
726 See respective options for IPython and Python kernels.
743 See respective options for IPython and Python kernels.
727 """
744 """
728 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
745 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
729 self.rep_address, self.hb_address
746 self.rep_address, self.hb_address
730 if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
747 if xreq[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
731 rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
748 rep[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
732 raise RuntimeError("Can only launch a kernel on a local interface. "
749 raise RuntimeError("Can only launch a kernel on a local interface. "
733 "Make sure that the '*_address' attributes are "
750 "Make sure that the '*_address' attributes are "
734 "configured properly. "
751 "configured properly. "
735 "Currently valid addresses are: %s"%LOCAL_IPS
752 "Currently valid addresses are: %s"%LOCAL_IPS
736 )
753 )
737
754
738 self._launch_args = kw.copy()
755 self._launch_args = kw.copy()
739 if kw.pop('ipython', True):
756 if kw.pop('ipython', True):
740 from ipkernel import launch_kernel
757 from ipkernel import launch_kernel
741 else:
758 else:
742 from pykernel import launch_kernel
759 from pykernel import launch_kernel
743 self.kernel, xrep, pub, req, _hb = launch_kernel(
760 self.kernel, xrep, pub, req, _hb = launch_kernel(
744 xrep_port=xreq[1], pub_port=sub[1],
761 xrep_port=xreq[1], pub_port=sub[1],
745 req_port=rep[1], hb_port=hb[1], **kw)
762 req_port=rep[1], hb_port=hb[1], **kw)
746 self.xreq_address = (xreq[0], xrep)
763 self.xreq_address = (xreq[0], xrep)
747 self.sub_address = (sub[0], pub)
764 self.sub_address = (sub[0], pub)
748 self.rep_address = (rep[0], req)
765 self.rep_address = (rep[0], req)
749 self.hb_address = (hb[0], _hb)
766 self.hb_address = (hb[0], _hb)
750
767
751 def shutdown_kernel(self, restart=False):
768 def shutdown_kernel(self, restart=False):
752 """ Attempts to the stop the kernel process cleanly. If the kernel
769 """ Attempts to the stop the kernel process cleanly. If the kernel
753 cannot be stopped, it is killed, if possible.
770 cannot be stopped, it is killed, if possible.
754 """
771 """
755 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
772 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
756 if sys.platform == 'win32':
773 if sys.platform == 'win32':
757 self.kill_kernel()
774 self.kill_kernel()
758 return
775 return
759
776
760 # Pause the heart beat channel if it exists.
777 # Pause the heart beat channel if it exists.
761 if self._hb_channel is not None:
778 if self._hb_channel is not None:
762 self._hb_channel.pause()
779 self._hb_channel.pause()
763
780
764 # Don't send any additional kernel kill messages immediately, to give
781 # Don't send any additional kernel kill messages immediately, to give
765 # the kernel a chance to properly execute shutdown actions. Wait for at
782 # the kernel a chance to properly execute shutdown actions. Wait for at
766 # most 1s, checking every 0.1s.
783 # most 1s, checking every 0.1s.
767 self.xreq_channel.shutdown(restart=restart)
784 self.xreq_channel.shutdown(restart=restart)
768 for i in range(10):
785 for i in range(10):
769 if self.is_alive:
786 if self.is_alive:
770 time.sleep(0.1)
787 time.sleep(0.1)
771 else:
788 else:
772 break
789 break
773 else:
790 else:
774 # OK, we've waited long enough.
791 # OK, we've waited long enough.
775 if self.has_kernel:
792 if self.has_kernel:
776 self.kill_kernel()
793 self.kill_kernel()
777
794
778 def restart_kernel(self, now=False, **kw):
795 def restart_kernel(self, now=False, **kw):
779 """Restarts a kernel with the arguments that were used to launch it.
796 """Restarts a kernel with the arguments that were used to launch it.
780
797
781 If the old kernel was launched with random ports, the same ports will be
798 If the old kernel was launched with random ports, the same ports will be
782 used for the new kernel.
799 used for the new kernel.
783
800
784 Parameters
801 Parameters
785 ----------
802 ----------
786 now : bool, optional
803 now : bool, optional
787 If True, the kernel is forcefully restarted *immediately*, without
804 If True, the kernel is forcefully restarted *immediately*, without
788 having a chance to do any cleanup action. Otherwise the kernel is
805 having a chance to do any cleanup action. Otherwise the kernel is
789 given 1s to clean up before a forceful restart is issued.
806 given 1s to clean up before a forceful restart is issued.
790
807
791 In all cases the kernel is restarted, the only difference is whether
808 In all cases the kernel is restarted, the only difference is whether
792 it is given a chance to perform a clean shutdown or not.
809 it is given a chance to perform a clean shutdown or not.
793
810
794 **kw : optional
811 **kw : optional
795 Any options specified here will replace those used to launch the
812 Any options specified here will replace those used to launch the
796 kernel.
813 kernel.
797 """
814 """
798 if self._launch_args is None:
815 if self._launch_args is None:
799 raise RuntimeError("Cannot restart the kernel. "
816 raise RuntimeError("Cannot restart the kernel. "
800 "No previous call to 'start_kernel'.")
817 "No previous call to 'start_kernel'.")
801 else:
818 else:
802 # Stop currently running kernel.
819 # Stop currently running kernel.
803 if self.has_kernel:
820 if self.has_kernel:
804 if now:
821 if now:
805 self.kill_kernel()
822 self.kill_kernel()
806 else:
823 else:
807 self.shutdown_kernel(restart=True)
824 self.shutdown_kernel(restart=True)
808
825
809 # Start new kernel.
826 # Start new kernel.
810 self._launch_args.update(kw)
827 self._launch_args.update(kw)
811 self.start_kernel(**self._launch_args)
828 self.start_kernel(**self._launch_args)
812
829
813 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
830 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
814 # unless there is some delay here.
831 # unless there is some delay here.
815 if sys.platform == 'win32':
832 if sys.platform == 'win32':
816 time.sleep(0.2)
833 time.sleep(0.2)
817
834
818 @property
835 @property
819 def has_kernel(self):
836 def has_kernel(self):
820 """Returns whether a kernel process has been specified for the kernel
837 """Returns whether a kernel process has been specified for the kernel
821 manager.
838 manager.
822 """
839 """
823 return self.kernel is not None
840 return self.kernel is not None
824
841
825 def kill_kernel(self):
842 def kill_kernel(self):
826 """ Kill the running kernel. """
843 """ Kill the running kernel. """
827 if self.has_kernel:
844 if self.has_kernel:
828 # Pause the heart beat channel if it exists.
845 # Pause the heart beat channel if it exists.
829 if self._hb_channel is not None:
846 if self._hb_channel is not None:
830 self._hb_channel.pause()
847 self._hb_channel.pause()
831
848
832 # Attempt to kill the kernel.
849 # Attempt to kill the kernel.
833 try:
850 try:
834 self.kernel.kill()
851 self.kernel.kill()
835 except OSError, e:
852 except OSError, e:
836 # In Windows, we will get an Access Denied error if the process
853 # In Windows, we will get an Access Denied error if the process
837 # has already terminated. Ignore it.
854 # has already terminated. Ignore it.
838 if not (sys.platform == 'win32' and e.winerror == 5):
855 if not (sys.platform == 'win32' and e.winerror == 5):
839 raise
856 raise
840 self.kernel = None
857 self.kernel = None
841 else:
858 else:
842 raise RuntimeError("Cannot kill kernel. No kernel is running!")
859 raise RuntimeError("Cannot kill kernel. No kernel is running!")
843
860
844 def interrupt_kernel(self):
861 def interrupt_kernel(self):
845 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
862 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
846 well supported on all platforms.
863 well supported on all platforms.
847 """
864 """
848 if self.has_kernel:
865 if self.has_kernel:
849 if sys.platform == 'win32':
866 if sys.platform == 'win32':
850 from parentpoller import ParentPollerWindows as Poller
867 from parentpoller import ParentPollerWindows as Poller
851 Poller.send_interrupt(self.kernel.win32_interrupt_event)
868 Poller.send_interrupt(self.kernel.win32_interrupt_event)
852 else:
869 else:
853 self.kernel.send_signal(signal.SIGINT)
870 self.kernel.send_signal(signal.SIGINT)
854 else:
871 else:
855 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
872 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
856
873
857 def signal_kernel(self, signum):
874 def signal_kernel(self, signum):
858 """ Sends a signal to the kernel. Note that since only SIGTERM is
875 """ Sends a signal to the kernel. Note that since only SIGTERM is
859 supported on Windows, this function is only useful on Unix systems.
876 supported on Windows, this function is only useful on Unix systems.
860 """
877 """
861 if self.has_kernel:
878 if self.has_kernel:
862 self.kernel.send_signal(signum)
879 self.kernel.send_signal(signum)
863 else:
880 else:
864 raise RuntimeError("Cannot signal kernel. No kernel is running!")
881 raise RuntimeError("Cannot signal kernel. No kernel is running!")
865
882
866 @property
883 @property
867 def is_alive(self):
884 def is_alive(self):
868 """Is the kernel process still running?"""
885 """Is the kernel process still running?"""
869 # FIXME: not using a heartbeat means this method is broken for any
886 # FIXME: not using a heartbeat means this method is broken for any
870 # remote kernel, it's only capable of handling local kernels.
887 # remote kernel, it's only capable of handling local kernels.
871 if self.has_kernel:
888 if self.has_kernel:
872 if self.kernel.poll() is None:
889 if self.kernel.poll() is None:
873 return True
890 return True
874 else:
891 else:
875 return False
892 return False
876 else:
893 else:
877 # We didn't start the kernel with this KernelManager so we don't
894 # We didn't start the kernel with this KernelManager so we don't
878 # know if it is running. We should use a heartbeat for this case.
895 # know if it is running. We should use a heartbeat for this case.
879 return True
896 return True
880
897
881 #--------------------------------------------------------------------------
898 #--------------------------------------------------------------------------
882 # Channels used for communication with the kernel:
899 # Channels used for communication with the kernel:
883 #--------------------------------------------------------------------------
900 #--------------------------------------------------------------------------
884
901
885 @property
902 @property
886 def xreq_channel(self):
903 def xreq_channel(self):
887 """Get the REQ socket channel object to make requests of the kernel."""
904 """Get the REQ socket channel object to make requests of the kernel."""
888 if self._xreq_channel is None:
905 if self._xreq_channel is None:
889 self._xreq_channel = self.xreq_channel_class(self.context,
906 self._xreq_channel = self.xreq_channel_class(self.context,
890 self.session,
907 self.session,
891 self.xreq_address)
908 self.xreq_address)
892 return self._xreq_channel
909 return self._xreq_channel
893
910
894 @property
911 @property
895 def sub_channel(self):
912 def sub_channel(self):
896 """Get the SUB socket channel object."""
913 """Get the SUB socket channel object."""
897 if self._sub_channel is None:
914 if self._sub_channel is None:
898 self._sub_channel = self.sub_channel_class(self.context,
915 self._sub_channel = self.sub_channel_class(self.context,
899 self.session,
916 self.session,
900 self.sub_address)
917 self.sub_address)
901 return self._sub_channel
918 return self._sub_channel
902
919
903 @property
920 @property
904 def rep_channel(self):
921 def rep_channel(self):
905 """Get the REP socket channel object to handle stdin (raw_input)."""
922 """Get the REP socket channel object to handle stdin (raw_input)."""
906 if self._rep_channel is None:
923 if self._rep_channel is None:
907 self._rep_channel = self.rep_channel_class(self.context,
924 self._rep_channel = self.rep_channel_class(self.context,
908 self.session,
925 self.session,
909 self.rep_address)
926 self.rep_address)
910 return self._rep_channel
927 return self._rep_channel
911
928
912 @property
929 @property
913 def hb_channel(self):
930 def hb_channel(self):
914 """Get the heartbeat socket channel object to check that the
931 """Get the heartbeat socket channel object to check that the
915 kernel is alive."""
932 kernel is alive."""
916 if self._hb_channel is None:
933 if self._hb_channel is None:
917 self._hb_channel = self.hb_channel_class(self.context,
934 self._hb_channel = self.hb_channel_class(self.context,
918 self.session,
935 self.session,
919 self.hb_address)
936 self.hb_address)
920 return self._hb_channel
937 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now