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