##// END OF EJS Templates
Backport PR #3978: fix `--existing` with non-localhost IP...
Paul Ivanov -
Show More
@@ -1,392 +1,393 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
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. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6
6
7 Authors:
7 Authors:
8
8
9 * Evan Patterson
9 * Evan Patterson
10 * Min RK
10 * Min RK
11 * Erik Tollerud
11 * Erik Tollerud
12 * Fernando Perez
12 * Fernando Perez
13 * Bussonnier Matthias
13 * Bussonnier Matthias
14 * Thomas Kluyver
14 * Thomas Kluyver
15 * Paul Ivanov
15 * Paul Ivanov
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # stdlib imports
23 # stdlib imports
24 import atexit
24 import atexit
25 import json
25 import json
26 import os
26 import os
27 import shutil
28 import signal
27 import signal
29 import sys
28 import sys
30 import uuid
29 import uuid
31
30
32
31
33 # Local imports
32 # Local imports
34 from IPython.config.application import boolean_flag
33 from IPython.config.application import boolean_flag
35 from IPython.config.configurable import Configurable
36 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
37 from IPython.kernel.blocking import BlockingKernelClient
35 from IPython.kernel.blocking import BlockingKernelClient
38 from IPython.kernel import KernelManager
36 from IPython.kernel import KernelManager
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 from IPython.utils.path import filefind
38 from IPython.utils.path import filefind
41 from IPython.utils.py3compat import str_to_bytes
39 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.traitlets import (
40 from IPython.utils.traitlets import (
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
44 )
42 )
45 from IPython.kernel.zmq.kernelapp import (
43 from IPython.kernel.zmq.kernelapp import (
46 kernel_flags,
44 kernel_flags,
47 kernel_aliases,
45 kernel_aliases,
48 IPKernelApp
46 IPKernelApp
49 )
47 )
50 from IPython.kernel.zmq.session import Session, default_secure
48 from IPython.kernel.zmq.session import Session, default_secure
51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
49 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.kernel.connect import ConnectionFileMixin
52
51
53 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
54 # Network Constants
53 # Network Constants
55 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
56
55
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
56 from IPython.utils.localinterfaces import LOCALHOST
58
57
59 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
60 # Globals
59 # Globals
61 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
62
61
63
62
64 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
65 # Aliases and Flags
64 # Aliases and Flags
66 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
67
66
68 flags = dict(kernel_flags)
67 flags = dict(kernel_flags)
69
68
70 # the flags that are specific to the frontend
69 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
70 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
71 # or it will raise an error on unrecognized flags
73 app_flags = {
72 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
73 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
74 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
75 }
77 app_flags.update(boolean_flag(
76 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
77 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
78 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
79 to force a direct exit without any confirmation.
81 """,
80 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
81 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
82 if it is owned by the frontend, and leave it alive if it is external.
84 """
83 """
85 ))
84 ))
86 flags.update(app_flags)
85 flags.update(app_flags)
87
86
88 aliases = dict(kernel_aliases)
87 aliases = dict(kernel_aliases)
89
88
90 # also scrub aliases from the frontend
89 # also scrub aliases from the frontend
91 app_aliases = dict(
90 app_aliases = dict(
92 ip = 'KernelManager.ip',
91 ip = 'IPythonConsoleApp.ip',
93 transport = 'KernelManager.transport',
92 transport = 'IPythonConsoleApp.transport',
94 hb = 'IPythonConsoleApp.hb_port',
93 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
94 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
95 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
96 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
97 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
98 f = 'IPythonConsoleApp.connection_file',
100
99
101
100
102 ssh = 'IPythonConsoleApp.sshserver',
101 ssh = 'IPythonConsoleApp.sshserver',
103 )
102 )
104 aliases.update(app_aliases)
103 aliases.update(app_aliases)
105
104
106 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
107 # Classes
106 # Classes
108 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
109
108
110 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
111 # IPythonConsole
110 # IPythonConsole
112 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
113
112
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
113 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115
114
116 try:
115 try:
117 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
116 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
118 except ImportError:
117 except ImportError:
119 pass
118 pass
120 else:
119 else:
121 classes.append(InlineBackend)
120 classes.append(InlineBackend)
122
121
123 class IPythonConsoleApp(Configurable):
122 class IPythonConsoleApp(ConnectionFileMixin):
124 name = 'ipython-console-mixin'
123 name = 'ipython-console-mixin'
125
124
126 description = """
125 description = """
127 The IPython Mixin Console.
126 The IPython Mixin Console.
128
127
129 This class contains the common portions of console client (QtConsole,
128 This class contains the common portions of console client (QtConsole,
130 ZMQ-based terminal console, etc). It is not a full console, in that
129 ZMQ-based terminal console, etc). It is not a full console, in that
131 launched terminal subprocesses will not be able to accept input.
130 launched terminal subprocesses will not be able to accept input.
132
131
133 The Console using this mixing supports various extra features beyond
132 The Console using this mixing supports various extra features beyond
134 the single-process Terminal IPython shell, such as connecting to
133 the single-process Terminal IPython shell, such as connecting to
135 existing kernel, via:
134 existing kernel, via:
136
135
137 ipython <appname> --existing
136 ipython <appname> --existing
138
137
139 as well as tunnel via SSH
138 as well as tunnel via SSH
140
139
141 """
140 """
142
141
143 classes = classes
142 classes = classes
144 flags = Dict(flags)
143 flags = Dict(flags)
145 aliases = Dict(aliases)
144 aliases = Dict(aliases)
146 kernel_manager_class = KernelManager
145 kernel_manager_class = KernelManager
147 kernel_client_class = BlockingKernelClient
146 kernel_client_class = BlockingKernelClient
148
147
149 kernel_argv = List(Unicode)
148 kernel_argv = List(Unicode)
150 # frontend flags&aliases to be stripped when building kernel_argv
149 # frontend flags&aliases to be stripped when building kernel_argv
151 frontend_flags = Any(app_flags)
150 frontend_flags = Any(app_flags)
152 frontend_aliases = Any(app_aliases)
151 frontend_aliases = Any(app_aliases)
153
152
154 # create requested profiles by default, if they don't exist:
153 # create requested profiles by default, if they don't exist:
155 auto_create = CBool(True)
154 auto_create = CBool(True)
156 # connection info:
155 # connection info:
157
156
158 sshserver = Unicode('', config=True,
157 sshserver = Unicode('', config=True,
159 help="""The SSH server to use to connect to the kernel.""")
158 help="""The SSH server to use to connect to the kernel.""")
160 sshkey = Unicode('', config=True,
159 sshkey = Unicode('', config=True,
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
160 help="""Path to the ssh key to use for logging in to the ssh server.""")
162
161
163 hb_port = Int(0, config=True,
162 hb_port = Int(0, config=True,
164 help="set the heartbeat port [default: random]")
163 help="set the heartbeat port [default: random]")
165 shell_port = Int(0, config=True,
164 shell_port = Int(0, config=True,
166 help="set the shell (ROUTER) port [default: random]")
165 help="set the shell (ROUTER) port [default: random]")
167 iopub_port = Int(0, config=True,
166 iopub_port = Int(0, config=True,
168 help="set the iopub (PUB) port [default: random]")
167 help="set the iopub (PUB) port [default: random]")
169 stdin_port = Int(0, config=True,
168 stdin_port = Int(0, config=True,
170 help="set the stdin (DEALER) port [default: random]")
169 help="set the stdin (DEALER) port [default: random]")
171 connection_file = Unicode('', config=True,
170 connection_file = Unicode('', config=True,
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
171 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173
172
174 This file will contain the IP, ports, and authentication key needed to connect
173 This file will contain the IP, ports, and authentication key needed to connect
175 clients to this kernel. By default, this file will be created in the security-dir
174 clients to this kernel. By default, this file will be created in the security-dir
176 of the current profile, but can be specified by absolute path.
175 of the current profile, but can be specified by absolute path.
177 """)
176 """)
178 def _connection_file_default(self):
177 def _connection_file_default(self):
179 return 'kernel-%i.json' % os.getpid()
178 return 'kernel-%i.json' % os.getpid()
180
179
181 existing = CUnicode('', config=True,
180 existing = CUnicode('', config=True,
182 help="""Connect to an already running kernel""")
181 help="""Connect to an already running kernel""")
183
182
184 confirm_exit = CBool(True, config=True,
183 confirm_exit = CBool(True, config=True,
185 help="""
184 help="""
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
185 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.""",
186 to force a direct exit without any confirmation.""",
188 )
187 )
189
188
190
189
191 def build_kernel_argv(self, argv=None):
190 def build_kernel_argv(self, argv=None):
192 """build argv to be passed to kernel subprocess"""
191 """build argv to be passed to kernel subprocess"""
193 if argv is None:
192 if argv is None:
194 argv = sys.argv[1:]
193 argv = sys.argv[1:]
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
194 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 # kernel should inherit default config file from frontend
195 # kernel should inherit default config file from frontend
197 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
196 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
198
197
199 def init_connection_file(self):
198 def init_connection_file(self):
200 """find the connection file, and load the info if found.
199 """find the connection file, and load the info if found.
201
200
202 The current working directory and the current profile's security
201 The current working directory and the current profile's security
203 directory will be searched for the file if it is not given by
202 directory will be searched for the file if it is not given by
204 absolute path.
203 absolute path.
205
204
206 When attempting to connect to an existing kernel and the `--existing`
205 When attempting to connect to an existing kernel and the `--existing`
207 argument does not match an existing file, it will be interpreted as a
206 argument does not match an existing file, it will be interpreted as a
208 fileglob, and the matching file in the current profile's security dir
207 fileglob, and the matching file in the current profile's security dir
209 with the latest access time will be used.
208 with the latest access time will be used.
210
209
211 After this method is called, self.connection_file contains the *full path*
210 After this method is called, self.connection_file contains the *full path*
212 to the connection file, never just its name.
211 to the connection file, never just its name.
213 """
212 """
214 if self.existing:
213 if self.existing:
215 try:
214 try:
216 cf = find_connection_file(self.existing)
215 cf = find_connection_file(self.existing)
217 except Exception:
216 except Exception:
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
217 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 self.exit(1)
218 self.exit(1)
220 self.log.info("Connecting to existing kernel: %s" % cf)
219 self.log.info("Connecting to existing kernel: %s" % cf)
221 self.connection_file = cf
220 self.connection_file = cf
222 else:
221 else:
223 # not existing, check if we are going to write the file
222 # not existing, check if we are going to write the file
224 # and ensure that self.connection_file is a full path, not just the shortname
223 # and ensure that self.connection_file is a full path, not just the shortname
225 try:
224 try:
226 cf = find_connection_file(self.connection_file)
225 cf = find_connection_file(self.connection_file)
227 except Exception:
226 except Exception:
228 # file might not exist
227 # file might not exist
229 if self.connection_file == os.path.basename(self.connection_file):
228 if self.connection_file == os.path.basename(self.connection_file):
230 # just shortname, put it in security dir
229 # just shortname, put it in security dir
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
230 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 else:
231 else:
233 cf = self.connection_file
232 cf = self.connection_file
234 self.connection_file = cf
233 self.connection_file = cf
235
234
236 # should load_connection_file only be used for existing?
235 # should load_connection_file only be used for existing?
237 # as it is now, this allows reusing ports if an existing
236 # as it is now, this allows reusing ports if an existing
238 # file is requested
237 # file is requested
239 try:
238 try:
240 self.load_connection_file()
239 self.load_connection_file()
241 except Exception:
240 except Exception:
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
241 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 self.exit(1)
242 self.exit(1)
244
243
245 def load_connection_file(self):
244 def load_connection_file(self):
246 """load ip/port/hmac config from JSON connection file"""
245 """load ip/port/hmac config from JSON connection file"""
247 # this is identical to IPKernelApp.load_connection_file
246 # this is identical to IPKernelApp.load_connection_file
248 # perhaps it can be centralized somewhere?
247 # perhaps it can be centralized somewhere?
249 try:
248 try:
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
249 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 except IOError:
250 except IOError:
252 self.log.debug("Connection File not found: %s", self.connection_file)
251 self.log.debug("Connection File not found: %s", self.connection_file)
253 return
252 return
254 self.log.debug(u"Loading connection file %s", fname)
253 self.log.debug(u"Loading connection file %s", fname)
255 with open(fname) as f:
254 with open(fname) as f:
256 cfg = json.load(f)
255 cfg = json.load(f)
256 self.transport = cfg.get('transport', 'tcp')
257 self.ip = cfg.get('ip', LOCALHOST)
257
258
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 name = channel + '_port'
260 name = channel + '_port'
263 if getattr(self, name) == 0 and name in cfg:
261 if getattr(self, name) == 0 and name in cfg:
264 # not overridden by config or cl_args
262 # not overridden by config or cl_args
265 setattr(self, name, cfg[name])
263 setattr(self, name, cfg[name])
266 if 'key' in cfg:
264 if 'key' in cfg:
267 self.config.Session.key = str_to_bytes(cfg['key'])
265 self.config.Session.key = str_to_bytes(cfg['key'])
268 if 'signature_scheme' in cfg:
266 if 'signature_scheme' in cfg:
269 self.config.Session.signature_scheme = cfg['signature_scheme']
267 self.config.Session.signature_scheme = cfg['signature_scheme']
270
268
271 def init_ssh(self):
269 def init_ssh(self):
272 """set up ssh tunnels, if needed."""
270 """set up ssh tunnels, if needed."""
273 if not self.existing or (not self.sshserver and not self.sshkey):
271 if not self.existing or (not self.sshserver and not self.sshkey):
274 return
272 return
275
276 self.load_connection_file()
273 self.load_connection_file()
277
274
278 transport = self.config.KernelManager.transport
275 transport = self.transport
279 ip = self.config.KernelManager.ip
276 ip = self.ip
280
277
281 if transport != 'tcp':
278 if transport != 'tcp':
282 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
279 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
283 sys.exit(-1)
280 sys.exit(-1)
284
281
285 if self.sshkey and not self.sshserver:
282 if self.sshkey and not self.sshserver:
286 # specifying just the key implies that we are connecting directly
283 # specifying just the key implies that we are connecting directly
287 self.sshserver = ip
284 self.sshserver = ip
288 ip = LOCALHOST
285 ip = LOCALHOST
289
286
290 # build connection dict for tunnels:
287 # build connection dict for tunnels:
291 info = dict(ip=ip,
288 info = dict(ip=ip,
292 shell_port=self.shell_port,
289 shell_port=self.shell_port,
293 iopub_port=self.iopub_port,
290 iopub_port=self.iopub_port,
294 stdin_port=self.stdin_port,
291 stdin_port=self.stdin_port,
295 hb_port=self.hb_port
292 hb_port=self.hb_port
296 )
293 )
297
294
298 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
295 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
299
296
300 # tunnels return a new set of ports, which will be on localhost:
297 # tunnels return a new set of ports, which will be on localhost:
301 self.config.KernelManager.ip = LOCALHOST
298 self.ip = LOCALHOST
302 try:
299 try:
303 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
300 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
304 except:
301 except:
305 # even catch KeyboardInterrupt
302 # even catch KeyboardInterrupt
306 self.log.error("Could not setup tunnels", exc_info=True)
303 self.log.error("Could not setup tunnels", exc_info=True)
307 self.exit(1)
304 self.exit(1)
308
305
309 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
306 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
310
307
311 cf = self.connection_file
308 cf = self.connection_file
312 base,ext = os.path.splitext(cf)
309 base,ext = os.path.splitext(cf)
313 base = os.path.basename(base)
310 base = os.path.basename(base)
314 self.connection_file = os.path.basename(base)+'-ssh'+ext
311 self.connection_file = os.path.basename(base)+'-ssh'+ext
315 self.log.critical("To connect another client via this tunnel, use:")
312 self.log.critical("To connect another client via this tunnel, use:")
316 self.log.critical("--existing %s" % self.connection_file)
313 self.log.critical("--existing %s" % self.connection_file)
317
314
318 def _new_connection_file(self):
315 def _new_connection_file(self):
319 cf = ''
316 cf = ''
320 while not cf:
317 while not cf:
321 # we don't need a 128b id to distinguish kernels, use more readable
318 # we don't need a 128b id to distinguish kernels, use more readable
322 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
319 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
323 # kernels can subclass.
320 # kernels can subclass.
324 ident = str(uuid.uuid4()).split('-')[-1]
321 ident = str(uuid.uuid4()).split('-')[-1]
325 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
322 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
326 # only keep if it's actually new. Protect against unlikely collision
323 # only keep if it's actually new. Protect against unlikely collision
327 # in 48b random search space
324 # in 48b random search space
328 cf = cf if not os.path.exists(cf) else ''
325 cf = cf if not os.path.exists(cf) else ''
329 return cf
326 return cf
330
327
331 def init_kernel_manager(self):
328 def init_kernel_manager(self):
332 # Don't let Qt or ZMQ swallow KeyboardInterupts.
329 # Don't let Qt or ZMQ swallow KeyboardInterupts.
333 if self.existing:
330 if self.existing:
334 self.kernel_manager = None
331 self.kernel_manager = None
335 return
332 return
336 signal.signal(signal.SIGINT, signal.SIG_DFL)
333 signal.signal(signal.SIGINT, signal.SIG_DFL)
337
334
338 # Create a KernelManager and start a kernel.
335 # Create a KernelManager and start a kernel.
339 self.kernel_manager = self.kernel_manager_class(
336 self.kernel_manager = self.kernel_manager_class(
337 ip=self.ip,
338 transport=self.transport,
340 shell_port=self.shell_port,
339 shell_port=self.shell_port,
341 iopub_port=self.iopub_port,
340 iopub_port=self.iopub_port,
342 stdin_port=self.stdin_port,
341 stdin_port=self.stdin_port,
343 hb_port=self.hb_port,
342 hb_port=self.hb_port,
344 connection_file=self.connection_file,
343 connection_file=self.connection_file,
345 parent=self,
344 parent=self,
346 )
345 )
347 self.kernel_manager.client_factory = self.kernel_client_class
346 self.kernel_manager.client_factory = self.kernel_client_class
348 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
347 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
349 atexit.register(self.kernel_manager.cleanup_ipc_files)
348 atexit.register(self.kernel_manager.cleanup_ipc_files)
350
349
351 if self.sshserver:
350 if self.sshserver:
352 # ssh, write new connection file
351 # ssh, write new connection file
353 self.kernel_manager.write_connection_file()
352 self.kernel_manager.write_connection_file()
354
353
355 # in case KM defaults / ssh writing changes things:
354 # in case KM defaults / ssh writing changes things:
356 km = self.kernel_manager
355 km = self.kernel_manager
357 self.shell_port=km.shell_port
356 self.shell_port=km.shell_port
358 self.iopub_port=km.iopub_port
357 self.iopub_port=km.iopub_port
359 self.stdin_port=km.stdin_port
358 self.stdin_port=km.stdin_port
360 self.hb_port=km.hb_port
359 self.hb_port=km.hb_port
361 self.connection_file = km.connection_file
360 self.connection_file = km.connection_file
362
361
363 atexit.register(self.kernel_manager.cleanup_connection_file)
362 atexit.register(self.kernel_manager.cleanup_connection_file)
364
363
365 def init_kernel_client(self):
364 def init_kernel_client(self):
366 if self.kernel_manager is not None:
365 if self.kernel_manager is not None:
367 self.kernel_client = self.kernel_manager.client()
366 self.kernel_client = self.kernel_manager.client()
368 else:
367 else:
369 self.kernel_client = self.kernel_client_class(
368 self.kernel_client = self.kernel_client_class(
369 ip=self.ip,
370 transport=self.transport,
370 shell_port=self.shell_port,
371 shell_port=self.shell_port,
371 iopub_port=self.iopub_port,
372 iopub_port=self.iopub_port,
372 stdin_port=self.stdin_port,
373 stdin_port=self.stdin_port,
373 hb_port=self.hb_port,
374 hb_port=self.hb_port,
374 connection_file=self.connection_file,
375 connection_file=self.connection_file,
375 parent=self,
376 parent=self,
376 )
377 )
377
378
378 self.kernel_client.start_channels()
379 self.kernel_client.start_channels()
379
380
380
381
381
382
382 def initialize(self, argv=None):
383 def initialize(self, argv=None):
383 """
384 """
384 Classes which mix this class in should call:
385 Classes which mix this class in should call:
385 IPythonConsoleApp.initialize(self,argv)
386 IPythonConsoleApp.initialize(self,argv)
386 """
387 """
387 self.init_connection_file()
388 self.init_connection_file()
388 default_secure(self.config)
389 default_secure(self.config)
389 self.init_ssh()
390 self.init_ssh()
390 self.init_kernel_manager()
391 self.init_kernel_manager()
391 self.init_kernel_client()
392 self.init_kernel_client()
392
393
@@ -1,198 +1,206 b''
1 """Base class to manage the interaction with a running kernel
1 """Base class to manage the interaction with a running kernel
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import absolute_import
15 from __future__ import absolute_import
16
16
17 import zmq
17 import zmq
18
18
19 # Local imports
19 # Local imports
20 from IPython.config.configurable import LoggingConfigurable
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
21 from IPython.utils.traitlets import (
22 Any, Instance, Type,
22 Any, Instance, Type,
23 )
23 )
24
24
25 from .zmq.session import Session
25 from .zmq.session import Session
26 from .channels import (
26 from .channels import (
27 ShellChannel, IOPubChannel,
27 ShellChannel, IOPubChannel,
28 HBChannel, StdInChannel,
28 HBChannel, StdInChannel,
29 )
29 )
30 from .clientabc import KernelClientABC
30 from .clientabc import KernelClientABC
31 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
32
32
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Main kernel client class
35 # Main kernel client class
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
39 """Communicates with a single kernel on any host via zmq channels.
39 """Communicates with a single kernel on any host via zmq channels.
40
40
41 There are four channels associated with each kernel:
41 There are four channels associated with each kernel:
42
42
43 * shell: for request/reply calls to the kernel.
43 * shell: for request/reply calls to the kernel.
44 * iopub: for the kernel to publish results to frontends.
44 * iopub: for the kernel to publish results to frontends.
45 * hb: for monitoring the kernel's heartbeat.
45 * hb: for monitoring the kernel's heartbeat.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
47
47
48 The methods of the channels are exposed as methods of the client itself
48 The methods of the channels are exposed as methods of the client itself
49 (KernelClient.execute, complete, history, etc.).
49 (KernelClient.execute, complete, history, etc.).
50 See the channels themselves for documentation of these methods.
50 See the channels themselves for documentation of these methods.
51
51
52 """
52 """
53
53
54 # The PyZMQ Context to use for communication with the kernel.
54 # The PyZMQ Context to use for communication with the kernel.
55 context = Instance(zmq.Context)
55 context = Instance(zmq.Context)
56 def _context_default(self):
56 def _context_default(self):
57 return zmq.Context.instance()
57 return zmq.Context.instance()
58
58
59 # The Session to use for communication with the kernel.
59 # The Session to use for communication with the kernel.
60 session = Instance(Session)
60 session = Instance(Session)
61 def _session_default(self):
61 def _session_default(self):
62 return Session(parent=self)
62 return Session(parent=self)
63
63
64 # The classes to use for the various channels
64 # The classes to use for the various channels
65 shell_channel_class = Type(ShellChannel)
65 shell_channel_class = Type(ShellChannel)
66 iopub_channel_class = Type(IOPubChannel)
66 iopub_channel_class = Type(IOPubChannel)
67 stdin_channel_class = Type(StdInChannel)
67 stdin_channel_class = Type(StdInChannel)
68 hb_channel_class = Type(HBChannel)
68 hb_channel_class = Type(HBChannel)
69
69
70 # Protected traits
70 # Protected traits
71 _shell_channel = Any
71 _shell_channel = Any
72 _iopub_channel = Any
72 _iopub_channel = Any
73 _stdin_channel = Any
73 _stdin_channel = Any
74 _hb_channel = Any
74 _hb_channel = Any
75
75
76 #--------------------------------------------------------------------------
76 #--------------------------------------------------------------------------
77 # Channel proxy methods
77 # Channel proxy methods
78 #--------------------------------------------------------------------------
78 #--------------------------------------------------------------------------
79
79
80 def _get_msg(channel, *args, **kwargs):
80 def _get_msg(channel, *args, **kwargs):
81 return channel.get_msg(*args, **kwargs)
81 return channel.get_msg(*args, **kwargs)
82
82
83 def get_shell_msg(self, *args, **kwargs):
83 def get_shell_msg(self, *args, **kwargs):
84 """Get a message from the shell channel"""
84 """Get a message from the shell channel"""
85 return self.shell_channel.get_msg(*args, **kwargs)
85 return self.shell_channel.get_msg(*args, **kwargs)
86
86
87 def get_iopub_msg(self, *args, **kwargs):
87 def get_iopub_msg(self, *args, **kwargs):
88 """Get a message from the iopub channel"""
88 """Get a message from the iopub channel"""
89 return self.iopub_channel.get_msg(*args, **kwargs)
89 return self.iopub_channel.get_msg(*args, **kwargs)
90
90
91 def get_stdin_msg(self, *args, **kwargs):
91 def get_stdin_msg(self, *args, **kwargs):
92 """Get a message from the stdin channel"""
92 """Get a message from the stdin channel"""
93 return self.stdin_channel.get_msg(*args, **kwargs)
93 return self.stdin_channel.get_msg(*args, **kwargs)
94
94
95 #--------------------------------------------------------------------------
95 #--------------------------------------------------------------------------
96 # Channel management methods
96 # Channel management methods
97 #--------------------------------------------------------------------------
97 #--------------------------------------------------------------------------
98
98
99 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
99 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
100 """Starts the channels for this kernel.
100 """Starts the channels for this kernel.
101
101
102 This will create the channels if they do not exist and then start
102 This will create the channels if they do not exist and then start
103 them (their activity runs in a thread). If port numbers of 0 are
103 them (their activity runs in a thread). If port numbers of 0 are
104 being used (random ports) then you must first call
104 being used (random ports) then you must first call
105 :method:`start_kernel`. If the channels have been stopped and you
105 :method:`start_kernel`. If the channels have been stopped and you
106 call this, :class:`RuntimeError` will be raised.
106 call this, :class:`RuntimeError` will be raised.
107 """
107 """
108 if shell:
108 if shell:
109 self.shell_channel.start()
109 self.shell_channel.start()
110 for method in self.shell_channel.proxy_methods:
110 for method in self.shell_channel.proxy_methods:
111 setattr(self, method, getattr(self.shell_channel, method))
111 setattr(self, method, getattr(self.shell_channel, method))
112 if iopub:
112 if iopub:
113 self.iopub_channel.start()
113 self.iopub_channel.start()
114 for method in self.iopub_channel.proxy_methods:
114 for method in self.iopub_channel.proxy_methods:
115 setattr(self, method, getattr(self.iopub_channel, method))
115 setattr(self, method, getattr(self.iopub_channel, method))
116 if stdin:
116 if stdin:
117 self.stdin_channel.start()
117 self.stdin_channel.start()
118 for method in self.stdin_channel.proxy_methods:
118 for method in self.stdin_channel.proxy_methods:
119 setattr(self, method, getattr(self.stdin_channel, method))
119 setattr(self, method, getattr(self.stdin_channel, method))
120 self.shell_channel.allow_stdin = True
120 self.shell_channel.allow_stdin = True
121 else:
121 else:
122 self.shell_channel.allow_stdin = False
122 self.shell_channel.allow_stdin = False
123 if hb:
123 if hb:
124 self.hb_channel.start()
124 self.hb_channel.start()
125
125
126 def stop_channels(self):
126 def stop_channels(self):
127 """Stops all the running channels for this kernel.
127 """Stops all the running channels for this kernel.
128
128
129 This stops their event loops and joins their threads.
129 This stops their event loops and joins their threads.
130 """
130 """
131 if self.shell_channel.is_alive():
131 if self.shell_channel.is_alive():
132 self.shell_channel.stop()
132 self.shell_channel.stop()
133 if self.iopub_channel.is_alive():
133 if self.iopub_channel.is_alive():
134 self.iopub_channel.stop()
134 self.iopub_channel.stop()
135 if self.stdin_channel.is_alive():
135 if self.stdin_channel.is_alive():
136 self.stdin_channel.stop()
136 self.stdin_channel.stop()
137 if self.hb_channel.is_alive():
137 if self.hb_channel.is_alive():
138 self.hb_channel.stop()
138 self.hb_channel.stop()
139
139
140 @property
140 @property
141 def channels_running(self):
141 def channels_running(self):
142 """Are any of the channels created and running?"""
142 """Are any of the channels created and running?"""
143 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
143 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
144 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
144 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
145
145
146 @property
146 @property
147 def shell_channel(self):
147 def shell_channel(self):
148 """Get the shell channel object for this kernel."""
148 """Get the shell channel object for this kernel."""
149 if self._shell_channel is None:
149 if self._shell_channel is None:
150 url = self._make_url('shell')
151 self.log.debug("connecting shell channel to %s", url)
150 self._shell_channel = self.shell_channel_class(
152 self._shell_channel = self.shell_channel_class(
151 self.context, self.session, self._make_url('shell')
153 self.context, self.session, url
152 )
154 )
153 return self._shell_channel
155 return self._shell_channel
154
156
155 @property
157 @property
156 def iopub_channel(self):
158 def iopub_channel(self):
157 """Get the iopub channel object for this kernel."""
159 """Get the iopub channel object for this kernel."""
158 if self._iopub_channel is None:
160 if self._iopub_channel is None:
161 url = self._make_url('iopub')
162 self.log.debug("connecting iopub channel to %s", url)
159 self._iopub_channel = self.iopub_channel_class(
163 self._iopub_channel = self.iopub_channel_class(
160 self.context, self.session, self._make_url('iopub')
164 self.context, self.session, url
161 )
165 )
162 return self._iopub_channel
166 return self._iopub_channel
163
167
164 @property
168 @property
165 def stdin_channel(self):
169 def stdin_channel(self):
166 """Get the stdin channel object for this kernel."""
170 """Get the stdin channel object for this kernel."""
167 if self._stdin_channel is None:
171 if self._stdin_channel is None:
172 url = self._make_url('stdin')
173 self.log.debug("connecting stdin channel to %s", url)
168 self._stdin_channel = self.stdin_channel_class(
174 self._stdin_channel = self.stdin_channel_class(
169 self.context, self.session, self._make_url('stdin')
175 self.context, self.session, url
170 )
176 )
171 return self._stdin_channel
177 return self._stdin_channel
172
178
173 @property
179 @property
174 def hb_channel(self):
180 def hb_channel(self):
175 """Get the hb channel object for this kernel."""
181 """Get the hb channel object for this kernel."""
176 if self._hb_channel is None:
182 if self._hb_channel is None:
183 url = self._make_url('hb')
184 self.log.debug("connecting heartbeat channel to %s", url)
177 self._hb_channel = self.hb_channel_class(
185 self._hb_channel = self.hb_channel_class(
178 self.context, self.session, self._make_url('hb')
186 self.context, self.session, url
179 )
187 )
180 return self._hb_channel
188 return self._hb_channel
181
189
182 def is_alive(self):
190 def is_alive(self):
183 """Is the kernel process still running?"""
191 """Is the kernel process still running?"""
184 if self._hb_channel is not None:
192 if self._hb_channel is not None:
185 # We didn't start the kernel with this KernelManager so we
193 # We didn't start the kernel with this KernelManager so we
186 # use the heartbeat.
194 # use the heartbeat.
187 return self._hb_channel.is_beating()
195 return self._hb_channel.is_beating()
188 else:
196 else:
189 # no heartbeat and not local, we can't tell if it's running,
197 # no heartbeat and not local, we can't tell if it's running,
190 # so naively return True
198 # so naively return True
191 return True
199 return True
192
200
193
201
194 #-----------------------------------------------------------------------------
202 #-----------------------------------------------------------------------------
195 # ABC Registration
203 # ABC Registration
196 #-----------------------------------------------------------------------------
204 #-----------------------------------------------------------------------------
197
205
198 KernelClientABC.register(KernelClient)
206 KernelClientABC.register(KernelClient)
@@ -1,385 +1,382 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 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26
26
27 # If run on Windows, install an exception hook which pops up a
27 # If run on Windows, install an exception hook which pops up a
28 # message box. Pythonw.exe hides the console, so without this
28 # message box. Pythonw.exe hides the console, so without this
29 # the application silently fails to load.
29 # the application silently fails to load.
30 #
30 #
31 # We always install this handler, because the expectation is for
31 # We always install this handler, because the expectation is for
32 # qtconsole to bring up a GUI even if called from the console.
32 # qtconsole to bring up a GUI even if called from the console.
33 # The old handler is called, so the exception is printed as well.
33 # The old handler is called, so the exception is printed as well.
34 # If desired, check for pythonw with an additional condition
34 # If desired, check for pythonw with an additional condition
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 if os.name == 'nt':
36 if os.name == 'nt':
37 old_excepthook = sys.excepthook
37 old_excepthook = sys.excepthook
38
38
39 def gui_excepthook(exctype, value, tb):
39 def gui_excepthook(exctype, value, tb):
40 try:
40 try:
41 import ctypes, traceback
41 import ctypes, traceback
42 MB_ICONERROR = 0x00000010L
42 MB_ICONERROR = 0x00000010L
43 title = u'Error starting IPython QtConsole'
43 title = u'Error starting IPython QtConsole'
44 msg = u''.join(traceback.format_exception(exctype, value, tb))
44 msg = u''.join(traceback.format_exception(exctype, value, tb))
45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
46 finally:
46 finally:
47 # Also call the old exception hook to let it do
47 # Also call the old exception hook to let it do
48 # its thing too.
48 # its thing too.
49 old_excepthook(exctype, value, tb)
49 old_excepthook(exctype, value, tb)
50
50
51 sys.excepthook = gui_excepthook
51 sys.excepthook = gui_excepthook
52
52
53 # System library imports
53 # System library imports
54 from IPython.external.qt import QtCore, QtGui
54 from IPython.external.qt import QtCore, QtGui
55
55
56 # Local imports
56 # Local imports
57 from IPython.config.application import catch_config_error
57 from IPython.config.application import catch_config_error
58 from IPython.core.application import BaseIPythonApplication
58 from IPython.core.application import BaseIPythonApplication
59 from IPython.qt.console.ipython_widget import IPythonWidget
59 from IPython.qt.console.ipython_widget import IPythonWidget
60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
61 from IPython.qt.console import styles
61 from IPython.qt.console import styles
62 from IPython.qt.console.mainwindow import MainWindow
62 from IPython.qt.console.mainwindow import MainWindow
63 from IPython.qt.client import QtKernelClient
63 from IPython.qt.client import QtKernelClient
64 from IPython.qt.manager import QtKernelManager
64 from IPython.qt.manager import QtKernelManager
65 from IPython.utils.traitlets import (
65 from IPython.utils.traitlets import (
66 Dict, Unicode, CBool, Any
66 Dict, Unicode, CBool, Any
67 )
67 )
68
68
69 from IPython.consoleapp import (
69 from IPython.consoleapp import (
70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
71 )
71 )
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Network Constants
74 # Network Constants
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Globals
80 # Globals
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82
82
83 _examples = """
83 _examples = """
84 ipython qtconsole # start the qtconsole
84 ipython qtconsole # start the qtconsole
85 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
85 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
86 """
86 """
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Aliases and Flags
89 # Aliases and Flags
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 # start with copy of flags
92 # start with copy of flags
93 flags = dict(flags)
93 flags = dict(flags)
94 qt_flags = {
94 qt_flags = {
95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
96 "Disable rich text support."),
96 "Disable rich text support."),
97 }
97 }
98
98
99 # and app_flags from the Console Mixin
99 # and app_flags from the Console Mixin
100 qt_flags.update(app_flags)
100 qt_flags.update(app_flags)
101 # add frontend flags to the full set
101 # add frontend flags to the full set
102 flags.update(qt_flags)
102 flags.update(qt_flags)
103
103
104 # start with copy of front&backend aliases list
104 # start with copy of front&backend aliases list
105 aliases = dict(aliases)
105 aliases = dict(aliases)
106 qt_aliases = dict(
106 qt_aliases = dict(
107 style = 'IPythonWidget.syntax_style',
107 style = 'IPythonWidget.syntax_style',
108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
109 colors = 'ZMQInteractiveShell.colors',
109 colors = 'ZMQInteractiveShell.colors',
110
110
111 editor = 'IPythonWidget.editor',
111 editor = 'IPythonWidget.editor',
112 paging = 'ConsoleWidget.paging',
112 paging = 'ConsoleWidget.paging',
113 )
113 )
114 # and app_aliases from the Console Mixin
114 # and app_aliases from the Console Mixin
115 qt_aliases.update(app_aliases)
115 qt_aliases.update(app_aliases)
116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
117 # add frontend aliases to the full set
117 # add frontend aliases to the full set
118 aliases.update(qt_aliases)
118 aliases.update(qt_aliases)
119
119
120 # get flags&aliases into sets, and remove a couple that
120 # get flags&aliases into sets, and remove a couple that
121 # shouldn't be scrubbed from backend flags:
121 # shouldn't be scrubbed from backend flags:
122 qt_aliases = set(qt_aliases.keys())
122 qt_aliases = set(qt_aliases.keys())
123 qt_aliases.remove('colors')
123 qt_aliases.remove('colors')
124 qt_flags = set(qt_flags.keys())
124 qt_flags = set(qt_flags.keys())
125
125
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127 # Classes
127 # Classes
128 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
129
129
130 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
131 # IPythonQtConsole
131 # IPythonQtConsole
132 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
133
133
134
134
135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
136 name = 'ipython-qtconsole'
136 name = 'ipython-qtconsole'
137
137
138 description = """
138 description = """
139 The IPython QtConsole.
139 The IPython QtConsole.
140
140
141 This launches a Console-style application using Qt. It is not a full
141 This launches a Console-style application using Qt. It is not a full
142 console, in that launched terminal subprocesses will not be able to accept
142 console, in that launched terminal subprocesses will not be able to accept
143 input.
143 input.
144
144
145 The QtConsole supports various extra features beyond the Terminal IPython
145 The QtConsole supports various extra features beyond the Terminal IPython
146 shell, such as inline plotting with matplotlib, via:
146 shell, such as inline plotting with matplotlib, via:
147
147
148 ipython qtconsole --matplotlib=inline
148 ipython qtconsole --matplotlib=inline
149
149
150 as well as saving your session as HTML, and printing the output.
150 as well as saving your session as HTML, and printing the output.
151
151
152 """
152 """
153 examples = _examples
153 examples = _examples
154
154
155 classes = [IPythonWidget] + IPythonConsoleApp.classes
155 classes = [IPythonWidget] + IPythonConsoleApp.classes
156 flags = Dict(flags)
156 flags = Dict(flags)
157 aliases = Dict(aliases)
157 aliases = Dict(aliases)
158 frontend_flags = Any(qt_flags)
158 frontend_flags = Any(qt_flags)
159 frontend_aliases = Any(qt_aliases)
159 frontend_aliases = Any(qt_aliases)
160 kernel_client_class = QtKernelClient
160 kernel_client_class = QtKernelClient
161 kernel_manager_class = QtKernelManager
161 kernel_manager_class = QtKernelManager
162
162
163 stylesheet = Unicode('', config=True,
163 stylesheet = Unicode('', config=True,
164 help="path to a custom CSS stylesheet")
164 help="path to a custom CSS stylesheet")
165
165
166 hide_menubar = CBool(False, config=True,
166 hide_menubar = CBool(False, config=True,
167 help="Start the console window with the menu bar hidden.")
167 help="Start the console window with the menu bar hidden.")
168
168
169 maximize = CBool(False, config=True,
169 maximize = CBool(False, config=True,
170 help="Start the console window maximized.")
170 help="Start the console window maximized.")
171
171
172 plain = CBool(False, config=True,
172 plain = CBool(False, config=True,
173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
174
174
175 def _plain_changed(self, name, old, new):
175 def _plain_changed(self, name, old, new):
176 kind = 'plain' if new else 'rich'
176 kind = 'plain' if new else 'rich'
177 self.config.ConsoleWidget.kind = kind
177 self.config.ConsoleWidget.kind = kind
178 if new:
178 if new:
179 self.widget_factory = IPythonWidget
179 self.widget_factory = IPythonWidget
180 else:
180 else:
181 self.widget_factory = RichIPythonWidget
181 self.widget_factory = RichIPythonWidget
182
182
183 # the factory for creating a widget
183 # the factory for creating a widget
184 widget_factory = Any(RichIPythonWidget)
184 widget_factory = Any(RichIPythonWidget)
185
185
186 def parse_command_line(self, argv=None):
186 def parse_command_line(self, argv=None):
187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
188 self.build_kernel_argv(argv)
188 self.build_kernel_argv(argv)
189
189
190
190
191 def new_frontend_master(self):
191 def new_frontend_master(self):
192 """ Create and return new frontend attached to new kernel, launched on localhost.
192 """ Create and return new frontend attached to new kernel, launched on localhost.
193 """
193 """
194 kernel_manager = self.kernel_manager_class(
194 kernel_manager = self.kernel_manager_class(
195 connection_file=self._new_connection_file(),
195 connection_file=self._new_connection_file(),
196 parent=self,
196 parent=self,
197 autorestart=True,
197 autorestart=True,
198 )
198 )
199 # start the kernel
199 # start the kernel
200 kwargs = dict()
200 kwargs = dict()
201 kwargs['extra_arguments'] = self.kernel_argv
201 kwargs['extra_arguments'] = self.kernel_argv
202 kernel_manager.start_kernel(**kwargs)
202 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.client_factory = self.kernel_client_class
203 kernel_manager.client_factory = self.kernel_client_class
204 kernel_client = kernel_manager.client()
204 kernel_client = kernel_manager.client()
205 kernel_client.start_channels(shell=True, iopub=True)
205 kernel_client.start_channels(shell=True, iopub=True)
206 widget = self.widget_factory(config=self.config,
206 widget = self.widget_factory(config=self.config,
207 local_kernel=True)
207 local_kernel=True)
208 self.init_colors(widget)
208 self.init_colors(widget)
209 widget.kernel_manager = kernel_manager
209 widget.kernel_manager = kernel_manager
210 widget.kernel_client = kernel_client
210 widget.kernel_client = kernel_client
211 widget._existing = False
211 widget._existing = False
212 widget._may_close = True
212 widget._may_close = True
213 widget._confirm_exit = self.confirm_exit
213 widget._confirm_exit = self.confirm_exit
214 return widget
214 return widget
215
215
216 def new_frontend_slave(self, current_widget):
216 def new_frontend_slave(self, current_widget):
217 """Create and return a new frontend attached to an existing kernel.
217 """Create and return a new frontend attached to an existing kernel.
218
218
219 Parameters
219 Parameters
220 ----------
220 ----------
221 current_widget : IPythonWidget
221 current_widget : IPythonWidget
222 The IPythonWidget whose kernel this frontend is to share
222 The IPythonWidget whose kernel this frontend is to share
223 """
223 """
224 kernel_client = self.kernel_client_class(
224 kernel_client = self.kernel_client_class(
225 connection_file=current_widget.kernel_client.connection_file,
225 connection_file=current_widget.kernel_client.connection_file,
226 config = self.config,
226 config = self.config,
227 )
227 )
228 kernel_client.load_connection_file()
228 kernel_client.load_connection_file()
229 kernel_client.start_channels()
229 kernel_client.start_channels()
230 widget = self.widget_factory(config=self.config,
230 widget = self.widget_factory(config=self.config,
231 local_kernel=False)
231 local_kernel=False)
232 self.init_colors(widget)
232 self.init_colors(widget)
233 widget._existing = True
233 widget._existing = True
234 widget._may_close = False
234 widget._may_close = False
235 widget._confirm_exit = False
235 widget._confirm_exit = False
236 widget.kernel_client = kernel_client
236 widget.kernel_client = kernel_client
237 widget.kernel_manager = current_widget.kernel_manager
237 widget.kernel_manager = current_widget.kernel_manager
238 return widget
238 return widget
239
239
240 def init_qt_app(self):
240 def init_qt_app(self):
241 # separate from qt_elements, because it must run first
241 # separate from qt_elements, because it must run first
242 self.app = QtGui.QApplication([])
242 self.app = QtGui.QApplication([])
243
243
244 def init_qt_elements(self):
244 def init_qt_elements(self):
245 # Create the widget.
245 # Create the widget.
246
246
247 base_path = os.path.abspath(os.path.dirname(__file__))
247 base_path = os.path.abspath(os.path.dirname(__file__))
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
249 self.app.icon = QtGui.QIcon(icon_path)
249 self.app.icon = QtGui.QIcon(icon_path)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
251
251
252 try:
252 ip = self.ip
253 ip = self.config.KernelManager.ip
254 except AttributeError:
255 ip = LOCALHOST
256 local_kernel = (not self.existing) or ip in LOCAL_IPS
253 local_kernel = (not self.existing) or ip in LOCAL_IPS
257 self.widget = self.widget_factory(config=self.config,
254 self.widget = self.widget_factory(config=self.config,
258 local_kernel=local_kernel)
255 local_kernel=local_kernel)
259 self.init_colors(self.widget)
256 self.init_colors(self.widget)
260 self.widget._existing = self.existing
257 self.widget._existing = self.existing
261 self.widget._may_close = not self.existing
258 self.widget._may_close = not self.existing
262 self.widget._confirm_exit = self.confirm_exit
259 self.widget._confirm_exit = self.confirm_exit
263
260
264 self.widget.kernel_manager = self.kernel_manager
261 self.widget.kernel_manager = self.kernel_manager
265 self.widget.kernel_client = self.kernel_client
262 self.widget.kernel_client = self.kernel_client
266 self.window = MainWindow(self.app,
263 self.window = MainWindow(self.app,
267 confirm_exit=self.confirm_exit,
264 confirm_exit=self.confirm_exit,
268 new_frontend_factory=self.new_frontend_master,
265 new_frontend_factory=self.new_frontend_master,
269 slave_frontend_factory=self.new_frontend_slave,
266 slave_frontend_factory=self.new_frontend_slave,
270 )
267 )
271 self.window.log = self.log
268 self.window.log = self.log
272 self.window.add_tab_with_frontend(self.widget)
269 self.window.add_tab_with_frontend(self.widget)
273 self.window.init_menu_bar()
270 self.window.init_menu_bar()
274
271
275 # Ignore on OSX, where there is always a menu bar
272 # Ignore on OSX, where there is always a menu bar
276 if sys.platform != 'darwin' and self.hide_menubar:
273 if sys.platform != 'darwin' and self.hide_menubar:
277 self.window.menuBar().setVisible(False)
274 self.window.menuBar().setVisible(False)
278
275
279 self.window.setWindowTitle('IPython')
276 self.window.setWindowTitle('IPython')
280
277
281 def init_colors(self, widget):
278 def init_colors(self, widget):
282 """Configure the coloring of the widget"""
279 """Configure the coloring of the widget"""
283 # Note: This will be dramatically simplified when colors
280 # Note: This will be dramatically simplified when colors
284 # are removed from the backend.
281 # are removed from the backend.
285
282
286 # parse the colors arg down to current known labels
283 # parse the colors arg down to current known labels
287 try:
284 try:
288 colors = self.config.ZMQInteractiveShell.colors
285 colors = self.config.ZMQInteractiveShell.colors
289 except AttributeError:
286 except AttributeError:
290 colors = None
287 colors = None
291 try:
288 try:
292 style = self.config.IPythonWidget.syntax_style
289 style = self.config.IPythonWidget.syntax_style
293 except AttributeError:
290 except AttributeError:
294 style = None
291 style = None
295 try:
292 try:
296 sheet = self.config.IPythonWidget.style_sheet
293 sheet = self.config.IPythonWidget.style_sheet
297 except AttributeError:
294 except AttributeError:
298 sheet = None
295 sheet = None
299
296
300 # find the value for colors:
297 # find the value for colors:
301 if colors:
298 if colors:
302 colors=colors.lower()
299 colors=colors.lower()
303 if colors in ('lightbg', 'light'):
300 if colors in ('lightbg', 'light'):
304 colors='lightbg'
301 colors='lightbg'
305 elif colors in ('dark', 'linux'):
302 elif colors in ('dark', 'linux'):
306 colors='linux'
303 colors='linux'
307 else:
304 else:
308 colors='nocolor'
305 colors='nocolor'
309 elif style:
306 elif style:
310 if style=='bw':
307 if style=='bw':
311 colors='nocolor'
308 colors='nocolor'
312 elif styles.dark_style(style):
309 elif styles.dark_style(style):
313 colors='linux'
310 colors='linux'
314 else:
311 else:
315 colors='lightbg'
312 colors='lightbg'
316 else:
313 else:
317 colors=None
314 colors=None
318
315
319 # Configure the style
316 # Configure the style
320 if style:
317 if style:
321 widget.style_sheet = styles.sheet_from_template(style, colors)
318 widget.style_sheet = styles.sheet_from_template(style, colors)
322 widget.syntax_style = style
319 widget.syntax_style = style
323 widget._syntax_style_changed()
320 widget._syntax_style_changed()
324 widget._style_sheet_changed()
321 widget._style_sheet_changed()
325 elif colors:
322 elif colors:
326 # use a default dark/light/bw style
323 # use a default dark/light/bw style
327 widget.set_default_style(colors=colors)
324 widget.set_default_style(colors=colors)
328
325
329 if self.stylesheet:
326 if self.stylesheet:
330 # we got an explicit stylesheet
327 # we got an explicit stylesheet
331 if os.path.isfile(self.stylesheet):
328 if os.path.isfile(self.stylesheet):
332 with open(self.stylesheet) as f:
329 with open(self.stylesheet) as f:
333 sheet = f.read()
330 sheet = f.read()
334 else:
331 else:
335 raise IOError("Stylesheet %r not found." % self.stylesheet)
332 raise IOError("Stylesheet %r not found." % self.stylesheet)
336 if sheet:
333 if sheet:
337 widget.style_sheet = sheet
334 widget.style_sheet = sheet
338 widget._style_sheet_changed()
335 widget._style_sheet_changed()
339
336
340
337
341 def init_signal(self):
338 def init_signal(self):
342 """allow clean shutdown on sigint"""
339 """allow clean shutdown on sigint"""
343 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
340 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
344 # need a timer, so that QApplication doesn't block until a real
341 # need a timer, so that QApplication doesn't block until a real
345 # Qt event fires (can require mouse movement)
342 # Qt event fires (can require mouse movement)
346 # timer trick from http://stackoverflow.com/q/4938723/938949
343 # timer trick from http://stackoverflow.com/q/4938723/938949
347 timer = QtCore.QTimer()
344 timer = QtCore.QTimer()
348 # Let the interpreter run each 200 ms:
345 # Let the interpreter run each 200 ms:
349 timer.timeout.connect(lambda: None)
346 timer.timeout.connect(lambda: None)
350 timer.start(200)
347 timer.start(200)
351 # hold onto ref, so the timer doesn't get cleaned up
348 # hold onto ref, so the timer doesn't get cleaned up
352 self._sigint_timer = timer
349 self._sigint_timer = timer
353
350
354 @catch_config_error
351 @catch_config_error
355 def initialize(self, argv=None):
352 def initialize(self, argv=None):
356 self.init_qt_app()
353 self.init_qt_app()
357 super(IPythonQtConsoleApp, self).initialize(argv)
354 super(IPythonQtConsoleApp, self).initialize(argv)
358 IPythonConsoleApp.initialize(self,argv)
355 IPythonConsoleApp.initialize(self,argv)
359 self.init_qt_elements()
356 self.init_qt_elements()
360 self.init_signal()
357 self.init_signal()
361
358
362 def start(self):
359 def start(self):
363
360
364 # draw the window
361 # draw the window
365 if self.maximize:
362 if self.maximize:
366 self.window.showMaximized()
363 self.window.showMaximized()
367 else:
364 else:
368 self.window.show()
365 self.window.show()
369 self.window.raise_()
366 self.window.raise_()
370
367
371 # Start the application main loop.
368 # Start the application main loop.
372 self.app.exec_()
369 self.app.exec_()
373
370
374 #-----------------------------------------------------------------------------
371 #-----------------------------------------------------------------------------
375 # Main entry point
372 # Main entry point
376 #-----------------------------------------------------------------------------
373 #-----------------------------------------------------------------------------
377
374
378 def main():
375 def main():
379 app = IPythonQtConsoleApp()
376 app = IPythonQtConsoleApp()
380 app.initialize()
377 app.initialize()
381 app.start()
378 app.start()
382
379
383
380
384 if __name__ == '__main__':
381 if __name__ == '__main__':
385 main()
382 main()
General Comments 0
You need to be logged in to leave comments. Login now