##// END OF EJS Templates
Fix another small bug in stripping kernel args...
MinRK -
Show More
@@ -1,538 +1,547 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 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import json
22 import json
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 import uuid
26 import uuid
27
27
28 # System library imports
28 # System library imports
29 from IPython.external.qt import QtGui
29 from IPython.external.qt import QtGui
30
30
31 # Local imports
31 # Local imports
32 from IPython.config.application import boolean_flag
32 from IPython.config.application import boolean_flag
33 from IPython.core.application import BaseIPythonApplication
33 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console import styles
39 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console.mainwindow import MainWindow
40 from IPython.frontend.qt.console.mainwindow import MainWindow
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 from IPython.utils.path import filefind
42 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import str_to_bytes
43 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.traitlets import (
44 from IPython.utils.traitlets import (
45 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
45 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
46 )
46 )
47 from IPython.zmq.ipkernel import (
47 from IPython.zmq.ipkernel import (
48 flags as ipkernel_flags,
48 flags as ipkernel_flags,
49 aliases as ipkernel_aliases,
49 aliases as ipkernel_aliases,
50 IPKernelApp
50 IPKernelApp
51 )
51 )
52 from IPython.zmq.session import Session, default_secure
52 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Network Constants
56 # Network Constants
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Globals
62 # Globals
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 _examples = """
65 _examples = """
66 ipython qtconsole # start the qtconsole
66 ipython qtconsole # start the qtconsole
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 """
68 """
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Aliases and Flags
71 # Aliases and Flags
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 flags = dict(ipkernel_flags)
74 flags = dict(ipkernel_flags)
75 qt_flags = {
75 qt_flags = {
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 "Use a pure Python kernel instead of an IPython kernel."),
79 "Use a pure Python kernel instead of an IPython kernel."),
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 "Disable rich text support."),
81 "Disable rich text support."),
82 }
82 }
83 qt_flags.update(boolean_flag(
83 qt_flags.update(boolean_flag(
84 'gui-completion', 'ConsoleWidget.gui_completion',
84 'gui-completion', 'ConsoleWidget.gui_completion',
85 "use a GUI widget for tab completion",
85 "use a GUI widget for tab completion",
86 "use plaintext output for completion"
86 "use plaintext output for completion"
87 ))
87 ))
88 qt_flags.update(boolean_flag(
88 qt_flags.update(boolean_flag(
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
91 to force a direct exit without any confirmation.
91 to force a direct exit without any confirmation.
92 """,
92 """,
93 """Don't prompt the user when exiting. This will terminate the kernel
93 """Don't prompt the user when exiting. This will terminate the kernel
94 if it is owned by the frontend, and leave it alive if it is external.
94 if it is owned by the frontend, and leave it alive if it is external.
95 """
95 """
96 ))
96 ))
97 flags.update(qt_flags)
97 flags.update(qt_flags)
98
98
99 aliases = dict(ipkernel_aliases)
99 aliases = dict(ipkernel_aliases)
100
100
101 qt_aliases = dict(
101 qt_aliases = dict(
102 hb = 'IPythonQtConsoleApp.hb_port',
102 hb = 'IPythonQtConsoleApp.hb_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
106 ip = 'IPythonQtConsoleApp.ip',
106 ip = 'IPythonQtConsoleApp.ip',
107 existing = 'IPythonQtConsoleApp.existing',
107 existing = 'IPythonQtConsoleApp.existing',
108 f = 'IPythonQtConsoleApp.connection_file',
108 f = 'IPythonQtConsoleApp.connection_file',
109
109
110 style = 'IPythonWidget.syntax_style',
110 style = 'IPythonWidget.syntax_style',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 colors = 'ZMQInteractiveShell.colors',
112 colors = 'ZMQInteractiveShell.colors',
113
113
114 editor = 'IPythonWidget.editor',
114 editor = 'IPythonWidget.editor',
115 paging = 'ConsoleWidget.paging',
115 paging = 'ConsoleWidget.paging',
116 ssh = 'IPythonQtConsoleApp.sshserver',
116 ssh = 'IPythonQtConsoleApp.sshserver',
117 )
117 )
118 aliases.update(qt_aliases)
118 aliases.update(qt_aliases)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # Classes
121 # Classes
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123
123
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125 # IPythonQtConsole
125 # IPythonQtConsole
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127
127
128
128
129 class IPythonQtConsoleApp(BaseIPythonApplication):
129 class IPythonQtConsoleApp(BaseIPythonApplication):
130 name = 'ipython-qtconsole'
130 name = 'ipython-qtconsole'
131 default_config_file_name='ipython_config.py'
131 default_config_file_name='ipython_config.py'
132
132
133 description = """
133 description = """
134 The IPython QtConsole.
134 The IPython QtConsole.
135
135
136 This launches a Console-style application using Qt. It is not a full
136 This launches a Console-style application using Qt. It is not a full
137 console, in that launched terminal subprocesses will not be able to accept
137 console, in that launched terminal subprocesses will not be able to accept
138 input.
138 input.
139
139
140 The QtConsole supports various extra features beyond the Terminal IPython
140 The QtConsole supports various extra features beyond the Terminal IPython
141 shell, such as inline plotting with matplotlib, via:
141 shell, such as inline plotting with matplotlib, via:
142
142
143 ipython qtconsole --pylab=inline
143 ipython qtconsole --pylab=inline
144
144
145 as well as saving your session as HTML, and printing the output.
145 as well as saving your session as HTML, and printing the output.
146
146
147 """
147 """
148 examples = _examples
148 examples = _examples
149
149
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
151 flags = Dict(flags)
151 flags = Dict(flags)
152 aliases = Dict(aliases)
152 aliases = Dict(aliases)
153
153
154 kernel_argv = List(Unicode)
154 kernel_argv = List(Unicode)
155
155
156 # create requested profiles by default, if they don't exist:
156 # create requested profiles by default, if they don't exist:
157 auto_create = CBool(True)
157 auto_create = CBool(True)
158 # connection info:
158 # connection info:
159 ip = Unicode(LOCALHOST, config=True,
159 ip = Unicode(LOCALHOST, config=True,
160 help="""Set the kernel\'s IP address [default localhost].
160 help="""Set the kernel\'s IP address [default localhost].
161 If the IP address is something other than localhost, then
161 If the IP address is something other than localhost, then
162 Consoles on other machines will be able to connect
162 Consoles on other machines will be able to connect
163 to the Kernel, so be careful!"""
163 to the Kernel, so be careful!"""
164 )
164 )
165
165
166 sshserver = Unicode('', config=True,
166 sshserver = Unicode('', config=True,
167 help="""The SSH server to use to connect to the kernel.""")
167 help="""The SSH server to use to connect to the kernel.""")
168 sshkey = Unicode('', config=True,
168 sshkey = Unicode('', config=True,
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
170
170
171 hb_port = Int(0, config=True,
171 hb_port = Int(0, config=True,
172 help="set the heartbeat port [default: random]")
172 help="set the heartbeat port [default: random]")
173 shell_port = Int(0, config=True,
173 shell_port = Int(0, config=True,
174 help="set the shell (XREP) port [default: random]")
174 help="set the shell (XREP) port [default: random]")
175 iopub_port = Int(0, config=True,
175 iopub_port = Int(0, config=True,
176 help="set the iopub (PUB) port [default: random]")
176 help="set the iopub (PUB) port [default: random]")
177 stdin_port = Int(0, config=True,
177 stdin_port = Int(0, config=True,
178 help="set the stdin (XREQ) port [default: random]")
178 help="set the stdin (XREQ) port [default: random]")
179 connection_file = Unicode('', config=True,
179 connection_file = Unicode('', config=True,
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
181
181
182 This file will contain the IP, ports, and authentication key needed to connect
182 This file will contain the IP, ports, and authentication key needed to connect
183 clients to this kernel. By default, this file will be created in the security-dir
183 clients to this kernel. By default, this file will be created in the security-dir
184 of the current profile, but can be specified by absolute path.
184 of the current profile, but can be specified by absolute path.
185 """)
185 """)
186 def _connection_file_default(self):
186 def _connection_file_default(self):
187 return 'kernel-%i.json' % os.getpid()
187 return 'kernel-%i.json' % os.getpid()
188
188
189 existing = Unicode('', config=True,
189 existing = Unicode('', config=True,
190 help="""Connect to an already running kernel""")
190 help="""Connect to an already running kernel""")
191
191
192 stylesheet = Unicode('', config=True,
192 stylesheet = Unicode('', config=True,
193 help="path to a custom CSS stylesheet")
193 help="path to a custom CSS stylesheet")
194
194
195 pure = CBool(False, config=True,
195 pure = CBool(False, config=True,
196 help="Use a pure Python kernel instead of an IPython kernel.")
196 help="Use a pure Python kernel instead of an IPython kernel.")
197 plain = CBool(False, config=True,
197 plain = CBool(False, config=True,
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
199
199
200 def _pure_changed(self, name, old, new):
200 def _pure_changed(self, name, old, new):
201 kind = 'plain' if self.plain else 'rich'
201 kind = 'plain' if self.plain else 'rich'
202 self.config.ConsoleWidget.kind = kind
202 self.config.ConsoleWidget.kind = kind
203 if self.pure:
203 if self.pure:
204 self.widget_factory = FrontendWidget
204 self.widget_factory = FrontendWidget
205 elif self.plain:
205 elif self.plain:
206 self.widget_factory = IPythonWidget
206 self.widget_factory = IPythonWidget
207 else:
207 else:
208 self.widget_factory = RichIPythonWidget
208 self.widget_factory = RichIPythonWidget
209
209
210 _plain_changed = _pure_changed
210 _plain_changed = _pure_changed
211
211
212 confirm_exit = CBool(True, config=True,
212 confirm_exit = CBool(True, config=True,
213 help="""
213 help="""
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
215 to force a direct exit without any confirmation.""",
215 to force a direct exit without any confirmation.""",
216 )
216 )
217
217
218 # the factory for creating a widget
218 # the factory for creating a widget
219 widget_factory = Any(RichIPythonWidget)
219 widget_factory = Any(RichIPythonWidget)
220
220
221 def parse_command_line(self, argv=None):
221 def parse_command_line(self, argv=None):
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
223 if argv is None:
223 if argv is None:
224 argv = sys.argv[1:]
224 argv = sys.argv[1:]
225
226 self.kernel_argv = list(argv) # copy
225 self.kernel_argv = list(argv) # copy
227 # kernel should inherit default config file from frontend
226 # kernel should inherit default config file from frontend
228 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
227 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
229 # Scrub frontend-specific flags
228 # Scrub frontend-specific flags
230 for a in argv:
231 if a.startswith('-') and a.lstrip('-') in qt_flags:
232 self.kernel_argv.remove(a)
233 swallow_next = False
229 swallow_next = False
230 was_flag = False
231 # copy again, in case some aliases have the same name as a flag
232 # argv = list(self.kernel_argv)
234 for a in argv:
233 for a in argv:
235 if swallow_next:
234 if swallow_next:
236 self.kernel_argv.remove(a)
237 swallow_next = False
235 swallow_next = False
238 continue
236 # last arg was an alias, remove the next one
237 # *unless* the last alias has a no-arg flag version, in which
238 # case, don't swallow the next arg if it's also a flag:
239 if not (was_flag and a.startswith('-')):
240 self.kernel_argv.remove(a)
241 continue
239 if a.startswith('-'):
242 if a.startswith('-'):
240 split = a.lstrip('-').split('=')
243 split = a.lstrip('-').split('=')
241 alias = split[0]
244 alias = split[0]
242 if alias in qt_aliases:
245 if alias in qt_aliases:
243 self.kernel_argv.remove(a)
246 self.kernel_argv.remove(a)
244 if len(split) == 1:
247 if len(split) == 1:
245 # alias passed with arg via space
248 # alias passed with arg via space
246 swallow_next = True
249 swallow_next = True
250 # could have been a flag that matches an alias, e.g. `existing`
251 # in which case, we might not swallow the next arg
252 was_flag = alias in qt_flags
253 elif alias in qt_flags:
254 # strip flag, but don't swallow next, as flags don't take args
255 self.kernel_argv.remove(a)
247
256
248 def init_connection_file(self):
257 def init_connection_file(self):
249 """find the connection file, and load the info if found.
258 """find the connection file, and load the info if found.
250
259
251 The current working directory and the current profile's security
260 The current working directory and the current profile's security
252 directory will be searched for the file if it is not given by
261 directory will be searched for the file if it is not given by
253 absolute path.
262 absolute path.
254
263
255 When attempting to connect to an existing kernel and the `--existing`
264 When attempting to connect to an existing kernel and the `--existing`
256 argument does not match an existing file, it will be interpreted as a
265 argument does not match an existing file, it will be interpreted as a
257 fileglob, and the matching file in the current profile's security dir
266 fileglob, and the matching file in the current profile's security dir
258 with the latest access time will be used.
267 with the latest access time will be used.
259 """
268 """
260 if self.existing:
269 if self.existing:
261 try:
270 try:
262 cf = find_connection_file(self.existing)
271 cf = find_connection_file(self.existing)
263 except Exception:
272 except Exception:
264 self.log.critical("Could not find existing kernel connection file %s", self.existing)
273 self.log.critical("Could not find existing kernel connection file %s", self.existing)
265 self.exit(1)
274 self.exit(1)
266 self.log.info("Connecting to existing kernel: %s" % cf)
275 self.log.info("Connecting to existing kernel: %s" % cf)
267 self.connection_file = cf
276 self.connection_file = cf
268 # should load_connection_file only be used for existing?
277 # should load_connection_file only be used for existing?
269 # as it is now, this allows reusing ports if an existing
278 # as it is now, this allows reusing ports if an existing
270 # file is requested
279 # file is requested
271 try:
280 try:
272 self.load_connection_file()
281 self.load_connection_file()
273 except Exception:
282 except Exception:
274 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
283 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
275 self.exit(1)
284 self.exit(1)
276
285
277 def load_connection_file(self):
286 def load_connection_file(self):
278 """load ip/port/hmac config from JSON connection file"""
287 """load ip/port/hmac config from JSON connection file"""
279 # this is identical to KernelApp.load_connection_file
288 # this is identical to KernelApp.load_connection_file
280 # perhaps it can be centralized somewhere?
289 # perhaps it can be centralized somewhere?
281 try:
290 try:
282 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
291 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
283 except IOError:
292 except IOError:
284 self.log.debug("Connection File not found: %s", self.connection_file)
293 self.log.debug("Connection File not found: %s", self.connection_file)
285 return
294 return
286 self.log.debug(u"Loading connection file %s", fname)
295 self.log.debug(u"Loading connection file %s", fname)
287 with open(fname) as f:
296 with open(fname) as f:
288 s = f.read()
297 s = f.read()
289 cfg = json.loads(s)
298 cfg = json.loads(s)
290 if self.ip == LOCALHOST and 'ip' in cfg:
299 if self.ip == LOCALHOST and 'ip' in cfg:
291 # not overridden by config or cl_args
300 # not overridden by config or cl_args
292 self.ip = cfg['ip']
301 self.ip = cfg['ip']
293 for channel in ('hb', 'shell', 'iopub', 'stdin'):
302 for channel in ('hb', 'shell', 'iopub', 'stdin'):
294 name = channel + '_port'
303 name = channel + '_port'
295 if getattr(self, name) == 0 and name in cfg:
304 if getattr(self, name) == 0 and name in cfg:
296 # not overridden by config or cl_args
305 # not overridden by config or cl_args
297 setattr(self, name, cfg[name])
306 setattr(self, name, cfg[name])
298 if 'key' in cfg:
307 if 'key' in cfg:
299 self.config.Session.key = str_to_bytes(cfg['key'])
308 self.config.Session.key = str_to_bytes(cfg['key'])
300
309
301 def init_ssh(self):
310 def init_ssh(self):
302 """set up ssh tunnels, if needed."""
311 """set up ssh tunnels, if needed."""
303 if not self.sshserver and not self.sshkey:
312 if not self.sshserver and not self.sshkey:
304 return
313 return
305
314
306 if self.sshkey and not self.sshserver:
315 if self.sshkey and not self.sshserver:
307 # specifying just the key implies that we are connecting directly
316 # specifying just the key implies that we are connecting directly
308 self.sshserver = self.ip
317 self.sshserver = self.ip
309 self.ip = LOCALHOST
318 self.ip = LOCALHOST
310
319
311 # build connection dict for tunnels:
320 # build connection dict for tunnels:
312 info = dict(ip=self.ip,
321 info = dict(ip=self.ip,
313 shell_port=self.shell_port,
322 shell_port=self.shell_port,
314 iopub_port=self.iopub_port,
323 iopub_port=self.iopub_port,
315 stdin_port=self.stdin_port,
324 stdin_port=self.stdin_port,
316 hb_port=self.hb_port
325 hb_port=self.hb_port
317 )
326 )
318
327
319 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
328 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
320
329
321 # tunnels return a new set of ports, which will be on localhost:
330 # tunnels return a new set of ports, which will be on localhost:
322 self.ip = LOCALHOST
331 self.ip = LOCALHOST
323 try:
332 try:
324 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
333 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
325 except:
334 except:
326 # even catch KeyboardInterrupt
335 # even catch KeyboardInterrupt
327 self.log.error("Could not setup tunnels", exc_info=True)
336 self.log.error("Could not setup tunnels", exc_info=True)
328 self.exit(1)
337 self.exit(1)
329
338
330 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
339 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
331
340
332 cf = self.connection_file
341 cf = self.connection_file
333 base,ext = os.path.splitext(cf)
342 base,ext = os.path.splitext(cf)
334 base = os.path.basename(base)
343 base = os.path.basename(base)
335 self.connection_file = os.path.basename(base)+'-ssh'+ext
344 self.connection_file = os.path.basename(base)+'-ssh'+ext
336 self.log.critical("To connect another client via this tunnel, use:")
345 self.log.critical("To connect another client via this tunnel, use:")
337 self.log.critical("--existing %s" % self.connection_file)
346 self.log.critical("--existing %s" % self.connection_file)
338
347
339 def _new_connection_file(self):
348 def _new_connection_file(self):
340 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
349 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
341
350
342 def init_kernel_manager(self):
351 def init_kernel_manager(self):
343 # Don't let Qt or ZMQ swallow KeyboardInterupts.
352 # Don't let Qt or ZMQ swallow KeyboardInterupts.
344 signal.signal(signal.SIGINT, signal.SIG_DFL)
353 signal.signal(signal.SIGINT, signal.SIG_DFL)
345 sec = self.profile_dir.security_dir
354 sec = self.profile_dir.security_dir
346 try:
355 try:
347 cf = filefind(self.connection_file, ['.', sec])
356 cf = filefind(self.connection_file, ['.', sec])
348 except IOError:
357 except IOError:
349 # file might not exist
358 # file might not exist
350 if self.connection_file == os.path.basename(self.connection_file):
359 if self.connection_file == os.path.basename(self.connection_file):
351 # just shortname, put it in security dir
360 # just shortname, put it in security dir
352 cf = os.path.join(sec, self.connection_file)
361 cf = os.path.join(sec, self.connection_file)
353 else:
362 else:
354 cf = self.connection_file
363 cf = self.connection_file
355
364
356 # Create a KernelManager and start a kernel.
365 # Create a KernelManager and start a kernel.
357 self.kernel_manager = QtKernelManager(
366 self.kernel_manager = QtKernelManager(
358 ip=self.ip,
367 ip=self.ip,
359 shell_port=self.shell_port,
368 shell_port=self.shell_port,
360 iopub_port=self.iopub_port,
369 iopub_port=self.iopub_port,
361 stdin_port=self.stdin_port,
370 stdin_port=self.stdin_port,
362 hb_port=self.hb_port,
371 hb_port=self.hb_port,
363 connection_file=cf,
372 connection_file=cf,
364 config=self.config,
373 config=self.config,
365 )
374 )
366 # start the kernel
375 # start the kernel
367 if not self.existing:
376 if not self.existing:
368 kwargs = dict(ipython=not self.pure)
377 kwargs = dict(ipython=not self.pure)
369 kwargs['extra_arguments'] = self.kernel_argv
378 kwargs['extra_arguments'] = self.kernel_argv
370 self.kernel_manager.start_kernel(**kwargs)
379 self.kernel_manager.start_kernel(**kwargs)
371 elif self.sshserver:
380 elif self.sshserver:
372 # ssh, write new connection file
381 # ssh, write new connection file
373 self.kernel_manager.write_connection_file()
382 self.kernel_manager.write_connection_file()
374 self.kernel_manager.start_channels()
383 self.kernel_manager.start_channels()
375
384
376 def new_frontend_master(self):
385 def new_frontend_master(self):
377 """ Create and return new frontend attached to new kernel, launched on localhost.
386 """ Create and return new frontend attached to new kernel, launched on localhost.
378 """
387 """
379 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
388 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
380 kernel_manager = QtKernelManager(
389 kernel_manager = QtKernelManager(
381 ip=ip,
390 ip=ip,
382 connection_file=self._new_connection_file(),
391 connection_file=self._new_connection_file(),
383 config=self.config,
392 config=self.config,
384 )
393 )
385 # start the kernel
394 # start the kernel
386 kwargs = dict(ipython=not self.pure)
395 kwargs = dict(ipython=not self.pure)
387 kwargs['extra_arguments'] = self.kernel_argv
396 kwargs['extra_arguments'] = self.kernel_argv
388 kernel_manager.start_kernel(**kwargs)
397 kernel_manager.start_kernel(**kwargs)
389 kernel_manager.start_channels()
398 kernel_manager.start_channels()
390 widget = self.widget_factory(config=self.config,
399 widget = self.widget_factory(config=self.config,
391 local_kernel=True)
400 local_kernel=True)
392 widget.kernel_manager = kernel_manager
401 widget.kernel_manager = kernel_manager
393 widget._existing = False
402 widget._existing = False
394 widget._may_close = True
403 widget._may_close = True
395 widget._confirm_exit = self.confirm_exit
404 widget._confirm_exit = self.confirm_exit
396 return widget
405 return widget
397
406
398 def new_frontend_slave(self, current_widget):
407 def new_frontend_slave(self, current_widget):
399 """Create and return a new frontend attached to an existing kernel.
408 """Create and return a new frontend attached to an existing kernel.
400
409
401 Parameters
410 Parameters
402 ----------
411 ----------
403 current_widget : IPythonWidget
412 current_widget : IPythonWidget
404 The IPythonWidget whose kernel this frontend is to share
413 The IPythonWidget whose kernel this frontend is to share
405 """
414 """
406 kernel_manager = QtKernelManager(
415 kernel_manager = QtKernelManager(
407 connection_file=current_widget.kernel_manager.connection_file,
416 connection_file=current_widget.kernel_manager.connection_file,
408 config = self.config,
417 config = self.config,
409 )
418 )
410 kernel_manager.load_connection_file()
419 kernel_manager.load_connection_file()
411 kernel_manager.start_channels()
420 kernel_manager.start_channels()
412 widget = self.widget_factory(config=self.config,
421 widget = self.widget_factory(config=self.config,
413 local_kernel=False)
422 local_kernel=False)
414 widget._existing = True
423 widget._existing = True
415 widget._may_close = False
424 widget._may_close = False
416 widget._confirm_exit = False
425 widget._confirm_exit = False
417 widget.kernel_manager = kernel_manager
426 widget.kernel_manager = kernel_manager
418 return widget
427 return widget
419
428
420 def init_qt_elements(self):
429 def init_qt_elements(self):
421 # Create the widget.
430 # Create the widget.
422 self.app = QtGui.QApplication([])
431 self.app = QtGui.QApplication([])
423
432
424 base_path = os.path.abspath(os.path.dirname(__file__))
433 base_path = os.path.abspath(os.path.dirname(__file__))
425 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
434 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
426 self.app.icon = QtGui.QIcon(icon_path)
435 self.app.icon = QtGui.QIcon(icon_path)
427 QtGui.QApplication.setWindowIcon(self.app.icon)
436 QtGui.QApplication.setWindowIcon(self.app.icon)
428
437
429 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
438 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
430 self.widget = self.widget_factory(config=self.config,
439 self.widget = self.widget_factory(config=self.config,
431 local_kernel=local_kernel)
440 local_kernel=local_kernel)
432 self.widget._existing = self.existing
441 self.widget._existing = self.existing
433 self.widget._may_close = not self.existing
442 self.widget._may_close = not self.existing
434 self.widget._confirm_exit = self.confirm_exit
443 self.widget._confirm_exit = self.confirm_exit
435
444
436 self.widget.kernel_manager = self.kernel_manager
445 self.widget.kernel_manager = self.kernel_manager
437 self.window = MainWindow(self.app,
446 self.window = MainWindow(self.app,
438 confirm_exit=self.confirm_exit,
447 confirm_exit=self.confirm_exit,
439 new_frontend_factory=self.new_frontend_master,
448 new_frontend_factory=self.new_frontend_master,
440 slave_frontend_factory=self.new_frontend_slave,
449 slave_frontend_factory=self.new_frontend_slave,
441 )
450 )
442 self.window.log = self.log
451 self.window.log = self.log
443 self.window.add_tab_with_frontend(self.widget)
452 self.window.add_tab_with_frontend(self.widget)
444 self.window.init_menu_bar()
453 self.window.init_menu_bar()
445 self.window.setWindowTitle('Python' if self.pure else 'IPython')
454 self.window.setWindowTitle('Python' if self.pure else 'IPython')
446
455
447 def init_colors(self):
456 def init_colors(self):
448 """Configure the coloring of the widget"""
457 """Configure the coloring of the widget"""
449 # Note: This will be dramatically simplified when colors
458 # Note: This will be dramatically simplified when colors
450 # are removed from the backend.
459 # are removed from the backend.
451
460
452 if self.pure:
461 if self.pure:
453 # only IPythonWidget supports styling
462 # only IPythonWidget supports styling
454 return
463 return
455
464
456 # parse the colors arg down to current known labels
465 # parse the colors arg down to current known labels
457 try:
466 try:
458 colors = self.config.ZMQInteractiveShell.colors
467 colors = self.config.ZMQInteractiveShell.colors
459 except AttributeError:
468 except AttributeError:
460 colors = None
469 colors = None
461 try:
470 try:
462 style = self.config.IPythonWidget.colors
471 style = self.config.IPythonWidget.colors
463 except AttributeError:
472 except AttributeError:
464 style = None
473 style = None
465
474
466 # find the value for colors:
475 # find the value for colors:
467 if colors:
476 if colors:
468 colors=colors.lower()
477 colors=colors.lower()
469 if colors in ('lightbg', 'light'):
478 if colors in ('lightbg', 'light'):
470 colors='lightbg'
479 colors='lightbg'
471 elif colors in ('dark', 'linux'):
480 elif colors in ('dark', 'linux'):
472 colors='linux'
481 colors='linux'
473 else:
482 else:
474 colors='nocolor'
483 colors='nocolor'
475 elif style:
484 elif style:
476 if style=='bw':
485 if style=='bw':
477 colors='nocolor'
486 colors='nocolor'
478 elif styles.dark_style(style):
487 elif styles.dark_style(style):
479 colors='linux'
488 colors='linux'
480 else:
489 else:
481 colors='lightbg'
490 colors='lightbg'
482 else:
491 else:
483 colors=None
492 colors=None
484
493
485 # Configure the style.
494 # Configure the style.
486 widget = self.widget
495 widget = self.widget
487 if style:
496 if style:
488 widget.style_sheet = styles.sheet_from_template(style, colors)
497 widget.style_sheet = styles.sheet_from_template(style, colors)
489 widget.syntax_style = style
498 widget.syntax_style = style
490 widget._syntax_style_changed()
499 widget._syntax_style_changed()
491 widget._style_sheet_changed()
500 widget._style_sheet_changed()
492 elif colors:
501 elif colors:
493 # use a default style
502 # use a default style
494 widget.set_default_style(colors=colors)
503 widget.set_default_style(colors=colors)
495 else:
504 else:
496 # this is redundant for now, but allows the widget's
505 # this is redundant for now, but allows the widget's
497 # defaults to change
506 # defaults to change
498 widget.set_default_style()
507 widget.set_default_style()
499
508
500 if self.stylesheet:
509 if self.stylesheet:
501 # we got an expicit stylesheet
510 # we got an expicit stylesheet
502 if os.path.isfile(self.stylesheet):
511 if os.path.isfile(self.stylesheet):
503 with open(self.stylesheet) as f:
512 with open(self.stylesheet) as f:
504 sheet = f.read()
513 sheet = f.read()
505 widget.style_sheet = sheet
514 widget.style_sheet = sheet
506 widget._style_sheet_changed()
515 widget._style_sheet_changed()
507 else:
516 else:
508 raise IOError("Stylesheet %r not found."%self.stylesheet)
517 raise IOError("Stylesheet %r not found."%self.stylesheet)
509
518
510 def initialize(self, argv=None):
519 def initialize(self, argv=None):
511 super(IPythonQtConsoleApp, self).initialize(argv)
520 super(IPythonQtConsoleApp, self).initialize(argv)
512 self.init_connection_file()
521 self.init_connection_file()
513 default_secure(self.config)
522 default_secure(self.config)
514 self.init_ssh()
523 self.init_ssh()
515 self.init_kernel_manager()
524 self.init_kernel_manager()
516 self.init_qt_elements()
525 self.init_qt_elements()
517 self.init_colors()
526 self.init_colors()
518
527
519 def start(self):
528 def start(self):
520
529
521 # draw the window
530 # draw the window
522 self.window.show()
531 self.window.show()
523
532
524 # Start the application main loop.
533 # Start the application main loop.
525 self.app.exec_()
534 self.app.exec_()
526
535
527 #-----------------------------------------------------------------------------
536 #-----------------------------------------------------------------------------
528 # Main entry point
537 # Main entry point
529 #-----------------------------------------------------------------------------
538 #-----------------------------------------------------------------------------
530
539
531 def main():
540 def main():
532 app = IPythonQtConsoleApp()
541 app = IPythonQtConsoleApp()
533 app.initialize()
542 app.initialize()
534 app.start()
543 app.start()
535
544
536
545
537 if __name__ == '__main__':
546 if __name__ == '__main__':
538 main()
547 main()
General Comments 0
You need to be logged in to leave comments. Login now