##// END OF EJS Templates
update flags&aliases for two-process apps...
MinRK -
Show More
@@ -1,383 +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' : ({'IPythonMixinConsoleApp' : {'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', 'IPythonMixinConsoleApp.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 = 'IPythonMixinConsoleApp.hb_port',
91 shell = 'IPythonMixinConsoleApp.shell_port',
91 shell = 'IPythonMixinConsoleApp.shell_port',
92 iopub = 'IPythonMixinConsoleApp.iopub_port',
92 iopub = 'IPythonMixinConsoleApp.iopub_port',
93 stdin = 'IPythonMixinConsoleApp.stdin_port',
93 stdin = 'IPythonMixinConsoleApp.stdin_port',
94 ip = 'IPythonMixinConsoleApp.ip',
94 ip = 'IPythonMixinConsoleApp.ip',
95 existing = 'IPythonMixinConsoleApp.existing',
95 existing = 'IPythonMixinConsoleApp.existing',
96 f = 'IPythonMixinConsoleApp.connection_file',
96 f = 'IPythonMixinConsoleApp.connection_file',
97
97
98
98
99 ssh = 'IPythonMixinConsoleApp.sshserver',
99 ssh = 'IPythonMixinConsoleApp.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 # IPythonMixinConsole
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110
110
111
111
112 class IPythonMixinConsoleApp(Configurable):
112 class IPythonMixinConsoleApp(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 # copy again, in case some aliases have the same name as a flag
202 # argv = list(self.kernel_argv)
203 for a in argv:
201 for a in argv:
204 if swallow_next:
202 if swallow_next:
205 swallow_next = False
203 swallow_next = False
206 # last arg was an alias, remove the next one
204 # last arg was an alias, remove the next one
207 # *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
208 # 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:
209 if not (was_flag and a.startswith('-')):
207 if not (was_flag and a.startswith('-')):
210 self.kernel_argv.remove(a)
208 self.kernel_argv.remove(a)
211 continue
209 continue
212 if a.startswith('-'):
210 if a.startswith('-'):
213 split = a.lstrip('-').split('=')
211 split = a.lstrip('-').split('=')
214 alias = split[0]
212 alias = split[0]
215 if alias in aliases:
213 if alias in aliases:
216 self.kernel_argv.remove(a)
214 self.kernel_argv.remove(a)
217 if len(split) == 1:
215 if len(split) == 1:
218 # alias passed with arg via space
216 # alias passed with arg via space
219 swallow_next = True
217 swallow_next = True
220 # 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`
221 # in which case, we might not swallow the next arg
219 # in which case, we might not swallow the next arg
222 was_flag = alias in flags
220 was_flag = alias in flags
223 elif alias in flags:
221 elif alias in flags:
224 # 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
225 self.kernel_argv.remove(a)
223 self.kernel_argv.remove(a)
226
224
227 def init_connection_file(self):
225 def init_connection_file(self):
228 """find the connection file, and load the info if found.
226 """find the connection file, and load the info if found.
229
227
230 The current working directory and the current profile's security
228 The current working directory and the current profile's security
231 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
232 absolute path.
230 absolute path.
233
231
234 When attempting to connect to an existing kernel and the `--existing`
232 When attempting to connect to an existing kernel and the `--existing`
235 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
236 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
237 with the latest access time will be used.
235 with the latest access time will be used.
238
236
239 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*
240 to the connection file, never just its name.
238 to the connection file, never just its name.
241 """
239 """
242 if self.existing:
240 if self.existing:
243 try:
241 try:
244 cf = find_connection_file(self.existing)
242 cf = find_connection_file(self.existing)
245 except Exception:
243 except Exception:
246 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)
247 self.exit(1)
245 self.exit(1)
248 self.log.info("Connecting to existing kernel: %s" % cf)
246 self.log.info("Connecting to existing kernel: %s" % cf)
249 self.connection_file = cf
247 self.connection_file = cf
250 else:
248 else:
251 # not existing, check if we are going to write the file
249 # not existing, check if we are going to write the file
252 # 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
253 try:
251 try:
254 cf = find_connection_file(self.connection_file)
252 cf = find_connection_file(self.connection_file)
255 except Exception:
253 except Exception:
256 # file might not exist
254 # file might not exist
257 if self.connection_file == os.path.basename(self.connection_file):
255 if self.connection_file == os.path.basename(self.connection_file):
258 # just shortname, put it in security dir
256 # just shortname, put it in security dir
259 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)
260 else:
258 else:
261 cf = self.connection_file
259 cf = self.connection_file
262 self.connection_file = cf
260 self.connection_file = cf
263
261
264 # should load_connection_file only be used for existing?
262 # should load_connection_file only be used for existing?
265 # as it is now, this allows reusing ports if an existing
263 # as it is now, this allows reusing ports if an existing
266 # file is requested
264 # file is requested
267 try:
265 try:
268 self.load_connection_file()
266 self.load_connection_file()
269 except Exception:
267 except Exception:
270 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)
271 self.exit(1)
269 self.exit(1)
272
270
273 def load_connection_file(self):
271 def load_connection_file(self):
274 """load ip/port/hmac config from JSON connection file"""
272 """load ip/port/hmac config from JSON connection file"""
275 # this is identical to KernelApp.load_connection_file
273 # this is identical to KernelApp.load_connection_file
276 # perhaps it can be centralized somewhere?
274 # perhaps it can be centralized somewhere?
277 try:
275 try:
278 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
276 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
279 except IOError:
277 except IOError:
280 self.log.debug("Connection File not found: %s", self.connection_file)
278 self.log.debug("Connection File not found: %s", self.connection_file)
281 return
279 return
282 self.log.debug(u"Loading connection file %s", fname)
280 self.log.debug(u"Loading connection file %s", fname)
283 with open(fname) as f:
281 with open(fname) as f:
284 s = f.read()
282 s = f.read()
285 cfg = json.loads(s)
283 cfg = json.loads(s)
286 if self.ip == LOCALHOST and 'ip' in cfg:
284 if self.ip == LOCALHOST and 'ip' in cfg:
287 # not overridden by config or cl_args
285 # not overridden by config or cl_args
288 self.ip = cfg['ip']
286 self.ip = cfg['ip']
289 for channel in ('hb', 'shell', 'iopub', 'stdin'):
287 for channel in ('hb', 'shell', 'iopub', 'stdin'):
290 name = channel + '_port'
288 name = channel + '_port'
291 if getattr(self, name) == 0 and name in cfg:
289 if getattr(self, name) == 0 and name in cfg:
292 # not overridden by config or cl_args
290 # not overridden by config or cl_args
293 setattr(self, name, cfg[name])
291 setattr(self, name, cfg[name])
294 if 'key' in cfg:
292 if 'key' in cfg:
295 self.config.Session.key = str_to_bytes(cfg['key'])
293 self.config.Session.key = str_to_bytes(cfg['key'])
296
294
297 def init_ssh(self):
295 def init_ssh(self):
298 """set up ssh tunnels, if needed."""
296 """set up ssh tunnels, if needed."""
299 if not self.sshserver and not self.sshkey:
297 if not self.sshserver and not self.sshkey:
300 return
298 return
301
299
302 if self.sshkey and not self.sshserver:
300 if self.sshkey and not self.sshserver:
303 # specifying just the key implies that we are connecting directly
301 # specifying just the key implies that we are connecting directly
304 self.sshserver = self.ip
302 self.sshserver = self.ip
305 self.ip = LOCALHOST
303 self.ip = LOCALHOST
306
304
307 # build connection dict for tunnels:
305 # build connection dict for tunnels:
308 info = dict(ip=self.ip,
306 info = dict(ip=self.ip,
309 shell_port=self.shell_port,
307 shell_port=self.shell_port,
310 iopub_port=self.iopub_port,
308 iopub_port=self.iopub_port,
311 stdin_port=self.stdin_port,
309 stdin_port=self.stdin_port,
312 hb_port=self.hb_port
310 hb_port=self.hb_port
313 )
311 )
314
312
315 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))
316
314
317 # 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:
318 self.ip = LOCALHOST
316 self.ip = LOCALHOST
319 try:
317 try:
320 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
318 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
321 except:
319 except:
322 # even catch KeyboardInterrupt
320 # even catch KeyboardInterrupt
323 self.log.error("Could not setup tunnels", exc_info=True)
321 self.log.error("Could not setup tunnels", exc_info=True)
324 self.exit(1)
322 self.exit(1)
325
323
326 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
327
325
328 cf = self.connection_file
326 cf = self.connection_file
329 base,ext = os.path.splitext(cf)
327 base,ext = os.path.splitext(cf)
330 base = os.path.basename(base)
328 base = os.path.basename(base)
331 self.connection_file = os.path.basename(base)+'-ssh'+ext
329 self.connection_file = os.path.basename(base)+'-ssh'+ext
332 self.log.critical("To connect another client via this tunnel, use:")
330 self.log.critical("To connect another client via this tunnel, use:")
333 self.log.critical("--existing %s" % self.connection_file)
331 self.log.critical("--existing %s" % self.connection_file)
334
332
335 def _new_connection_file(self):
333 def _new_connection_file(self):
336 cf = ''
334 cf = ''
337 while not cf:
335 while not cf:
338 # 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
339 # 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
340 # kernels can subclass.
338 # kernels can subclass.
341 ident = str(uuid.uuid4()).split('-')[-1]
339 ident = str(uuid.uuid4()).split('-')[-1]
342 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)
343 # only keep if it's actually new. Protect against unlikely collision
341 # only keep if it's actually new. Protect against unlikely collision
344 # in 48b random search space
342 # in 48b random search space
345 cf = cf if not os.path.exists(cf) else ''
343 cf = cf if not os.path.exists(cf) else ''
346 return cf
344 return cf
347
345
348 def init_kernel_manager(self):
346 def init_kernel_manager(self):
349 # Don't let Qt or ZMQ swallow KeyboardInterupts.
347 # Don't let Qt or ZMQ swallow KeyboardInterupts.
350 signal.signal(signal.SIGINT, signal.SIG_DFL)
348 signal.signal(signal.SIGINT, signal.SIG_DFL)
351
349
352 # Create a KernelManager and start a kernel.
350 # Create a KernelManager and start a kernel.
353 self.kernel_manager = self.kernel_manager_class(
351 self.kernel_manager = self.kernel_manager_class(
354 ip=self.ip,
352 ip=self.ip,
355 shell_port=self.shell_port,
353 shell_port=self.shell_port,
356 iopub_port=self.iopub_port,
354 iopub_port=self.iopub_port,
357 stdin_port=self.stdin_port,
355 stdin_port=self.stdin_port,
358 hb_port=self.hb_port,
356 hb_port=self.hb_port,
359 connection_file=self.connection_file,
357 connection_file=self.connection_file,
360 config=self.config,
358 config=self.config,
361 )
359 )
362 # start the kernel
360 # start the kernel
363 if not self.existing:
361 if not self.existing:
364 kwargs = dict(ipython=not self.pure)
362 kwargs = dict(ipython=not self.pure)
365 kwargs['extra_arguments'] = self.kernel_argv
363 kwargs['extra_arguments'] = self.kernel_argv
366 self.kernel_manager.start_kernel(**kwargs)
364 self.kernel_manager.start_kernel(**kwargs)
367 elif self.sshserver:
365 elif self.sshserver:
368 # ssh, write new connection file
366 # ssh, write new connection file
369 self.kernel_manager.write_connection_file()
367 self.kernel_manager.write_connection_file()
370 atexit.register(self.kernel_manager.cleanup_connection_file)
368 atexit.register(self.kernel_manager.cleanup_connection_file)
371 self.kernel_manager.start_channels()
369 self.kernel_manager.start_channels()
372
370
373
371
374 def initialize(self, argv=None):
372 def initialize(self, argv=None):
375 """
373 """
376 Classes which mix this class in should call:
374 Classes which mix this class in should call:
377 IPythonMixinConsoleApp.initialize(self,argv)
375 IPythonMixinConsoleApp.initialize(self,argv)
378 """
376 """
379 self.init_connection_file()
377 self.init_connection_file()
380 default_secure(self.config)
378 default_secure(self.config)
381 self.init_ssh()
379 self.init_ssh()
382 self.init_kernel_manager()
380 self.init_kernel_manager()
383
381
@@ -1,341 +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.kernelmixinapp import (
53 IPythonMixinConsoleApp, app_aliases, app_flags
53 IPythonMixinConsoleApp, 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 # XXX: the app_flags should really be flags from the mixin
75 # start with copy of flags
76 flags = dict(app_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
89 qt_flags.update(app_flags)
90 # add frontend flags to the full set
88 flags.update(qt_flags)
91 flags.update(qt_flags)
89
92
90 aliases = dict(app_aliases)
93 # start with copy of front&backend aliases list
91
94 aliases = dict(aliases)
92 qt_aliases = dict(
95 qt_aliases = dict(
93
96
94 style = 'IPythonWidget.syntax_style',
97 style = 'IPythonWidget.syntax_style',
95 stylesheet = 'IPythonQtConsoleApp.stylesheet',
98 stylesheet = 'IPythonQtConsoleApp.stylesheet',
96 colors = 'ZMQInteractiveShell.colors',
99 colors = 'ZMQInteractiveShell.colors',
97
100
98 editor = 'IPythonWidget.editor',
101 editor = 'IPythonWidget.editor',
99 paging = 'ConsoleWidget.paging',
102 paging = 'ConsoleWidget.paging',
100 )
103 )
104 # and app_aliases from the Console Mixin
105 qt_aliases.update(app_aliases)
106 # add frontend aliases to the full set
101 aliases.update(qt_aliases)
107 aliases.update(qt_aliases)
102
108
109 # get flags&aliases into sets, and remove a couple that
110 # shouldn't be scrubbed from backend flags:
111 qt_aliases = set(qt_aliases.keys())
112 qt_aliases.remove('colors')
113 qt_flags = set(qt_flags.keys())
114
103 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
104 # Classes
116 # Classes
105 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
106
118
107 #-----------------------------------------------------------------------------
119 #-----------------------------------------------------------------------------
108 # IPythonQtConsole
120 # IPythonQtConsole
109 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
110
122
111
123
112 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonMixinConsoleApp):
124 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonMixinConsoleApp):
113 name = 'ipython-qtconsole'
125 name = 'ipython-qtconsole'
114
126
115 description = """
127 description = """
116 The IPython QtConsole.
128 The IPython QtConsole.
117
129
118 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
119 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
120 input.
132 input.
121
133
122 The QtConsole supports various extra features beyond the Terminal IPython
134 The QtConsole supports various extra features beyond the Terminal IPython
123 shell, such as inline plotting with matplotlib, via:
135 shell, such as inline plotting with matplotlib, via:
124
136
125 ipython qtconsole --pylab=inline
137 ipython qtconsole --pylab=inline
126
138
127 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.
128
140
129 """
141 """
130 examples = _examples
142 examples = _examples
131
143
132 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
144 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
133 flags = Dict(flags)
145 flags = Dict(flags)
134 aliases = Dict(aliases)
146 aliases = Dict(aliases)
135 kernel_manager_class = QtKernelManager
147 kernel_manager_class = QtKernelManager
136
148
137 stylesheet = Unicode('', config=True,
149 stylesheet = Unicode('', config=True,
138 help="path to a custom CSS stylesheet")
150 help="path to a custom CSS stylesheet")
139
151
140 plain = CBool(False, config=True,
152 plain = CBool(False, config=True,
141 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).")
142
154
143 def _pure_changed(self, name, old, new):
155 def _pure_changed(self, name, old, new):
144 kind = 'plain' if self.plain else 'rich'
156 kind = 'plain' if self.plain else 'rich'
145 self.config.ConsoleWidget.kind = kind
157 self.config.ConsoleWidget.kind = kind
146 if self.pure:
158 if self.pure:
147 self.widget_factory = FrontendWidget
159 self.widget_factory = FrontendWidget
148 elif self.plain:
160 elif self.plain:
149 self.widget_factory = IPythonWidget
161 self.widget_factory = IPythonWidget
150 else:
162 else:
151 self.widget_factory = RichIPythonWidget
163 self.widget_factory = RichIPythonWidget
152
164
153 _plain_changed = _pure_changed
165 _plain_changed = _pure_changed
154
166
155 # the factory for creating a widget
167 # the factory for creating a widget
156 widget_factory = Any(RichIPythonWidget)
168 widget_factory = Any(RichIPythonWidget)
157
169
158 def parse_command_line(self, argv=None):
170 def parse_command_line(self, argv=None):
159 super(IPythonQtConsoleApp, self).parse_command_line(argv)
171 super(IPythonQtConsoleApp, self).parse_command_line(argv)
160 IPythonMixinConsoleApp.parse_command_line(self,argv)
161 self.swallow_args(qt_aliases,qt_flags,argv=argv)
172 self.swallow_args(qt_aliases,qt_flags,argv=argv)
162
173
163
164
165
174
166 def new_frontend_master(self):
175 def new_frontend_master(self):
167 """ 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.
168 """
177 """
169 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
178 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
170 kernel_manager = QtKernelManager(
179 kernel_manager = QtKernelManager(
171 ip=ip,
180 ip=ip,
172 connection_file=self._new_connection_file(),
181 connection_file=self._new_connection_file(),
173 config=self.config,
182 config=self.config,
174 )
183 )
175 # start the kernel
184 # start the kernel
176 kwargs = dict(ipython=not self.pure)
185 kwargs = dict(ipython=not self.pure)
177 kwargs['extra_arguments'] = self.kernel_argv
186 kwargs['extra_arguments'] = self.kernel_argv
178 kernel_manager.start_kernel(**kwargs)
187 kernel_manager.start_kernel(**kwargs)
179 kernel_manager.start_channels()
188 kernel_manager.start_channels()
180 widget = self.widget_factory(config=self.config,
189 widget = self.widget_factory(config=self.config,
181 local_kernel=True)
190 local_kernel=True)
182 widget.kernel_manager = kernel_manager
191 widget.kernel_manager = kernel_manager
183 widget._existing = False
192 widget._existing = False
184 widget._may_close = True
193 widget._may_close = True
185 widget._confirm_exit = self.confirm_exit
194 widget._confirm_exit = self.confirm_exit
186 return widget
195 return widget
187
196
188 def new_frontend_slave(self, current_widget):
197 def new_frontend_slave(self, current_widget):
189 """Create and return a new frontend attached to an existing kernel.
198 """Create and return a new frontend attached to an existing kernel.
190
199
191 Parameters
200 Parameters
192 ----------
201 ----------
193 current_widget : IPythonWidget
202 current_widget : IPythonWidget
194 The IPythonWidget whose kernel this frontend is to share
203 The IPythonWidget whose kernel this frontend is to share
195 """
204 """
196 kernel_manager = QtKernelManager(
205 kernel_manager = QtKernelManager(
197 connection_file=current_widget.kernel_manager.connection_file,
206 connection_file=current_widget.kernel_manager.connection_file,
198 config = self.config,
207 config = self.config,
199 )
208 )
200 kernel_manager.load_connection_file()
209 kernel_manager.load_connection_file()
201 kernel_manager.start_channels()
210 kernel_manager.start_channels()
202 widget = self.widget_factory(config=self.config,
211 widget = self.widget_factory(config=self.config,
203 local_kernel=False)
212 local_kernel=False)
204 widget._existing = True
213 widget._existing = True
205 widget._may_close = False
214 widget._may_close = False
206 widget._confirm_exit = False
215 widget._confirm_exit = False
207 widget.kernel_manager = kernel_manager
216 widget.kernel_manager = kernel_manager
208 return widget
217 return widget
209
218
210 def init_qt_elements(self):
219 def init_qt_elements(self):
211 # Create the widget.
220 # Create the widget.
212 self.app = QtGui.QApplication([])
221 self.app = QtGui.QApplication([])
213
222
214 base_path = os.path.abspath(os.path.dirname(__file__))
223 base_path = os.path.abspath(os.path.dirname(__file__))
215 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
224 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
216 self.app.icon = QtGui.QIcon(icon_path)
225 self.app.icon = QtGui.QIcon(icon_path)
217 QtGui.QApplication.setWindowIcon(self.app.icon)
226 QtGui.QApplication.setWindowIcon(self.app.icon)
218
227
219 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
228 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
220 self.widget = self.widget_factory(config=self.config,
229 self.widget = self.widget_factory(config=self.config,
221 local_kernel=local_kernel)
230 local_kernel=local_kernel)
222 self.widget._existing = self.existing
231 self.widget._existing = self.existing
223 self.widget._may_close = not self.existing
232 self.widget._may_close = not self.existing
224 self.widget._confirm_exit = self.confirm_exit
233 self.widget._confirm_exit = self.confirm_exit
225
234
226 self.widget.kernel_manager = self.kernel_manager
235 self.widget.kernel_manager = self.kernel_manager
227 self.window = MainWindow(self.app,
236 self.window = MainWindow(self.app,
228 confirm_exit=self.confirm_exit,
237 confirm_exit=self.confirm_exit,
229 new_frontend_factory=self.new_frontend_master,
238 new_frontend_factory=self.new_frontend_master,
230 slave_frontend_factory=self.new_frontend_slave,
239 slave_frontend_factory=self.new_frontend_slave,
231 )
240 )
232 self.window.log = self.log
241 self.window.log = self.log
233 self.window.add_tab_with_frontend(self.widget)
242 self.window.add_tab_with_frontend(self.widget)
234 self.window.init_menu_bar()
243 self.window.init_menu_bar()
235
244
236 self.window.setWindowTitle('Python' if self.pure else 'IPython')
245 self.window.setWindowTitle('Python' if self.pure else 'IPython')
237
246
238 def init_colors(self):
247 def init_colors(self):
239 """Configure the coloring of the widget"""
248 """Configure the coloring of the widget"""
240 # Note: This will be dramatically simplified when colors
249 # Note: This will be dramatically simplified when colors
241 # are removed from the backend.
250 # are removed from the backend.
242
251
243 if self.pure:
252 if self.pure:
244 # only IPythonWidget supports styling
253 # only IPythonWidget supports styling
245 return
254 return
246
255
247 # parse the colors arg down to current known labels
256 # parse the colors arg down to current known labels
248 try:
257 try:
249 colors = self.config.ZMQInteractiveShell.colors
258 colors = self.config.ZMQInteractiveShell.colors
250 except AttributeError:
259 except AttributeError:
251 colors = None
260 colors = None
252 try:
261 try:
253 style = self.config.IPythonWidget.syntax_style
262 style = self.config.IPythonWidget.syntax_style
254 except AttributeError:
263 except AttributeError:
255 style = None
264 style = None
256
265
257 # find the value for colors:
266 # find the value for colors:
258 if colors:
267 if colors:
259 colors=colors.lower()
268 colors=colors.lower()
260 if colors in ('lightbg', 'light'):
269 if colors in ('lightbg', 'light'):
261 colors='lightbg'
270 colors='lightbg'
262 elif colors in ('dark', 'linux'):
271 elif colors in ('dark', 'linux'):
263 colors='linux'
272 colors='linux'
264 else:
273 else:
265 colors='nocolor'
274 colors='nocolor'
266 elif style:
275 elif style:
267 if style=='bw':
276 if style=='bw':
268 colors='nocolor'
277 colors='nocolor'
269 elif styles.dark_style(style):
278 elif styles.dark_style(style):
270 colors='linux'
279 colors='linux'
271 else:
280 else:
272 colors='lightbg'
281 colors='lightbg'
273 else:
282 else:
274 colors=None
283 colors=None
275
284
276 # Configure the style.
285 # Configure the style.
277 widget = self.widget
286 widget = self.widget
278 if style:
287 if style:
279 widget.style_sheet = styles.sheet_from_template(style, colors)
288 widget.style_sheet = styles.sheet_from_template(style, colors)
280 widget.syntax_style = style
289 widget.syntax_style = style
281 widget._syntax_style_changed()
290 widget._syntax_style_changed()
282 widget._style_sheet_changed()
291 widget._style_sheet_changed()
283 elif colors:
292 elif colors:
284 # use a default style
293 # use a default style
285 widget.set_default_style(colors=colors)
294 widget.set_default_style(colors=colors)
286 else:
295 else:
287 # this is redundant for now, but allows the widget's
296 # this is redundant for now, but allows the widget's
288 # defaults to change
297 # defaults to change
289 widget.set_default_style()
298 widget.set_default_style()
290
299
291 if self.stylesheet:
300 if self.stylesheet:
292 # we got an expicit stylesheet
301 # we got an expicit stylesheet
293 if os.path.isfile(self.stylesheet):
302 if os.path.isfile(self.stylesheet):
294 with open(self.stylesheet) as f:
303 with open(self.stylesheet) as f:
295 sheet = f.read()
304 sheet = f.read()
296 widget.style_sheet = sheet
305 widget.style_sheet = sheet
297 widget._style_sheet_changed()
306 widget._style_sheet_changed()
298 else:
307 else:
299 raise IOError("Stylesheet %r not found."%self.stylesheet)
308 raise IOError("Stylesheet %r not found."%self.stylesheet)
300
309
301 def init_signal(self):
310 def init_signal(self):
302 """allow clean shutdown on sigint"""
311 """allow clean shutdown on sigint"""
303 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
312 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
304 # 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
305 # Qt event fires (can require mouse movement)
314 # Qt event fires (can require mouse movement)
306 # timer trick from http://stackoverflow.com/q/4938723/938949
315 # timer trick from http://stackoverflow.com/q/4938723/938949
307 timer = QtCore.QTimer()
316 timer = QtCore.QTimer()
308 # Let the interpreter run each 200 ms:
317 # Let the interpreter run each 200 ms:
309 timer.timeout.connect(lambda: None)
318 timer.timeout.connect(lambda: None)
310 timer.start(200)
319 timer.start(200)
311 # hold onto ref, so the timer doesn't get cleaned up
320 # hold onto ref, so the timer doesn't get cleaned up
312 self._sigint_timer = timer
321 self._sigint_timer = timer
313
322
314 @catch_config_error
323 @catch_config_error
315 def initialize(self, argv=None):
324 def initialize(self, argv=None):
316 super(IPythonQtConsoleApp, self).initialize(argv)
325 super(IPythonQtConsoleApp, self).initialize(argv)
317 IPythonMixinConsoleApp.initialize(self,argv)
326 IPythonMixinConsoleApp.initialize(self,argv)
318 self.init_qt_elements()
327 self.init_qt_elements()
319 self.init_colors()
328 self.init_colors()
320 self.init_signal()
329 self.init_signal()
321
330
322 def start(self):
331 def start(self):
323
332
324 # draw the window
333 # draw the window
325 self.window.show()
334 self.window.show()
326
335
327 # Start the application main loop.
336 # Start the application main loop.
328 self.app.exec_()
337 self.app.exec_()
329
338
330 #-----------------------------------------------------------------------------
339 #-----------------------------------------------------------------------------
331 # Main entry point
340 # Main entry point
332 #-----------------------------------------------------------------------------
341 #-----------------------------------------------------------------------------
333
342
334 def main():
343 def main():
335 app = IPythonQtConsoleApp()
344 app = IPythonQtConsoleApp()
336 app.initialize()
345 app.initialize()
337 app.start()
346 app.start()
338
347
339
348
340 if __name__ == '__main__':
349 if __name__ == '__main__':
341 main()
350 main()
@@ -1,405 +1,407 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import sys
30 import sys
31
31
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader, ConfigFileNotFound
33 Config, PyFileConfigLoader, ConfigFileNotFound
34 )
34 )
35 from IPython.config.application import boolean_flag, catch_config_error
35 from IPython.config.application import boolean_flag, catch_config_error
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.core import usage
37 from IPython.core import usage
38 from IPython.core.completer import IPCompleter
38 from IPython.core.completer import IPCompleter
39 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.crashhandler import CrashHandler
40 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.formatters import PlainTextFormatter
41 from IPython.core.prompts import PromptManager
41 from IPython.core.prompts import PromptManager
42 from IPython.core.application import (
42 from IPython.core.application import (
43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
44 )
44 )
45 from IPython.core.shellapp import (
45 from IPython.core.shellapp import (
46 InteractiveShellApp, shell_flags, shell_aliases
46 InteractiveShellApp, shell_flags, shell_aliases
47 )
47 )
48 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
48 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
49 from IPython.lib import inputhook
49 from IPython.lib import inputhook
50 from IPython.utils import warn
50 from IPython.utils import warn
51 from IPython.utils.path import get_ipython_dir, check_for_old_config
51 from IPython.utils.path import get_ipython_dir, check_for_old_config
52 from IPython.utils.traitlets import (
52 from IPython.utils.traitlets import (
53 Bool, List, Dict, CaselessStrEnum
53 Bool, List, Dict, CaselessStrEnum
54 )
54 )
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Globals, utilities and helpers
57 # Globals, utilities and helpers
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 #: The default config file name for this application.
60 #: The default config file name for this application.
61 default_config_file_name = u'ipython_config.py'
61 default_config_file_name = u'ipython_config.py'
62
62
63 _examples = """
63 _examples = """
64 ipython --pylab # start in pylab mode
64 ipython --pylab # start in pylab mode
65 ipython --pylab=qt # start in pylab mode with the qt4 backend
65 ipython --pylab=qt # start in pylab mode with the qt4 backend
66 ipython --log-level=DEBUG # set logging to DEBUG
66 ipython --log-level=DEBUG # set logging to DEBUG
67 ipython --profile=foo # start with profile foo
67 ipython --profile=foo # start with profile foo
68
68
69 ipython qtconsole # start the qtconsole GUI application
69 ipython qtconsole # start the qtconsole GUI application
70 ipython qtconsole -h # show the help string for the qtconsole subcmd
70 ipython qtconsole -h # show the help string for the qtconsole subcmd
71
71
72 ipython console # start the terminal-based console application
72 ipython console # start the terminal-based console application
73 ipython console -h # show the help string for the console subcmd
73 ipython console -h # show the help string for the console subcmd
74
74
75 ipython profile create foo # create profile foo w/ default config files
75 ipython profile create foo # create profile foo w/ default config files
76 ipython profile -h # show the help string for the profile subcmd
76 ipython profile -h # show the help string for the profile subcmd
77 """
77 """
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Crash handler for this application
80 # Crash handler for this application
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82
82
83 class IPAppCrashHandler(CrashHandler):
83 class IPAppCrashHandler(CrashHandler):
84 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
84 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
85
85
86 def __init__(self, app):
86 def __init__(self, app):
87 contact_name = release.authors['Fernando'][0]
87 contact_name = release.authors['Fernando'][0]
88 contact_email = release.author_email
88 contact_email = release.author_email
89 bug_tracker = 'https://github.com/ipython/ipython/issues'
89 bug_tracker = 'https://github.com/ipython/ipython/issues'
90 super(IPAppCrashHandler,self).__init__(
90 super(IPAppCrashHandler,self).__init__(
91 app, contact_name, contact_email, bug_tracker
91 app, contact_name, contact_email, bug_tracker
92 )
92 )
93
93
94 def make_report(self,traceback):
94 def make_report(self,traceback):
95 """Return a string containing a crash report."""
95 """Return a string containing a crash report."""
96
96
97 sec_sep = self.section_sep
97 sec_sep = self.section_sep
98 # Start with parent report
98 # Start with parent report
99 report = [super(IPAppCrashHandler, self).make_report(traceback)]
99 report = [super(IPAppCrashHandler, self).make_report(traceback)]
100 # Add interactive-specific info we may have
100 # Add interactive-specific info we may have
101 rpt_add = report.append
101 rpt_add = report.append
102 try:
102 try:
103 rpt_add(sec_sep+"History of session input:")
103 rpt_add(sec_sep+"History of session input:")
104 for line in self.app.shell.user_ns['_ih']:
104 for line in self.app.shell.user_ns['_ih']:
105 rpt_add(line)
105 rpt_add(line)
106 rpt_add('\n*** Last line of input (may not be in above history):\n')
106 rpt_add('\n*** Last line of input (may not be in above history):\n')
107 rpt_add(self.app.shell._last_input_line+'\n')
107 rpt_add(self.app.shell._last_input_line+'\n')
108 except:
108 except:
109 pass
109 pass
110
110
111 return ''.join(report)
111 return ''.join(report)
112
112
113 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
114 # Aliases and Flags
114 # Aliases and Flags
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 flags = dict(base_flags)
116 flags = dict(base_flags)
117 flags.update(shell_flags)
117 flags.update(shell_flags)
118 addflag = lambda *args: flags.update(boolean_flag(*args))
118 frontend_flags = {}
119 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
119 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
120 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
120 'Turn on auto editing of files with syntax errors.',
121 'Turn on auto editing of files with syntax errors.',
121 'Turn off auto editing of files with syntax errors.'
122 'Turn off auto editing of files with syntax errors.'
122 )
123 )
123 addflag('banner', 'TerminalIPythonApp.display_banner',
124 addflag('banner', 'TerminalIPythonApp.display_banner',
124 "Display a banner upon starting IPython.",
125 "Display a banner upon starting IPython.",
125 "Don't display a banner upon starting IPython."
126 "Don't display a banner upon starting IPython."
126 )
127 )
127 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
128 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
128 """Set to confirm when you try to exit IPython with an EOF (Control-D
129 """Set to confirm when you try to exit IPython with an EOF (Control-D
129 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
130 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
130 you can force a direct exit without any confirmation.""",
131 you can force a direct exit without any confirmation.""",
131 "Don't prompt the user when exiting."
132 "Don't prompt the user when exiting."
132 )
133 )
133 addflag('term-title', 'TerminalInteractiveShell.term_title',
134 addflag('term-title', 'TerminalInteractiveShell.term_title',
134 "Enable auto setting the terminal title.",
135 "Enable auto setting the terminal title.",
135 "Disable auto setting the terminal title."
136 "Disable auto setting the terminal title."
136 )
137 )
137 classic_config = Config()
138 classic_config = Config()
138 classic_config.InteractiveShell.cache_size = 0
139 classic_config.InteractiveShell.cache_size = 0
139 classic_config.PlainTextFormatter.pprint = False
140 classic_config.PlainTextFormatter.pprint = False
140 classic_config.PromptManager.in_template = '>>> '
141 classic_config.PromptManager.in_template = '>>> '
141 classic_config.PromptManager.in2_template = '... '
142 classic_config.PromptManager.in2_template = '... '
142 classic_config.PromptManager.out_template = ''
143 classic_config.PromptManager.out_template = ''
143 classic_config.InteractiveShell.separate_in = ''
144 classic_config.InteractiveShell.separate_in = ''
144 classic_config.InteractiveShell.separate_out = ''
145 classic_config.InteractiveShell.separate_out = ''
145 classic_config.InteractiveShell.separate_out2 = ''
146 classic_config.InteractiveShell.separate_out2 = ''
146 classic_config.InteractiveShell.colors = 'NoColor'
147 classic_config.InteractiveShell.colors = 'NoColor'
147 classic_config.InteractiveShell.xmode = 'Plain'
148 classic_config.InteractiveShell.xmode = 'Plain'
148
149
149 flags['classic']=(
150 frontend_flags['classic']=(
150 classic_config,
151 classic_config,
151 "Gives IPython a similar feel to the classic Python prompt."
152 "Gives IPython a similar feel to the classic Python prompt."
152 )
153 )
153 # # log doesn't make so much sense this way anymore
154 # # log doesn't make so much sense this way anymore
154 # paa('--log','-l',
155 # paa('--log','-l',
155 # action='store_true', dest='InteractiveShell.logstart',
156 # action='store_true', dest='InteractiveShell.logstart',
156 # help="Start logging to the default log file (./ipython_log.py).")
157 # help="Start logging to the default log file (./ipython_log.py).")
157 #
158 #
158 # # quick is harder to implement
159 # # quick is harder to implement
159 flags['quick']=(
160 frontend_flags['quick']=(
160 {'TerminalIPythonApp' : {'quick' : True}},
161 {'TerminalIPythonApp' : {'quick' : True}},
161 "Enable quick startup with no config files."
162 "Enable quick startup with no config files."
162 )
163 )
163
164
164 flags['i'] = (
165 frontend_flags['i'] = (
165 {'TerminalIPythonApp' : {'force_interact' : True}},
166 {'TerminalIPythonApp' : {'force_interact' : True}},
166 """If running code from the command line, become interactive afterwards.
167 """If running code from the command line, become interactive afterwards.
167 Note: can also be given simply as '-i.'"""
168 Note: can also be given simply as '-i.'"""
168 )
169 )
169 flags['pylab'] = (
170 frontend_flags['pylab'] = (
170 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
171 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
171 """Pre-load matplotlib and numpy for interactive use with
172 """Pre-load matplotlib and numpy for interactive use with
172 the default matplotlib backend."""
173 the default matplotlib backend."""
173 )
174 )
175 flags.update(frontend_flags)
174
176
175 aliases = dict(base_aliases)
177 aliases = dict(base_aliases)
176 aliases.update(shell_aliases)
178 aliases.update(shell_aliases)
177
179
178 # it's possible we don't want short aliases for *all* of these:
180 # it's possible we don't want short aliases for *all* of these:
179 aliases.update(dict(
181 aliases.update(dict(
180 gui='TerminalIPythonApp.gui',
182 gui='TerminalIPythonApp.gui',
181 pylab='TerminalIPythonApp.pylab',
183 pylab='TerminalIPythonApp.pylab',
182 ))
184 ))
183
185
184 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
185 # Main classes and functions
187 # Main classes and functions
186 #-----------------------------------------------------------------------------
188 #-----------------------------------------------------------------------------
187
189
188 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
190 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
189 name = u'ipython'
191 name = u'ipython'
190 description = usage.cl_usage
192 description = usage.cl_usage
191 default_config_file_name = default_config_file_name
193 default_config_file_name = default_config_file_name
192 crash_handler_class = IPAppCrashHandler
194 crash_handler_class = IPAppCrashHandler
193 examples = _examples
195 examples = _examples
194
196
195 flags = Dict(flags)
197 flags = Dict(flags)
196 aliases = Dict(aliases)
198 aliases = Dict(aliases)
197 classes = List()
199 classes = List()
198 def _classes_default(self):
200 def _classes_default(self):
199 """This has to be in a method, for TerminalIPythonApp to be available."""
201 """This has to be in a method, for TerminalIPythonApp to be available."""
200 return [
202 return [
201 InteractiveShellApp, # ShellApp comes before TerminalApp, because
203 InteractiveShellApp, # ShellApp comes before TerminalApp, because
202 self.__class__, # it will also affect subclasses (e.g. QtConsole)
204 self.__class__, # it will also affect subclasses (e.g. QtConsole)
203 TerminalInteractiveShell,
205 TerminalInteractiveShell,
204 PromptManager,
206 PromptManager,
205 ProfileDir,
207 ProfileDir,
206 PlainTextFormatter,
208 PlainTextFormatter,
207 IPCompleter,
209 IPCompleter,
208 ]
210 ]
209
211
210 subcommands = Dict(dict(
212 subcommands = Dict(dict(
211 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
213 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
212 """Launch the IPython Qt Console."""
214 """Launch the IPython Qt Console."""
213 ),
215 ),
214 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
216 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
215 """Launch the IPython HTML Notebook Server."""
217 """Launch the IPython HTML Notebook Server."""
216 ),
218 ),
217 profile = ("IPython.core.profileapp.ProfileApp",
219 profile = ("IPython.core.profileapp.ProfileApp",
218 "Create and manage IPython profiles."
220 "Create and manage IPython profiles."
219 ),
221 ),
220 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
222 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
221 "Start a kernel without an attached frontend."
223 "Start a kernel without an attached frontend."
222 ),
224 ),
223 console=('IPython.frontend.zmqterminal.app.ZMQTerminalIPythonApp',
225 console=('IPython.frontend.zmqterminal.app.ZMQTerminalIPythonApp',
224 """Launch the IPython terminal-based Console."""
226 """Launch the IPython terminal-based Console."""
225 ),
227 ),
226 ))
228 ))
227
229
228 # *do* autocreate requested profile, but don't create the config file.
230 # *do* autocreate requested profile, but don't create the config file.
229 auto_create=Bool(True)
231 auto_create=Bool(True)
230 # configurables
232 # configurables
231 ignore_old_config=Bool(False, config=True,
233 ignore_old_config=Bool(False, config=True,
232 help="Suppress warning messages about legacy config files"
234 help="Suppress warning messages about legacy config files"
233 )
235 )
234 quick = Bool(False, config=True,
236 quick = Bool(False, config=True,
235 help="""Start IPython quickly by skipping the loading of config files."""
237 help="""Start IPython quickly by skipping the loading of config files."""
236 )
238 )
237 def _quick_changed(self, name, old, new):
239 def _quick_changed(self, name, old, new):
238 if new:
240 if new:
239 self.load_config_file = lambda *a, **kw: None
241 self.load_config_file = lambda *a, **kw: None
240 self.ignore_old_config=True
242 self.ignore_old_config=True
241
243
242 gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True,
244 gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True,
243 help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')."
245 help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')."
244 )
246 )
245 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
247 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
246 config=True,
248 config=True,
247 help="""Pre-load matplotlib and numpy for interactive use,
249 help="""Pre-load matplotlib and numpy for interactive use,
248 selecting a particular matplotlib backend and loop integration.
250 selecting a particular matplotlib backend and loop integration.
249 """
251 """
250 )
252 )
251 display_banner = Bool(True, config=True,
253 display_banner = Bool(True, config=True,
252 help="Whether to display a banner upon starting IPython."
254 help="Whether to display a banner upon starting IPython."
253 )
255 )
254
256
255 # if there is code of files to run from the cmd line, don't interact
257 # if there is code of files to run from the cmd line, don't interact
256 # unless the --i flag (App.force_interact) is true.
258 # unless the --i flag (App.force_interact) is true.
257 force_interact = Bool(False, config=True,
259 force_interact = Bool(False, config=True,
258 help="""If a command or file is given via the command-line,
260 help="""If a command or file is given via the command-line,
259 e.g. 'ipython foo.py"""
261 e.g. 'ipython foo.py"""
260 )
262 )
261 def _force_interact_changed(self, name, old, new):
263 def _force_interact_changed(self, name, old, new):
262 if new:
264 if new:
263 self.interact = True
265 self.interact = True
264
266
265 def _file_to_run_changed(self, name, old, new):
267 def _file_to_run_changed(self, name, old, new):
266 if new and not self.force_interact:
268 if new and not self.force_interact:
267 self.interact = False
269 self.interact = False
268 _code_to_run_changed = _file_to_run_changed
270 _code_to_run_changed = _file_to_run_changed
269
271
270 # internal, not-configurable
272 # internal, not-configurable
271 interact=Bool(True)
273 interact=Bool(True)
272
274
273
275
274 def parse_command_line(self, argv=None):
276 def parse_command_line(self, argv=None):
275 """override to allow old '-pylab' flag with deprecation warning"""
277 """override to allow old '-pylab' flag with deprecation warning"""
276
278
277 argv = sys.argv[1:] if argv is None else argv
279 argv = sys.argv[1:] if argv is None else argv
278
280
279 if '-pylab' in argv:
281 if '-pylab' in argv:
280 # deprecated `-pylab` given,
282 # deprecated `-pylab` given,
281 # warn and transform into current syntax
283 # warn and transform into current syntax
282 argv = argv[:] # copy, don't clobber
284 argv = argv[:] # copy, don't clobber
283 idx = argv.index('-pylab')
285 idx = argv.index('-pylab')
284 warn.warn("`-pylab` flag has been deprecated.\n"
286 warn.warn("`-pylab` flag has been deprecated.\n"
285 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
287 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
286 sub = '--pylab'
288 sub = '--pylab'
287 if len(argv) > idx+1:
289 if len(argv) > idx+1:
288 # check for gui arg, as in '-pylab qt'
290 # check for gui arg, as in '-pylab qt'
289 gui = argv[idx+1]
291 gui = argv[idx+1]
290 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
292 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
291 sub = '--pylab='+gui
293 sub = '--pylab='+gui
292 argv.pop(idx+1)
294 argv.pop(idx+1)
293 argv[idx] = sub
295 argv[idx] = sub
294
296
295 return super(TerminalIPythonApp, self).parse_command_line(argv)
297 return super(TerminalIPythonApp, self).parse_command_line(argv)
296
298
297 @catch_config_error
299 @catch_config_error
298 def initialize(self, argv=None):
300 def initialize(self, argv=None):
299 """Do actions after construct, but before starting the app."""
301 """Do actions after construct, but before starting the app."""
300 super(TerminalIPythonApp, self).initialize(argv)
302 super(TerminalIPythonApp, self).initialize(argv)
301 if self.subapp is not None:
303 if self.subapp is not None:
302 # don't bother initializing further, starting subapp
304 # don't bother initializing further, starting subapp
303 return
305 return
304 if not self.ignore_old_config:
306 if not self.ignore_old_config:
305 check_for_old_config(self.ipython_dir)
307 check_for_old_config(self.ipython_dir)
306 # print self.extra_args
308 # print self.extra_args
307 if self.extra_args:
309 if self.extra_args:
308 self.file_to_run = self.extra_args[0]
310 self.file_to_run = self.extra_args[0]
309 # create the shell
311 # create the shell
310 self.init_shell()
312 self.init_shell()
311 # and draw the banner
313 # and draw the banner
312 self.init_banner()
314 self.init_banner()
313 # Now a variety of things that happen after the banner is printed.
315 # Now a variety of things that happen after the banner is printed.
314 self.init_gui_pylab()
316 self.init_gui_pylab()
315 self.init_extensions()
317 self.init_extensions()
316 self.init_code()
318 self.init_code()
317
319
318 def init_shell(self):
320 def init_shell(self):
319 """initialize the InteractiveShell instance"""
321 """initialize the InteractiveShell instance"""
320 # I am a little hesitant to put these into InteractiveShell itself.
322 # I am a little hesitant to put these into InteractiveShell itself.
321 # But that might be the place for them
323 # But that might be the place for them
322 sys.path.insert(0, '')
324 sys.path.insert(0, '')
323
325
324 # Create an InteractiveShell instance.
326 # Create an InteractiveShell instance.
325 # shell.display_banner should always be False for the terminal
327 # shell.display_banner should always be False for the terminal
326 # based app, because we call shell.show_banner() by hand below
328 # based app, because we call shell.show_banner() by hand below
327 # so the banner shows *before* all extension loading stuff.
329 # so the banner shows *before* all extension loading stuff.
328 self.shell = TerminalInteractiveShell.instance(config=self.config,
330 self.shell = TerminalInteractiveShell.instance(config=self.config,
329 display_banner=False, profile_dir=self.profile_dir,
331 display_banner=False, profile_dir=self.profile_dir,
330 ipython_dir=self.ipython_dir)
332 ipython_dir=self.ipython_dir)
331 self.shell.configurables.append(self)
333 self.shell.configurables.append(self)
332
334
333 def init_banner(self):
335 def init_banner(self):
334 """optionally display the banner"""
336 """optionally display the banner"""
335 if self.display_banner and self.interact:
337 if self.display_banner and self.interact:
336 self.shell.show_banner()
338 self.shell.show_banner()
337 # Make sure there is a space below the banner.
339 # Make sure there is a space below the banner.
338 if self.log_level <= logging.INFO: print
340 if self.log_level <= logging.INFO: print
339
341
340
342
341 def init_gui_pylab(self):
343 def init_gui_pylab(self):
342 """Enable GUI event loop integration, taking pylab into account."""
344 """Enable GUI event loop integration, taking pylab into account."""
343 gui = self.gui
345 gui = self.gui
344
346
345 # Using `pylab` will also require gui activation, though which toolkit
347 # Using `pylab` will also require gui activation, though which toolkit
346 # to use may be chosen automatically based on mpl configuration.
348 # to use may be chosen automatically based on mpl configuration.
347 if self.pylab:
349 if self.pylab:
348 activate = self.shell.enable_pylab
350 activate = self.shell.enable_pylab
349 if self.pylab == 'auto':
351 if self.pylab == 'auto':
350 gui = None
352 gui = None
351 else:
353 else:
352 gui = self.pylab
354 gui = self.pylab
353 else:
355 else:
354 # Enable only GUI integration, no pylab
356 # Enable only GUI integration, no pylab
355 activate = inputhook.enable_gui
357 activate = inputhook.enable_gui
356
358
357 if gui or self.pylab:
359 if gui or self.pylab:
358 try:
360 try:
359 self.log.info("Enabling GUI event loop integration, "
361 self.log.info("Enabling GUI event loop integration, "
360 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
362 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
361 if self.pylab:
363 if self.pylab:
362 activate(gui, import_all=self.pylab_import_all)
364 activate(gui, import_all=self.pylab_import_all)
363 else:
365 else:
364 activate(gui)
366 activate(gui)
365 except:
367 except:
366 self.log.warn("Error in enabling GUI event loop integration:")
368 self.log.warn("Error in enabling GUI event loop integration:")
367 self.shell.showtraceback()
369 self.shell.showtraceback()
368
370
369 def start(self):
371 def start(self):
370 if self.subapp is not None:
372 if self.subapp is not None:
371 return self.subapp.start()
373 return self.subapp.start()
372 # perform any prexec steps:
374 # perform any prexec steps:
373 if self.interact:
375 if self.interact:
374 self.log.debug("Starting IPython's mainloop...")
376 self.log.debug("Starting IPython's mainloop...")
375 self.shell.mainloop()
377 self.shell.mainloop()
376 else:
378 else:
377 self.log.debug("IPython not interactive...")
379 self.log.debug("IPython not interactive...")
378
380
379
381
380 def load_default_config(ipython_dir=None):
382 def load_default_config(ipython_dir=None):
381 """Load the default config file from the default ipython_dir.
383 """Load the default config file from the default ipython_dir.
382
384
383 This is useful for embedded shells.
385 This is useful for embedded shells.
384 """
386 """
385 if ipython_dir is None:
387 if ipython_dir is None:
386 ipython_dir = get_ipython_dir()
388 ipython_dir = get_ipython_dir()
387 profile_dir = os.path.join(ipython_dir, 'profile_default')
389 profile_dir = os.path.join(ipython_dir, 'profile_default')
388 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
390 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
389 try:
391 try:
390 config = cl.load_config()
392 config = cl.load_config()
391 except ConfigFileNotFound:
393 except ConfigFileNotFound:
392 # no config found
394 # no config found
393 config = Config()
395 config = Config()
394 return config
396 return config
395
397
396
398
397 def launch_new_instance():
399 def launch_new_instance():
398 """Create and run a full blown IPython instance"""
400 """Create and run a full blown IPython instance"""
399 app = TerminalIPythonApp.instance()
401 app = TerminalIPythonApp.instance()
400 app.initialize()
402 app.initialize()
401 app.start()
403 app.start()
402
404
403
405
404 if __name__ == '__main__':
406 if __name__ == '__main__':
405 launch_new_instance()
407 launch_new_instance()
@@ -1,124 +1,137 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
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.zmq.ipkernel import IPKernelApp
25 from IPython.zmq.ipkernel import IPKernelApp
26 from IPython.zmq.session import Session, default_secure
26 from IPython.zmq.session import Session, default_secure
27 from IPython.zmq.zmqshell import ZMQInteractiveShell
27 from IPython.zmq.zmqshell import ZMQInteractiveShell
28 from IPython.frontend.kernelmixinapp import (
28 from IPython.frontend.kernelmixinapp import (
29 IPythonMixinConsoleApp, app_aliases, app_flags
29 IPythonMixinConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
30 )
30 )
31
31
32 from IPython.frontend.zmqterminal.interactiveshell import ZMQTerminalInteractiveShell
32 from IPython.frontend.zmqterminal.interactiveshell import ZMQTerminalInteractiveShell
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Globals
35 # Globals
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 _examples = """
38 _examples = """
39 ipython console # start the ZMQ-based console
39 ipython console # start the ZMQ-based console
40 ipython console --existing # connect to an existing ipython session
40 ipython console --existing # connect to an existing ipython session
41 """
41 """
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Flags and Aliases
44 # Flags and Aliases
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 # XXX: the app_flags should really be flags from the mixin
47 # copy flags from mixin:
48 flags = dict(app_flags)
48 flags = dict(flags)
49 frontend_flags = { }
49 # start with mixin frontend flags:
50 frontend_flags = dict(app_flags)
51 # add TerminalIPApp flags:
52 frontend_flags.update(term_flags)
53 # pylab is not frontend-specific in two-process IPython
54 frontend_flags.pop('pylab')
55 # disable quick startup, as it won't propagate to the kernel anyway
56 frontend_flags.pop('quick')
57 # update full dict with frontend flags:
50 flags.update(frontend_flags)
58 flags.update(frontend_flags)
51
59
52 frontend_flags = frontend_flags.keys()
60 # copy flags from mixin
53
61 aliases = dict(aliases)
54 aliases = dict(app_aliases)
62 # start with mixin frontend flags
63 frontend_aliases = dict(app_aliases)
64 # load updated frontend flags into full dict
65 aliases.update(frontend_aliases)
55
66
56 frontend_aliases = dict()
67 # get flags&aliases into sets, and remove a couple that
68 # shouldn't be scrubbed from backend flags:
69 frontend_aliases = set(frontend_aliases.keys())
70 frontend_flags = set(frontend_flags.keys())
57
71
58 aliases.update(frontend_aliases)
59
72
60 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
61 # Classes
74 # Classes
62 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
63
76
64
77
65 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonMixinConsoleApp):
78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonMixinConsoleApp):
66 name = "ipython console"
79 name = "ipython-console"
67 """Start a terminal frontend to the IPython zmq kernel."""
80 """Start a terminal frontend to the IPython zmq kernel."""
68
81
69 description = """
82 description = """
70 The IPython terminal-based Console.
83 The IPython terminal-based Console.
71
84
72 This launches a Console application inside a terminal.
85 This launches a Console application inside a terminal.
73
86
74 The Console supports various extra features beyond the traditional
87 The Console supports various extra features beyond the traditional
75 single-process Terminal IPython shell, such as connecting to an
88 single-process Terminal IPython shell, such as connecting to an
76 existing ipython session, via:
89 existing ipython session, via:
77
90
78 ipython console --existing
91 ipython console --existing
79
92
80 where the previous session could have been created by another ipython
93 where the previous session could have been created by another ipython
81 console, an ipython qtconsole, or by opening an ipython notebook.
94 console, an ipython qtconsole, or by opening an ipython notebook.
82
95
83 """
96 """
84 examples = _examples
97 examples = _examples
85
98
86 classes = List([IPKernelApp, ZMQTerminalInteractiveShell])
99 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
87 flags = Dict(flags)
100 flags = Dict(flags)
88 aliases = Dict(aliases)
101 aliases = Dict(aliases)
89 subcommands = Dict()
102 subcommands = Dict()
103
90 def parse_command_line(self, argv=None):
104 def parse_command_line(self, argv=None):
91 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
105 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
92 IPythonMixinConsoleApp.parse_command_line(self,argv)
93 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
106 self.swallow_args(frontend_aliases,frontend_flags,argv=argv)
94
107
95 def init_shell(self):
108 def init_shell(self):
96 IPythonMixinConsoleApp.initialize(self)
109 IPythonMixinConsoleApp.initialize(self)
97 # relay sigint to kernel
110 # relay sigint to kernel
98 signal.signal(signal.SIGINT, self.handle_sigint)
111 signal.signal(signal.SIGINT, self.handle_sigint)
99 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
112 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
100 display_banner=False, profile_dir=self.profile_dir,
113 display_banner=False, profile_dir=self.profile_dir,
101 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
114 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
102
115
103 def handle_sigint(self, *args):
116 def handle_sigint(self, *args):
104 self.shell.write('KeyboardInterrupt\n')
117 self.shell.write('KeyboardInterrupt\n')
105 if self.kernel_manager.has_kernel:
118 if self.kernel_manager.has_kernel:
106 self.kernel_manager.interrupt_kernel()
119 self.kernel_manager.interrupt_kernel()
107 else:
120 else:
108 print 'Kernel process is either remote or unspecified.',
121 print 'Kernel process is either remote or unspecified.',
109 print 'Cannot interrupt.'
122 print 'Cannot interrupt.'
110
123
111 def init_code(self):
124 def init_code(self):
112 # no-op in the frontend, code gets run in the backend
125 # no-op in the frontend, code gets run in the backend
113 pass
126 pass
114
127
115 def launch_new_instance():
128 def launch_new_instance():
116 """Create and run a full blown IPython instance"""
129 """Create and run a full blown IPython instance"""
117 app = ZMQTerminalIPythonApp.instance()
130 app = ZMQTerminalIPythonApp.instance()
118 app.initialize()
131 app.initialize()
119 app.start()
132 app.start()
120
133
121
134
122 if __name__ == '__main__':
135 if __name__ == '__main__':
123 launch_new_instance()
136 launch_new_instance()
124
137
General Comments 0
You need to be logged in to leave comments. Login now