##// END OF EJS Templates
protect kernelapp/qtconsole from invalid connection files...
MinRK -
Show More
@@ -1,591 +1,595 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 import json
20 21 import os
21 22 import signal
22 23 import sys
23 24
24 25 # System library imports
25 26 from IPython.external.qt import QtGui
26 27 from pygments.styles import get_all_styles
27 from zmq.utils import jsonapi as json
28 28
29 29 # Local imports
30 30 from IPython.config.application import boolean_flag
31 31 from IPython.core.application import BaseIPythonApplication
32 32 from IPython.core.profiledir import ProfileDir
33 33 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
34 34 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
35 35 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
36 36 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
37 37 from IPython.frontend.qt.console import styles
38 38 from IPython.frontend.qt.kernelmanager import QtKernelManager
39 39 from IPython.utils.path import filefind
40 40 from IPython.utils.py3compat import str_to_bytes
41 41 from IPython.utils.traitlets import (
42 42 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
43 43 )
44 44 from IPython.zmq.ipkernel import (
45 45 flags as ipkernel_flags,
46 46 aliases as ipkernel_aliases,
47 47 IPKernelApp
48 48 )
49 49 from IPython.zmq.session import Session, default_secure
50 50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 51
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Network Constants
55 55 #-----------------------------------------------------------------------------
56 56
57 57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 58
59 59 #-----------------------------------------------------------------------------
60 60 # Globals
61 61 #-----------------------------------------------------------------------------
62 62
63 63 _examples = """
64 64 ipython qtconsole # start the qtconsole
65 65 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
66 66 """
67 67
68 68 #-----------------------------------------------------------------------------
69 69 # Classes
70 70 #-----------------------------------------------------------------------------
71 71
72 72 class MainWindow(QtGui.QMainWindow):
73 73
74 74 #---------------------------------------------------------------------------
75 75 # 'object' interface
76 76 #---------------------------------------------------------------------------
77 77
78 78 def __init__(self, app, frontend, existing=False, may_close=True,
79 79 confirm_exit=True):
80 80 """ Create a MainWindow for the specified FrontendWidget.
81 81
82 82 The app is passed as an argument to allow for different
83 83 closing behavior depending on whether we are the Kernel's parent.
84 84
85 85 If existing is True, then this Console does not own the Kernel.
86 86
87 87 If may_close is True, then this Console is permitted to close the kernel
88 88 """
89 89 super(MainWindow, self).__init__()
90 90 self._app = app
91 91 self._frontend = frontend
92 92 self._existing = existing
93 93 if existing:
94 94 self._may_close = may_close
95 95 else:
96 96 self._may_close = True
97 97 self._frontend.exit_requested.connect(self.close)
98 98 self._confirm_exit = confirm_exit
99 99 self.setCentralWidget(frontend)
100 100
101 101 #---------------------------------------------------------------------------
102 102 # QWidget interface
103 103 #---------------------------------------------------------------------------
104 104
105 105 def closeEvent(self, event):
106 106 """ Close the window and the kernel (if necessary).
107 107
108 108 This will prompt the user if they are finished with the kernel, and if
109 109 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
110 110 it closes without prompt.
111 111 """
112 112 keepkernel = None #Use the prompt by default
113 113 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
114 114 keepkernel = self._frontend._keep_kernel_on_exit
115 115
116 116 kernel_manager = self._frontend.kernel_manager
117 117
118 118 if keepkernel is None and not self._confirm_exit:
119 119 # don't prompt, just terminate the kernel if we own it
120 120 # or leave it alone if we don't
121 121 keepkernel = not self._existing
122 122
123 123 if keepkernel is None: #show prompt
124 124 if kernel_manager and kernel_manager.channels_running:
125 125 title = self.window().windowTitle()
126 126 cancel = QtGui.QMessageBox.Cancel
127 127 okay = QtGui.QMessageBox.Ok
128 128 if self._may_close:
129 129 msg = "You are closing this Console window."
130 130 info = "Would you like to quit the Kernel and all attached Consoles as well?"
131 131 justthis = QtGui.QPushButton("&No, just this Console", self)
132 132 justthis.setShortcut('N')
133 133 closeall = QtGui.QPushButton("&Yes, quit everything", self)
134 134 closeall.setShortcut('Y')
135 135 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
136 136 title, msg)
137 137 box.setInformativeText(info)
138 138 box.addButton(cancel)
139 139 box.addButton(justthis, QtGui.QMessageBox.NoRole)
140 140 box.addButton(closeall, QtGui.QMessageBox.YesRole)
141 141 box.setDefaultButton(closeall)
142 142 box.setEscapeButton(cancel)
143 143 reply = box.exec_()
144 144 if reply == 1: # close All
145 145 kernel_manager.shutdown_kernel()
146 146 #kernel_manager.stop_channels()
147 147 event.accept()
148 148 elif reply == 0: # close Console
149 149 if not self._existing:
150 150 # Have kernel: don't quit, just close the window
151 151 self._app.setQuitOnLastWindowClosed(False)
152 152 self.deleteLater()
153 153 event.accept()
154 154 else:
155 155 event.ignore()
156 156 else:
157 157 reply = QtGui.QMessageBox.question(self, title,
158 158 "Are you sure you want to close this Console?"+
159 159 "\nThe Kernel and other Consoles will remain active.",
160 160 okay|cancel,
161 161 defaultButton=okay
162 162 )
163 163 if reply == okay:
164 164 event.accept()
165 165 else:
166 166 event.ignore()
167 167 elif keepkernel: #close console but leave kernel running (no prompt)
168 168 if kernel_manager and kernel_manager.channels_running:
169 169 if not self._existing:
170 170 # I have the kernel: don't quit, just close the window
171 171 self._app.setQuitOnLastWindowClosed(False)
172 172 event.accept()
173 173 else: #close console and kernel (no prompt)
174 174 if kernel_manager and kernel_manager.channels_running:
175 175 kernel_manager.shutdown_kernel()
176 176 event.accept()
177 177
178 178 #-----------------------------------------------------------------------------
179 179 # Aliases and Flags
180 180 #-----------------------------------------------------------------------------
181 181
182 182 flags = dict(ipkernel_flags)
183 183 qt_flags = {
184 184 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
185 185 "Connect to an existing kernel. If no argument specified, guess most recent"),
186 186 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
187 187 "Use a pure Python kernel instead of an IPython kernel."),
188 188 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
189 189 "Disable rich text support."),
190 190 }
191 191 qt_flags.update(boolean_flag(
192 192 'gui-completion', 'ConsoleWidget.gui_completion',
193 193 "use a GUI widget for tab completion",
194 194 "use plaintext output for completion"
195 195 ))
196 196 qt_flags.update(boolean_flag(
197 197 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
198 198 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
199 199 to force a direct exit without any confirmation.
200 200 """,
201 201 """Don't prompt the user when exiting. This will terminate the kernel
202 202 if it is owned by the frontend, and leave it alive if it is external.
203 203 """
204 204 ))
205 205 flags.update(qt_flags)
206 206
207 207 aliases = dict(ipkernel_aliases)
208 208
209 209 qt_aliases = dict(
210 210 hb = 'IPythonQtConsoleApp.hb_port',
211 211 shell = 'IPythonQtConsoleApp.shell_port',
212 212 iopub = 'IPythonQtConsoleApp.iopub_port',
213 213 stdin = 'IPythonQtConsoleApp.stdin_port',
214 214 ip = 'IPythonQtConsoleApp.ip',
215 215 existing = 'IPythonQtConsoleApp.existing',
216 216 f = 'IPythonQtConsoleApp.connection_file',
217 217
218 218 style = 'IPythonWidget.syntax_style',
219 219 stylesheet = 'IPythonQtConsoleApp.stylesheet',
220 220 colors = 'ZMQInteractiveShell.colors',
221 221
222 222 editor = 'IPythonWidget.editor',
223 223 paging = 'ConsoleWidget.paging',
224 224 ssh = 'IPythonQtConsoleApp.sshserver',
225 225 )
226 226 aliases.update(qt_aliases)
227 227
228 228
229 229 #-----------------------------------------------------------------------------
230 230 # IPythonQtConsole
231 231 #-----------------------------------------------------------------------------
232 232
233 233
234 234 class IPythonQtConsoleApp(BaseIPythonApplication):
235 235 name = 'ipython-qtconsole'
236 236 default_config_file_name='ipython_config.py'
237 237
238 238 description = """
239 239 The IPython QtConsole.
240 240
241 241 This launches a Console-style application using Qt. It is not a full
242 242 console, in that launched terminal subprocesses will not be able to accept
243 243 input.
244 244
245 245 The QtConsole supports various extra features beyond the Terminal IPython
246 246 shell, such as inline plotting with matplotlib, via:
247 247
248 248 ipython qtconsole --pylab=inline
249 249
250 250 as well as saving your session as HTML, and printing the output.
251 251
252 252 """
253 253 examples = _examples
254 254
255 255 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
256 256 flags = Dict(flags)
257 257 aliases = Dict(aliases)
258 258
259 259 kernel_argv = List(Unicode)
260 260
261 261 # create requested profiles by default, if they don't exist:
262 262 auto_create = CBool(True)
263 263 # connection info:
264 264 ip = Unicode(LOCALHOST, config=True,
265 265 help="""Set the kernel\'s IP address [default localhost].
266 266 If the IP address is something other than localhost, then
267 267 Consoles on other machines will be able to connect
268 268 to the Kernel, so be careful!"""
269 269 )
270 270
271 271 sshserver = Unicode('', config=True,
272 272 help="""The SSH server to use to connect to the kernel.""")
273 273 sshkey = Unicode('', config=True,
274 274 help="""Path to the ssh key to use for logging in to the ssh server.""")
275 275
276 276 hb_port = Int(0, config=True,
277 277 help="set the heartbeat port [default: random]")
278 278 shell_port = Int(0, config=True,
279 279 help="set the shell (XREP) port [default: random]")
280 280 iopub_port = Int(0, config=True,
281 281 help="set the iopub (PUB) port [default: random]")
282 282 stdin_port = Int(0, config=True,
283 283 help="set the stdin (XREQ) port [default: random]")
284 284 connection_file = Unicode('', config=True,
285 285 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
286 286
287 287 This file will contain the IP, ports, and authentication key needed to connect
288 288 clients to this kernel. By default, this file will be created in the security-dir
289 289 of the current profile, but can be specified by absolute path.
290 290 """)
291 291 def _connection_file_default(self):
292 292 return 'kernel-%i.json' % os.getpid()
293 293
294 294 existing = Unicode('', config=True,
295 295 help="""Connect to an already running kernel""")
296 296
297 297 stylesheet = Unicode('', config=True,
298 298 help="path to a custom CSS stylesheet")
299 299
300 300 pure = CBool(False, config=True,
301 301 help="Use a pure Python kernel instead of an IPython kernel.")
302 302 plain = CBool(False, config=True,
303 303 help="Use a plaintext widget instead of rich text (plain can't print/save).")
304 304
305 305 def _pure_changed(self, name, old, new):
306 306 kind = 'plain' if self.plain else 'rich'
307 307 self.config.ConsoleWidget.kind = kind
308 308 if self.pure:
309 309 self.widget_factory = FrontendWidget
310 310 elif self.plain:
311 311 self.widget_factory = IPythonWidget
312 312 else:
313 313 self.widget_factory = RichIPythonWidget
314 314
315 315 _plain_changed = _pure_changed
316 316
317 317 confirm_exit = CBool(True, config=True,
318 318 help="""
319 319 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
320 320 to force a direct exit without any confirmation.""",
321 321 )
322 322
323 323 # the factory for creating a widget
324 324 widget_factory = Any(RichIPythonWidget)
325 325
326 326 def parse_command_line(self, argv=None):
327 327 super(IPythonQtConsoleApp, self).parse_command_line(argv)
328 328 if argv is None:
329 329 argv = sys.argv[1:]
330 330
331 331 self.kernel_argv = list(argv) # copy
332 332 # kernel should inherit default config file from frontend
333 333 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
334 334 # Scrub frontend-specific flags
335 335 for a in argv:
336 336 if a.startswith('-') and a.lstrip('-') in qt_flags:
337 337 self.kernel_argv.remove(a)
338 338 swallow_next = False
339 339 for a in argv:
340 340 if swallow_next:
341 341 self.kernel_argv.remove(a)
342 342 swallow_next = False
343 343 continue
344 344 if a.startswith('-'):
345 345 split = a.lstrip('-').split('=')[0]
346 346 alias = split[0]
347 347 if alias in qt_aliases:
348 348 self.kernel_argv.remove(a)
349 349 if len(split) == 1:
350 350 # alias passed with arg via space
351 351 swallow_next = True
352 352
353 353 def init_connection_file(self):
354 354 """find the connection file, and load the info if found.
355 355
356 356 The current working directory and the current profile's security
357 357 directory will be searched for the file if it is not given by
358 358 absolute path.
359 359
360 360 When attempting to connect to an existing kernel and the `--existing`
361 361 argument does not match an existing file, it will be interpreted as a
362 362 fileglob, and the matching file in the current profile's security dir
363 363 with the latest access time will be used.
364 364 """
365 365 if self.existing:
366 366 try:
367 367 cf = find_connection_file(self.existing)
368 368 except Exception:
369 369 self.log.critical("Could not find existing kernel connection file %s", self.existing)
370 370 self.exit(1)
371 371 self.log.info("Connecting to existing kernel: %s" % cf)
372 372 self.connection_file = cf
373 373 # should load_connection_file only be used for existing?
374 374 # as it is now, this allows reusing ports if an existing
375 375 # file is requested
376 self.load_connection_file()
376 try:
377 self.load_connection_file()
378 except Exception:
379 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
380 self.exit(1)
377 381
378 382 def load_connection_file(self):
379 383 """load ip/port/hmac config from JSON connection file"""
380 384 # this is identical to KernelApp.load_connection_file
381 385 # perhaps it can be centralized somewhere?
382 386 try:
383 387 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
384 388 except IOError:
385 389 self.log.debug("Connection File not found: %s", self.connection_file)
386 390 return
387 391 self.log.debug(u"Loading connection file %s", fname)
388 392 with open(fname) as f:
389 393 s = f.read()
390 394 cfg = json.loads(s)
391 395 if self.ip == LOCALHOST and 'ip' in cfg:
392 396 # not overridden by config or cl_args
393 397 self.ip = cfg['ip']
394 398 for channel in ('hb', 'shell', 'iopub', 'stdin'):
395 399 name = channel + '_port'
396 400 if getattr(self, name) == 0 and name in cfg:
397 401 # not overridden by config or cl_args
398 402 setattr(self, name, cfg[name])
399 403 if 'key' in cfg:
400 404 self.config.Session.key = str_to_bytes(cfg['key'])
401 405
402 406 def init_ssh(self):
403 407 """set up ssh tunnels, if needed."""
404 408 if not self.sshserver and not self.sshkey:
405 409 return
406 410
407 411 if self.sshkey and not self.sshserver:
408 412 # specifying just the key implies that we are connecting directly
409 413 self.sshserver = self.ip
410 414 self.ip = LOCALHOST
411 415
412 416 # build connection dict for tunnels:
413 417 info = dict(ip=self.ip,
414 418 shell_port=self.shell_port,
415 419 iopub_port=self.iopub_port,
416 420 stdin_port=self.stdin_port,
417 421 hb_port=self.hb_port
418 422 )
419 423
420 424 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
421 425
422 426 # tunnels return a new set of ports, which will be on localhost:
423 427 self.ip = LOCALHOST
424 428 try:
425 429 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
426 430 except:
427 431 # even catch KeyboardInterrupt
428 432 self.log.error("Could not setup tunnels", exc_info=True)
429 433 self.exit(1)
430 434
431 435 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
432 436
433 437 cf = self.connection_file
434 438 base,ext = os.path.splitext(cf)
435 439 base = os.path.basename(base)
436 440 self.connection_file = os.path.basename(base)+'-ssh'+ext
437 441 self.log.critical("To connect another client via this tunnel, use:")
438 442 self.log.critical("--existing %s" % self.connection_file)
439 443
440 444 def init_kernel_manager(self):
441 445 # Don't let Qt or ZMQ swallow KeyboardInterupts.
442 446 signal.signal(signal.SIGINT, signal.SIG_DFL)
443 447 sec = self.profile_dir.security_dir
444 448 try:
445 449 cf = filefind(self.connection_file, ['.', sec])
446 450 except IOError:
447 451 # file might not exist
448 452 if self.connection_file == os.path.basename(self.connection_file):
449 453 # just shortname, put it in security dir
450 454 cf = os.path.join(sec, self.connection_file)
451 455 else:
452 456 cf = self.connection_file
453 457
454 458 # Create a KernelManager and start a kernel.
455 459 self.kernel_manager = QtKernelManager(
456 460 ip=self.ip,
457 461 shell_port=self.shell_port,
458 462 iopub_port=self.iopub_port,
459 463 stdin_port=self.stdin_port,
460 464 hb_port=self.hb_port,
461 465 connection_file=cf,
462 466 config=self.config,
463 467 )
464 468 # start the kernel
465 469 if not self.existing:
466 470 kwargs = dict(ipython=not self.pure)
467 471 kwargs['extra_arguments'] = self.kernel_argv
468 472 self.kernel_manager.start_kernel(**kwargs)
469 473 elif self.sshserver:
470 474 # ssh, write new connection file
471 475 self.kernel_manager.write_connection_file()
472 476 self.kernel_manager.start_channels()
473 477
474 478
475 479 def init_qt_elements(self):
476 480 # Create the widget.
477 481 self.app = QtGui.QApplication([])
478 482 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
479 483 self.widget = self.widget_factory(config=self.config,
480 484 local_kernel=local_kernel)
481 485 self.widget.kernel_manager = self.kernel_manager
482 486 self.window = MainWindow(self.app, self.widget, self.existing,
483 487 may_close=local_kernel,
484 488 confirm_exit=self.confirm_exit)
485 489 self.window.setWindowTitle('Python' if self.pure else 'IPython')
486 490
487 491 def init_colors(self):
488 492 """Configure the coloring of the widget"""
489 493 # Note: This will be dramatically simplified when colors
490 494 # are removed from the backend.
491 495
492 496 if self.pure:
493 497 # only IPythonWidget supports styling
494 498 return
495 499
496 500 # parse the colors arg down to current known labels
497 501 try:
498 502 colors = self.config.ZMQInteractiveShell.colors
499 503 except AttributeError:
500 504 colors = None
501 505 try:
502 506 style = self.config.IPythonWidget.colors
503 507 except AttributeError:
504 508 style = None
505 509
506 510 # find the value for colors:
507 511 if colors:
508 512 colors=colors.lower()
509 513 if colors in ('lightbg', 'light'):
510 514 colors='lightbg'
511 515 elif colors in ('dark', 'linux'):
512 516 colors='linux'
513 517 else:
514 518 colors='nocolor'
515 519 elif style:
516 520 if style=='bw':
517 521 colors='nocolor'
518 522 elif styles.dark_style(style):
519 523 colors='linux'
520 524 else:
521 525 colors='lightbg'
522 526 else:
523 527 colors=None
524 528
525 529 # Configure the style.
526 530 widget = self.widget
527 531 if style:
528 532 widget.style_sheet = styles.sheet_from_template(style, colors)
529 533 widget.syntax_style = style
530 534 widget._syntax_style_changed()
531 535 widget._style_sheet_changed()
532 536 elif colors:
533 537 # use a default style
534 538 widget.set_default_style(colors=colors)
535 539 else:
536 540 # this is redundant for now, but allows the widget's
537 541 # defaults to change
538 542 widget.set_default_style()
539 543
540 544 if self.stylesheet:
541 545 # we got an expicit stylesheet
542 546 if os.path.isfile(self.stylesheet):
543 547 with open(self.stylesheet) as f:
544 548 sheet = f.read()
545 549 widget.style_sheet = sheet
546 550 widget._style_sheet_changed()
547 551 else:
548 552 raise IOError("Stylesheet %r not found."%self.stylesheet)
549 553
550 554 def initialize(self, argv=None):
551 555 super(IPythonQtConsoleApp, self).initialize(argv)
552 556 self.init_connection_file()
553 557 default_secure(self.config)
554 558 self.init_ssh()
555 559 self.init_kernel_manager()
556 560 self.init_qt_elements()
557 561 self.init_colors()
558 562 self.init_window_shortcut()
559 563
560 564 def init_window_shortcut(self):
561 565 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
562 566 fullScreenAction.setShortcut('Ctrl+Meta+Space')
563 567 fullScreenAction.triggered.connect(self.toggleFullScreen)
564 568 self.window.addAction(fullScreenAction)
565 569
566 570 def toggleFullScreen(self):
567 571 if not self.window.isFullScreen():
568 572 self.window.showFullScreen()
569 573 else:
570 574 self.window.showNormal()
571 575
572 576 def start(self):
573 577
574 578 # draw the window
575 579 self.window.show()
576 580
577 581 # Start the application main loop.
578 582 self.app.exec_()
579 583
580 584 #-----------------------------------------------------------------------------
581 585 # Main entry point
582 586 #-----------------------------------------------------------------------------
583 587
584 588 def main():
585 589 app = IPythonQtConsoleApp()
586 590 app.initialize()
587 591 app.start()
588 592
589 593
590 594 if __name__ == '__main__':
591 595 main()
@@ -1,295 +1,298 b''
1 1 """An Application for launching a kernel
2 2
3 3 Authors
4 4 -------
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2011 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.txt, 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 import json
19 20 import os
20 21 import sys
21 22
22 23 # System library imports.
23 24 import zmq
24 from zmq.utils import jsonapi as json
25 25
26 26 # IPython imports.
27 27 from IPython.core.ultratb import FormattedTB
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags, base_aliases
30 30 )
31 31 from IPython.utils import io
32 32 from IPython.utils.localinterfaces import LOCALHOST
33 33 from IPython.utils.path import filefind
34 34 from IPython.utils.py3compat import str_to_bytes
35 35 from IPython.utils.traitlets import (Any, Instance, Dict, Unicode, Int, Bool,
36 36 DottedObjectName)
37 37 from IPython.utils.importstring import import_item
38 38 # local imports
39 39 from IPython.zmq.entry_point import write_connection_file
40 40 from IPython.zmq.heartbeat import Heartbeat
41 41 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
42 42 from IPython.zmq.session import (
43 43 Session, session_flags, session_aliases, default_secure,
44 44 )
45 45
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Flags and Aliases
49 49 #-----------------------------------------------------------------------------
50 50
51 51 kernel_aliases = dict(base_aliases)
52 52 kernel_aliases.update({
53 53 'ip' : 'KernelApp.ip',
54 54 'hb' : 'KernelApp.hb_port',
55 55 'shell' : 'KernelApp.shell_port',
56 56 'iopub' : 'KernelApp.iopub_port',
57 57 'stdin' : 'KernelApp.stdin_port',
58 58 'f' : 'KernelApp.connection_file',
59 59 'parent': 'KernelApp.parent',
60 60 })
61 61 if sys.platform.startswith('win'):
62 62 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
63 63
64 64 kernel_flags = dict(base_flags)
65 65 kernel_flags.update({
66 66 'no-stdout' : (
67 67 {'KernelApp' : {'no_stdout' : True}},
68 68 "redirect stdout to the null device"),
69 69 'no-stderr' : (
70 70 {'KernelApp' : {'no_stderr' : True}},
71 71 "redirect stderr to the null device"),
72 72 })
73 73
74 74 # inherit flags&aliases for Sessions
75 75 kernel_aliases.update(session_aliases)
76 76 kernel_flags.update(session_flags)
77 77
78 78
79 79
80 80 #-----------------------------------------------------------------------------
81 81 # Application class for starting a Kernel
82 82 #-----------------------------------------------------------------------------
83 83
84 84 class KernelApp(BaseIPythonApplication):
85 85 name='pykernel'
86 86 aliases = Dict(kernel_aliases)
87 87 flags = Dict(kernel_flags)
88 88 classes = [Session]
89 89 # the kernel class, as an importstring
90 90 kernel_class = DottedObjectName('IPython.zmq.pykernel.Kernel')
91 91 kernel = Any()
92 92 poller = Any() # don't restrict this even though current pollers are all Threads
93 93 heartbeat = Instance(Heartbeat)
94 94 session = Instance('IPython.zmq.session.Session')
95 95 ports = Dict()
96 96
97 97 # inherit config file name from parent:
98 98 parent_appname = Unicode(config=True)
99 99 def _parent_appname_changed(self, name, old, new):
100 100 if self.config_file_specified:
101 101 # it was manually specified, ignore
102 102 return
103 103 self.config_file_name = new.replace('-','_') + u'_config.py'
104 104 # don't let this count as specifying the config file
105 105 self.config_file_specified = False
106 106
107 107 # connection info:
108 108 ip = Unicode(LOCALHOST, config=True,
109 109 help="Set the IP or interface on which the kernel will listen.")
110 110 hb_port = Int(0, config=True, help="set the heartbeat port [default: random]")
111 111 shell_port = Int(0, config=True, help="set the shell (XREP) port [default: random]")
112 112 iopub_port = Int(0, config=True, help="set the iopub (PUB) port [default: random]")
113 113 stdin_port = Int(0, config=True, help="set the stdin (XREQ) port [default: random]")
114 114 connection_file = Unicode('', config=True,
115 115 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
116 116
117 117 This file will contain the IP, ports, and authentication key needed to connect
118 118 clients to this kernel. By default, this file will be created in the security-dir
119 119 of the current profile, but can be specified by absolute path.
120 120 """)
121 121
122 122 # streams, etc.
123 123 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
124 124 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
125 125 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
126 126 config=True, help="The importstring for the OutStream factory")
127 127 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
128 128 config=True, help="The importstring for the DisplayHook factory")
129 129
130 130 # polling
131 131 parent = Int(0, config=True,
132 132 help="""kill this process if its parent dies. On Windows, the argument
133 133 specifies the HANDLE of the parent process, otherwise it is simply boolean.
134 134 """)
135 135 interrupt = Int(0, config=True,
136 136 help="""ONLY USED ON WINDOWS
137 137 Interrupt this process when the parent is signalled.
138 138 """)
139 139
140 140 def init_crash_handler(self):
141 141 # Install minimal exception handling
142 142 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
143 143 ostream=sys.__stdout__)
144 144
145 145 def init_poller(self):
146 146 if sys.platform == 'win32':
147 147 if self.interrupt or self.parent:
148 148 self.poller = ParentPollerWindows(self.interrupt, self.parent)
149 149 elif self.parent:
150 150 self.poller = ParentPollerUnix()
151 151
152 152 def _bind_socket(self, s, port):
153 153 iface = 'tcp://%s' % self.ip
154 154 if port <= 0:
155 155 port = s.bind_to_random_port(iface)
156 156 else:
157 157 s.bind(iface + ':%i'%port)
158 158 return port
159 159
160 160 def load_connection_file(self):
161 161 """load ip/port/hmac config from JSON connection file"""
162 162 try:
163 163 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
164 164 except IOError:
165 165 self.log.debug("Connection file not found: %s", self.connection_file)
166 166 return
167 167 self.log.debug(u"Loading connection file %s", fname)
168 168 with open(fname) as f:
169 169 s = f.read()
170 170 cfg = json.loads(s)
171 171 if self.ip == LOCALHOST and 'ip' in cfg:
172 172 # not overridden by config or cl_args
173 173 self.ip = cfg['ip']
174 174 for channel in ('hb', 'shell', 'iopub', 'stdin'):
175 175 name = channel + '_port'
176 176 if getattr(self, name) == 0 and name in cfg:
177 177 # not overridden by config or cl_args
178 178 setattr(self, name, cfg[name])
179 179 if 'key' in cfg:
180 180 self.config.Session.key = str_to_bytes(cfg['key'])
181 181
182 182 def write_connection_file(self):
183 183 """write connection info to JSON file"""
184 184 if os.path.basename(self.connection_file) == self.connection_file:
185 185 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
186 186 else:
187 187 cf = self.connection_file
188 188 write_connection_file(cf, ip=self.ip, key=self.session.key,
189 189 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
190 190 iopub_port=self.iopub_port)
191 191
192 192 def init_connection_file(self):
193 193 if not self.connection_file:
194 194 self.connection_file = "kernel-%s.json"%os.getpid()
195
196 self.load_connection_file()
195 try:
196 self.load_connection_file()
197 except Exception:
198 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
199 self.exit(1)
197 200
198 201 def init_sockets(self):
199 202 # Create a context, a session, and the kernel sockets.
200 203 self.log.info("Starting the kernel at pid: %i", os.getpid())
201 204 context = zmq.Context.instance()
202 205 # Uncomment this to try closing the context.
203 206 # atexit.register(context.term)
204 207
205 208 self.shell_socket = context.socket(zmq.ROUTER)
206 209 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
207 210 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
208 211
209 212 self.iopub_socket = context.socket(zmq.PUB)
210 213 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
211 214 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
212 215
213 216 self.stdin_socket = context.socket(zmq.ROUTER)
214 217 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
215 218 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
216 219
217 220 self.heartbeat = Heartbeat(context, (self.ip, self.hb_port))
218 221 self.hb_port = self.heartbeat.port
219 222 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
220 223
221 224 # Helper to make it easier to connect to an existing kernel.
222 225 # set log-level to critical, to make sure it is output
223 226 self.log.critical("To connect another client to this kernel, use:")
224 227
225 228 basename = os.path.basename(self.connection_file)
226 229 if basename == self.connection_file or \
227 230 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
228 231 # use shortname
229 232 tail = basename
230 233 if self.profile != 'default':
231 234 tail += " --profile %s" % self.profile
232 235 else:
233 236 tail = self.connection_file
234 237 self.log.critical("--existing %s", tail)
235 238
236 239
237 240 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
238 241 stdin=self.stdin_port, hb=self.hb_port)
239 242
240 243 def init_session(self):
241 244 """create our session object"""
242 245 default_secure(self.config)
243 246 self.session = Session(config=self.config, username=u'kernel')
244 247
245 248 def init_blackhole(self):
246 249 """redirects stdout/stderr to devnull if necessary"""
247 250 if self.no_stdout or self.no_stderr:
248 251 blackhole = file(os.devnull, 'w')
249 252 if self.no_stdout:
250 253 sys.stdout = sys.__stdout__ = blackhole
251 254 if self.no_stderr:
252 255 sys.stderr = sys.__stderr__ = blackhole
253 256
254 257 def init_io(self):
255 258 """Redirect input streams and set a display hook."""
256 259 if self.outstream_class:
257 260 outstream_factory = import_item(str(self.outstream_class))
258 261 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
259 262 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
260 263 if self.displayhook_class:
261 264 displayhook_factory = import_item(str(self.displayhook_class))
262 265 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
263 266
264 267 def init_kernel(self):
265 268 """Create the Kernel object itself"""
266 269 kernel_factory = import_item(str(self.kernel_class))
267 270 self.kernel = kernel_factory(config=self.config, session=self.session,
268 271 shell_socket=self.shell_socket,
269 272 iopub_socket=self.iopub_socket,
270 273 stdin_socket=self.stdin_socket,
271 274 log=self.log
272 275 )
273 276 self.kernel.record_ports(self.ports)
274 277
275 278 def initialize(self, argv=None):
276 279 super(KernelApp, self).initialize(argv)
277 280 self.init_blackhole()
278 281 self.init_connection_file()
279 282 self.init_session()
280 283 self.init_poller()
281 284 self.init_sockets()
282 285 # writing connection file must be *after* init_sockets
283 286 self.write_connection_file()
284 287 self.init_io()
285 288 self.init_kernel()
286 289
287 290 def start(self):
288 291 self.heartbeat.start()
289 292 if self.poller is not None:
290 293 self.poller.start()
291 294 try:
292 295 self.kernel.start()
293 296 except KeyboardInterrupt:
294 297 pass
295 298
General Comments 0
You need to be logged in to leave comments. Login now