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