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