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