##// END OF EJS Templates
add InlineBackend to ConsoleApp class list...
MinRK -
Show More
@@ -1,348 +1,356 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, swallow_argv
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
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' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
72 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
73 "Connect to an existing kernel. If no argument specified, guess most recent"),
73 "Connect to an existing kernel. If no argument specified, guess most recent"),
74 }
74 }
75 app_flags.update(boolean_flag(
75 app_flags.update(boolean_flag(
76 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
76 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
77 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
77 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
78 to force a direct exit without any confirmation.
78 to force a direct exit without any confirmation.
79 """,
79 """,
80 """Don't prompt the user when exiting. This will terminate the kernel
80 """Don't prompt the user when exiting. This will terminate the kernel
81 if it is owned by the frontend, and leave it alive if it is external.
81 if it is owned by the frontend, and leave it alive if it is external.
82 """
82 """
83 ))
83 ))
84 flags.update(app_flags)
84 flags.update(app_flags)
85
85
86 aliases = dict(ipkernel_aliases)
86 aliases = dict(ipkernel_aliases)
87
87
88 # also scrub aliases from the frontend
88 # also scrub aliases from the frontend
89 app_aliases = dict(
89 app_aliases = dict(
90 hb = 'IPythonConsoleApp.hb_port',
90 hb = 'IPythonConsoleApp.hb_port',
91 shell = 'IPythonConsoleApp.shell_port',
91 shell = 'IPythonConsoleApp.shell_port',
92 iopub = 'IPythonConsoleApp.iopub_port',
92 iopub = 'IPythonConsoleApp.iopub_port',
93 stdin = 'IPythonConsoleApp.stdin_port',
93 stdin = 'IPythonConsoleApp.stdin_port',
94 ip = 'IPythonConsoleApp.ip',
94 ip = 'IPythonConsoleApp.ip',
95 existing = 'IPythonConsoleApp.existing',
95 existing = 'IPythonConsoleApp.existing',
96 f = 'IPythonConsoleApp.connection_file',
96 f = 'IPythonConsoleApp.connection_file',
97
97
98
98
99 ssh = 'IPythonConsoleApp.sshserver',
99 ssh = 'IPythonConsoleApp.sshserver',
100 )
100 )
101 aliases.update(app_aliases)
101 aliases.update(app_aliases)
102
102
103 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
104 # Classes
104 # Classes
105 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
106
106
107 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
108 # IPythonConsole
108 # IPythonConsole
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110
110
111 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
112
113 try:
114 from IPython.zmq.pylab.backend_inline import InlineBackend
115 except ImportError:
116 pass
117 else:
118 classes.append(InlineBackend)
111
119
112 class IPythonConsoleApp(Configurable):
120 class IPythonConsoleApp(Configurable):
113 name = 'ipython-console-mixin'
121 name = 'ipython-console-mixin'
114 default_config_file_name='ipython_config.py'
122 default_config_file_name='ipython_config.py'
115
123
116 description = """
124 description = """
117 The IPython Mixin Console.
125 The IPython Mixin Console.
118
126
119 This class contains the common portions of console client (QtConsole,
127 This class contains the common portions of console client (QtConsole,
120 ZMQ-based terminal console, etc). It is not a full console, in that
128 ZMQ-based terminal console, etc). It is not a full console, in that
121 launched terminal subprocesses will not be able to accept input.
129 launched terminal subprocesses will not be able to accept input.
122
130
123 The Console using this mixing supports various extra features beyond
131 The Console using this mixing supports various extra features beyond
124 the single-process Terminal IPython shell, such as connecting to
132 the single-process Terminal IPython shell, such as connecting to
125 existing kernel, via:
133 existing kernel, via:
126
134
127 ipython <appname> --existing
135 ipython <appname> --existing
128
136
129 as well as tunnel via SSH
137 as well as tunnel via SSH
130
138
131 """
139 """
132
140
133 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
141 classes = classes
134 flags = Dict(flags)
142 flags = Dict(flags)
135 aliases = Dict(aliases)
143 aliases = Dict(aliases)
136 kernel_manager_class = BlockingKernelManager
144 kernel_manager_class = BlockingKernelManager
137
145
138 kernel_argv = List(Unicode)
146 kernel_argv = List(Unicode)
139 # frontend flags&aliases to be stripped when building kernel_argv
147 # frontend flags&aliases to be stripped when building kernel_argv
140 frontend_flags = Any(app_flags)
148 frontend_flags = Any(app_flags)
141 frontend_aliases = Any(app_aliases)
149 frontend_aliases = Any(app_aliases)
142
150
143 # create requested profiles by default, if they don't exist:
151 # create requested profiles by default, if they don't exist:
144 auto_create = CBool(True)
152 auto_create = CBool(True)
145 # connection info:
153 # connection info:
146 ip = Unicode(LOCALHOST, config=True,
154 ip = Unicode(LOCALHOST, config=True,
147 help="""Set the kernel\'s IP address [default localhost].
155 help="""Set the kernel\'s IP address [default localhost].
148 If the IP address is something other than localhost, then
156 If the IP address is something other than localhost, then
149 Consoles on other machines will be able to connect
157 Consoles on other machines will be able to connect
150 to the Kernel, so be careful!"""
158 to the Kernel, so be careful!"""
151 )
159 )
152
160
153 sshserver = Unicode('', config=True,
161 sshserver = Unicode('', config=True,
154 help="""The SSH server to use to connect to the kernel.""")
162 help="""The SSH server to use to connect to the kernel.""")
155 sshkey = Unicode('', config=True,
163 sshkey = Unicode('', config=True,
156 help="""Path to the ssh key to use for logging in to the ssh server.""")
164 help="""Path to the ssh key to use for logging in to the ssh server.""")
157
165
158 hb_port = Int(0, config=True,
166 hb_port = Int(0, config=True,
159 help="set the heartbeat port [default: random]")
167 help="set the heartbeat port [default: random]")
160 shell_port = Int(0, config=True,
168 shell_port = Int(0, config=True,
161 help="set the shell (XREP) port [default: random]")
169 help="set the shell (XREP) port [default: random]")
162 iopub_port = Int(0, config=True,
170 iopub_port = Int(0, config=True,
163 help="set the iopub (PUB) port [default: random]")
171 help="set the iopub (PUB) port [default: random]")
164 stdin_port = Int(0, config=True,
172 stdin_port = Int(0, config=True,
165 help="set the stdin (XREQ) port [default: random]")
173 help="set the stdin (XREQ) port [default: random]")
166 connection_file = Unicode('', config=True,
174 connection_file = Unicode('', config=True,
167 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
175 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
168
176
169 This file will contain the IP, ports, and authentication key needed to connect
177 This file will contain the IP, ports, and authentication key needed to connect
170 clients to this kernel. By default, this file will be created in the security-dir
178 clients to this kernel. By default, this file will be created in the security-dir
171 of the current profile, but can be specified by absolute path.
179 of the current profile, but can be specified by absolute path.
172 """)
180 """)
173 def _connection_file_default(self):
181 def _connection_file_default(self):
174 return 'kernel-%i.json' % os.getpid()
182 return 'kernel-%i.json' % os.getpid()
175
183
176 existing = CUnicode('', config=True,
184 existing = CUnicode('', config=True,
177 help="""Connect to an already running kernel""")
185 help="""Connect to an already running kernel""")
178
186
179 confirm_exit = CBool(True, config=True,
187 confirm_exit = CBool(True, config=True,
180 help="""
188 help="""
181 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
189 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
182 to force a direct exit without any confirmation.""",
190 to force a direct exit without any confirmation.""",
183 )
191 )
184
192
185
193
186 def build_kernel_argv(self, argv=None):
194 def build_kernel_argv(self, argv=None):
187 """build argv to be passed to kernel subprocess"""
195 """build argv to be passed to kernel subprocess"""
188 if argv is None:
196 if argv is None:
189 argv = sys.argv[1:]
197 argv = sys.argv[1:]
190 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
198 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
191 # kernel should inherit default config file from frontend
199 # kernel should inherit default config file from frontend
192 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
200 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
193
201
194 def init_connection_file(self):
202 def init_connection_file(self):
195 """find the connection file, and load the info if found.
203 """find the connection file, and load the info if found.
196
204
197 The current working directory and the current profile's security
205 The current working directory and the current profile's security
198 directory will be searched for the file if it is not given by
206 directory will be searched for the file if it is not given by
199 absolute path.
207 absolute path.
200
208
201 When attempting to connect to an existing kernel and the `--existing`
209 When attempting to connect to an existing kernel and the `--existing`
202 argument does not match an existing file, it will be interpreted as a
210 argument does not match an existing file, it will be interpreted as a
203 fileglob, and the matching file in the current profile's security dir
211 fileglob, and the matching file in the current profile's security dir
204 with the latest access time will be used.
212 with the latest access time will be used.
205
213
206 After this method is called, self.connection_file contains the *full path*
214 After this method is called, self.connection_file contains the *full path*
207 to the connection file, never just its name.
215 to the connection file, never just its name.
208 """
216 """
209 if self.existing:
217 if self.existing:
210 try:
218 try:
211 cf = find_connection_file(self.existing)
219 cf = find_connection_file(self.existing)
212 except Exception:
220 except Exception:
213 self.log.critical("Could not find existing kernel connection file %s", self.existing)
221 self.log.critical("Could not find existing kernel connection file %s", self.existing)
214 self.exit(1)
222 self.exit(1)
215 self.log.info("Connecting to existing kernel: %s" % cf)
223 self.log.info("Connecting to existing kernel: %s" % cf)
216 self.connection_file = cf
224 self.connection_file = cf
217 else:
225 else:
218 # not existing, check if we are going to write the file
226 # not existing, check if we are going to write the file
219 # and ensure that self.connection_file is a full path, not just the shortname
227 # and ensure that self.connection_file is a full path, not just the shortname
220 try:
228 try:
221 cf = find_connection_file(self.connection_file)
229 cf = find_connection_file(self.connection_file)
222 except Exception:
230 except Exception:
223 # file might not exist
231 # file might not exist
224 if self.connection_file == os.path.basename(self.connection_file):
232 if self.connection_file == os.path.basename(self.connection_file):
225 # just shortname, put it in security dir
233 # just shortname, put it in security dir
226 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
234 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
227 else:
235 else:
228 cf = self.connection_file
236 cf = self.connection_file
229 self.connection_file = cf
237 self.connection_file = cf
230
238
231 # should load_connection_file only be used for existing?
239 # should load_connection_file only be used for existing?
232 # as it is now, this allows reusing ports if an existing
240 # as it is now, this allows reusing ports if an existing
233 # file is requested
241 # file is requested
234 try:
242 try:
235 self.load_connection_file()
243 self.load_connection_file()
236 except Exception:
244 except Exception:
237 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
245 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
238 self.exit(1)
246 self.exit(1)
239
247
240 def load_connection_file(self):
248 def load_connection_file(self):
241 """load ip/port/hmac config from JSON connection file"""
249 """load ip/port/hmac config from JSON connection file"""
242 # this is identical to KernelApp.load_connection_file
250 # this is identical to KernelApp.load_connection_file
243 # perhaps it can be centralized somewhere?
251 # perhaps it can be centralized somewhere?
244 try:
252 try:
245 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
253 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
246 except IOError:
254 except IOError:
247 self.log.debug("Connection File not found: %s", self.connection_file)
255 self.log.debug("Connection File not found: %s", self.connection_file)
248 return
256 return
249 self.log.debug(u"Loading connection file %s", fname)
257 self.log.debug(u"Loading connection file %s", fname)
250 with open(fname) as f:
258 with open(fname) as f:
251 s = f.read()
259 s = f.read()
252 cfg = json.loads(s)
260 cfg = json.loads(s)
253 if self.ip == LOCALHOST and 'ip' in cfg:
261 if self.ip == LOCALHOST and 'ip' in cfg:
254 # not overridden by config or cl_args
262 # not overridden by config or cl_args
255 self.ip = cfg['ip']
263 self.ip = cfg['ip']
256 for channel in ('hb', 'shell', 'iopub', 'stdin'):
264 for channel in ('hb', 'shell', 'iopub', 'stdin'):
257 name = channel + '_port'
265 name = channel + '_port'
258 if getattr(self, name) == 0 and name in cfg:
266 if getattr(self, name) == 0 and name in cfg:
259 # not overridden by config or cl_args
267 # not overridden by config or cl_args
260 setattr(self, name, cfg[name])
268 setattr(self, name, cfg[name])
261 if 'key' in cfg:
269 if 'key' in cfg:
262 self.config.Session.key = str_to_bytes(cfg['key'])
270 self.config.Session.key = str_to_bytes(cfg['key'])
263
271
264 def init_ssh(self):
272 def init_ssh(self):
265 """set up ssh tunnels, if needed."""
273 """set up ssh tunnels, if needed."""
266 if not self.sshserver and not self.sshkey:
274 if not self.sshserver and not self.sshkey:
267 return
275 return
268
276
269 if self.sshkey and not self.sshserver:
277 if self.sshkey and not self.sshserver:
270 # specifying just the key implies that we are connecting directly
278 # specifying just the key implies that we are connecting directly
271 self.sshserver = self.ip
279 self.sshserver = self.ip
272 self.ip = LOCALHOST
280 self.ip = LOCALHOST
273
281
274 # build connection dict for tunnels:
282 # build connection dict for tunnels:
275 info = dict(ip=self.ip,
283 info = dict(ip=self.ip,
276 shell_port=self.shell_port,
284 shell_port=self.shell_port,
277 iopub_port=self.iopub_port,
285 iopub_port=self.iopub_port,
278 stdin_port=self.stdin_port,
286 stdin_port=self.stdin_port,
279 hb_port=self.hb_port
287 hb_port=self.hb_port
280 )
288 )
281
289
282 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
290 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
283
291
284 # tunnels return a new set of ports, which will be on localhost:
292 # tunnels return a new set of ports, which will be on localhost:
285 self.ip = LOCALHOST
293 self.ip = LOCALHOST
286 try:
294 try:
287 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
295 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
288 except:
296 except:
289 # even catch KeyboardInterrupt
297 # even catch KeyboardInterrupt
290 self.log.error("Could not setup tunnels", exc_info=True)
298 self.log.error("Could not setup tunnels", exc_info=True)
291 self.exit(1)
299 self.exit(1)
292
300
293 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
301 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
294
302
295 cf = self.connection_file
303 cf = self.connection_file
296 base,ext = os.path.splitext(cf)
304 base,ext = os.path.splitext(cf)
297 base = os.path.basename(base)
305 base = os.path.basename(base)
298 self.connection_file = os.path.basename(base)+'-ssh'+ext
306 self.connection_file = os.path.basename(base)+'-ssh'+ext
299 self.log.critical("To connect another client via this tunnel, use:")
307 self.log.critical("To connect another client via this tunnel, use:")
300 self.log.critical("--existing %s" % self.connection_file)
308 self.log.critical("--existing %s" % self.connection_file)
301
309
302 def _new_connection_file(self):
310 def _new_connection_file(self):
303 cf = ''
311 cf = ''
304 while not cf:
312 while not cf:
305 # we don't need a 128b id to distinguish kernels, use more readable
313 # we don't need a 128b id to distinguish kernels, use more readable
306 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
314 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
307 # kernels can subclass.
315 # kernels can subclass.
308 ident = str(uuid.uuid4()).split('-')[-1]
316 ident = str(uuid.uuid4()).split('-')[-1]
309 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
317 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
310 # only keep if it's actually new. Protect against unlikely collision
318 # only keep if it's actually new. Protect against unlikely collision
311 # in 48b random search space
319 # in 48b random search space
312 cf = cf if not os.path.exists(cf) else ''
320 cf = cf if not os.path.exists(cf) else ''
313 return cf
321 return cf
314
322
315 def init_kernel_manager(self):
323 def init_kernel_manager(self):
316 # Don't let Qt or ZMQ swallow KeyboardInterupts.
324 # Don't let Qt or ZMQ swallow KeyboardInterupts.
317 signal.signal(signal.SIGINT, signal.SIG_DFL)
325 signal.signal(signal.SIGINT, signal.SIG_DFL)
318
326
319 # Create a KernelManager and start a kernel.
327 # Create a KernelManager and start a kernel.
320 self.kernel_manager = self.kernel_manager_class(
328 self.kernel_manager = self.kernel_manager_class(
321 ip=self.ip,
329 ip=self.ip,
322 shell_port=self.shell_port,
330 shell_port=self.shell_port,
323 iopub_port=self.iopub_port,
331 iopub_port=self.iopub_port,
324 stdin_port=self.stdin_port,
332 stdin_port=self.stdin_port,
325 hb_port=self.hb_port,
333 hb_port=self.hb_port,
326 connection_file=self.connection_file,
334 connection_file=self.connection_file,
327 config=self.config,
335 config=self.config,
328 )
336 )
329 # start the kernel
337 # start the kernel
330 if not self.existing:
338 if not self.existing:
331 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
339 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
332 elif self.sshserver:
340 elif self.sshserver:
333 # ssh, write new connection file
341 # ssh, write new connection file
334 self.kernel_manager.write_connection_file()
342 self.kernel_manager.write_connection_file()
335 atexit.register(self.kernel_manager.cleanup_connection_file)
343 atexit.register(self.kernel_manager.cleanup_connection_file)
336 self.kernel_manager.start_channels()
344 self.kernel_manager.start_channels()
337
345
338
346
339 def initialize(self, argv=None):
347 def initialize(self, argv=None):
340 """
348 """
341 Classes which mix this class in should call:
349 Classes which mix this class in should call:
342 IPythonConsoleApp.initialize(self,argv)
350 IPythonConsoleApp.initialize(self,argv)
343 """
351 """
344 self.init_connection_file()
352 self.init_connection_file()
345 default_secure(self.config)
353 default_secure(self.config)
346 self.init_ssh()
354 self.init_ssh()
347 self.init_kernel_manager()
355 self.init_kernel_manager()
348
356
@@ -1,584 +1,584 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import re
24 import re
25 import select
25 import select
26 import signal
26 import signal
27 import socket
27 import socket
28 import sys
28 import sys
29 import threading
29 import threading
30 import time
30 import time
31 import webbrowser
31 import webbrowser
32
32
33 # Third party
33 # Third party
34 import zmq
34 import zmq
35
35
36 # Install the pyzmq ioloop. This has to be done before anything else from
36 # Install the pyzmq ioloop. This has to be done before anything else from
37 # tornado is imported.
37 # tornado is imported.
38 from zmq.eventloop import ioloop
38 from zmq.eventloop import ioloop
39 ioloop.install()
39 ioloop.install()
40
40
41 from tornado import httpserver
41 from tornado import httpserver
42 from tornado import web
42 from tornado import web
43
43
44 # Our own libraries
44 # Our own libraries
45 from .kernelmanager import MappingKernelManager
45 from .kernelmanager import MappingKernelManager
46 from .handlers import (LoginHandler, LogoutHandler,
46 from .handlers import (LoginHandler, LogoutHandler,
47 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
47 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
48 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
48 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
49 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
49 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
50 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
50 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
52 )
52 )
53 from .notebookmanager import NotebookManager
53 from .notebookmanager import NotebookManager
54 from .clustermanager import ClusterManager
54 from .clustermanager import ClusterManager
55
55
56 from IPython.config.application import catch_config_error, boolean_flag
56 from IPython.config.application import catch_config_error, boolean_flag
57 from IPython.core.application import BaseIPythonApplication
57 from IPython.core.application import BaseIPythonApplication
58 from IPython.core.profiledir import ProfileDir
58 from IPython.core.profiledir import ProfileDir
59 from IPython.frontend.consoleapp import IPythonConsoleApp
59 from IPython.lib.kernel import swallow_argv
60 from IPython.lib.kernel import swallow_argv
60 from IPython.zmq.session import Session, default_secure
61 from IPython.zmq.session import Session, default_secure
61 from IPython.zmq.zmqshell import ZMQInteractiveShell
62 from IPython.zmq.zmqshell import ZMQInteractiveShell
62 from IPython.zmq.ipkernel import (
63 from IPython.zmq.ipkernel import (
63 flags as ipkernel_flags,
64 flags as ipkernel_flags,
64 aliases as ipkernel_aliases,
65 aliases as ipkernel_aliases,
65 IPKernelApp
66 IPKernelApp
66 )
67 )
67 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
68 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
68 from IPython.utils import py3compat
69 from IPython.utils import py3compat
69
70
70 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
71 # Module globals
72 # Module globals
72 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
73
74
74 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
75 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
75 _kernel_action_regex = r"(?P<action>restart|interrupt)"
76 _kernel_action_regex = r"(?P<action>restart|interrupt)"
76 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
77 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
77 _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)"
78 _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)"
78 _cluster_action_regex = r"(?P<action>start|stop)"
79 _cluster_action_regex = r"(?P<action>start|stop)"
79
80
80
81
81 LOCALHOST = '127.0.0.1'
82 LOCALHOST = '127.0.0.1'
82
83
83 _examples = """
84 _examples = """
84 ipython notebook # start the notebook
85 ipython notebook # start the notebook
85 ipython notebook --profile=sympy # use the sympy profile
86 ipython notebook --profile=sympy # use the sympy profile
86 ipython notebook --pylab=inline # pylab in inline plotting mode
87 ipython notebook --pylab=inline # pylab in inline plotting mode
87 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
88 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
88 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
89 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
89 """
90 """
90
91
91 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
92 # Helper functions
93 # Helper functions
93 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
94
95
95 def url_path_join(a,b):
96 def url_path_join(a,b):
96 if a.endswith('/') and b.startswith('/'):
97 if a.endswith('/') and b.startswith('/'):
97 return a[:-1]+b
98 return a[:-1]+b
98 else:
99 else:
99 return a+b
100 return a+b
100
101
101 def random_ports(port, n):
102 def random_ports(port, n):
102 """Generate a list of n random ports near the given port.
103 """Generate a list of n random ports near the given port.
103
104
104 The first 5 ports will be sequential, and the remaining n-5 will be
105 The first 5 ports will be sequential, and the remaining n-5 will be
105 randomly selected in the range [port-2*n, port+2*n].
106 randomly selected in the range [port-2*n, port+2*n].
106 """
107 """
107 for i in range(min(5, n)):
108 for i in range(min(5, n)):
108 yield port + i
109 yield port + i
109 for i in range(n-5):
110 for i in range(n-5):
110 yield port + random.randint(-2*n, 2*n)
111 yield port + random.randint(-2*n, 2*n)
111
112
112 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
113 # The Tornado web application
114 # The Tornado web application
114 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
115
116
116 class NotebookWebApplication(web.Application):
117 class NotebookWebApplication(web.Application):
117
118
118 def __init__(self, ipython_app, kernel_manager, notebook_manager,
119 def __init__(self, ipython_app, kernel_manager, notebook_manager,
119 cluster_manager, log,
120 cluster_manager, log,
120 base_project_url, settings_overrides):
121 base_project_url, settings_overrides):
121 handlers = [
122 handlers = [
122 (r"/", ProjectDashboardHandler),
123 (r"/", ProjectDashboardHandler),
123 (r"/login", LoginHandler),
124 (r"/login", LoginHandler),
124 (r"/logout", LogoutHandler),
125 (r"/logout", LogoutHandler),
125 (r"/new", NewHandler),
126 (r"/new", NewHandler),
126 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
127 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
127 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
128 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
128 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
129 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
129 (r"/kernels", MainKernelHandler),
130 (r"/kernels", MainKernelHandler),
130 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
131 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
131 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
132 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
132 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
133 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
133 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
134 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
134 (r"/notebooks", NotebookRootHandler),
135 (r"/notebooks", NotebookRootHandler),
135 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
136 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
136 (r"/rstservice/render", RSTHandler),
137 (r"/rstservice/render", RSTHandler),
137 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
138 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
138 (r"/clusters", MainClusterHandler),
139 (r"/clusters", MainClusterHandler),
139 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
140 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
140 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
141 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
141 ]
142 ]
142 settings = dict(
143 settings = dict(
143 template_path=os.path.join(os.path.dirname(__file__), "templates"),
144 template_path=os.path.join(os.path.dirname(__file__), "templates"),
144 static_path=os.path.join(os.path.dirname(__file__), "static"),
145 static_path=os.path.join(os.path.dirname(__file__), "static"),
145 cookie_secret=os.urandom(1024),
146 cookie_secret=os.urandom(1024),
146 login_url="/login",
147 login_url="/login",
147 )
148 )
148
149
149 # allow custom overrides for the tornado web app.
150 # allow custom overrides for the tornado web app.
150 settings.update(settings_overrides)
151 settings.update(settings_overrides)
151
152
152 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
153 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
153 # base_project_url will always be unicode, which will in turn
154 # base_project_url will always be unicode, which will in turn
154 # make the patterns unicode, and ultimately result in unicode
155 # make the patterns unicode, and ultimately result in unicode
155 # keys in kwargs to handler._execute(**kwargs) in tornado.
156 # keys in kwargs to handler._execute(**kwargs) in tornado.
156 # This enforces that base_project_url be ascii in that situation.
157 # This enforces that base_project_url be ascii in that situation.
157 #
158 #
158 # Note that the URLs these patterns check against are escaped,
159 # Note that the URLs these patterns check against are escaped,
159 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
160 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
160 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
161 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
161
162
162 # prepend base_project_url onto the patterns that we match
163 # prepend base_project_url onto the patterns that we match
163 new_handlers = []
164 new_handlers = []
164 for handler in handlers:
165 for handler in handlers:
165 pattern = url_path_join(base_project_url, handler[0])
166 pattern = url_path_join(base_project_url, handler[0])
166 new_handler = tuple([pattern]+list(handler[1:]))
167 new_handler = tuple([pattern]+list(handler[1:]))
167 new_handlers.append( new_handler )
168 new_handlers.append( new_handler )
168
169
169 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
170 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
170
171
171 self.kernel_manager = kernel_manager
172 self.kernel_manager = kernel_manager
172 self.notebook_manager = notebook_manager
173 self.notebook_manager = notebook_manager
173 self.cluster_manager = cluster_manager
174 self.cluster_manager = cluster_manager
174 self.ipython_app = ipython_app
175 self.ipython_app = ipython_app
175 self.read_only = self.ipython_app.read_only
176 self.read_only = self.ipython_app.read_only
176 self.log = log
177 self.log = log
177
178
178
179
179 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
180 # Aliases and Flags
181 # Aliases and Flags
181 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
182
183
183 flags = dict(ipkernel_flags)
184 flags = dict(ipkernel_flags)
184 flags['no-browser']=(
185 flags['no-browser']=(
185 {'NotebookApp' : {'open_browser' : False}},
186 {'NotebookApp' : {'open_browser' : False}},
186 "Don't open the notebook in a browser after startup."
187 "Don't open the notebook in a browser after startup."
187 )
188 )
188 flags['no-mathjax']=(
189 flags['no-mathjax']=(
189 {'NotebookApp' : {'enable_mathjax' : False}},
190 {'NotebookApp' : {'enable_mathjax' : False}},
190 """Disable MathJax
191 """Disable MathJax
191
192
192 MathJax is the javascript library IPython uses to render math/LaTeX. It is
193 MathJax is the javascript library IPython uses to render math/LaTeX. It is
193 very large, so you may want to disable it if you have a slow internet
194 very large, so you may want to disable it if you have a slow internet
194 connection, or for offline use of the notebook.
195 connection, or for offline use of the notebook.
195
196
196 When disabled, equations etc. will appear as their untransformed TeX source.
197 When disabled, equations etc. will appear as their untransformed TeX source.
197 """
198 """
198 )
199 )
199 flags['read-only'] = (
200 flags['read-only'] = (
200 {'NotebookApp' : {'read_only' : True}},
201 {'NotebookApp' : {'read_only' : True}},
201 """Allow read-only access to notebooks.
202 """Allow read-only access to notebooks.
202
203
203 When using a password to protect the notebook server, this flag
204 When using a password to protect the notebook server, this flag
204 allows unauthenticated clients to view the notebook list, and
205 allows unauthenticated clients to view the notebook list, and
205 individual notebooks, but not edit them, start kernels, or run
206 individual notebooks, but not edit them, start kernels, or run
206 code.
207 code.
207
208
208 If no password is set, the server will be entirely read-only.
209 If no password is set, the server will be entirely read-only.
209 """
210 """
210 )
211 )
211
212
212 # Add notebook manager flags
213 # Add notebook manager flags
213 flags.update(boolean_flag('script', 'NotebookManager.save_script',
214 flags.update(boolean_flag('script', 'NotebookManager.save_script',
214 'Auto-save a .py script everytime the .ipynb notebook is saved',
215 'Auto-save a .py script everytime the .ipynb notebook is saved',
215 'Do not auto-save .py scripts for every notebook'))
216 'Do not auto-save .py scripts for every notebook'))
216
217
217 # the flags that are specific to the frontend
218 # the flags that are specific to the frontend
218 # these must be scrubbed before being passed to the kernel,
219 # these must be scrubbed before being passed to the kernel,
219 # or it will raise an error on unrecognized flags
220 # or it will raise an error on unrecognized flags
220 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
221 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
221
222
222 aliases = dict(ipkernel_aliases)
223 aliases = dict(ipkernel_aliases)
223
224
224 aliases.update({
225 aliases.update({
225 'ip': 'NotebookApp.ip',
226 'ip': 'NotebookApp.ip',
226 'port': 'NotebookApp.port',
227 'port': 'NotebookApp.port',
227 'port-retries': 'NotebookApp.port_retries',
228 'port-retries': 'NotebookApp.port_retries',
228 'keyfile': 'NotebookApp.keyfile',
229 'keyfile': 'NotebookApp.keyfile',
229 'certfile': 'NotebookApp.certfile',
230 'certfile': 'NotebookApp.certfile',
230 'notebook-dir': 'NotebookManager.notebook_dir',
231 'notebook-dir': 'NotebookManager.notebook_dir',
231 'browser': 'NotebookApp.browser',
232 'browser': 'NotebookApp.browser',
232 })
233 })
233
234
234 # remove ipkernel flags that are singletons, and don't make sense in
235 # remove ipkernel flags that are singletons, and don't make sense in
235 # multi-kernel evironment:
236 # multi-kernel evironment:
236 aliases.pop('f', None)
237 aliases.pop('f', None)
237
238
238 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
239 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
239 u'notebook-dir']
240 u'notebook-dir']
240
241
241 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
242 # NotebookApp
243 # NotebookApp
243 #-----------------------------------------------------------------------------
244 #-----------------------------------------------------------------------------
244
245
245 class NotebookApp(BaseIPythonApplication):
246 class NotebookApp(BaseIPythonApplication):
246
247
247 name = 'ipython-notebook'
248 name = 'ipython-notebook'
248 default_config_file_name='ipython_notebook_config.py'
249 default_config_file_name='ipython_notebook_config.py'
249
250
250 description = """
251 description = """
251 The IPython HTML Notebook.
252 The IPython HTML Notebook.
252
253
253 This launches a Tornado based HTML Notebook Server that serves up an
254 This launches a Tornado based HTML Notebook Server that serves up an
254 HTML5/Javascript Notebook client.
255 HTML5/Javascript Notebook client.
255 """
256 """
256 examples = _examples
257 examples = _examples
257
258
258 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
259 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager]
259 MappingKernelManager, NotebookManager]
260 flags = Dict(flags)
260 flags = Dict(flags)
261 aliases = Dict(aliases)
261 aliases = Dict(aliases)
262
262
263 kernel_argv = List(Unicode)
263 kernel_argv = List(Unicode)
264
264
265 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
265 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
266 default_value=logging.INFO,
266 default_value=logging.INFO,
267 config=True,
267 config=True,
268 help="Set the log level by value or name.")
268 help="Set the log level by value or name.")
269
269
270 # create requested profiles by default, if they don't exist:
270 # create requested profiles by default, if they don't exist:
271 auto_create = Bool(True)
271 auto_create = Bool(True)
272
272
273 # file to be opened in the notebook server
273 # file to be opened in the notebook server
274 file_to_run = Unicode('')
274 file_to_run = Unicode('')
275
275
276 # Network related information.
276 # Network related information.
277
277
278 ip = Unicode(LOCALHOST, config=True,
278 ip = Unicode(LOCALHOST, config=True,
279 help="The IP address the notebook server will listen on."
279 help="The IP address the notebook server will listen on."
280 )
280 )
281
281
282 def _ip_changed(self, name, old, new):
282 def _ip_changed(self, name, old, new):
283 if new == u'*': self.ip = u''
283 if new == u'*': self.ip = u''
284
284
285 port = Integer(8888, config=True,
285 port = Integer(8888, config=True,
286 help="The port the notebook server will listen on."
286 help="The port the notebook server will listen on."
287 )
287 )
288 port_retries = Integer(50, config=True,
288 port_retries = Integer(50, config=True,
289 help="The number of additional ports to try if the specified port is not available."
289 help="The number of additional ports to try if the specified port is not available."
290 )
290 )
291
291
292 certfile = Unicode(u'', config=True,
292 certfile = Unicode(u'', config=True,
293 help="""The full path to an SSL/TLS certificate file."""
293 help="""The full path to an SSL/TLS certificate file."""
294 )
294 )
295
295
296 keyfile = Unicode(u'', config=True,
296 keyfile = Unicode(u'', config=True,
297 help="""The full path to a private key file for usage with SSL/TLS."""
297 help="""The full path to a private key file for usage with SSL/TLS."""
298 )
298 )
299
299
300 password = Unicode(u'', config=True,
300 password = Unicode(u'', config=True,
301 help="""Hashed password to use for web authentication.
301 help="""Hashed password to use for web authentication.
302
302
303 To generate, type in a python/IPython shell:
303 To generate, type in a python/IPython shell:
304
304
305 from IPython.lib import passwd; passwd()
305 from IPython.lib import passwd; passwd()
306
306
307 The string should be of the form type:salt:hashed-password.
307 The string should be of the form type:salt:hashed-password.
308 """
308 """
309 )
309 )
310
310
311 open_browser = Bool(True, config=True,
311 open_browser = Bool(True, config=True,
312 help="""Whether to open in a browser after starting.
312 help="""Whether to open in a browser after starting.
313 The specific browser used is platform dependent and
313 The specific browser used is platform dependent and
314 determined by the python standard library `webbrowser`
314 determined by the python standard library `webbrowser`
315 module, unless it is overridden using the --browser
315 module, unless it is overridden using the --browser
316 (NotebookApp.browser) configuration option.
316 (NotebookApp.browser) configuration option.
317 """)
317 """)
318
318
319 browser = Unicode(u'', config=True,
319 browser = Unicode(u'', config=True,
320 help="""Specify what command to use to invoke a web
320 help="""Specify what command to use to invoke a web
321 browser when opening the notebook. If not specified, the
321 browser when opening the notebook. If not specified, the
322 default browser will be determined by the `webbrowser`
322 default browser will be determined by the `webbrowser`
323 standard library module, which allows setting of the
323 standard library module, which allows setting of the
324 BROWSER environment variable to override it.
324 BROWSER environment variable to override it.
325 """)
325 """)
326
326
327 read_only = Bool(False, config=True,
327 read_only = Bool(False, config=True,
328 help="Whether to prevent editing/execution of notebooks."
328 help="Whether to prevent editing/execution of notebooks."
329 )
329 )
330
330
331 webapp_settings = Dict(config=True,
331 webapp_settings = Dict(config=True,
332 help="Supply overrides for the tornado.web.Application that the "
332 help="Supply overrides for the tornado.web.Application that the "
333 "IPython notebook uses.")
333 "IPython notebook uses.")
334
334
335 enable_mathjax = Bool(True, config=True,
335 enable_mathjax = Bool(True, config=True,
336 help="""Whether to enable MathJax for typesetting math/TeX
336 help="""Whether to enable MathJax for typesetting math/TeX
337
337
338 MathJax is the javascript library IPython uses to render math/LaTeX. It is
338 MathJax is the javascript library IPython uses to render math/LaTeX. It is
339 very large, so you may want to disable it if you have a slow internet
339 very large, so you may want to disable it if you have a slow internet
340 connection, or for offline use of the notebook.
340 connection, or for offline use of the notebook.
341
341
342 When disabled, equations etc. will appear as their untransformed TeX source.
342 When disabled, equations etc. will appear as their untransformed TeX source.
343 """
343 """
344 )
344 )
345 def _enable_mathjax_changed(self, name, old, new):
345 def _enable_mathjax_changed(self, name, old, new):
346 """set mathjax url to empty if mathjax is disabled"""
346 """set mathjax url to empty if mathjax is disabled"""
347 if not new:
347 if not new:
348 self.mathjax_url = u''
348 self.mathjax_url = u''
349
349
350 base_project_url = Unicode('/', config=True,
350 base_project_url = Unicode('/', config=True,
351 help='''The base URL for the notebook server''')
351 help='''The base URL for the notebook server''')
352 base_kernel_url = Unicode('/', config=True,
352 base_kernel_url = Unicode('/', config=True,
353 help='''The base URL for the kernel server''')
353 help='''The base URL for the kernel server''')
354 websocket_host = Unicode("", config=True,
354 websocket_host = Unicode("", config=True,
355 help="""The hostname for the websocket server."""
355 help="""The hostname for the websocket server."""
356 )
356 )
357
357
358 mathjax_url = Unicode("", config=True,
358 mathjax_url = Unicode("", config=True,
359 help="""The url for MathJax.js."""
359 help="""The url for MathJax.js."""
360 )
360 )
361 def _mathjax_url_default(self):
361 def _mathjax_url_default(self):
362 if not self.enable_mathjax:
362 if not self.enable_mathjax:
363 return u''
363 return u''
364 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
364 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
365 static_url_prefix = self.webapp_settings.get("static_url_prefix",
365 static_url_prefix = self.webapp_settings.get("static_url_prefix",
366 "/static/")
366 "/static/")
367 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
367 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
368 self.log.info("Using local MathJax")
368 self.log.info("Using local MathJax")
369 return static_url_prefix+u"mathjax/MathJax.js"
369 return static_url_prefix+u"mathjax/MathJax.js"
370 else:
370 else:
371 if self.certfile:
371 if self.certfile:
372 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
372 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
373 base = u"https://c328740.ssl.cf1.rackcdn.com"
373 base = u"https://c328740.ssl.cf1.rackcdn.com"
374 else:
374 else:
375 base = u"http://cdn.mathjax.org"
375 base = u"http://cdn.mathjax.org"
376
376
377 url = base + u"/mathjax/latest/MathJax.js"
377 url = base + u"/mathjax/latest/MathJax.js"
378 self.log.info("Using MathJax from CDN: %s", url)
378 self.log.info("Using MathJax from CDN: %s", url)
379 return url
379 return url
380
380
381 def _mathjax_url_changed(self, name, old, new):
381 def _mathjax_url_changed(self, name, old, new):
382 if new and not self.enable_mathjax:
382 if new and not self.enable_mathjax:
383 # enable_mathjax=False overrides mathjax_url
383 # enable_mathjax=False overrides mathjax_url
384 self.mathjax_url = u''
384 self.mathjax_url = u''
385 else:
385 else:
386 self.log.info("Using MathJax: %s", new)
386 self.log.info("Using MathJax: %s", new)
387
387
388 def parse_command_line(self, argv=None):
388 def parse_command_line(self, argv=None):
389 super(NotebookApp, self).parse_command_line(argv)
389 super(NotebookApp, self).parse_command_line(argv)
390 if argv is None:
390 if argv is None:
391 argv = sys.argv[1:]
391 argv = sys.argv[1:]
392
392
393 # Scrub frontend-specific flags
393 # Scrub frontend-specific flags
394 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
394 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
395 # Kernel should inherit default config file from frontend
395 # Kernel should inherit default config file from frontend
396 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
396 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
397
397
398 if self.extra_args:
398 if self.extra_args:
399 self.file_to_run = os.path.abspath(self.extra_args[0])
399 self.file_to_run = os.path.abspath(self.extra_args[0])
400 self.config.NotebookManager.notebook_dir = os.path.dirname(self.file_to_run)
400 self.config.NotebookManager.notebook_dir = os.path.dirname(self.file_to_run)
401
401
402 def init_configurables(self):
402 def init_configurables(self):
403 # force Session default to be secure
403 # force Session default to be secure
404 default_secure(self.config)
404 default_secure(self.config)
405 # Create a KernelManager and start a kernel.
405 # Create a KernelManager and start a kernel.
406 self.kernel_manager = MappingKernelManager(
406 self.kernel_manager = MappingKernelManager(
407 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
407 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
408 connection_dir = self.profile_dir.security_dir,
408 connection_dir = self.profile_dir.security_dir,
409 )
409 )
410 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
410 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
411 self.notebook_manager.list_notebooks()
411 self.notebook_manager.list_notebooks()
412 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
412 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
413 self.cluster_manager.update_profiles()
413 self.cluster_manager.update_profiles()
414
414
415 def init_logging(self):
415 def init_logging(self):
416 # This prevents double log messages because tornado use a root logger that
416 # This prevents double log messages because tornado use a root logger that
417 # self.log is a child of. The logging module dipatches log messages to a log
417 # self.log is a child of. The logging module dipatches log messages to a log
418 # and all of its ancenstors until propagate is set to False.
418 # and all of its ancenstors until propagate is set to False.
419 self.log.propagate = False
419 self.log.propagate = False
420
420
421 def init_webapp(self):
421 def init_webapp(self):
422 """initialize tornado webapp and httpserver"""
422 """initialize tornado webapp and httpserver"""
423 self.web_app = NotebookWebApplication(
423 self.web_app = NotebookWebApplication(
424 self, self.kernel_manager, self.notebook_manager,
424 self, self.kernel_manager, self.notebook_manager,
425 self.cluster_manager, self.log,
425 self.cluster_manager, self.log,
426 self.base_project_url, self.webapp_settings
426 self.base_project_url, self.webapp_settings
427 )
427 )
428 if self.certfile:
428 if self.certfile:
429 ssl_options = dict(certfile=self.certfile)
429 ssl_options = dict(certfile=self.certfile)
430 if self.keyfile:
430 if self.keyfile:
431 ssl_options['keyfile'] = self.keyfile
431 ssl_options['keyfile'] = self.keyfile
432 else:
432 else:
433 ssl_options = None
433 ssl_options = None
434 self.web_app.password = self.password
434 self.web_app.password = self.password
435 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
435 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
436 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
436 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
437 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
437 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
438 'but not using any encryption or authentication. This is highly '
438 'but not using any encryption or authentication. This is highly '
439 'insecure and not recommended.')
439 'insecure and not recommended.')
440
440
441 success = None
441 success = None
442 for port in random_ports(self.port, self.port_retries+1):
442 for port in random_ports(self.port, self.port_retries+1):
443 try:
443 try:
444 self.http_server.listen(port, self.ip)
444 self.http_server.listen(port, self.ip)
445 except socket.error, e:
445 except socket.error, e:
446 if e.errno != errno.EADDRINUSE:
446 if e.errno != errno.EADDRINUSE:
447 raise
447 raise
448 self.log.info('The port %i is already in use, trying another random port.' % port)
448 self.log.info('The port %i is already in use, trying another random port.' % port)
449 else:
449 else:
450 self.port = port
450 self.port = port
451 success = True
451 success = True
452 break
452 break
453 if not success:
453 if not success:
454 self.log.critical('ERROR: the notebook server could not be started because '
454 self.log.critical('ERROR: the notebook server could not be started because '
455 'no available port could be found.')
455 'no available port could be found.')
456 self.exit(1)
456 self.exit(1)
457
457
458 def init_signal(self):
458 def init_signal(self):
459 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
459 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
460 # safely extract zmq version info:
460 # safely extract zmq version info:
461 try:
461 try:
462 zmq_v = zmq.pyzmq_version_info()
462 zmq_v = zmq.pyzmq_version_info()
463 except AttributeError:
463 except AttributeError:
464 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
464 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
465 if 'dev' in zmq.__version__:
465 if 'dev' in zmq.__version__:
466 zmq_v.append(999)
466 zmq_v.append(999)
467 zmq_v = tuple(zmq_v)
467 zmq_v = tuple(zmq_v)
468 if zmq_v >= (2,1,9):
468 if zmq_v >= (2,1,9):
469 # This won't work with 2.1.7 and
469 # This won't work with 2.1.7 and
470 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
470 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
471 # but it will work
471 # but it will work
472 signal.signal(signal.SIGINT, self._handle_sigint)
472 signal.signal(signal.SIGINT, self._handle_sigint)
473 signal.signal(signal.SIGTERM, self._signal_stop)
473 signal.signal(signal.SIGTERM, self._signal_stop)
474
474
475 def _handle_sigint(self, sig, frame):
475 def _handle_sigint(self, sig, frame):
476 """SIGINT handler spawns confirmation dialog"""
476 """SIGINT handler spawns confirmation dialog"""
477 # register more forceful signal handler for ^C^C case
477 # register more forceful signal handler for ^C^C case
478 signal.signal(signal.SIGINT, self._signal_stop)
478 signal.signal(signal.SIGINT, self._signal_stop)
479 # request confirmation dialog in bg thread, to avoid
479 # request confirmation dialog in bg thread, to avoid
480 # blocking the App
480 # blocking the App
481 thread = threading.Thread(target=self._confirm_exit)
481 thread = threading.Thread(target=self._confirm_exit)
482 thread.daemon = True
482 thread.daemon = True
483 thread.start()
483 thread.start()
484
484
485 def _restore_sigint_handler(self):
485 def _restore_sigint_handler(self):
486 """callback for restoring original SIGINT handler"""
486 """callback for restoring original SIGINT handler"""
487 signal.signal(signal.SIGINT, self._handle_sigint)
487 signal.signal(signal.SIGINT, self._handle_sigint)
488
488
489 def _confirm_exit(self):
489 def _confirm_exit(self):
490 """confirm shutdown on ^C
490 """confirm shutdown on ^C
491
491
492 A second ^C, or answering 'y' within 5s will cause shutdown,
492 A second ^C, or answering 'y' within 5s will cause shutdown,
493 otherwise original SIGINT handler will be restored.
493 otherwise original SIGINT handler will be restored.
494 """
494 """
495 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
495 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
496 time.sleep(0.1)
496 time.sleep(0.1)
497 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
497 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
498 sys.stdout.flush()
498 sys.stdout.flush()
499 r,w,x = select.select([sys.stdin], [], [], 5)
499 r,w,x = select.select([sys.stdin], [], [], 5)
500 if r:
500 if r:
501 line = sys.stdin.readline()
501 line = sys.stdin.readline()
502 if line.lower().startswith('y'):
502 if line.lower().startswith('y'):
503 self.log.critical("Shutdown confirmed")
503 self.log.critical("Shutdown confirmed")
504 ioloop.IOLoop.instance().stop()
504 ioloop.IOLoop.instance().stop()
505 return
505 return
506 else:
506 else:
507 print "No answer for 5s:",
507 print "No answer for 5s:",
508 print "resuming operation..."
508 print "resuming operation..."
509 # no answer, or answer is no:
509 # no answer, or answer is no:
510 # set it back to original SIGINT handler
510 # set it back to original SIGINT handler
511 # use IOLoop.add_callback because signal.signal must be called
511 # use IOLoop.add_callback because signal.signal must be called
512 # from main thread
512 # from main thread
513 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
513 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
514
514
515 def _signal_stop(self, sig, frame):
515 def _signal_stop(self, sig, frame):
516 self.log.critical("received signal %s, stopping", sig)
516 self.log.critical("received signal %s, stopping", sig)
517 ioloop.IOLoop.instance().stop()
517 ioloop.IOLoop.instance().stop()
518
518
519 @catch_config_error
519 @catch_config_error
520 def initialize(self, argv=None):
520 def initialize(self, argv=None):
521 self.init_logging()
521 self.init_logging()
522 super(NotebookApp, self).initialize(argv)
522 super(NotebookApp, self).initialize(argv)
523 self.init_configurables()
523 self.init_configurables()
524 self.init_webapp()
524 self.init_webapp()
525 self.init_signal()
525 self.init_signal()
526
526
527 def cleanup_kernels(self):
527 def cleanup_kernels(self):
528 """shutdown all kernels
528 """shutdown all kernels
529
529
530 The kernels will shutdown themselves when this process no longer exists,
530 The kernels will shutdown themselves when this process no longer exists,
531 but explicit shutdown allows the KernelManagers to cleanup the connection files.
531 but explicit shutdown allows the KernelManagers to cleanup the connection files.
532 """
532 """
533 self.log.info('Shutting down kernels')
533 self.log.info('Shutting down kernels')
534 km = self.kernel_manager
534 km = self.kernel_manager
535 # copy list, since kill_kernel deletes keys
535 # copy list, since kill_kernel deletes keys
536 for kid in list(km.kernel_ids):
536 for kid in list(km.kernel_ids):
537 km.kill_kernel(kid)
537 km.kill_kernel(kid)
538
538
539 def start(self):
539 def start(self):
540 ip = self.ip if self.ip else '[all ip addresses on your system]'
540 ip = self.ip if self.ip else '[all ip addresses on your system]'
541 proto = 'https' if self.certfile else 'http'
541 proto = 'https' if self.certfile else 'http'
542 info = self.log.info
542 info = self.log.info
543 info("The IPython Notebook is running at: %s://%s:%i%s" %
543 info("The IPython Notebook is running at: %s://%s:%i%s" %
544 (proto, ip, self.port,self.base_project_url) )
544 (proto, ip, self.port,self.base_project_url) )
545 info("Use Control-C to stop this server and shut down all kernels.")
545 info("Use Control-C to stop this server and shut down all kernels.")
546
546
547 if self.open_browser:
547 if self.open_browser:
548 ip = self.ip or '127.0.0.1'
548 ip = self.ip or '127.0.0.1'
549 if self.browser:
549 if self.browser:
550 browser = webbrowser.get(self.browser)
550 browser = webbrowser.get(self.browser)
551 else:
551 else:
552 browser = webbrowser.get()
552 browser = webbrowser.get()
553
553
554 if self.file_to_run:
554 if self.file_to_run:
555 filename, _ = os.path.splitext(os.path.basename(self.file_to_run))
555 filename, _ = os.path.splitext(os.path.basename(self.file_to_run))
556 for nb in self.notebook_manager.list_notebooks():
556 for nb in self.notebook_manager.list_notebooks():
557 if filename == nb['name']:
557 if filename == nb['name']:
558 url = nb['notebook_id']
558 url = nb['notebook_id']
559 break
559 break
560 else:
560 else:
561 url = ''
561 url = ''
562 else:
562 else:
563 url = ''
563 url = ''
564 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
564 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
565 self.port, self.base_project_url, url),
565 self.port, self.base_project_url, url),
566 new=2)
566 new=2)
567 threading.Thread(target=b).start()
567 threading.Thread(target=b).start()
568 try:
568 try:
569 ioloop.IOLoop.instance().start()
569 ioloop.IOLoop.instance().start()
570 except KeyboardInterrupt:
570 except KeyboardInterrupt:
571 info("Interrupted...")
571 info("Interrupted...")
572 finally:
572 finally:
573 self.cleanup_kernels()
573 self.cleanup_kernels()
574
574
575
575
576 #-----------------------------------------------------------------------------
576 #-----------------------------------------------------------------------------
577 # Main entry point
577 # Main entry point
578 #-----------------------------------------------------------------------------
578 #-----------------------------------------------------------------------------
579
579
580 def launch_new_instance():
580 def launch_new_instance():
581 app = NotebookApp.instance()
581 app = NotebookApp.instance()
582 app.initialize()
582 app.initialize()
583 app.start()
583 app.start()
584
584
@@ -1,372 +1,372 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 # If run on Windows, install an exception hook which pops up a
29 # If run on Windows, install an exception hook which pops up a
30 # message box. Pythonw.exe hides the console, so without this
30 # message box. Pythonw.exe hides the console, so without this
31 # the application silently fails to load.
31 # the application silently fails to load.
32 #
32 #
33 # We always install this handler, because the expectation is for
33 # We always install this handler, because the expectation is for
34 # qtconsole to bring up a GUI even if called from the console.
34 # qtconsole to bring up a GUI even if called from the console.
35 # The old handler is called, so the exception is printed as well.
35 # The old handler is called, so the exception is printed as well.
36 # If desired, check for pythonw with an additional condition
36 # If desired, check for pythonw with an additional condition
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 if os.name == 'nt':
38 if os.name == 'nt':
39 old_excepthook = sys.excepthook
39 old_excepthook = sys.excepthook
40
40
41 def gui_excepthook(exctype, value, tb):
41 def gui_excepthook(exctype, value, tb):
42 try:
42 try:
43 import ctypes, traceback
43 import ctypes, traceback
44 MB_ICONERROR = 0x00000010L
44 MB_ICONERROR = 0x00000010L
45 title = u'Error starting IPython QtConsole'
45 title = u'Error starting IPython QtConsole'
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 finally:
48 finally:
49 # Also call the old exception hook to let it do
49 # Also call the old exception hook to let it do
50 # its thing too.
50 # its thing too.
51 old_excepthook(exctype, value, tb)
51 old_excepthook(exctype, value, tb)
52
52
53 sys.excepthook = gui_excepthook
53 sys.excepthook = gui_excepthook
54
54
55 # System library imports
55 # System library imports
56 from IPython.external.qt import QtCore, QtGui
56 from IPython.external.qt import QtCore, QtGui
57
57
58 # Local imports
58 # Local imports
59 from IPython.config.application import boolean_flag, catch_config_error
59 from IPython.config.application import boolean_flag, catch_config_error
60 from IPython.core.application import BaseIPythonApplication
60 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.profiledir import ProfileDir
61 from IPython.core.profiledir import ProfileDir
62 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
62 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
63 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
64 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
65 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
66 from IPython.frontend.qt.console import styles
66 from IPython.frontend.qt.console import styles
67 from IPython.frontend.qt.console.mainwindow import MainWindow
67 from IPython.frontend.qt.console.mainwindow import MainWindow
68 from IPython.frontend.qt.kernelmanager import QtKernelManager
68 from IPython.frontend.qt.kernelmanager import QtKernelManager
69 from IPython.utils.path import filefind
69 from IPython.utils.path import filefind
70 from IPython.utils.py3compat import str_to_bytes
70 from IPython.utils.py3compat import str_to_bytes
71 from IPython.utils.traitlets import (
71 from IPython.utils.traitlets import (
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 )
73 )
74 from IPython.zmq.ipkernel import IPKernelApp
74 from IPython.zmq.ipkernel import IPKernelApp
75 from IPython.zmq.session import Session, default_secure
75 from IPython.zmq.session import Session, default_secure
76 from IPython.zmq.zmqshell import ZMQInteractiveShell
76 from IPython.zmq.zmqshell import ZMQInteractiveShell
77
77
78 from IPython.frontend.consoleapp import (
78 from IPython.frontend.consoleapp import (
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 )
80 )
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Network Constants
83 # Network Constants
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Globals
89 # Globals
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 _examples = """
92 _examples = """
93 ipython qtconsole # start the qtconsole
93 ipython qtconsole # start the qtconsole
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 """
95 """
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # Aliases and Flags
98 # Aliases and Flags
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 # start with copy of flags
101 # start with copy of flags
102 flags = dict(flags)
102 flags = dict(flags)
103 qt_flags = {
103 qt_flags = {
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 "Disable rich text support."),
105 "Disable rich text support."),
106 }
106 }
107 qt_flags.update(boolean_flag(
107 qt_flags.update(boolean_flag(
108 'gui-completion', 'ConsoleWidget.gui_completion',
108 'gui-completion', 'ConsoleWidget.gui_completion',
109 "use a GUI widget for tab completion",
109 "use a GUI widget for tab completion",
110 "use plaintext output for completion"
110 "use plaintext output for completion"
111 ))
111 ))
112 # and app_flags from the Console Mixin
112 # and app_flags from the Console Mixin
113 qt_flags.update(app_flags)
113 qt_flags.update(app_flags)
114 # add frontend flags to the full set
114 # add frontend flags to the full set
115 flags.update(qt_flags)
115 flags.update(qt_flags)
116
116
117 # start with copy of front&backend aliases list
117 # start with copy of front&backend aliases list
118 aliases = dict(aliases)
118 aliases = dict(aliases)
119 qt_aliases = dict(
119 qt_aliases = dict(
120
120
121 style = 'IPythonWidget.syntax_style',
121 style = 'IPythonWidget.syntax_style',
122 stylesheet = 'IPythonQtConsoleApp.stylesheet',
122 stylesheet = 'IPythonQtConsoleApp.stylesheet',
123 colors = 'ZMQInteractiveShell.colors',
123 colors = 'ZMQInteractiveShell.colors',
124
124
125 editor = 'IPythonWidget.editor',
125 editor = 'IPythonWidget.editor',
126 paging = 'ConsoleWidget.paging',
126 paging = 'ConsoleWidget.paging',
127 )
127 )
128 # and app_aliases from the Console Mixin
128 # and app_aliases from the Console Mixin
129 qt_aliases.update(app_aliases)
129 qt_aliases.update(app_aliases)
130 # add frontend aliases to the full set
130 # add frontend aliases to the full set
131 aliases.update(qt_aliases)
131 aliases.update(qt_aliases)
132
132
133 # get flags&aliases into sets, and remove a couple that
133 # get flags&aliases into sets, and remove a couple that
134 # shouldn't be scrubbed from backend flags:
134 # shouldn't be scrubbed from backend flags:
135 qt_aliases = set(qt_aliases.keys())
135 qt_aliases = set(qt_aliases.keys())
136 qt_aliases.remove('colors')
136 qt_aliases.remove('colors')
137 qt_flags = set(qt_flags.keys())
137 qt_flags = set(qt_flags.keys())
138
138
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140 # Classes
140 # Classes
141 #-----------------------------------------------------------------------------
141 #-----------------------------------------------------------------------------
142
142
143 #-----------------------------------------------------------------------------
143 #-----------------------------------------------------------------------------
144 # IPythonQtConsole
144 # IPythonQtConsole
145 #-----------------------------------------------------------------------------
145 #-----------------------------------------------------------------------------
146
146
147
147
148 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
148 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
149 name = 'ipython-qtconsole'
149 name = 'ipython-qtconsole'
150
150
151 description = """
151 description = """
152 The IPython QtConsole.
152 The IPython QtConsole.
153
153
154 This launches a Console-style application using Qt. It is not a full
154 This launches a Console-style application using Qt. It is not a full
155 console, in that launched terminal subprocesses will not be able to accept
155 console, in that launched terminal subprocesses will not be able to accept
156 input.
156 input.
157
157
158 The QtConsole supports various extra features beyond the Terminal IPython
158 The QtConsole supports various extra features beyond the Terminal IPython
159 shell, such as inline plotting with matplotlib, via:
159 shell, such as inline plotting with matplotlib, via:
160
160
161 ipython qtconsole --pylab=inline
161 ipython qtconsole --pylab=inline
162
162
163 as well as saving your session as HTML, and printing the output.
163 as well as saving your session as HTML, and printing the output.
164
164
165 """
165 """
166 examples = _examples
166 examples = _examples
167
167
168 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
168 classes = [IPythonWidget] + IPythonConsoleApp.classes
169 flags = Dict(flags)
169 flags = Dict(flags)
170 aliases = Dict(aliases)
170 aliases = Dict(aliases)
171 frontend_flags = Any(qt_flags)
171 frontend_flags = Any(qt_flags)
172 frontend_aliases = Any(qt_aliases)
172 frontend_aliases = Any(qt_aliases)
173 kernel_manager_class = QtKernelManager
173 kernel_manager_class = QtKernelManager
174
174
175 stylesheet = Unicode('', config=True,
175 stylesheet = Unicode('', config=True,
176 help="path to a custom CSS stylesheet")
176 help="path to a custom CSS stylesheet")
177
177
178 plain = CBool(False, config=True,
178 plain = CBool(False, config=True,
179 help="Use a plaintext widget instead of rich text (plain can't print/save).")
179 help="Use a plaintext widget instead of rich text (plain can't print/save).")
180
180
181 def _plain_changed(self, name, old, new):
181 def _plain_changed(self, name, old, new):
182 kind = 'plain' if new else 'rich'
182 kind = 'plain' if new else 'rich'
183 self.config.ConsoleWidget.kind = kind
183 self.config.ConsoleWidget.kind = kind
184 if new:
184 if new:
185 self.widget_factory = IPythonWidget
185 self.widget_factory = IPythonWidget
186 else:
186 else:
187 self.widget_factory = RichIPythonWidget
187 self.widget_factory = RichIPythonWidget
188
188
189 # the factory for creating a widget
189 # the factory for creating a widget
190 widget_factory = Any(RichIPythonWidget)
190 widget_factory = Any(RichIPythonWidget)
191
191
192 def parse_command_line(self, argv=None):
192 def parse_command_line(self, argv=None):
193 super(IPythonQtConsoleApp, self).parse_command_line(argv)
193 super(IPythonQtConsoleApp, self).parse_command_line(argv)
194 self.build_kernel_argv(argv)
194 self.build_kernel_argv(argv)
195
195
196
196
197 def new_frontend_master(self):
197 def new_frontend_master(self):
198 """ Create and return new frontend attached to new kernel, launched on localhost.
198 """ Create and return new frontend attached to new kernel, launched on localhost.
199 """
199 """
200 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
200 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
201 kernel_manager = self.kernel_manager_class(
201 kernel_manager = self.kernel_manager_class(
202 ip=ip,
202 ip=ip,
203 connection_file=self._new_connection_file(),
203 connection_file=self._new_connection_file(),
204 config=self.config,
204 config=self.config,
205 )
205 )
206 # start the kernel
206 # start the kernel
207 kwargs = dict()
207 kwargs = dict()
208 kwargs['extra_arguments'] = self.kernel_argv
208 kwargs['extra_arguments'] = self.kernel_argv
209 kernel_manager.start_kernel(**kwargs)
209 kernel_manager.start_kernel(**kwargs)
210 kernel_manager.start_channels()
210 kernel_manager.start_channels()
211 widget = self.widget_factory(config=self.config,
211 widget = self.widget_factory(config=self.config,
212 local_kernel=True)
212 local_kernel=True)
213 self.init_colors(widget)
213 self.init_colors(widget)
214 widget.kernel_manager = kernel_manager
214 widget.kernel_manager = kernel_manager
215 widget._existing = False
215 widget._existing = False
216 widget._may_close = True
216 widget._may_close = True
217 widget._confirm_exit = self.confirm_exit
217 widget._confirm_exit = self.confirm_exit
218 return widget
218 return widget
219
219
220 def new_frontend_slave(self, current_widget):
220 def new_frontend_slave(self, current_widget):
221 """Create and return a new frontend attached to an existing kernel.
221 """Create and return a new frontend attached to an existing kernel.
222
222
223 Parameters
223 Parameters
224 ----------
224 ----------
225 current_widget : IPythonWidget
225 current_widget : IPythonWidget
226 The IPythonWidget whose kernel this frontend is to share
226 The IPythonWidget whose kernel this frontend is to share
227 """
227 """
228 kernel_manager = self.kernel_manager_class(
228 kernel_manager = self.kernel_manager_class(
229 connection_file=current_widget.kernel_manager.connection_file,
229 connection_file=current_widget.kernel_manager.connection_file,
230 config = self.config,
230 config = self.config,
231 )
231 )
232 kernel_manager.load_connection_file()
232 kernel_manager.load_connection_file()
233 kernel_manager.start_channels()
233 kernel_manager.start_channels()
234 widget = self.widget_factory(config=self.config,
234 widget = self.widget_factory(config=self.config,
235 local_kernel=False)
235 local_kernel=False)
236 self.init_colors(widget)
236 self.init_colors(widget)
237 widget._existing = True
237 widget._existing = True
238 widget._may_close = False
238 widget._may_close = False
239 widget._confirm_exit = False
239 widget._confirm_exit = False
240 widget.kernel_manager = kernel_manager
240 widget.kernel_manager = kernel_manager
241 return widget
241 return widget
242
242
243 def init_qt_elements(self):
243 def init_qt_elements(self):
244 # Create the widget.
244 # Create the widget.
245 self.app = QtGui.QApplication([])
245 self.app = QtGui.QApplication([])
246
246
247 base_path = os.path.abspath(os.path.dirname(__file__))
247 base_path = os.path.abspath(os.path.dirname(__file__))
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
249 self.app.icon = QtGui.QIcon(icon_path)
249 self.app.icon = QtGui.QIcon(icon_path)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
251
251
252 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
252 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
253 self.widget = self.widget_factory(config=self.config,
253 self.widget = self.widget_factory(config=self.config,
254 local_kernel=local_kernel)
254 local_kernel=local_kernel)
255 self.init_colors(self.widget)
255 self.init_colors(self.widget)
256 self.widget._existing = self.existing
256 self.widget._existing = self.existing
257 self.widget._may_close = not self.existing
257 self.widget._may_close = not self.existing
258 self.widget._confirm_exit = self.confirm_exit
258 self.widget._confirm_exit = self.confirm_exit
259
259
260 self.widget.kernel_manager = self.kernel_manager
260 self.widget.kernel_manager = self.kernel_manager
261 self.window = MainWindow(self.app,
261 self.window = MainWindow(self.app,
262 confirm_exit=self.confirm_exit,
262 confirm_exit=self.confirm_exit,
263 new_frontend_factory=self.new_frontend_master,
263 new_frontend_factory=self.new_frontend_master,
264 slave_frontend_factory=self.new_frontend_slave,
264 slave_frontend_factory=self.new_frontend_slave,
265 )
265 )
266 self.window.log = self.log
266 self.window.log = self.log
267 self.window.add_tab_with_frontend(self.widget)
267 self.window.add_tab_with_frontend(self.widget)
268 self.window.init_menu_bar()
268 self.window.init_menu_bar()
269
269
270 self.window.setWindowTitle('IPython')
270 self.window.setWindowTitle('IPython')
271
271
272 def init_colors(self, widget):
272 def init_colors(self, widget):
273 """Configure the coloring of the widget"""
273 """Configure the coloring of the widget"""
274 # Note: This will be dramatically simplified when colors
274 # Note: This will be dramatically simplified when colors
275 # are removed from the backend.
275 # are removed from the backend.
276
276
277 # parse the colors arg down to current known labels
277 # parse the colors arg down to current known labels
278 try:
278 try:
279 colors = self.config.ZMQInteractiveShell.colors
279 colors = self.config.ZMQInteractiveShell.colors
280 except AttributeError:
280 except AttributeError:
281 colors = None
281 colors = None
282 try:
282 try:
283 style = self.config.IPythonWidget.syntax_style
283 style = self.config.IPythonWidget.syntax_style
284 except AttributeError:
284 except AttributeError:
285 style = None
285 style = None
286 try:
286 try:
287 sheet = self.config.IPythonWidget.style_sheet
287 sheet = self.config.IPythonWidget.style_sheet
288 except AttributeError:
288 except AttributeError:
289 sheet = None
289 sheet = None
290
290
291 # find the value for colors:
291 # find the value for colors:
292 if colors:
292 if colors:
293 colors=colors.lower()
293 colors=colors.lower()
294 if colors in ('lightbg', 'light'):
294 if colors in ('lightbg', 'light'):
295 colors='lightbg'
295 colors='lightbg'
296 elif colors in ('dark', 'linux'):
296 elif colors in ('dark', 'linux'):
297 colors='linux'
297 colors='linux'
298 else:
298 else:
299 colors='nocolor'
299 colors='nocolor'
300 elif style:
300 elif style:
301 if style=='bw':
301 if style=='bw':
302 colors='nocolor'
302 colors='nocolor'
303 elif styles.dark_style(style):
303 elif styles.dark_style(style):
304 colors='linux'
304 colors='linux'
305 else:
305 else:
306 colors='lightbg'
306 colors='lightbg'
307 else:
307 else:
308 colors=None
308 colors=None
309
309
310 # Configure the style
310 # Configure the style
311 if style:
311 if style:
312 widget.style_sheet = styles.sheet_from_template(style, colors)
312 widget.style_sheet = styles.sheet_from_template(style, colors)
313 widget.syntax_style = style
313 widget.syntax_style = style
314 widget._syntax_style_changed()
314 widget._syntax_style_changed()
315 widget._style_sheet_changed()
315 widget._style_sheet_changed()
316 elif colors:
316 elif colors:
317 # use a default dark/light/bw style
317 # use a default dark/light/bw style
318 widget.set_default_style(colors=colors)
318 widget.set_default_style(colors=colors)
319
319
320 if self.stylesheet:
320 if self.stylesheet:
321 # we got an explicit stylesheet
321 # we got an explicit stylesheet
322 if os.path.isfile(self.stylesheet):
322 if os.path.isfile(self.stylesheet):
323 with open(self.stylesheet) as f:
323 with open(self.stylesheet) as f:
324 sheet = f.read()
324 sheet = f.read()
325 else:
325 else:
326 raise IOError("Stylesheet %r not found." % self.stylesheet)
326 raise IOError("Stylesheet %r not found." % self.stylesheet)
327 if sheet:
327 if sheet:
328 widget.style_sheet = sheet
328 widget.style_sheet = sheet
329 widget._style_sheet_changed()
329 widget._style_sheet_changed()
330
330
331
331
332 def init_signal(self):
332 def init_signal(self):
333 """allow clean shutdown on sigint"""
333 """allow clean shutdown on sigint"""
334 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
334 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
335 # need a timer, so that QApplication doesn't block until a real
335 # need a timer, so that QApplication doesn't block until a real
336 # Qt event fires (can require mouse movement)
336 # Qt event fires (can require mouse movement)
337 # timer trick from http://stackoverflow.com/q/4938723/938949
337 # timer trick from http://stackoverflow.com/q/4938723/938949
338 timer = QtCore.QTimer()
338 timer = QtCore.QTimer()
339 # Let the interpreter run each 200 ms:
339 # Let the interpreter run each 200 ms:
340 timer.timeout.connect(lambda: None)
340 timer.timeout.connect(lambda: None)
341 timer.start(200)
341 timer.start(200)
342 # hold onto ref, so the timer doesn't get cleaned up
342 # hold onto ref, so the timer doesn't get cleaned up
343 self._sigint_timer = timer
343 self._sigint_timer = timer
344
344
345 @catch_config_error
345 @catch_config_error
346 def initialize(self, argv=None):
346 def initialize(self, argv=None):
347 super(IPythonQtConsoleApp, self).initialize(argv)
347 super(IPythonQtConsoleApp, self).initialize(argv)
348 IPythonConsoleApp.initialize(self,argv)
348 IPythonConsoleApp.initialize(self,argv)
349 self.init_qt_elements()
349 self.init_qt_elements()
350 self.init_signal()
350 self.init_signal()
351
351
352 def start(self):
352 def start(self):
353
353
354 # draw the window
354 # draw the window
355 self.window.show()
355 self.window.show()
356 self.window.raise_()
356 self.window.raise_()
357
357
358 # Start the application main loop.
358 # Start the application main loop.
359 self.app.exec_()
359 self.app.exec_()
360
360
361 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
362 # Main entry point
362 # Main entry point
363 #-----------------------------------------------------------------------------
363 #-----------------------------------------------------------------------------
364
364
365 def main():
365 def main():
366 app = IPythonQtConsoleApp()
366 app = IPythonQtConsoleApp()
367 app.initialize()
367 app.initialize()
368 app.start()
368 app.start()
369
369
370
370
371 if __name__ == '__main__':
371 if __name__ == '__main__':
372 main()
372 main()
@@ -1,154 +1,154 b''
1 """ A minimal application using the ZMQ-based terminal IPython frontend.
1 """ A minimal application using the ZMQ-based terminal IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Min RK
8 * Min RK
9 * Paul Ivanov
9 * Paul Ivanov
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19
19
20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21
21
22 from IPython.utils.traitlets import (
22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 )
24 )
25 from IPython.utils.warn import warn,error
25 from IPython.utils.warn import warn,error
26
26
27 from IPython.zmq.ipkernel import IPKernelApp
27 from IPython.zmq.ipkernel import IPKernelApp
28 from IPython.zmq.session import Session, default_secure
28 from IPython.zmq.session import Session, default_secure
29 from IPython.zmq.zmqshell import ZMQInteractiveShell
29 from IPython.zmq.zmqshell import ZMQInteractiveShell
30 from IPython.frontend.consoleapp import (
30 from IPython.frontend.consoleapp import (
31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 )
32 )
33
33
34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Globals
37 # Globals
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 _examples = """
40 _examples = """
41 ipython console # start the ZMQ-based console
41 ipython console # start the ZMQ-based console
42 ipython console --existing # connect to an existing ipython session
42 ipython console --existing # connect to an existing ipython session
43 """
43 """
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Flags and Aliases
46 # Flags and Aliases
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 # copy flags from mixin:
49 # copy flags from mixin:
50 flags = dict(flags)
50 flags = dict(flags)
51 # start with mixin frontend flags:
51 # start with mixin frontend flags:
52 frontend_flags = dict(app_flags)
52 frontend_flags = dict(app_flags)
53 # add TerminalIPApp flags:
53 # add TerminalIPApp flags:
54 frontend_flags.update(term_flags)
54 frontend_flags.update(term_flags)
55 # disable quick startup, as it won't propagate to the kernel anyway
55 # disable quick startup, as it won't propagate to the kernel anyway
56 frontend_flags.pop('quick')
56 frontend_flags.pop('quick')
57 # update full dict with frontend flags:
57 # update full dict with frontend flags:
58 flags.update(frontend_flags)
58 flags.update(frontend_flags)
59
59
60 # copy flags from mixin
60 # copy flags from mixin
61 aliases = dict(aliases)
61 aliases = dict(aliases)
62 # start with mixin frontend flags
62 # start with mixin frontend flags
63 frontend_aliases = dict(app_aliases)
63 frontend_aliases = dict(app_aliases)
64 # load updated frontend flags into full dict
64 # load updated frontend flags into full dict
65 aliases.update(frontend_aliases)
65 aliases.update(frontend_aliases)
66
66
67 # get flags&aliases into sets, and remove a couple that
67 # get flags&aliases into sets, and remove a couple that
68 # shouldn't be scrubbed from backend flags:
68 # shouldn't be scrubbed from backend flags:
69 frontend_aliases = set(frontend_aliases.keys())
69 frontend_aliases = set(frontend_aliases.keys())
70 frontend_flags = set(frontend_flags.keys())
70 frontend_flags = set(frontend_flags.keys())
71
71
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Classes
74 # Classes
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77
77
78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
79 name = "ipython-console"
79 name = "ipython-console"
80 """Start a terminal frontend to the IPython zmq kernel."""
80 """Start a terminal frontend to the IPython zmq kernel."""
81
81
82 description = """
82 description = """
83 The IPython terminal-based Console.
83 The IPython terminal-based Console.
84
84
85 This launches a Console application inside a terminal.
85 This launches a Console application inside a terminal.
86
86
87 The Console supports various extra features beyond the traditional
87 The Console supports various extra features beyond the traditional
88 single-process Terminal IPython shell, such as connecting to an
88 single-process Terminal IPython shell, such as connecting to an
89 existing ipython session, via:
89 existing ipython session, via:
90
90
91 ipython console --existing
91 ipython console --existing
92
92
93 where the previous session could have been created by another ipython
93 where the previous session could have been created by another ipython
94 console, an ipython qtconsole, or by opening an ipython notebook.
94 console, an ipython qtconsole, or by opening an ipython notebook.
95
95
96 """
96 """
97 examples = _examples
97 examples = _examples
98
98
99 classes = List([IPKernelApp, ZMQTerminalInteractiveShell, Session])
99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
100 flags = Dict(flags)
100 flags = Dict(flags)
101 aliases = Dict(aliases)
101 aliases = Dict(aliases)
102 frontend_aliases = Any(frontend_aliases)
102 frontend_aliases = Any(frontend_aliases)
103 frontend_flags = Any(frontend_flags)
103 frontend_flags = Any(frontend_flags)
104
104
105 subcommands = Dict()
105 subcommands = Dict()
106
106
107 def parse_command_line(self, argv=None):
107 def parse_command_line(self, argv=None):
108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
109 self.build_kernel_argv(argv)
109 self.build_kernel_argv(argv)
110
110
111 def init_shell(self):
111 def init_shell(self):
112 IPythonConsoleApp.initialize(self)
112 IPythonConsoleApp.initialize(self)
113 # relay sigint to kernel
113 # relay sigint to kernel
114 signal.signal(signal.SIGINT, self.handle_sigint)
114 signal.signal(signal.SIGINT, self.handle_sigint)
115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 display_banner=False, profile_dir=self.profile_dir,
116 display_banner=False, profile_dir=self.profile_dir,
117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
118
118
119 def init_gui_pylab(self):
119 def init_gui_pylab(self):
120 # no-op, because we don't want to import matplotlib in the frontend.
120 # no-op, because we don't want to import matplotlib in the frontend.
121 pass
121 pass
122
122
123 def handle_sigint(self, *args):
123 def handle_sigint(self, *args):
124 if self.shell._executing:
124 if self.shell._executing:
125 if self.kernel_manager.has_kernel:
125 if self.kernel_manager.has_kernel:
126 # interrupt already gets passed to subprocess by signal handler.
126 # interrupt already gets passed to subprocess by signal handler.
127 # Only if we prevent that should we need to explicitly call
127 # Only if we prevent that should we need to explicitly call
128 # interrupt_kernel, until which time, this would result in a
128 # interrupt_kernel, until which time, this would result in a
129 # double-interrupt:
129 # double-interrupt:
130 # self.kernel_manager.interrupt_kernel()
130 # self.kernel_manager.interrupt_kernel()
131 pass
131 pass
132 else:
132 else:
133 self.shell.write_err('\n')
133 self.shell.write_err('\n')
134 error("Cannot interrupt kernels we didn't start.\n")
134 error("Cannot interrupt kernels we didn't start.\n")
135 else:
135 else:
136 # raise the KeyboardInterrupt if we aren't waiting for execution,
136 # raise the KeyboardInterrupt if we aren't waiting for execution,
137 # so that the interact loop advances, and prompt is redrawn, etc.
137 # so that the interact loop advances, and prompt is redrawn, etc.
138 raise KeyboardInterrupt
138 raise KeyboardInterrupt
139
139
140
140
141 def init_code(self):
141 def init_code(self):
142 # no-op in the frontend, code gets run in the backend
142 # no-op in the frontend, code gets run in the backend
143 pass
143 pass
144
144
145 def launch_new_instance():
145 def launch_new_instance():
146 """Create and run a full blown IPython instance"""
146 """Create and run a full blown IPython instance"""
147 app = ZMQTerminalIPythonApp.instance()
147 app = ZMQTerminalIPythonApp.instance()
148 app.initialize()
148 app.initialize()
149 app.start()
149 app.start()
150
150
151
151
152 if __name__ == '__main__':
152 if __name__ == '__main__':
153 launch_new_instance()
153 launch_new_instance()
154
154
General Comments 0
You need to be logged in to leave comments. Login now