##// END OF EJS Templates
Remove IPython dependency in external.ssh...
MinRK -
Show More
@@ -1,315 +1,336
1 1 """Basic ssh tunneling utilities."""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2008-2010 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 from __future__ import print_function
17 17
18 18 import os,sys, atexit
19 import socket
19 20 from multiprocessing import Process
20 21 from getpass import getpass, getuser
21 22 import warnings
22 23
23 24 try:
24 25 with warnings.catch_warnings():
25 26 warnings.simplefilter('ignore', DeprecationWarning)
26 27 import paramiko
27 28 except ImportError:
28 29 paramiko = None
29 30 else:
30 31 from forward import forward_tunnel
31 32
32 33 try:
33 34 from IPython.external import pexpect
34 35 except ImportError:
35 36 pexpect = None
36 37
37 from IPython.parallel.util import select_random_ports
38
39 38 #-----------------------------------------------------------------------------
40 39 # Code
41 40 #-----------------------------------------------------------------------------
42 41
42 # select_random_ports copied from IPython.parallel.util
43 _random_ports = set()
44
45 def select_random_ports(n):
46 """Selects and return n random ports that are available."""
47 ports = []
48 for i in xrange(n):
49 sock = socket.socket()
50 sock.bind(('', 0))
51 while sock.getsockname()[1] in _random_ports:
52 sock.close()
53 sock = socket.socket()
54 sock.bind(('', 0))
55 ports.append(sock)
56 for i, sock in enumerate(ports):
57 port = sock.getsockname()[1]
58 sock.close()
59 ports[i] = port
60 _random_ports.add(port)
61 return ports
62
63
43 64 #-----------------------------------------------------------------------------
44 65 # Check for passwordless login
45 66 #-----------------------------------------------------------------------------
46 67
47 68 def try_passwordless_ssh(server, keyfile, paramiko=None):
48 69 """Attempt to make an ssh connection without a password.
49 70 This is mainly used for requiring password input only once
50 71 when many tunnels may be connected to the same server.
51 72
52 73 If paramiko is None, the default for the platform is chosen.
53 74 """
54 75 if paramiko is None:
55 76 paramiko = sys.platform == 'win32'
56 77 if not paramiko:
57 78 f = _try_passwordless_openssh
58 79 else:
59 80 f = _try_passwordless_paramiko
60 81 return f(server, keyfile)
61 82
62 83 def _try_passwordless_openssh(server, keyfile):
63 84 """Try passwordless login with shell ssh command."""
64 85 if pexpect is None:
65 86 raise ImportError("pexpect unavailable, use paramiko")
66 87 cmd = 'ssh -f '+ server
67 88 if keyfile:
68 89 cmd += ' -i ' + keyfile
69 90 cmd += ' exit'
70 91 p = pexpect.spawn(cmd)
71 92 while True:
72 93 try:
73 94 p.expect('[Ppassword]:', timeout=.1)
74 95 except pexpect.TIMEOUT:
75 96 continue
76 97 except pexpect.EOF:
77 98 return True
78 99 else:
79 100 return False
80 101
81 102 def _try_passwordless_paramiko(server, keyfile):
82 103 """Try passwordless login with paramiko."""
83 104 if paramiko is None:
84 105 msg = "Paramiko unavaliable, "
85 106 if sys.platform == 'win32':
86 107 msg += "Paramiko is required for ssh tunneled connections on Windows."
87 108 else:
88 109 msg += "use OpenSSH."
89 110 raise ImportError(msg)
90 111 username, server, port = _split_server(server)
91 112 client = paramiko.SSHClient()
92 113 client.load_system_host_keys()
93 114 client.set_missing_host_key_policy(paramiko.WarningPolicy())
94 115 try:
95 116 client.connect(server, port, username=username, key_filename=keyfile,
96 117 look_for_keys=True)
97 118 except paramiko.AuthenticationException:
98 119 return False
99 120 else:
100 121 client.close()
101 122 return True
102 123
103 124
104 125 def tunnel_connection(socket, addr, server, keyfile=None, password=None, paramiko=None):
105 126 """Connect a socket to an address via an ssh tunnel.
106 127
107 128 This is a wrapper for socket.connect(addr), when addr is not accessible
108 129 from the local machine. It simply creates an ssh tunnel using the remaining args,
109 130 and calls socket.connect('tcp://localhost:lport') where lport is the randomly
110 131 selected local port of the tunnel.
111 132
112 133 """
113 134 new_url, tunnel = open_tunnel(addr, server, keyfile=keyfile, password=password, paramiko=paramiko)
114 135 socket.connect(new_url)
115 136 return tunnel
116 137
117 138
118 139 def open_tunnel(addr, server, keyfile=None, password=None, paramiko=None):
119 140 """Open a tunneled connection from a 0MQ url.
120 141
121 142 For use inside tunnel_connection.
122 143
123 144 Returns
124 145 -------
125 146
126 147 (url, tunnel): The 0MQ url that has been forwarded, and the tunnel object
127 148 """
128 149
129 150 lport = select_random_ports(1)[0]
130 151 transport, addr = addr.split('://')
131 152 ip,rport = addr.split(':')
132 153 rport = int(rport)
133 154 if paramiko is None:
134 155 paramiko = sys.platform == 'win32'
135 156 if paramiko:
136 157 tunnelf = paramiko_tunnel
137 158 else:
138 159 tunnelf = openssh_tunnel
139 160 tunnel = tunnelf(lport, rport, server, remoteip=ip, keyfile=keyfile, password=password)
140 161 return 'tcp://127.0.0.1:%i'%lport, tunnel
141 162
142 163 def openssh_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
143 164 """Create an ssh tunnel using command-line ssh that connects port lport
144 165 on this machine to localhost:rport on server. The tunnel
145 166 will automatically close when not in use, remaining open
146 167 for a minimum of timeout seconds for an initial connection.
147 168
148 169 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
149 170 as seen from `server`.
150 171
151 172 keyfile and password may be specified, but ssh config is checked for defaults.
152 173
153 174 Parameters
154 175 ----------
155 176
156 177 lport : int
157 178 local port for connecting to the tunnel from this machine.
158 179 rport : int
159 180 port on the remote machine to connect to.
160 181 server : str
161 182 The ssh server to connect to. The full ssh server string will be parsed.
162 183 user@server:port
163 184 remoteip : str [Default: 127.0.0.1]
164 185 The remote ip, specifying the destination of the tunnel.
165 186 Default is localhost, which means that the tunnel would redirect
166 187 localhost:lport on this machine to localhost:rport on the *server*.
167 188
168 189 keyfile : str; path to public key file
169 190 This specifies a key to be used in ssh login, default None.
170 191 Regular default ssh keys will be used without specifying this argument.
171 192 password : str;
172 193 Your ssh password to the ssh server. Note that if this is left None,
173 194 you will be prompted for it if passwordless key based login is unavailable.
174 195
175 196 """
176 197 if pexpect is None:
177 198 raise ImportError("pexpect unavailable, use paramiko_tunnel")
178 199 ssh="ssh "
179 200 if keyfile:
180 201 ssh += "-i " + keyfile
181 202 cmd = ssh + " -f -L 127.0.0.1:%i:%s:%i %s sleep %i"%(lport, remoteip, rport, server, timeout)
182 203 tunnel = pexpect.spawn(cmd)
183 204 failed = False
184 205 while True:
185 206 try:
186 207 tunnel.expect('[Pp]assword:', timeout=.1)
187 208 except pexpect.TIMEOUT:
188 209 continue
189 210 except pexpect.EOF:
190 211 if tunnel.exitstatus:
191 212 print (tunnel.exitstatus)
192 213 print (tunnel.before)
193 214 print (tunnel.after)
194 215 raise RuntimeError("tunnel '%s' failed to start"%(cmd))
195 216 else:
196 217 return tunnel.pid
197 218 else:
198 219 if failed:
199 220 print("Password rejected, try again")
200 221 password=None
201 222 if password is None:
202 223 password = getpass("%s's password: "%(server))
203 224 tunnel.sendline(password)
204 225 failed = True
205 226
206 227 def _split_server(server):
207 228 if '@' in server:
208 229 username,server = server.split('@', 1)
209 230 else:
210 231 username = getuser()
211 232 if ':' in server:
212 233 server, port = server.split(':')
213 234 port = int(port)
214 235 else:
215 236 port = 22
216 237 return username, server, port
217 238
218 239 def paramiko_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
219 240 """launch a tunner with paramiko in a subprocess. This should only be used
220 241 when shell ssh is unavailable (e.g. Windows).
221 242
222 243 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
223 244 as seen from `server`.
224 245
225 246 If you are familiar with ssh tunnels, this creates the tunnel:
226 247
227 248 ssh server -L localhost:lport:remoteip:rport
228 249
229 250 keyfile and password may be specified, but ssh config is checked for defaults.
230 251
231 252
232 253 Parameters
233 254 ----------
234 255
235 256 lport : int
236 257 local port for connecting to the tunnel from this machine.
237 258 rport : int
238 259 port on the remote machine to connect to.
239 260 server : str
240 261 The ssh server to connect to. The full ssh server string will be parsed.
241 262 user@server:port
242 263 remoteip : str [Default: 127.0.0.1]
243 264 The remote ip, specifying the destination of the tunnel.
244 265 Default is localhost, which means that the tunnel would redirect
245 266 localhost:lport on this machine to localhost:rport on the *server*.
246 267
247 268 keyfile : str; path to public key file
248 269 This specifies a key to be used in ssh login, default None.
249 270 Regular default ssh keys will be used without specifying this argument.
250 271 password : str;
251 272 Your ssh password to the ssh server. Note that if this is left None,
252 273 you will be prompted for it if passwordless key based login is unavailable.
253 274
254 275 """
255 276 if paramiko is None:
256 277 raise ImportError("Paramiko not available")
257 278
258 279 if password is None:
259 280 if not _check_passwordless_paramiko(server, keyfile):
260 281 password = getpass("%s's password: "%(server))
261 282
262 283 p = Process(target=_paramiko_tunnel,
263 284 args=(lport, rport, server, remoteip),
264 285 kwargs=dict(keyfile=keyfile, password=password))
265 286 p.daemon=False
266 287 p.start()
267 288 atexit.register(_shutdown_process, p)
268 289 return p
269 290
270 291 def _shutdown_process(p):
271 292 if p.isalive():
272 293 p.terminate()
273 294
274 295 def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
275 296 """Function for actually starting a paramiko tunnel, to be passed
276 297 to multiprocessing.Process(target=this), and not called directly.
277 298 """
278 299 username, server, port = _split_server(server)
279 300 client = paramiko.SSHClient()
280 301 client.load_system_host_keys()
281 302 client.set_missing_host_key_policy(paramiko.WarningPolicy())
282 303
283 304 try:
284 305 client.connect(server, port, username=username, key_filename=keyfile,
285 306 look_for_keys=True, password=password)
286 307 # except paramiko.AuthenticationException:
287 308 # if password is None:
288 309 # password = getpass("%s@%s's password: "%(username, server))
289 310 # client.connect(server, port, username=username, password=password)
290 311 # else:
291 312 # raise
292 313 except Exception as e:
293 314 print ('*** Failed to connect to %s:%d: %r' % (server, port, e))
294 315 sys.exit(1)
295 316
296 317 # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport))
297 318
298 319 try:
299 320 forward_tunnel(lport, remoteip, rport, client.get_transport())
300 321 except KeyboardInterrupt:
301 322 print ('SIGINT: Port forwarding stopped cleanly')
302 323 sys.exit(0)
303 324 except Exception as e:
304 325 print ("Port forwarding stopped uncleanly: %s"%e)
305 326 sys.exit(255)
306 327
307 328 if sys.platform == 'win32':
308 329 ssh_tunnel = paramiko_tunnel
309 330 else:
310 331 ssh_tunnel = openssh_tunnel
311 332
312 333
313 334 __all__ = ['tunnel_connection', 'ssh_tunnel', 'openssh_tunnel', 'paramiko_tunnel', 'try_passwordless_ssh']
314 335
315 336
@@ -1,483 +1,484
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 20 import os
21 21 import signal
22 22 import sys
23 23 from getpass import getpass
24 24
25 25 # System library imports
26 26 from IPython.external.qt import QtGui
27 27 from pygments.styles import get_all_styles
28 28
29 # from IPython.external.ssh import tunnel
29 # external imports
30 from IPython.external.ssh import tunnel
31
30 32 # Local imports
31 33 from IPython.config.application import boolean_flag
32 34 from IPython.core.application import BaseIPythonApplication
33 35 from IPython.core.profiledir import ProfileDir
34 36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
35 37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
36 38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
37 39 from IPython.frontend.qt.console import styles
38 40 from IPython.frontend.qt.kernelmanager import QtKernelManager
39 41 from IPython.parallel.util import select_random_ports
40 42 from IPython.utils.traitlets import (
41 43 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
42 44 )
43 45 from IPython.zmq.ipkernel import (
44 46 flags as ipkernel_flags,
45 47 aliases as ipkernel_aliases,
46 48 IPKernelApp
47 49 )
48 50 from IPython.zmq.session import Session
49 51 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 52
51 53
52 54 #-----------------------------------------------------------------------------
53 55 # Network Constants
54 56 #-----------------------------------------------------------------------------
55 57
56 58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 59
58 60 #-----------------------------------------------------------------------------
59 61 # Globals
60 62 #-----------------------------------------------------------------------------
61 63
62 64 _examples = """
63 65 ipython qtconsole # start the qtconsole
64 66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
65 67 """
66 68
67 69 #-----------------------------------------------------------------------------
68 70 # Classes
69 71 #-----------------------------------------------------------------------------
70 72
71 73 class MainWindow(QtGui.QMainWindow):
72 74
73 75 #---------------------------------------------------------------------------
74 76 # 'object' interface
75 77 #---------------------------------------------------------------------------
76 78
77 79 def __init__(self, app, frontend, existing=False, may_close=True,
78 80 confirm_exit=True):
79 81 """ Create a MainWindow for the specified FrontendWidget.
80 82
81 83 The app is passed as an argument to allow for different
82 84 closing behavior depending on whether we are the Kernel's parent.
83 85
84 86 If existing is True, then this Console does not own the Kernel.
85 87
86 88 If may_close is True, then this Console is permitted to close the kernel
87 89 """
88 90 super(MainWindow, self).__init__()
89 91 self._app = app
90 92 self._frontend = frontend
91 93 self._existing = existing
92 94 if existing:
93 95 self._may_close = may_close
94 96 else:
95 97 self._may_close = True
96 98 self._frontend.exit_requested.connect(self.close)
97 99 self._confirm_exit = confirm_exit
98 100 self.setCentralWidget(frontend)
99 101
100 102 #---------------------------------------------------------------------------
101 103 # QWidget interface
102 104 #---------------------------------------------------------------------------
103 105
104 106 def closeEvent(self, event):
105 107 """ Close the window and the kernel (if necessary).
106 108
107 109 This will prompt the user if they are finished with the kernel, and if
108 110 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
109 111 it closes without prompt.
110 112 """
111 113 keepkernel = None #Use the prompt by default
112 114 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
113 115 keepkernel = self._frontend._keep_kernel_on_exit
114 116
115 117 kernel_manager = self._frontend.kernel_manager
116 118
117 119 if keepkernel is None and not self._confirm_exit:
118 120 # don't prompt, just terminate the kernel if we own it
119 121 # or leave it alone if we don't
120 122 keepkernel = not self._existing
121 123
122 124 if keepkernel is None: #show prompt
123 125 if kernel_manager and kernel_manager.channels_running:
124 126 title = self.window().windowTitle()
125 127 cancel = QtGui.QMessageBox.Cancel
126 128 okay = QtGui.QMessageBox.Ok
127 129 if self._may_close:
128 130 msg = "You are closing this Console window."
129 131 info = "Would you like to quit the Kernel and all attached Consoles as well?"
130 132 justthis = QtGui.QPushButton("&No, just this Console", self)
131 133 justthis.setShortcut('N')
132 134 closeall = QtGui.QPushButton("&Yes, quit everything", self)
133 135 closeall.setShortcut('Y')
134 136 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
135 137 title, msg)
136 138 box.setInformativeText(info)
137 139 box.addButton(cancel)
138 140 box.addButton(justthis, QtGui.QMessageBox.NoRole)
139 141 box.addButton(closeall, QtGui.QMessageBox.YesRole)
140 142 box.setDefaultButton(closeall)
141 143 box.setEscapeButton(cancel)
142 144 reply = box.exec_()
143 145 if reply == 1: # close All
144 146 kernel_manager.shutdown_kernel()
145 147 #kernel_manager.stop_channels()
146 148 event.accept()
147 149 elif reply == 0: # close Console
148 150 if not self._existing:
149 151 # Have kernel: don't quit, just close the window
150 152 self._app.setQuitOnLastWindowClosed(False)
151 153 self.deleteLater()
152 154 event.accept()
153 155 else:
154 156 event.ignore()
155 157 else:
156 158 reply = QtGui.QMessageBox.question(self, title,
157 159 "Are you sure you want to close this Console?"+
158 160 "\nThe Kernel and other Consoles will remain active.",
159 161 okay|cancel,
160 162 defaultButton=okay
161 163 )
162 164 if reply == okay:
163 165 event.accept()
164 166 else:
165 167 event.ignore()
166 168 elif keepkernel: #close console but leave kernel running (no prompt)
167 169 if kernel_manager and kernel_manager.channels_running:
168 170 if not self._existing:
169 171 # I have the kernel: don't quit, just close the window
170 172 self._app.setQuitOnLastWindowClosed(False)
171 173 event.accept()
172 174 else: #close console and kernel (no prompt)
173 175 if kernel_manager and kernel_manager.channels_running:
174 176 kernel_manager.shutdown_kernel()
175 177 event.accept()
176 178
177 179 #-----------------------------------------------------------------------------
178 180 # Aliases and Flags
179 181 #-----------------------------------------------------------------------------
180 182
181 183 flags = dict(ipkernel_flags)
182 184 qt_flags = {
183 185 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
184 186 "Connect to an existing kernel."),
185 187 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
186 188 "Use a pure Python kernel instead of an IPython kernel."),
187 189 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
188 190 "Disable rich text support."),
189 191 }
190 192 qt_flags.update(boolean_flag(
191 193 'gui-completion', 'ConsoleWidget.gui_completion',
192 194 "use a GUI widget for tab completion",
193 195 "use plaintext output for completion"
194 196 ))
195 197 qt_flags.update(boolean_flag(
196 198 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
197 199 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
198 200 to force a direct exit without any confirmation.
199 201 """,
200 202 """Don't prompt the user when exiting. This will terminate the kernel
201 203 if it is owned by the frontend, and leave it alive if it is external.
202 204 """
203 205 ))
204 206 flags.update(qt_flags)
205 207 # the flags that are specific to the frontend
206 208 # these must be scrubbed before being passed to the kernel,
207 209 # or it will raise an error on unrecognized flags
208 210 qt_flags = qt_flags.keys()
209 211
210 212 aliases = dict(ipkernel_aliases)
211 213
212 214 qt_aliases = dict(
213 215 hb = 'IPythonQtConsoleApp.hb_port',
214 216 shell = 'IPythonQtConsoleApp.shell_port',
215 217 iopub = 'IPythonQtConsoleApp.iopub_port',
216 218 stdin = 'IPythonQtConsoleApp.stdin_port',
217 219 ip = 'IPythonQtConsoleApp.ip',
218 220
219 221 style = 'IPythonWidget.syntax_style',
220 222 stylesheet = 'IPythonQtConsoleApp.stylesheet',
221 223 colors = 'ZMQInteractiveShell.colors',
222 224
223 225 editor = 'IPythonWidget.editor',
224 226 paging = 'ConsoleWidget.paging',
225 227 ssh = 'IPythonQtConsoleApp.sshserver',
226 228 )
227 229 aliases.update(qt_aliases)
228 230 # also scrub aliases from the frontend
229 231 qt_flags.extend(qt_aliases.keys())
230 232
231 233
232 234 #-----------------------------------------------------------------------------
233 235 # IPythonQtConsole
234 236 #-----------------------------------------------------------------------------
235 237
236 238
237 239 class IPythonQtConsoleApp(BaseIPythonApplication):
238 240 name = 'ipython-qtconsole'
239 241 default_config_file_name='ipython_config.py'
240 242
241 243 description = """
242 244 The IPython QtConsole.
243 245
244 246 This launches a Console-style application using Qt. It is not a full
245 247 console, in that launched terminal subprocesses will not be able to accept
246 248 input.
247 249
248 250 The QtConsole supports various extra features beyond the Terminal IPython
249 251 shell, such as inline plotting with matplotlib, via:
250 252
251 253 ipython qtconsole --pylab=inline
252 254
253 255 as well as saving your session as HTML, and printing the output.
254 256
255 257 """
256 258 examples = _examples
257 259
258 260 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
259 261 flags = Dict(flags)
260 262 aliases = Dict(aliases)
261 263
262 264 kernel_argv = List(Unicode)
263 265
264 266 # create requested profiles by default, if they don't exist:
265 267 auto_create = CBool(True)
266 268 # connection info:
267 269 ip = Unicode(LOCALHOST, config=True,
268 270 help="""Set the kernel\'s IP address [default localhost].
269 271 If the IP address is something other than localhost, then
270 272 Consoles on other machines will be able to connect
271 273 to the Kernel, so be careful!"""
272 274 )
273 275
274 276 sshserver = Unicode('', config=True,
275 277 help="""The SSH server to use to connect to the kernel.""")
276 278 sshkey = Unicode('', config=True,
277 279 help="""Path to the ssh key to use for logging in to the ssh server.""")
278 280
279 281 hb_port = Int(0, config=True,
280 282 help="set the heartbeat port [default: random]")
281 283 shell_port = Int(0, config=True,
282 284 help="set the shell (XREP) port [default: random]")
283 285 iopub_port = Int(0, config=True,
284 286 help="set the iopub (PUB) port [default: random]")
285 287 stdin_port = Int(0, config=True,
286 288 help="set the stdin (XREQ) port [default: random]")
287 289
288 290 existing = CBool(False, config=True,
289 291 help="Whether to connect to an already running Kernel.")
290 292
291 293 stylesheet = Unicode('', config=True,
292 294 help="path to a custom CSS stylesheet")
293 295
294 296 pure = CBool(False, config=True,
295 297 help="Use a pure Python kernel instead of an IPython kernel.")
296 298 plain = CBool(False, config=True,
297 299 help="Use a plaintext widget instead of rich text (plain can't print/save).")
298 300
299 301 def _pure_changed(self, name, old, new):
300 302 kind = 'plain' if self.plain else 'rich'
301 303 self.config.ConsoleWidget.kind = kind
302 304 if self.pure:
303 305 self.widget_factory = FrontendWidget
304 306 elif self.plain:
305 307 self.widget_factory = IPythonWidget
306 308 else:
307 309 self.widget_factory = RichIPythonWidget
308 310
309 311 _plain_changed = _pure_changed
310 312
311 313 confirm_exit = CBool(True, config=True,
312 314 help="""
313 315 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
314 316 to force a direct exit without any confirmation.""",
315 317 )
316 318
317 319 # the factory for creating a widget
318 320 widget_factory = Any(RichIPythonWidget)
319 321
320 322 def parse_command_line(self, argv=None):
321 323 super(IPythonQtConsoleApp, self).parse_command_line(argv)
322 324 if argv is None:
323 325 argv = sys.argv[1:]
324 326
325 327 self.kernel_argv = list(argv) # copy
326 328 # kernel should inherit default config file from frontend
327 329 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
328 330 # scrub frontend-specific flags
329 331 for a in argv:
330 332
331 333 if a.startswith('-'):
332 334 key = a.lstrip('-').split('=')[0]
333 335 if key in qt_flags:
334 336 self.kernel_argv.remove(a)
335 337
336 338 def init_ssh(self):
337 # import here, to prevent circular import
338 from IPython.external.ssh import tunnel
339 """set up ssh tunnels, if needed."""
339 340 if not self.sshserver and not self.sshkey:
340 341 return
341 342
342 343 if self.sshkey and not self.sshserver:
343 344 self.sshserver = self.ip
344 345 self.ip=LOCALHOST
345 346
346 347 lports = select_random_ports(4)
347 348 rports = self.shell_port, self.iopub_port, self.stdin_port, self.hb_port
348 349 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = lports
349 350
350 351 remote_ip = self.ip
351 352 self.ip = LOCALHOST
352 353 self.log.info("Forwarding connections to %s via %s"%(remote_ip, self.sshserver))
353 354
354 355 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey):
355 356 password=False
356 357 else:
357 358 password = getpass("SSH Password for %s: "%self.sshserver)
358 359
359 360 for lp,rp in zip(lports, rports):
360 361 tunnel.ssh_tunnel(lp, rp, self.sshserver, remote_ip, self.sshkey, password)
361 362
362 363 def init_kernel_manager(self):
363 364 # Don't let Qt or ZMQ swallow KeyboardInterupts.
364 365 signal.signal(signal.SIGINT, signal.SIG_DFL)
365 366
366 367 # Create a KernelManager and start a kernel.
367 368 self.kernel_manager = QtKernelManager(
368 369 shell_address=(self.ip, self.shell_port),
369 370 sub_address=(self.ip, self.iopub_port),
370 371 stdin_address=(self.ip, self.stdin_port),
371 372 hb_address=(self.ip, self.hb_port),
372 373 config=self.config
373 374 )
374 375 # start the kernel
375 376 if not self.existing:
376 377 kwargs = dict(ip=self.ip, ipython=not self.pure)
377 378 kwargs['extra_arguments'] = self.kernel_argv
378 379 self.kernel_manager.start_kernel(**kwargs)
379 380 self.kernel_manager.start_channels()
380 381
381 382
382 383 def init_qt_elements(self):
383 384 # Create the widget.
384 385 self.app = QtGui.QApplication([])
385 386 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
386 387 self.widget = self.widget_factory(config=self.config,
387 388 local_kernel=local_kernel)
388 389 self.widget.kernel_manager = self.kernel_manager
389 390 self.window = MainWindow(self.app, self.widget, self.existing,
390 391 may_close=local_kernel,
391 392 confirm_exit=self.confirm_exit)
392 393 self.window.setWindowTitle('Python' if self.pure else 'IPython')
393 394
394 395 def init_colors(self):
395 396 """Configure the coloring of the widget"""
396 397 # Note: This will be dramatically simplified when colors
397 398 # are removed from the backend.
398 399
399 400 if self.pure:
400 401 # only IPythonWidget supports styling
401 402 return
402 403
403 404 # parse the colors arg down to current known labels
404 405 try:
405 406 colors = self.config.ZMQInteractiveShell.colors
406 407 except AttributeError:
407 408 colors = None
408 409 try:
409 410 style = self.config.IPythonWidget.colors
410 411 except AttributeError:
411 412 style = None
412 413
413 414 # find the value for colors:
414 415 if colors:
415 416 colors=colors.lower()
416 417 if colors in ('lightbg', 'light'):
417 418 colors='lightbg'
418 419 elif colors in ('dark', 'linux'):
419 420 colors='linux'
420 421 else:
421 422 colors='nocolor'
422 423 elif style:
423 424 if style=='bw':
424 425 colors='nocolor'
425 426 elif styles.dark_style(style):
426 427 colors='linux'
427 428 else:
428 429 colors='lightbg'
429 430 else:
430 431 colors=None
431 432
432 433 # Configure the style.
433 434 widget = self.widget
434 435 if style:
435 436 widget.style_sheet = styles.sheet_from_template(style, colors)
436 437 widget.syntax_style = style
437 438 widget._syntax_style_changed()
438 439 widget._style_sheet_changed()
439 440 elif colors:
440 441 # use a default style
441 442 widget.set_default_style(colors=colors)
442 443 else:
443 444 # this is redundant for now, but allows the widget's
444 445 # defaults to change
445 446 widget.set_default_style()
446 447
447 448 if self.stylesheet:
448 449 # we got an expicit stylesheet
449 450 if os.path.isfile(self.stylesheet):
450 451 with open(self.stylesheet) as f:
451 452 sheet = f.read()
452 453 widget.style_sheet = sheet
453 454 widget._style_sheet_changed()
454 455 else:
455 456 raise IOError("Stylesheet %r not found."%self.stylesheet)
456 457
457 458 def initialize(self, argv=None):
458 459 super(IPythonQtConsoleApp, self).initialize(argv)
459 460 self.init_ssh()
460 461 self.init_kernel_manager()
461 462 self.init_qt_elements()
462 463 self.init_colors()
463 464
464 465 def start(self):
465 466
466 467 # draw the window
467 468 self.window.show()
468 469
469 470 # Start the application main loop.
470 471 self.app.exec_()
471 472
472 473 #-----------------------------------------------------------------------------
473 474 # Main entry point
474 475 #-----------------------------------------------------------------------------
475 476
476 477 def main():
477 478 app = IPythonQtConsoleApp()
478 479 app.initialize()
479 480 app.start()
480 481
481 482
482 483 if __name__ == '__main__':
483 484 main()
General Comments 0
You need to be logged in to leave comments. Login now