##// END OF EJS Templates
rename IPythonMixinConsoleApp to IPythonConsoleApp...
MinRK -
Show More
@@ -1,381 +1,381 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/frontend/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/frontend/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 signal
27 import signal
28 import sys
28 import sys
29 import uuid
29 import uuid
30
30
31
31
32 # Local imports
32 # Local imports
33 from IPython.config.application import boolean_flag
33 from IPython.config.application import boolean_flag
34 from IPython.config.configurable import Configurable
34 from IPython.config.configurable import Configurable
35 from IPython.core.profiledir import ProfileDir
35 from IPython.core.profiledir import ProfileDir
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 from IPython.utils.path import filefind
38 from IPython.utils.path import filefind
39 from IPython.utils.py3compat import str_to_bytes
39 from IPython.utils.py3compat import str_to_bytes
40 from IPython.utils.traitlets import (
40 from IPython.utils.traitlets import (
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
41 Dict, List, Unicode, CUnicode, Int, CBool, Any
42 )
42 )
43 from IPython.zmq.ipkernel import (
43 from IPython.zmq.ipkernel import (
44 flags as ipkernel_flags,
44 flags as ipkernel_flags,
45 aliases as ipkernel_aliases,
45 aliases as ipkernel_aliases,
46 IPKernelApp
46 IPKernelApp
47 )
47 )
48 from IPython.zmq.session import Session, default_secure
48 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.zmqshell import ZMQInteractiveShell
49 from IPython.zmq.zmqshell import ZMQInteractiveShell
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Network Constants
52 # Network Constants
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
55 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Globals
58 # Globals
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Aliases and Flags
63 # Aliases and Flags
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 flags = dict(ipkernel_flags)
66 flags = dict(ipkernel_flags)
67
67
68 # the flags that are specific to the frontend
68 # the flags that are specific to the frontend
69 # these must be scrubbed before being passed to the kernel,
69 # these must be scrubbed before being passed to the kernel,
70 # or it will raise an error on unrecognized flags
70 # or it will raise an error on unrecognized flags
71 app_flags = {
71 app_flags = {
72 'existing' : ({'IPythonMixinConsoleApp' : {'existing' : 'kernel*.json'}},
72 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
73 "Connect to an existing kernel. If no argument specified, guess most recent"),
73 "Connect to an existing kernel. If no argument specified, guess most recent"),
74 }
74 }
75 app_flags.update(boolean_flag(
75 app_flags.update(boolean_flag(
76 'confirm-exit', 'IPythonMixinConsoleApp.confirm_exit',
76 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
77 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
77 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
78 to force a direct exit without any confirmation.
78 to force a direct exit without any confirmation.
79 """,
79 """,
80 """Don't prompt the user when exiting. This will terminate the kernel
80 """Don't prompt the user when exiting. This will terminate the kernel
81 if it is owned by the frontend, and leave it alive if it is external.
81 if it is owned by the frontend, and leave it alive if it is external.
82 """
82 """
83 ))
83 ))
84 flags.update(app_flags)
84 flags.update(app_flags)
85
85
86 aliases = dict(ipkernel_aliases)
86 aliases = dict(ipkernel_aliases)
87
87
88 # also scrub aliases from the frontend
88 # also scrub aliases from the frontend
89 app_aliases = dict(
89 app_aliases = dict(
90 hb = 'IPythonMixinConsoleApp.hb_port',
90 hb = 'IPythonConsoleApp.hb_port',
91 shell = 'IPythonMixinConsoleApp.shell_port',
91 shell = 'IPythonConsoleApp.shell_port',
92 iopub = 'IPythonMixinConsoleApp.iopub_port',
92 iopub = 'IPythonConsoleApp.iopub_port',
93 stdin = 'IPythonMixinConsoleApp.stdin_port',
93 stdin = 'IPythonConsoleApp.stdin_port',
94 ip = 'IPythonMixinConsoleApp.ip',
94 ip = 'IPythonConsoleApp.ip',
95 existing = 'IPythonMixinConsoleApp.existing',
95 existing = 'IPythonConsoleApp.existing',
96 f = 'IPythonMixinConsoleApp.connection_file',
96 f = 'IPythonConsoleApp.connection_file',
97
97
98
98
99 ssh = 'IPythonMixinConsoleApp.sshserver',
99 ssh = 'IPythonConsoleApp.sshserver',
100 )
100 )
101 aliases.update(app_aliases)
101 aliases.update(app_aliases)
102
102
103 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
104 # Classes
104 # Classes
105 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
106
106
107 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
108 # IPythonMixinConsole
108 # IPythonConsole
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110
110
111
111
112 class IPythonMixinConsoleApp(Configurable):
112 class IPythonConsoleApp(Configurable):
113 name = 'ipython-console-mixin'
113 name = 'ipython-console-mixin'
114 default_config_file_name='ipython_config.py'
114 default_config_file_name='ipython_config.py'
115
115
116 description = """
116 description = """
117 The IPython Mixin Console.
117 The IPython Mixin Console.
118
118
119 This class contains the common portions of console client (QtConsole,
119 This class contains the common portions of console client (QtConsole,
120 ZMQ-based terminal console, etc). It is not a full console, in that
120 ZMQ-based terminal console, etc). It is not a full console, in that
121 launched terminal subprocesses will not be able to accept input.
121 launched terminal subprocesses will not be able to accept input.
122
122
123 The Console using this mixing supports various extra features beyond
123 The Console using this mixing supports various extra features beyond
124 the single-process Terminal IPython shell, such as connecting to
124 the single-process Terminal IPython shell, such as connecting to
125 existing kernel, via:
125 existing kernel, via:
126
126
127 ipython <appname> --existing
127 ipython <appname> --existing
128
128
129 as well as tunnel via SSH
129 as well as tunnel via SSH
130
130
131 """
131 """
132
132
133 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
133 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
134 flags = Dict(flags)
134 flags = Dict(flags)
135 aliases = Dict(aliases)
135 aliases = Dict(aliases)
136 kernel_manager_class = BlockingKernelManager
136 kernel_manager_class = BlockingKernelManager
137
137
138 kernel_argv = List(Unicode)
138 kernel_argv = List(Unicode)
139
139
140 pure = CBool(False, config=True,
140 pure = CBool(False, config=True,
141 help="Use a pure Python kernel instead of an IPython kernel.")
141 help="Use a pure Python kernel instead of an IPython kernel.")
142 # create requested profiles by default, if they don't exist:
142 # create requested profiles by default, if they don't exist:
143 auto_create = CBool(True)
143 auto_create = CBool(True)
144 # connection info:
144 # connection info:
145 ip = Unicode(LOCALHOST, config=True,
145 ip = Unicode(LOCALHOST, config=True,
146 help="""Set the kernel\'s IP address [default localhost].
146 help="""Set the kernel\'s IP address [default localhost].
147 If the IP address is something other than localhost, then
147 If the IP address is something other than localhost, then
148 Consoles on other machines will be able to connect
148 Consoles on other machines will be able to connect
149 to the Kernel, so be careful!"""
149 to the Kernel, so be careful!"""
150 )
150 )
151
151
152 sshserver = Unicode('', config=True,
152 sshserver = Unicode('', config=True,
153 help="""The SSH server to use to connect to the kernel.""")
153 help="""The SSH server to use to connect to the kernel.""")
154 sshkey = Unicode('', config=True,
154 sshkey = Unicode('', config=True,
155 help="""Path to the ssh key to use for logging in to the ssh server.""")
155 help="""Path to the ssh key to use for logging in to the ssh server.""")
156
156
157 hb_port = Int(0, config=True,
157 hb_port = Int(0, config=True,
158 help="set the heartbeat port [default: random]")
158 help="set the heartbeat port [default: random]")
159 shell_port = Int(0, config=True,
159 shell_port = Int(0, config=True,
160 help="set the shell (XREP) port [default: random]")
160 help="set the shell (XREP) port [default: random]")
161 iopub_port = Int(0, config=True,
161 iopub_port = Int(0, config=True,
162 help="set the iopub (PUB) port [default: random]")
162 help="set the iopub (PUB) port [default: random]")
163 stdin_port = Int(0, config=True,
163 stdin_port = Int(0, config=True,
164 help="set the stdin (XREQ) port [default: random]")
164 help="set the stdin (XREQ) port [default: random]")
165 connection_file = Unicode('', config=True,
165 connection_file = Unicode('', config=True,
166 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
166 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
167
167
168 This file will contain the IP, ports, and authentication key needed to connect
168 This file will contain the IP, ports, and authentication key needed to connect
169 clients to this kernel. By default, this file will be created in the security-dir
169 clients to this kernel. By default, this file will be created in the security-dir
170 of the current profile, but can be specified by absolute path.
170 of the current profile, but can be specified by absolute path.
171 """)
171 """)
172 def _connection_file_default(self):
172 def _connection_file_default(self):
173 return 'kernel-%i.json' % os.getpid()
173 return 'kernel-%i.json' % os.getpid()
174
174
175 existing = CUnicode('', config=True,
175 existing = CUnicode('', config=True,
176 help="""Connect to an already running kernel""")
176 help="""Connect to an already running kernel""")
177
177
178 confirm_exit = CBool(True, config=True,
178 confirm_exit = CBool(True, config=True,
179 help="""
179 help="""
180 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
180 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
181 to force a direct exit without any confirmation.""",
181 to force a direct exit without any confirmation.""",
182 )
182 )
183
183
184
184
185 def parse_command_line(self, argv=None):
185 def parse_command_line(self, argv=None):
186 #super(PythonBaseConsoleApp, self).parse_command_line(argv)
186 #super(PythonBaseConsoleApp, self).parse_command_line(argv)
187 # make this stuff after this a function, in case the super stuff goes
187 # make this stuff after this a function, in case the super stuff goes
188 # away. Also, Min notes that this functionality should be moved to a
188 # away. Also, Min notes that this functionality should be moved to a
189 # generic library of kernel stuff
189 # generic library of kernel stuff
190 self.swallow_args(app_aliases,app_flags,argv=argv)
190 self.swallow_args(app_aliases,app_flags,argv=argv)
191
191
192 def swallow_args(self, aliases,flags, argv=None):
192 def swallow_args(self, aliases,flags, argv=None):
193 if argv is None:
193 if argv is None:
194 argv = sys.argv[1:]
194 argv = sys.argv[1:]
195 self.kernel_argv = list(argv) # copy
195 self.kernel_argv = list(argv) # copy
196 # kernel should inherit default config file from frontend
196 # kernel should inherit default config file from frontend
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
198 # Scrub frontend-specific flags
198 # Scrub frontend-specific flags
199 swallow_next = False
199 swallow_next = False
200 was_flag = False
200 was_flag = False
201 for a in argv:
201 for a in argv:
202 if swallow_next:
202 if swallow_next:
203 swallow_next = False
203 swallow_next = False
204 # last arg was an alias, remove the next one
204 # last arg was an alias, remove the next one
205 # *unless* the last alias has a no-arg flag version, in which
205 # *unless* the last alias has a no-arg flag version, in which
206 # case, don't swallow the next arg if it's also a flag:
206 # case, don't swallow the next arg if it's also a flag:
207 if not (was_flag and a.startswith('-')):
207 if not (was_flag and a.startswith('-')):
208 self.kernel_argv.remove(a)
208 self.kernel_argv.remove(a)
209 continue
209 continue
210 if a.startswith('-'):
210 if a.startswith('-'):
211 split = a.lstrip('-').split('=')
211 split = a.lstrip('-').split('=')
212 alias = split[0]
212 alias = split[0]
213 if alias in aliases:
213 if alias in aliases:
214 self.kernel_argv.remove(a)
214 self.kernel_argv.remove(a)
215 if len(split) == 1:
215 if len(split) == 1:
216 # alias passed with arg via space
216 # alias passed with arg via space
217 swallow_next = True
217 swallow_next = True
218 # could have been a flag that matches an alias, e.g. `existing`
218 # could have been a flag that matches an alias, e.g. `existing`
219 # in which case, we might not swallow the next arg
219 # in which case, we might not swallow the next arg
220 was_flag = alias in flags
220 was_flag = alias in flags
221 elif alias in flags:
221 elif alias in flags:
222 # strip flag, but don't swallow next, as flags don't take args
222 # strip flag, but don't swallow next, as flags don't take args
223 self.kernel_argv.remove(a)
223 self.kernel_argv.remove(a)
224
224
225 def init_connection_file(self):
225 def init_connection_file(self):
226 """find the connection file, and load the info if found.
226 """find the connection file, and load the info if found.
227
227
228 The current working directory and the current profile's security
228 The current working directory and the current profile's security
229 directory will be searched for the file if it is not given by
229 directory will be searched for the file if it is not given by
230 absolute path.
230 absolute path.
231
231
232 When attempting to connect to an existing kernel and the `--existing`
232 When attempting to connect to an existing kernel and the `--existing`
233 argument does not match an existing file, it will be interpreted as a
233 argument does not match an existing file, it will be interpreted as a
234 fileglob, and the matching file in the current profile's security dir
234 fileglob, and the matching file in the current profile's security dir
235 with the latest access time will be used.
235 with the latest access time will be used.
236
236
237 After this method is called, self.connection_file contains the *full path*
237 After this method is called, self.connection_file contains the *full path*
238 to the connection file, never just its name.
238 to the connection file, never just its name.
239 """
239 """
240 if self.existing:
240 if self.existing:
241 try:
241 try:
242 cf = find_connection_file(self.existing)
242 cf = find_connection_file(self.existing)
243 except Exception:
243 except Exception:
244 self.log.critical("Could not find existing kernel connection file %s", self.existing)
244 self.log.critical("Could not find existing kernel connection file %s", self.existing)
245 self.exit(1)
245 self.exit(1)
246 self.log.info("Connecting to existing kernel: %s" % cf)
246 self.log.info("Connecting to existing kernel: %s" % cf)
247 self.connection_file = cf
247 self.connection_file = cf
248 else:
248 else:
249 # not existing, check if we are going to write the file
249 # not existing, check if we are going to write the file
250 # and ensure that self.connection_file is a full path, not just the shortname
250 # and ensure that self.connection_file is a full path, not just the shortname
251 try:
251 try:
252 cf = find_connection_file(self.connection_file)
252 cf = find_connection_file(self.connection_file)
253 except Exception:
253 except Exception:
254 # file might not exist
254 # file might not exist
255 if self.connection_file == os.path.basename(self.connection_file):
255 if self.connection_file == os.path.basename(self.connection_file):
256 # just shortname, put it in security dir
256 # just shortname, put it in security dir
257 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
257 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
258 else:
258 else:
259 cf = self.connection_file
259 cf = self.connection_file
260 self.connection_file = cf
260 self.connection_file = cf
261
261
262 # should load_connection_file only be used for existing?
262 # should load_connection_file only be used for existing?
263 # as it is now, this allows reusing ports if an existing
263 # as it is now, this allows reusing ports if an existing
264 # file is requested
264 # file is requested
265 try:
265 try:
266 self.load_connection_file()
266 self.load_connection_file()
267 except Exception:
267 except Exception:
268 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
268 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
269 self.exit(1)
269 self.exit(1)
270
270
271 def load_connection_file(self):
271 def load_connection_file(self):
272 """load ip/port/hmac config from JSON connection file"""
272 """load ip/port/hmac config from JSON connection file"""
273 # this is identical to KernelApp.load_connection_file
273 # this is identical to KernelApp.load_connection_file
274 # perhaps it can be centralized somewhere?
274 # perhaps it can be centralized somewhere?
275 try:
275 try:
276 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
276 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
277 except IOError:
277 except IOError:
278 self.log.debug("Connection File not found: %s", self.connection_file)
278 self.log.debug("Connection File not found: %s", self.connection_file)
279 return
279 return
280 self.log.debug(u"Loading connection file %s", fname)
280 self.log.debug(u"Loading connection file %s", fname)
281 with open(fname) as f:
281 with open(fname) as f:
282 s = f.read()
282 s = f.read()
283 cfg = json.loads(s)
283 cfg = json.loads(s)
284 if self.ip == LOCALHOST and 'ip' in cfg:
284 if self.ip == LOCALHOST and 'ip' in cfg:
285 # not overridden by config or cl_args
285 # not overridden by config or cl_args
286 self.ip = cfg['ip']
286 self.ip = cfg['ip']
287 for channel in ('hb', 'shell', 'iopub', 'stdin'):
287 for channel in ('hb', 'shell', 'iopub', 'stdin'):
288 name = channel + '_port'
288 name = channel + '_port'
289 if getattr(self, name) == 0 and name in cfg:
289 if getattr(self, name) == 0 and name in cfg:
290 # not overridden by config or cl_args
290 # not overridden by config or cl_args
291 setattr(self, name, cfg[name])
291 setattr(self, name, cfg[name])
292 if 'key' in cfg:
292 if 'key' in cfg:
293 self.config.Session.key = str_to_bytes(cfg['key'])
293 self.config.Session.key = str_to_bytes(cfg['key'])
294
294
295 def init_ssh(self):
295 def init_ssh(self):
296 """set up ssh tunnels, if needed."""
296 """set up ssh tunnels, if needed."""
297 if not self.sshserver and not self.sshkey:
297 if not self.sshserver and not self.sshkey:
298 return
298 return
299
299
300 if self.sshkey and not self.sshserver:
300 if self.sshkey and not self.sshserver:
301 # specifying just the key implies that we are connecting directly
301 # specifying just the key implies that we are connecting directly
302 self.sshserver = self.ip
302 self.sshserver = self.ip
303 self.ip = LOCALHOST
303 self.ip = LOCALHOST
304
304
305 # build connection dict for tunnels:
305 # build connection dict for tunnels:
306 info = dict(ip=self.ip,
306 info = dict(ip=self.ip,
307 shell_port=self.shell_port,
307 shell_port=self.shell_port,
308 iopub_port=self.iopub_port,
308 iopub_port=self.iopub_port,
309 stdin_port=self.stdin_port,
309 stdin_port=self.stdin_port,
310 hb_port=self.hb_port
310 hb_port=self.hb_port
311 )
311 )
312
312
313 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
313 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
314
314
315 # tunnels return a new set of ports, which will be on localhost:
315 # tunnels return a new set of ports, which will be on localhost:
316 self.ip = LOCALHOST
316 self.ip = LOCALHOST
317 try:
317 try:
318 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
318 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
319 except:
319 except:
320 # even catch KeyboardInterrupt
320 # even catch KeyboardInterrupt
321 self.log.error("Could not setup tunnels", exc_info=True)
321 self.log.error("Could not setup tunnels", exc_info=True)
322 self.exit(1)
322 self.exit(1)
323
323
324 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
324 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
325
325
326 cf = self.connection_file
326 cf = self.connection_file
327 base,ext = os.path.splitext(cf)
327 base,ext = os.path.splitext(cf)
328 base = os.path.basename(base)
328 base = os.path.basename(base)
329 self.connection_file = os.path.basename(base)+'-ssh'+ext
329 self.connection_file = os.path.basename(base)+'-ssh'+ext
330 self.log.critical("To connect another client via this tunnel, use:")
330 self.log.critical("To connect another client via this tunnel, use:")
331 self.log.critical("--existing %s" % self.connection_file)
331 self.log.critical("--existing %s" % self.connection_file)
332
332
333 def _new_connection_file(self):
333 def _new_connection_file(self):
334 cf = ''
334 cf = ''
335 while not cf:
335 while not cf:
336 # we don't need a 128b id to distinguish kernels, use more readable
336 # we don't need a 128b id to distinguish kernels, use more readable
337 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
337 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
338 # kernels can subclass.
338 # kernels can subclass.
339 ident = str(uuid.uuid4()).split('-')[-1]
339 ident = str(uuid.uuid4()).split('-')[-1]
340 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
340 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
341 # only keep if it's actually new. Protect against unlikely collision
341 # only keep if it's actually new. Protect against unlikely collision
342 # in 48b random search space
342 # in 48b random search space
343 cf = cf if not os.path.exists(cf) else ''
343 cf = cf if not os.path.exists(cf) else ''
344 return cf
344 return cf
345
345
346 def init_kernel_manager(self):
346 def init_kernel_manager(self):
347 # Don't let Qt or ZMQ swallow KeyboardInterupts.
347 # Don't let Qt or ZMQ swallow KeyboardInterupts.
348 signal.signal(signal.SIGINT, signal.SIG_DFL)
348 signal.signal(signal.SIGINT, signal.SIG_DFL)
349
349
350 # Create a KernelManager and start a kernel.
350 # Create a KernelManager and start a kernel.
351 self.kernel_manager = self.kernel_manager_class(
351 self.kernel_manager = self.kernel_manager_class(
352 ip=self.ip,
352 ip=self.ip,
353 shell_port=self.shell_port,
353 shell_port=self.shell_port,
354 iopub_port=self.iopub_port,
354 iopub_port=self.iopub_port,
355 stdin_port=self.stdin_port,
355 stdin_port=self.stdin_port,
356 hb_port=self.hb_port,
356 hb_port=self.hb_port,
357 connection_file=self.connection_file,
357 connection_file=self.connection_file,
358 config=self.config,
358 config=self.config,
359 )
359 )
360 # start the kernel
360 # start the kernel
361 if not self.existing:
361 if not self.existing:
362 kwargs = dict(ipython=not self.pure)
362 kwargs = dict(ipython=not self.pure)
363 kwargs['extra_arguments'] = self.kernel_argv
363 kwargs['extra_arguments'] = self.kernel_argv
364 self.kernel_manager.start_kernel(**kwargs)
364 self.kernel_manager.start_kernel(**kwargs)
365 elif self.sshserver:
365 elif self.sshserver:
366 # ssh, write new connection file
366 # ssh, write new connection file
367 self.kernel_manager.write_connection_file()
367 self.kernel_manager.write_connection_file()
368 atexit.register(self.kernel_manager.cleanup_connection_file)
368 atexit.register(self.kernel_manager.cleanup_connection_file)
369 self.kernel_manager.start_channels()
369 self.kernel_manager.start_channels()
370
370
371
371
372 def initialize(self, argv=None):
372 def initialize(self, argv=None):
373 """
373 """
374 Classes which mix this class in should call:
374 Classes which mix this class in should call:
375 IPythonMixinConsoleApp.initialize(self,argv)
375 IPythonConsoleApp.initialize(self,argv)
376 """
376 """
377 self.init_connection_file()
377 self.init_connection_file()
378 default_secure(self.config)
378 default_secure(self.config)
379 self.init_ssh()
379 self.init_ssh()
380 self.init_kernel_manager()
380 self.init_kernel_manager()
381
381
@@ -1,350 +1,350 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 json
23 import json
24 import os
24 import os
25 import signal
25 import signal
26 import sys
26 import sys
27 import uuid
27 import uuid
28
28
29 # System library imports
29 # System library imports
30 from IPython.external.qt import QtCore, QtGui
30 from IPython.external.qt import QtCore, QtGui
31
31
32 # Local imports
32 # Local imports
33 from IPython.config.application import boolean_flag, catch_config_error
33 from IPython.config.application import boolean_flag, catch_config_error
34 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.application import BaseIPythonApplication
35 from IPython.core.profiledir import ProfileDir
35 from IPython.core.profiledir import ProfileDir
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
40 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console import styles
41 from IPython.frontend.qt.console.mainwindow import MainWindow
41 from IPython.frontend.qt.console.mainwindow import MainWindow
42 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 from IPython.frontend.qt.kernelmanager import QtKernelManager
43 from IPython.utils.path import filefind
43 from IPython.utils.path import filefind
44 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.utils.traitlets import (
45 from IPython.utils.traitlets import (
46 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
46 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
47 )
47 )
48 from IPython.zmq.ipkernel import IPKernelApp
48 from IPython.zmq.ipkernel import IPKernelApp
49 from IPython.zmq.session import Session, default_secure
49 from IPython.zmq.session import Session, default_secure
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 from IPython.zmq.zmqshell import ZMQInteractiveShell
51
51
52 from IPython.frontend.kernelmixinapp import (
52 from IPython.frontend.consoleapp import (
53 IPythonMixinConsoleApp, app_aliases, app_flags, flags, aliases
53 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
54 )
54 )
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Network Constants
57 # Network Constants
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Globals
63 # Globals
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 _examples = """
66 _examples = """
67 ipython qtconsole # start the qtconsole
67 ipython qtconsole # start the qtconsole
68 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
69 """
69 """
70
70
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 # Aliases and Flags
72 # Aliases and Flags
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74
74
75 # start with copy of flags
75 # start with copy of flags
76 flags = dict(flags)
76 flags = dict(flags)
77 qt_flags = {
77 qt_flags = {
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 "Use a pure Python kernel instead of an IPython kernel."),
79 "Use a pure Python kernel instead of an IPython kernel."),
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 "Disable rich text support."),
81 "Disable rich text support."),
82 }
82 }
83 qt_flags.update(boolean_flag(
83 qt_flags.update(boolean_flag(
84 'gui-completion', 'ConsoleWidget.gui_completion',
84 'gui-completion', 'ConsoleWidget.gui_completion',
85 "use a GUI widget for tab completion",
85 "use a GUI widget for tab completion",
86 "use plaintext output for completion"
86 "use plaintext output for completion"
87 ))
87 ))
88 # and app_flags from the Console Mixin
88 # and app_flags from the Console Mixin
89 qt_flags.update(app_flags)
89 qt_flags.update(app_flags)
90 # add frontend flags to the full set
90 # add frontend flags to the full set
91 flags.update(qt_flags)
91 flags.update(qt_flags)
92
92
93 # start with copy of front&backend aliases list
93 # start with copy of front&backend aliases list
94 aliases = dict(aliases)
94 aliases = dict(aliases)
95 qt_aliases = dict(
95 qt_aliases = dict(
96
96
97 style = 'IPythonWidget.syntax_style',
97 style = 'IPythonWidget.syntax_style',
98 stylesheet = 'IPythonQtConsoleApp.stylesheet',
98 stylesheet = 'IPythonQtConsoleApp.stylesheet',
99 colors = 'ZMQInteractiveShell.colors',
99 colors = 'ZMQInteractiveShell.colors',
100
100
101 editor = 'IPythonWidget.editor',
101 editor = 'IPythonWidget.editor',
102 paging = 'ConsoleWidget.paging',
102 paging = 'ConsoleWidget.paging',
103 )
103 )
104 # and app_aliases from the Console Mixin
104 # and app_aliases from the Console Mixin
105 qt_aliases.update(app_aliases)
105 qt_aliases.update(app_aliases)
106 # add frontend aliases to the full set
106 # add frontend aliases to the full set
107 aliases.update(qt_aliases)
107 aliases.update(qt_aliases)
108
108
109 # get flags&aliases into sets, and remove a couple that
109 # get flags&aliases into sets, and remove a couple that
110 # shouldn't be scrubbed from backend flags:
110 # shouldn't be scrubbed from backend flags:
111 qt_aliases = set(qt_aliases.keys())
111 qt_aliases = set(qt_aliases.keys())
112 qt_aliases.remove('colors')
112 qt_aliases.remove('colors')
113 qt_flags = set(qt_flags.keys())
113 qt_flags = set(qt_flags.keys())
114
114
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 # Classes
116 # Classes
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118
118
119 #-----------------------------------------------------------------------------
119 #-----------------------------------------------------------------------------
120 # IPythonQtConsole
120 # IPythonQtConsole
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122
122
123
123
124 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonMixinConsoleApp):
124 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
125 name = 'ipython-qtconsole'
125 name = 'ipython-qtconsole'
126
126
127 description = """
127 description = """
128 The IPython QtConsole.
128 The IPython QtConsole.
129
129
130 This launches a Console-style application using Qt. It is not a full
130 This launches a Console-style application using Qt. It is not a full
131 console, in that launched terminal subprocesses will not be able to accept
131 console, in that launched terminal subprocesses will not be able to accept
132 input.
132 input.
133
133
134 The QtConsole supports various extra features beyond the Terminal IPython
134 The QtConsole supports various extra features beyond the Terminal IPython
135 shell, such as inline plotting with matplotlib, via:
135 shell, such as inline plotting with matplotlib, via:
136
136
137 ipython qtconsole --pylab=inline
137 ipython qtconsole --pylab=inline
138
138
139 as well as saving your session as HTML, and printing the output.
139 as well as saving your session as HTML, and printing the output.
140
140
141 """
141 """
142 examples = _examples
142 examples = _examples
143
143
144 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
144 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
145 flags = Dict(flags)
145 flags = Dict(flags)
146 aliases = Dict(aliases)
146 aliases = Dict(aliases)
147 kernel_manager_class = QtKernelManager
147 kernel_manager_class = QtKernelManager
148
148
149 stylesheet = Unicode('', config=True,
149 stylesheet = Unicode('', config=True,
150 help="path to a custom CSS stylesheet")
150 help="path to a custom CSS stylesheet")
151
151
152 plain = CBool(False, config=True,
152 plain = CBool(False, config=True,
153 help="Use a plaintext widget instead of rich text (plain can't print/save).")
153 help="Use a plaintext widget instead of rich text (plain can't print/save).")
154
154
155 def _pure_changed(self, name, old, new):
155 def _pure_changed(self, name, old, new):
156 kind = 'plain' if self.plain else 'rich'
156 kind = 'plain' if self.plain else 'rich'
157 self.config.ConsoleWidget.kind = kind
157 self.config.ConsoleWidget.kind = kind
158 if self.pure:
158 if self.pure:
159 self.widget_factory = FrontendWidget
159 self.widget_factory = FrontendWidget
160 elif self.plain:
160 elif self.plain:
161 self.widget_factory = IPythonWidget
161 self.widget_factory = IPythonWidget
162 else:
162 else:
163 self.widget_factory = RichIPythonWidget
163 self.widget_factory = RichIPythonWidget
164
164
165 _plain_changed = _pure_changed
165 _plain_changed = _pure_changed
166
166
167 # the factory for creating a widget
167 # the factory for creating a widget
168 widget_factory = Any(RichIPythonWidget)
168 widget_factory = Any(RichIPythonWidget)
169
169
170 def parse_command_line(self, argv=None):
170 def parse_command_line(self, argv=None):
171 super(IPythonQtConsoleApp, self).parse_command_line(argv)
171 super(IPythonQtConsoleApp, self).parse_command_line(argv)
172 self.swallow_args(qt_aliases,qt_flags,argv=argv)
172 self.swallow_args(qt_aliases,qt_flags,argv=argv)
173
173
174
174
175 def new_frontend_master(self):
175 def new_frontend_master(self):
176 """ Create and return new frontend attached to new kernel, launched on localhost.
176 """ Create and return new frontend attached to new kernel, launched on localhost.
177 """
177 """
178 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
178 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
179 kernel_manager = QtKernelManager(
179 kernel_manager = QtKernelManager(
180 ip=ip,
180 ip=ip,
181 connection_file=self._new_connection_file(),
181 connection_file=self._new_connection_file(),
182 config=self.config,
182 config=self.config,
183 )
183 )
184 # start the kernel
184 # start the kernel
185 kwargs = dict(ipython=not self.pure)
185 kwargs = dict(ipython=not self.pure)
186 kwargs['extra_arguments'] = self.kernel_argv
186 kwargs['extra_arguments'] = self.kernel_argv
187 kernel_manager.start_kernel(**kwargs)
187 kernel_manager.start_kernel(**kwargs)
188 kernel_manager.start_channels()
188 kernel_manager.start_channels()
189 widget = self.widget_factory(config=self.config,
189 widget = self.widget_factory(config=self.config,
190 local_kernel=True)
190 local_kernel=True)
191 widget.kernel_manager = kernel_manager
191 widget.kernel_manager = kernel_manager
192 widget._existing = False
192 widget._existing = False
193 widget._may_close = True
193 widget._may_close = True
194 widget._confirm_exit = self.confirm_exit
194 widget._confirm_exit = self.confirm_exit
195 return widget
195 return widget
196
196
197 def new_frontend_slave(self, current_widget):
197 def new_frontend_slave(self, current_widget):
198 """Create and return a new frontend attached to an existing kernel.
198 """Create and return a new frontend attached to an existing kernel.
199
199
200 Parameters
200 Parameters
201 ----------
201 ----------
202 current_widget : IPythonWidget
202 current_widget : IPythonWidget
203 The IPythonWidget whose kernel this frontend is to share
203 The IPythonWidget whose kernel this frontend is to share
204 """
204 """
205 kernel_manager = QtKernelManager(
205 kernel_manager = QtKernelManager(
206 connection_file=current_widget.kernel_manager.connection_file,
206 connection_file=current_widget.kernel_manager.connection_file,
207 config = self.config,
207 config = self.config,
208 )
208 )
209 kernel_manager.load_connection_file()
209 kernel_manager.load_connection_file()
210 kernel_manager.start_channels()
210 kernel_manager.start_channels()
211 widget = self.widget_factory(config=self.config,
211 widget = self.widget_factory(config=self.config,
212 local_kernel=False)
212 local_kernel=False)
213 widget._existing = True
213 widget._existing = True
214 widget._may_close = False
214 widget._may_close = False
215 widget._confirm_exit = False
215 widget._confirm_exit = False
216 widget.kernel_manager = kernel_manager
216 widget.kernel_manager = kernel_manager
217 return widget
217 return widget
218
218
219 def init_qt_elements(self):
219 def init_qt_elements(self):
220 # Create the widget.
220 # Create the widget.
221 self.app = QtGui.QApplication([])
221 self.app = QtGui.QApplication([])
222
222
223 base_path = os.path.abspath(os.path.dirname(__file__))
223 base_path = os.path.abspath(os.path.dirname(__file__))
224 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
224 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
225 self.app.icon = QtGui.QIcon(icon_path)
225 self.app.icon = QtGui.QIcon(icon_path)
226 QtGui.QApplication.setWindowIcon(self.app.icon)
226 QtGui.QApplication.setWindowIcon(self.app.icon)
227
227
228 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
228 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
229 self.widget = self.widget_factory(config=self.config,
229 self.widget = self.widget_factory(config=self.config,
230 local_kernel=local_kernel)
230 local_kernel=local_kernel)
231 self.widget._existing = self.existing
231 self.widget._existing = self.existing
232 self.widget._may_close = not self.existing
232 self.widget._may_close = not self.existing
233 self.widget._confirm_exit = self.confirm_exit
233 self.widget._confirm_exit = self.confirm_exit
234
234
235 self.widget.kernel_manager = self.kernel_manager
235 self.widget.kernel_manager = self.kernel_manager
236 self.window = MainWindow(self.app,
236 self.window = MainWindow(self.app,
237 confirm_exit=self.confirm_exit,
237 confirm_exit=self.confirm_exit,
238 new_frontend_factory=self.new_frontend_master,
238 new_frontend_factory=self.new_frontend_master,
239 slave_frontend_factory=self.new_frontend_slave,
239 slave_frontend_factory=self.new_frontend_slave,
240 )
240 )
241 self.window.log = self.log
241 self.window.log = self.log
242 self.window.add_tab_with_frontend(self.widget)
242 self.window.add_tab_with_frontend(self.widget)
243 self.window.init_menu_bar()
243 self.window.init_menu_bar()
244
244
245 self.window.setWindowTitle('Python' if self.pure else 'IPython')
245 self.window.setWindowTitle('Python' if self.pure else 'IPython')
246
246
247 def init_colors(self):
247 def init_colors(self):
248 """Configure the coloring of the widget"""
248 """Configure the coloring of the widget"""
249 # Note: This will be dramatically simplified when colors
249 # Note: This will be dramatically simplified when colors
250 # are removed from the backend.
250 # are removed from the backend.
251
251
252 if self.pure:
252 if self.pure:
253 # only IPythonWidget supports styling
253 # only IPythonWidget supports styling
254 return
254 return
255
255
256 # parse the colors arg down to current known labels
256 # parse the colors arg down to current known labels
257 try:
257 try:
258 colors = self.config.ZMQInteractiveShell.colors
258 colors = self.config.ZMQInteractiveShell.colors
259 except AttributeError:
259 except AttributeError:
260 colors = None
260 colors = None
261 try:
261 try:
262 style = self.config.IPythonWidget.syntax_style
262 style = self.config.IPythonWidget.syntax_style
263 except AttributeError:
263 except AttributeError:
264 style = None
264 style = None
265
265
266 # find the value for colors:
266 # find the value for colors:
267 if colors:
267 if colors:
268 colors=colors.lower()
268 colors=colors.lower()
269 if colors in ('lightbg', 'light'):
269 if colors in ('lightbg', 'light'):
270 colors='lightbg'
270 colors='lightbg'
271 elif colors in ('dark', 'linux'):
271 elif colors in ('dark', 'linux'):
272 colors='linux'
272 colors='linux'
273 else:
273 else:
274 colors='nocolor'
274 colors='nocolor'
275 elif style:
275 elif style:
276 if style=='bw':
276 if style=='bw':
277 colors='nocolor'
277 colors='nocolor'
278 elif styles.dark_style(style):
278 elif styles.dark_style(style):
279 colors='linux'
279 colors='linux'
280 else:
280 else:
281 colors='lightbg'
281 colors='lightbg'
282 else:
282 else:
283 colors=None
283 colors=None
284
284
285 # Configure the style.
285 # Configure the style.
286 widget = self.widget
286 widget = self.widget
287 if style:
287 if style:
288 widget.style_sheet = styles.sheet_from_template(style, colors)
288 widget.style_sheet = styles.sheet_from_template(style, colors)
289 widget.syntax_style = style
289 widget.syntax_style = style
290 widget._syntax_style_changed()
290 widget._syntax_style_changed()
291 widget._style_sheet_changed()
291 widget._style_sheet_changed()
292 elif colors:
292 elif colors:
293 # use a default style
293 # use a default style
294 widget.set_default_style(colors=colors)
294 widget.set_default_style(colors=colors)
295 else:
295 else:
296 # this is redundant for now, but allows the widget's
296 # this is redundant for now, but allows the widget's
297 # defaults to change
297 # defaults to change
298 widget.set_default_style()
298 widget.set_default_style()
299
299
300 if self.stylesheet:
300 if self.stylesheet:
301 # we got an expicit stylesheet
301 # we got an expicit stylesheet
302 if os.path.isfile(self.stylesheet):
302 if os.path.isfile(self.stylesheet):
303 with open(self.stylesheet) as f:
303 with open(self.stylesheet) as f:
304 sheet = f.read()
304 sheet = f.read()
305 widget.style_sheet = sheet
305 widget.style_sheet = sheet
306 widget._style_sheet_changed()
306 widget._style_sheet_changed()
307 else:
307 else:
308 raise IOError("Stylesheet %r not found."%self.stylesheet)
308 raise IOError("Stylesheet %r not found."%self.stylesheet)
309
309
310 def init_signal(self):
310 def init_signal(self):
311 """allow clean shutdown on sigint"""
311 """allow clean shutdown on sigint"""
312 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
312 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
313 # need a timer, so that QApplication doesn't block until a real
313 # need a timer, so that QApplication doesn't block until a real
314 # Qt event fires (can require mouse movement)
314 # Qt event fires (can require mouse movement)
315 # timer trick from http://stackoverflow.com/q/4938723/938949
315 # timer trick from http://stackoverflow.com/q/4938723/938949
316 timer = QtCore.QTimer()
316 timer = QtCore.QTimer()
317 # Let the interpreter run each 200 ms:
317 # Let the interpreter run each 200 ms:
318 timer.timeout.connect(lambda: None)
318 timer.timeout.connect(lambda: None)
319 timer.start(200)
319 timer.start(200)
320 # hold onto ref, so the timer doesn't get cleaned up
320 # hold onto ref, so the timer doesn't get cleaned up
321 self._sigint_timer = timer
321 self._sigint_timer = timer
322
322
323 @catch_config_error
323 @catch_config_error
324 def initialize(self, argv=None):
324 def initialize(self, argv=None):
325 super(IPythonQtConsoleApp, self).initialize(argv)
325 super(IPythonQtConsoleApp, self).initialize(argv)
326 IPythonMixinConsoleApp.initialize(self,argv)
326 IPythonConsoleApp.initialize(self,argv)
327 self.init_qt_elements()
327 self.init_qt_elements()
328 self.init_colors()
328 self.init_colors()
329 self.init_signal()
329 self.init_signal()
330
330
331 def start(self):
331 def start(self):
332
332
333 # draw the window
333 # draw the window
334 self.window.show()
334 self.window.show()
335
335
336 # Start the application main loop.
336 # Start the application main loop.
337 self.app.exec_()
337 self.app.exec_()
338
338
339 #-----------------------------------------------------------------------------
339 #-----------------------------------------------------------------------------
340 # Main entry point
340 # Main entry point
341 #-----------------------------------------------------------------------------
341 #-----------------------------------------------------------------------------
342
342
343 def main():
343 def main():
344 app = IPythonQtConsoleApp()
344 app = IPythonQtConsoleApp()
345 app.initialize()
345 app.initialize()
346 app.start()
346 app.start()
347
347
348
348
349 if __name__ == '__main__':
349 if __name__ == '__main__':
350 main()
350 main()
@@ -1,149 +1,149 b''
1 """ A minimal application using the ZMQ-based terminal IPython frontend.
1 """ A minimal application using the ZMQ-based terminal 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 * Min RK
8 * Min RK
9 * Paul Ivanov
9 * Paul Ivanov
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19
19
20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21
21
22 from IPython.utils.traitlets import (
22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 )
24 )
25 from IPython.utils.warn import warn,error
25 from IPython.utils.warn import warn,error
26
26
27 from IPython.zmq.ipkernel import IPKernelApp
27 from IPython.zmq.ipkernel import IPKernelApp
28 from IPython.zmq.session import Session, default_secure
28 from IPython.zmq.session import Session, default_secure
29 from IPython.zmq.zmqshell import ZMQInteractiveShell
29 from IPython.zmq.zmqshell import ZMQInteractiveShell
30 from IPython.frontend.kernelmixinapp import (
30 from IPython.frontend.consoleapp import (
31 IPythonMixinConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 )
32 )
33
33
34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Globals
37 # Globals
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 _examples = """
40 _examples = """
41 ipython console # start the ZMQ-based console
41 ipython console # start the ZMQ-based console
42 ipython console --existing # connect to an existing ipython session
42 ipython console --existing # connect to an existing ipython session
43 """
43 """
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Flags and Aliases
46 # Flags and Aliases
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 # copy flags from mixin:
49 # copy flags from mixin:
50 flags = dict(flags)
50 flags = dict(flags)
51 # start with mixin frontend flags:
51 # start with mixin frontend flags:
52 frontend_flags = dict(app_flags)
52 frontend_flags = dict(app_flags)
53 # add TerminalIPApp flags:
53 # add TerminalIPApp flags:
54 frontend_flags.update(term_flags)
54 frontend_flags.update(term_flags)
55 # pylab is not frontend-specific in two-process IPython
55 # pylab is not frontend-specific in two-process IPython
56 frontend_flags.pop('pylab')
56 frontend_flags.pop('pylab')
57 # disable quick startup, as it won't propagate to the kernel anyway
57 # disable quick startup, as it won't propagate to the kernel anyway
58 frontend_flags.pop('quick')
58 frontend_flags.pop('quick')
59 # update full dict with frontend flags:
59 # update full dict with frontend flags:
60 flags.update(frontend_flags)
60 flags.update(frontend_flags)
61
61
62 # copy flags from mixin
62 # copy flags from mixin
63 aliases = dict(aliases)
63 aliases = dict(aliases)
64 # start with mixin frontend flags
64 # start with mixin frontend flags
65 frontend_aliases = dict(app_aliases)
65 frontend_aliases = dict(app_aliases)
66 # load updated frontend flags into full dict
66 # load updated frontend flags into full dict
67 aliases.update(frontend_aliases)
67 aliases.update(frontend_aliases)
68
68
69 # get flags&aliases into sets, and remove a couple that
69 # get flags&aliases into sets, and remove a couple that
70 # shouldn't be scrubbed from backend flags:
70 # shouldn't be scrubbed from backend flags:
71 frontend_aliases = set(frontend_aliases.keys())
71 frontend_aliases = set(frontend_aliases.keys())
72 frontend_flags = set(frontend_flags.keys())
72 frontend_flags = set(frontend_flags.keys())
73
73
74
74
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76 # Classes
76 # Classes
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78
78
79
79
80 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonMixinConsoleApp):
80 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
81 name = "ipython-console"
81 name = "ipython-console"
82 """Start a terminal frontend to the IPython zmq kernel."""
82 """Start a terminal frontend to the IPython zmq kernel."""
83
83
84 description = """
84 description = """
85 The IPython terminal-based Console.
85 The IPython terminal-based Console.
86
86
87 This launches a Console application inside a terminal.
87 This launches a Console application inside a terminal.
88
88
89 The Console supports various extra features beyond the traditional
89 The Console supports various extra features beyond the traditional
90 single-process Terminal IPython shell, such as connecting to an
90 single-process Terminal IPython shell, such as connecting to an
91 existing ipython session, via:
91 existing ipython session, via:
92
92
93 ipython console --existing
93 ipython console --existing
94
94
95 where the previous session could have been created by another ipython
95 where the previous session could have been created by another ipython
96 console, an ipython qtconsole, or by opening an ipython notebook.
96 console, an ipython qtconsole, or by opening an ipython notebook.
97
97
98 """
98 """
99 examples = _examples
99 examples = _examples
100
100
101 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
101 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
102 flags = Dict(flags)
102 flags = Dict(flags)
103 aliases = Dict(aliases)
103 aliases = Dict(aliases)
104 subcommands = Dict()
104 subcommands = Dict()
105
105
106 def parse_command_line(self, argv=None):
106 def parse_command_line(self, argv=None):
107 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
107 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
108 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
108 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
109
109
110 def init_shell(self):
110 def init_shell(self):
111 IPythonMixinConsoleApp.initialize(self)
111 IPythonConsoleApp.initialize(self)
112 # relay sigint to kernel
112 # relay sigint to kernel
113 signal.signal(signal.SIGINT, self.handle_sigint)
113 signal.signal(signal.SIGINT, self.handle_sigint)
114 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
114 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
115 display_banner=False, profile_dir=self.profile_dir,
115 display_banner=False, profile_dir=self.profile_dir,
116 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
116 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
117
117
118 def handle_sigint(self, *args):
118 def handle_sigint(self, *args):
119 if self.shell._executing:
119 if self.shell._executing:
120 if self.kernel_manager.has_kernel:
120 if self.kernel_manager.has_kernel:
121 # interrupt already gets passed to subprocess by signal handler.
121 # interrupt already gets passed to subprocess by signal handler.
122 # Only if we prevent that should we need to explicitly call
122 # Only if we prevent that should we need to explicitly call
123 # interrupt_kernel, until which time, this would result in a
123 # interrupt_kernel, until which time, this would result in a
124 # double-interrupt:
124 # double-interrupt:
125 # self.kernel_manager.interrupt_kernel()
125 # self.kernel_manager.interrupt_kernel()
126 pass
126 pass
127 else:
127 else:
128 self.shell.write_err('\n')
128 self.shell.write_err('\n')
129 error("Cannot interrupt kernels we didn't start.\n")
129 error("Cannot interrupt kernels we didn't start.\n")
130 else:
130 else:
131 # raise the KeyboardInterrupt if we aren't waiting for execution,
131 # raise the KeyboardInterrupt if we aren't waiting for execution,
132 # so that the interact loop advances, and prompt is redrawn, etc.
132 # so that the interact loop advances, and prompt is redrawn, etc.
133 raise KeyboardInterrupt
133 raise KeyboardInterrupt
134
134
135
135
136 def init_code(self):
136 def init_code(self):
137 # no-op in the frontend, code gets run in the backend
137 # no-op in the frontend, code gets run in the backend
138 pass
138 pass
139
139
140 def launch_new_instance():
140 def launch_new_instance():
141 """Create and run a full blown IPython instance"""
141 """Create and run a full blown IPython instance"""
142 app = ZMQTerminalIPythonApp.instance()
142 app = ZMQTerminalIPythonApp.instance()
143 app.initialize()
143 app.initialize()
144 app.start()
144 app.start()
145
145
146
146
147 if __name__ == '__main__':
147 if __name__ == '__main__':
148 launch_new_instance()
148 launch_new_instance()
149
149
General Comments 0
You need to be logged in to leave comments. Login now