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