##// END OF EJS Templates
Merge pull request #6936 from minrk/profile-dir...
Matthias Bussonnier -
r20893:1d5c51bb merge
parent child Browse files
Show More
@@ -1,344 +1,330 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6 """
6 """
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import atexit
10 import atexit
11 import os
11 import os
12 import signal
12 import signal
13 import sys
13 import sys
14 import uuid
14 import uuid
15
15
16
16
17 from IPython.config.application import boolean_flag
17 from IPython.config.application import boolean_flag
18 from IPython.core.profiledir import ProfileDir
18 from IPython.core.profiledir import ProfileDir
19 from IPython.kernel.blocking import BlockingKernelClient
19 from IPython.kernel.blocking import BlockingKernelClient
20 from IPython.kernel import KernelManager
20 from IPython.kernel import KernelManager
21 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
21 from IPython.kernel import tunnel_to_kernel, find_connection_file
22 from IPython.kernel.kernelspec import NoSuchKernel
22 from IPython.kernel.kernelspec import NoSuchKernel
23 from IPython.utils.path import filefind
23 from IPython.utils.path import filefind
24 from IPython.utils.traitlets import (
24 from IPython.utils.traitlets import (
25 Dict, List, Unicode, CUnicode, CBool, Any
25 Dict, List, Unicode, CUnicode, CBool, Any
26 )
26 )
27 from IPython.kernel.zmq.kernelapp import (
28 kernel_flags,
29 kernel_aliases,
30 IPKernelApp
31 )
32 from IPython.kernel.zmq.pylab.config import InlineBackend
33 from IPython.kernel.zmq.session import Session
27 from IPython.kernel.zmq.session import Session
34 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
35 from IPython.kernel.connect import ConnectionFileMixin
28 from IPython.kernel.connect import ConnectionFileMixin
36
29
37 from IPython.utils.localinterfaces import localhost
30 from IPython.utils.localinterfaces import localhost
38
31
39 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
40 # Aliases and Flags
33 # Aliases and Flags
41 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
42
35
43 flags = dict(kernel_flags)
36 flags = {}
44
37
45 # the flags that are specific to the frontend
38 # the flags that are specific to the frontend
46 # these must be scrubbed before being passed to the kernel,
39 # these must be scrubbed before being passed to the kernel,
47 # or it will raise an error on unrecognized flags
40 # or it will raise an error on unrecognized flags
48 app_flags = {
41 app_flags = {
49 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
42 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
50 "Connect to an existing kernel. If no argument specified, guess most recent"),
43 "Connect to an existing kernel. If no argument specified, guess most recent"),
51 }
44 }
52 app_flags.update(boolean_flag(
45 app_flags.update(boolean_flag(
53 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
46 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
54 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
47 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
55 to force a direct exit without any confirmation.
48 to force a direct exit without any confirmation.
56 """,
49 """,
57 """Don't prompt the user when exiting. This will terminate the kernel
50 """Don't prompt the user when exiting. This will terminate the kernel
58 if it is owned by the frontend, and leave it alive if it is external.
51 if it is owned by the frontend, and leave it alive if it is external.
59 """
52 """
60 ))
53 ))
61 flags.update(app_flags)
54 flags.update(app_flags)
62
55
63 aliases = dict(kernel_aliases)
56 aliases = {}
64
57
65 # also scrub aliases from the frontend
58 # also scrub aliases from the frontend
66 app_aliases = dict(
59 app_aliases = dict(
67 ip = 'IPythonConsoleApp.ip',
60 ip = 'IPythonConsoleApp.ip',
68 transport = 'IPythonConsoleApp.transport',
61 transport = 'IPythonConsoleApp.transport',
69 hb = 'IPythonConsoleApp.hb_port',
62 hb = 'IPythonConsoleApp.hb_port',
70 shell = 'IPythonConsoleApp.shell_port',
63 shell = 'IPythonConsoleApp.shell_port',
71 iopub = 'IPythonConsoleApp.iopub_port',
64 iopub = 'IPythonConsoleApp.iopub_port',
72 stdin = 'IPythonConsoleApp.stdin_port',
65 stdin = 'IPythonConsoleApp.stdin_port',
73 existing = 'IPythonConsoleApp.existing',
66 existing = 'IPythonConsoleApp.existing',
74 f = 'IPythonConsoleApp.connection_file',
67 f = 'IPythonConsoleApp.connection_file',
75
68
76 kernel = 'IPythonConsoleApp.kernel_name',
69 kernel = 'IPythonConsoleApp.kernel_name',
77
70
78 ssh = 'IPythonConsoleApp.sshserver',
71 ssh = 'IPythonConsoleApp.sshserver',
79 )
72 )
80 aliases.update(app_aliases)
73 aliases.update(app_aliases)
81
74
82 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
83 # Classes
76 # Classes
84 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
85
78
86 classes = [KernelManager, ProfileDir, Session]
79 classes = [KernelManager, ProfileDir, Session]
87
80
88 class IPythonConsoleApp(ConnectionFileMixin):
81 class IPythonConsoleApp(ConnectionFileMixin):
89 name = 'ipython-console-mixin'
82 name = 'ipython-console-mixin'
90
83
91 description = """
84 description = """
92 The IPython Mixin Console.
85 The IPython Mixin Console.
93
86
94 This class contains the common portions of console client (QtConsole,
87 This class contains the common portions of console client (QtConsole,
95 ZMQ-based terminal console, etc). It is not a full console, in that
88 ZMQ-based terminal console, etc). It is not a full console, in that
96 launched terminal subprocesses will not be able to accept input.
89 launched terminal subprocesses will not be able to accept input.
97
90
98 The Console using this mixing supports various extra features beyond
91 The Console using this mixing supports various extra features beyond
99 the single-process Terminal IPython shell, such as connecting to
92 the single-process Terminal IPython shell, such as connecting to
100 existing kernel, via:
93 existing kernel, via:
101
94
102 ipython <appname> --existing
95 ipython <appname> --existing
103
96
104 as well as tunnel via SSH
97 as well as tunnel via SSH
105
98
106 """
99 """
107
100
108 classes = classes
101 classes = classes
109 flags = Dict(flags)
102 flags = Dict(flags)
110 aliases = Dict(aliases)
103 aliases = Dict(aliases)
111 kernel_manager_class = KernelManager
104 kernel_manager_class = KernelManager
112 kernel_client_class = BlockingKernelClient
105 kernel_client_class = BlockingKernelClient
113
106
114 kernel_argv = List(Unicode)
107 kernel_argv = List(Unicode)
115 # frontend flags&aliases to be stripped when building kernel_argv
108 # frontend flags&aliases to be stripped when building kernel_argv
116 frontend_flags = Any(app_flags)
109 frontend_flags = Any(app_flags)
117 frontend_aliases = Any(app_aliases)
110 frontend_aliases = Any(app_aliases)
118
111
119 # create requested profiles by default, if they don't exist:
112 # create requested profiles by default, if they don't exist:
120 auto_create = CBool(True)
113 auto_create = CBool(True)
121 # connection info:
114 # connection info:
122
115
123 sshserver = Unicode('', config=True,
116 sshserver = Unicode('', config=True,
124 help="""The SSH server to use to connect to the kernel.""")
117 help="""The SSH server to use to connect to the kernel.""")
125 sshkey = Unicode('', config=True,
118 sshkey = Unicode('', config=True,
126 help="""Path to the ssh key to use for logging in to the ssh server.""")
119 help="""Path to the ssh key to use for logging in to the ssh server.""")
127
120
128 def _connection_file_default(self):
121 def _connection_file_default(self):
129 return 'kernel-%i.json' % os.getpid()
122 return 'kernel-%i.json' % os.getpid()
130
123
131 existing = CUnicode('', config=True,
124 existing = CUnicode('', config=True,
132 help="""Connect to an already running kernel""")
125 help="""Connect to an already running kernel""")
133
126
134 kernel_name = Unicode('python', config=True,
127 kernel_name = Unicode('python', config=True,
135 help="""The name of the default kernel to start.""")
128 help="""The name of the default kernel to start.""")
136
129
137 confirm_exit = CBool(True, config=True,
130 confirm_exit = CBool(True, config=True,
138 help="""
131 help="""
139 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
132 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
140 to force a direct exit without any confirmation.""",
133 to force a direct exit without any confirmation.""",
141 )
134 )
142
135
143 @property
136 def build_kernel_argv(self, argv=None):
144 def help_classes(self):
137 """build argv to be passed to kernel subprocess
145 """ConsoleApps can configure kernels on the command-line
146
138
147 But this shouldn't be written to a file
139 Override in subclasses if any args should be passed to the kernel
148 """
140 """
149 return self.classes + [IPKernelApp] + IPKernelApp.classes
141 self.kernel_argv = self.extra_args
150
151 def build_kernel_argv(self, argv=None):
152 """build argv to be passed to kernel subprocess"""
153 if argv is None:
154 argv = sys.argv[1:]
155 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
156
142
157 def init_connection_file(self):
143 def init_connection_file(self):
158 """find the connection file, and load the info if found.
144 """find the connection file, and load the info if found.
159
145
160 The current working directory and the current profile's security
146 The current working directory and the current profile's security
161 directory will be searched for the file if it is not given by
147 directory will be searched for the file if it is not given by
162 absolute path.
148 absolute path.
163
149
164 When attempting to connect to an existing kernel and the `--existing`
150 When attempting to connect to an existing kernel and the `--existing`
165 argument does not match an existing file, it will be interpreted as a
151 argument does not match an existing file, it will be interpreted as a
166 fileglob, and the matching file in the current profile's security dir
152 fileglob, and the matching file in the current profile's security dir
167 with the latest access time will be used.
153 with the latest access time will be used.
168
154
169 After this method is called, self.connection_file contains the *full path*
155 After this method is called, self.connection_file contains the *full path*
170 to the connection file, never just its name.
156 to the connection file, never just its name.
171 """
157 """
172 if self.existing:
158 if self.existing:
173 try:
159 try:
174 cf = find_connection_file(self.existing)
160 cf = find_connection_file(self.existing)
175 except Exception:
161 except Exception:
176 self.log.critical("Could not find existing kernel connection file %s", self.existing)
162 self.log.critical("Could not find existing kernel connection file %s", self.existing)
177 self.exit(1)
163 self.exit(1)
178 self.log.debug("Connecting to existing kernel: %s" % cf)
164 self.log.debug("Connecting to existing kernel: %s" % cf)
179 self.connection_file = cf
165 self.connection_file = cf
180 else:
166 else:
181 # not existing, check if we are going to write the file
167 # not existing, check if we are going to write the file
182 # and ensure that self.connection_file is a full path, not just the shortname
168 # and ensure that self.connection_file is a full path, not just the shortname
183 try:
169 try:
184 cf = find_connection_file(self.connection_file)
170 cf = find_connection_file(self.connection_file)
185 except Exception:
171 except Exception:
186 # file might not exist
172 # file might not exist
187 if self.connection_file == os.path.basename(self.connection_file):
173 if self.connection_file == os.path.basename(self.connection_file):
188 # just shortname, put it in security dir
174 # just shortname, put it in security dir
189 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
175 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
190 else:
176 else:
191 cf = self.connection_file
177 cf = self.connection_file
192 self.connection_file = cf
178 self.connection_file = cf
193 try:
179 try:
194 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
180 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
195 except IOError:
181 except IOError:
196 self.log.debug("Connection File not found: %s", self.connection_file)
182 self.log.debug("Connection File not found: %s", self.connection_file)
197 return
183 return
198
184
199 # should load_connection_file only be used for existing?
185 # should load_connection_file only be used for existing?
200 # as it is now, this allows reusing ports if an existing
186 # as it is now, this allows reusing ports if an existing
201 # file is requested
187 # file is requested
202 try:
188 try:
203 self.load_connection_file()
189 self.load_connection_file()
204 except Exception:
190 except Exception:
205 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
191 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
206 self.exit(1)
192 self.exit(1)
207
193
208 def init_ssh(self):
194 def init_ssh(self):
209 """set up ssh tunnels, if needed."""
195 """set up ssh tunnels, if needed."""
210 if not self.existing or (not self.sshserver and not self.sshkey):
196 if not self.existing or (not self.sshserver and not self.sshkey):
211 return
197 return
212 self.load_connection_file()
198 self.load_connection_file()
213
199
214 transport = self.transport
200 transport = self.transport
215 ip = self.ip
201 ip = self.ip
216
202
217 if transport != 'tcp':
203 if transport != 'tcp':
218 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
204 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
219 sys.exit(-1)
205 sys.exit(-1)
220
206
221 if self.sshkey and not self.sshserver:
207 if self.sshkey and not self.sshserver:
222 # specifying just the key implies that we are connecting directly
208 # specifying just the key implies that we are connecting directly
223 self.sshserver = ip
209 self.sshserver = ip
224 ip = localhost()
210 ip = localhost()
225
211
226 # build connection dict for tunnels:
212 # build connection dict for tunnels:
227 info = dict(ip=ip,
213 info = dict(ip=ip,
228 shell_port=self.shell_port,
214 shell_port=self.shell_port,
229 iopub_port=self.iopub_port,
215 iopub_port=self.iopub_port,
230 stdin_port=self.stdin_port,
216 stdin_port=self.stdin_port,
231 hb_port=self.hb_port
217 hb_port=self.hb_port
232 )
218 )
233
219
234 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
220 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
235
221
236 # tunnels return a new set of ports, which will be on localhost:
222 # tunnels return a new set of ports, which will be on localhost:
237 self.ip = localhost()
223 self.ip = localhost()
238 try:
224 try:
239 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
225 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
240 except:
226 except:
241 # even catch KeyboardInterrupt
227 # even catch KeyboardInterrupt
242 self.log.error("Could not setup tunnels", exc_info=True)
228 self.log.error("Could not setup tunnels", exc_info=True)
243 self.exit(1)
229 self.exit(1)
244
230
245 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
231 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
246
232
247 cf = self.connection_file
233 cf = self.connection_file
248 base,ext = os.path.splitext(cf)
234 base,ext = os.path.splitext(cf)
249 base = os.path.basename(base)
235 base = os.path.basename(base)
250 self.connection_file = os.path.basename(base)+'-ssh'+ext
236 self.connection_file = os.path.basename(base)+'-ssh'+ext
251 self.log.info("To connect another client via this tunnel, use:")
237 self.log.info("To connect another client via this tunnel, use:")
252 self.log.info("--existing %s" % self.connection_file)
238 self.log.info("--existing %s" % self.connection_file)
253
239
254 def _new_connection_file(self):
240 def _new_connection_file(self):
255 cf = ''
241 cf = ''
256 while not cf:
242 while not cf:
257 # we don't need a 128b id to distinguish kernels, use more readable
243 # we don't need a 128b id to distinguish kernels, use more readable
258 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
244 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
259 # kernels can subclass.
245 # kernels can subclass.
260 ident = str(uuid.uuid4()).split('-')[-1]
246 ident = str(uuid.uuid4()).split('-')[-1]
261 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
247 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
262 # only keep if it's actually new. Protect against unlikely collision
248 # only keep if it's actually new. Protect against unlikely collision
263 # in 48b random search space
249 # in 48b random search space
264 cf = cf if not os.path.exists(cf) else ''
250 cf = cf if not os.path.exists(cf) else ''
265 return cf
251 return cf
266
252
267 def init_kernel_manager(self):
253 def init_kernel_manager(self):
268 # Don't let Qt or ZMQ swallow KeyboardInterupts.
254 # Don't let Qt or ZMQ swallow KeyboardInterupts.
269 if self.existing:
255 if self.existing:
270 self.kernel_manager = None
256 self.kernel_manager = None
271 return
257 return
272 signal.signal(signal.SIGINT, signal.SIG_DFL)
258 signal.signal(signal.SIGINT, signal.SIG_DFL)
273
259
274 # Create a KernelManager and start a kernel.
260 # Create a KernelManager and start a kernel.
275 try:
261 try:
276 self.kernel_manager = self.kernel_manager_class(
262 self.kernel_manager = self.kernel_manager_class(
277 ip=self.ip,
263 ip=self.ip,
278 session=self.session,
264 session=self.session,
279 transport=self.transport,
265 transport=self.transport,
280 shell_port=self.shell_port,
266 shell_port=self.shell_port,
281 iopub_port=self.iopub_port,
267 iopub_port=self.iopub_port,
282 stdin_port=self.stdin_port,
268 stdin_port=self.stdin_port,
283 hb_port=self.hb_port,
269 hb_port=self.hb_port,
284 connection_file=self.connection_file,
270 connection_file=self.connection_file,
285 kernel_name=self.kernel_name,
271 kernel_name=self.kernel_name,
286 parent=self,
272 parent=self,
287 ipython_dir=self.ipython_dir,
273 ipython_dir=self.ipython_dir,
288 )
274 )
289 except NoSuchKernel:
275 except NoSuchKernel:
290 self.log.critical("Could not find kernel %s", self.kernel_name)
276 self.log.critical("Could not find kernel %s", self.kernel_name)
291 self.exit(1)
277 self.exit(1)
292
278
293 self.kernel_manager.client_factory = self.kernel_client_class
279 self.kernel_manager.client_factory = self.kernel_client_class
294 # FIXME: remove special treatment of IPython kernels
280 # FIXME: remove special treatment of IPython kernels
295 kwargs = {}
281 kwargs = {}
296 if self.kernel_manager.ipython_kernel:
282 if self.kernel_manager.ipython_kernel:
297 kwargs['extra_arguments'] = self.kernel_argv
283 kwargs['extra_arguments'] = self.kernel_argv
298 self.kernel_manager.start_kernel(**kwargs)
284 self.kernel_manager.start_kernel(**kwargs)
299 atexit.register(self.kernel_manager.cleanup_ipc_files)
285 atexit.register(self.kernel_manager.cleanup_ipc_files)
300
286
301 if self.sshserver:
287 if self.sshserver:
302 # ssh, write new connection file
288 # ssh, write new connection file
303 self.kernel_manager.write_connection_file()
289 self.kernel_manager.write_connection_file()
304
290
305 # in case KM defaults / ssh writing changes things:
291 # in case KM defaults / ssh writing changes things:
306 km = self.kernel_manager
292 km = self.kernel_manager
307 self.shell_port=km.shell_port
293 self.shell_port=km.shell_port
308 self.iopub_port=km.iopub_port
294 self.iopub_port=km.iopub_port
309 self.stdin_port=km.stdin_port
295 self.stdin_port=km.stdin_port
310 self.hb_port=km.hb_port
296 self.hb_port=km.hb_port
311 self.connection_file = km.connection_file
297 self.connection_file = km.connection_file
312
298
313 atexit.register(self.kernel_manager.cleanup_connection_file)
299 atexit.register(self.kernel_manager.cleanup_connection_file)
314
300
315 def init_kernel_client(self):
301 def init_kernel_client(self):
316 if self.kernel_manager is not None:
302 if self.kernel_manager is not None:
317 self.kernel_client = self.kernel_manager.client()
303 self.kernel_client = self.kernel_manager.client()
318 else:
304 else:
319 self.kernel_client = self.kernel_client_class(
305 self.kernel_client = self.kernel_client_class(
320 session=self.session,
306 session=self.session,
321 ip=self.ip,
307 ip=self.ip,
322 transport=self.transport,
308 transport=self.transport,
323 shell_port=self.shell_port,
309 shell_port=self.shell_port,
324 iopub_port=self.iopub_port,
310 iopub_port=self.iopub_port,
325 stdin_port=self.stdin_port,
311 stdin_port=self.stdin_port,
326 hb_port=self.hb_port,
312 hb_port=self.hb_port,
327 connection_file=self.connection_file,
313 connection_file=self.connection_file,
328 parent=self,
314 parent=self,
329 )
315 )
330
316
331 self.kernel_client.start_channels()
317 self.kernel_client.start_channels()
332
318
333
319
334
320
335 def initialize(self, argv=None):
321 def initialize(self, argv=None):
336 """
322 """
337 Classes which mix this class in should call:
323 Classes which mix this class in should call:
338 IPythonConsoleApp.initialize(self,argv)
324 IPythonConsoleApp.initialize(self,argv)
339 """
325 """
340 self.init_connection_file()
326 self.init_connection_file()
341 self.init_ssh()
327 self.init_ssh()
342 self.init_kernel_manager()
328 self.init_kernel_manager()
343 self.init_kernel_client()
329 self.init_kernel_client()
344
330
@@ -1,1137 +1,1127 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 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import importlib
12 import importlib
13 import io
13 import io
14 import json
14 import json
15 import logging
15 import logging
16 import os
16 import os
17 import random
17 import random
18 import re
18 import re
19 import select
19 import select
20 import signal
20 import signal
21 import socket
21 import socket
22 import sys
22 import sys
23 import threading
23 import threading
24 import webbrowser
24 import webbrowser
25
25
26
26
27 # check for pyzmq
27 # check for pyzmq
28 from IPython.utils.zmqrelated import check_for_zmq
28 from IPython.utils.zmqrelated import check_for_zmq
29 check_for_zmq('13', 'IPython.html')
29 check_for_zmq('13', 'IPython.html')
30
30
31 from jinja2 import Environment, FileSystemLoader
31 from jinja2 import Environment, FileSystemLoader
32
32
33 # Install the pyzmq ioloop. This has to be done before anything else from
33 # Install the pyzmq ioloop. This has to be done before anything else from
34 # tornado is imported.
34 # tornado is imported.
35 from zmq.eventloop import ioloop
35 from zmq.eventloop import ioloop
36 ioloop.install()
36 ioloop.install()
37
37
38 # check for tornado 3.1.0
38 # check for tornado 3.1.0
39 msg = "The IPython Notebook requires tornado >= 4.0"
39 msg = "The IPython Notebook requires tornado >= 4.0"
40 try:
40 try:
41 import tornado
41 import tornado
42 except ImportError:
42 except ImportError:
43 raise ImportError(msg)
43 raise ImportError(msg)
44 try:
44 try:
45 version_info = tornado.version_info
45 version_info = tornado.version_info
46 except AttributeError:
46 except AttributeError:
47 raise ImportError(msg + ", but you have < 1.1.0")
47 raise ImportError(msg + ", but you have < 1.1.0")
48 if version_info < (4,0):
48 if version_info < (4,0):
49 raise ImportError(msg + ", but you have %s" % tornado.version)
49 raise ImportError(msg + ", but you have %s" % tornado.version)
50
50
51 from tornado import httpserver
51 from tornado import httpserver
52 from tornado import web
52 from tornado import web
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54
54
55 from IPython.html import (
55 from IPython.html import (
56 DEFAULT_STATIC_FILES_PATH,
56 DEFAULT_STATIC_FILES_PATH,
57 DEFAULT_TEMPLATE_PATH_LIST,
57 DEFAULT_TEMPLATE_PATH_LIST,
58 )
58 )
59 from .base.handlers import Template404
59 from .base.handlers import Template404
60 from .log import log_request
60 from .log import log_request
61 from .services.kernels.kernelmanager import MappingKernelManager
61 from .services.kernels.kernelmanager import MappingKernelManager
62 from .services.config import ConfigManager
62 from .services.config import ConfigManager
63 from .services.contents.manager import ContentsManager
63 from .services.contents.manager import ContentsManager
64 from .services.contents.filemanager import FileContentsManager
64 from .services.contents.filemanager import FileContentsManager
65 from .services.clusters.clustermanager import ClusterManager
65 from .services.clusters.clustermanager import ClusterManager
66 from .services.sessions.sessionmanager import SessionManager
66 from .services.sessions.sessionmanager import SessionManager
67
67
68 from .auth.login import LoginHandler
68 from .auth.login import LoginHandler
69 from .auth.logout import LogoutHandler
69 from .auth.logout import LogoutHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
71
71
72 from IPython.config import Config
72 from IPython.config import Config
73 from IPython.config.application import catch_config_error, boolean_flag
73 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import (
74 from IPython.core.application import (
75 BaseIPythonApplication, base_flags, base_aliases,
75 BaseIPythonApplication, base_flags, base_aliases,
76 )
76 )
77 from IPython.core.profiledir import ProfileDir
77 from IPython.core.profiledir import ProfileDir
78 from IPython.kernel import KernelManager
78 from IPython.kernel import KernelManager
79 from IPython.kernel.kernelspec import KernelSpecManager
79 from IPython.kernel.kernelspec import KernelSpecManager
80 from IPython.kernel.zmq.session import Session
80 from IPython.kernel.zmq.session import Session
81 from IPython.nbformat.sign import NotebookNotary
81 from IPython.nbformat.sign import NotebookNotary
82 from IPython.utils.importstring import import_item
82 from IPython.utils.importstring import import_item
83 from IPython.utils import submodule
83 from IPython.utils import submodule
84 from IPython.utils.process import check_pid
84 from IPython.utils.process import check_pid
85 from IPython.utils.traitlets import (
85 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 TraitError, Type,
87 TraitError, Type,
88 )
88 )
89 from IPython.utils import py3compat
89 from IPython.utils import py3compat
90 from IPython.utils.path import filefind, get_ipython_dir
90 from IPython.utils.path import filefind, get_ipython_dir
91 from IPython.utils.sysinfo import get_sys_info
91 from IPython.utils.sysinfo import get_sys_info
92
92
93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 from .utils import url_path_join
94 from .utils import url_path_join
95
95
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97 # Module globals
97 # Module globals
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99
99
100 _examples = """
100 _examples = """
101 ipython notebook # start the notebook
101 ipython notebook # start the notebook
102 ipython notebook --profile=sympy # use the sympy profile
102 ipython notebook --profile=sympy # use the sympy profile
103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 """
104 """
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Helper functions
107 # Helper functions
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 def random_ports(port, n):
110 def random_ports(port, n):
111 """Generate a list of n random ports near the given port.
111 """Generate a list of n random ports near the given port.
112
112
113 The first 5 ports will be sequential, and the remaining n-5 will be
113 The first 5 ports will be sequential, and the remaining n-5 will be
114 randomly selected in the range [port-2*n, port+2*n].
114 randomly selected in the range [port-2*n, port+2*n].
115 """
115 """
116 for i in range(min(5, n)):
116 for i in range(min(5, n)):
117 yield port + i
117 yield port + i
118 for i in range(n-5):
118 for i in range(n-5):
119 yield max(1, port + random.randint(-2*n, 2*n))
119 yield max(1, port + random.randint(-2*n, 2*n))
120
120
121 def load_handlers(name):
121 def load_handlers(name):
122 """Load the (URL pattern, handler) tuples for each component."""
122 """Load the (URL pattern, handler) tuples for each component."""
123 name = 'IPython.html.' + name
123 name = 'IPython.html.' + name
124 mod = __import__(name, fromlist=['default_handlers'])
124 mod = __import__(name, fromlist=['default_handlers'])
125 return mod.default_handlers
125 return mod.default_handlers
126
126
127 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
128 # The Tornado web application
128 # The Tornado web application
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130
130
131 class NotebookWebApplication(web.Application):
131 class NotebookWebApplication(web.Application):
132
132
133 def __init__(self, ipython_app, kernel_manager, contents_manager,
133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
135 config_manager, log,
135 config_manager, log,
136 base_url, default_url, settings_overrides, jinja_env_options):
136 base_url, default_url, settings_overrides, jinja_env_options):
137
137
138 settings = self.init_settings(
138 settings = self.init_settings(
139 ipython_app, kernel_manager, contents_manager, cluster_manager,
139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 session_manager, kernel_spec_manager, config_manager, log, base_url,
140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 default_url, settings_overrides, jinja_env_options)
141 default_url, settings_overrides, jinja_env_options)
142 handlers = self.init_handlers(settings)
142 handlers = self.init_handlers(settings)
143
143
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
145
146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 cluster_manager, session_manager, kernel_spec_manager,
147 cluster_manager, session_manager, kernel_spec_manager,
148 config_manager,
148 config_manager,
149 log, base_url, default_url, settings_overrides,
149 log, base_url, default_url, settings_overrides,
150 jinja_env_options=None):
150 jinja_env_options=None):
151
151
152 _template_path = settings_overrides.get(
152 _template_path = settings_overrides.get(
153 "template_path",
153 "template_path",
154 ipython_app.template_file_path,
154 ipython_app.template_file_path,
155 )
155 )
156 if isinstance(_template_path, str):
156 if isinstance(_template_path, str):
157 _template_path = (_template_path,)
157 _template_path = (_template_path,)
158 template_path = [os.path.expanduser(path) for path in _template_path]
158 template_path = [os.path.expanduser(path) for path in _template_path]
159
159
160 jenv_opt = jinja_env_options if jinja_env_options else {}
160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162
162
163 sys_info = get_sys_info()
163 sys_info = get_sys_info()
164 if sys_info['commit_source'] == 'repository':
164 if sys_info['commit_source'] == 'repository':
165 # don't cache (rely on 304) when working from master
165 # don't cache (rely on 304) when working from master
166 version_hash = ''
166 version_hash = ''
167 else:
167 else:
168 # reset the cache on server restart
168 # reset the cache on server restart
169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170
170
171 settings = dict(
171 settings = dict(
172 # basics
172 # basics
173 log_function=log_request,
173 log_function=log_request,
174 base_url=base_url,
174 base_url=base_url,
175 default_url=default_url,
175 default_url=default_url,
176 template_path=template_path,
176 template_path=template_path,
177 static_path=ipython_app.static_file_path,
177 static_path=ipython_app.static_file_path,
178 static_handler_class = FileFindHandler,
178 static_handler_class = FileFindHandler,
179 static_url_prefix = url_path_join(base_url,'/static/'),
179 static_url_prefix = url_path_join(base_url,'/static/'),
180 static_handler_args = {
180 static_handler_args = {
181 # don't cache custom.js
181 # don't cache custom.js
182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 },
183 },
184 version_hash=version_hash,
184 version_hash=version_hash,
185
185
186 # authentication
186 # authentication
187 cookie_secret=ipython_app.cookie_secret,
187 cookie_secret=ipython_app.cookie_secret,
188 login_url=url_path_join(base_url,'/login'),
188 login_url=url_path_join(base_url,'/login'),
189 login_handler_class=ipython_app.login_handler_class,
189 login_handler_class=ipython_app.login_handler_class,
190 logout_handler_class=ipython_app.logout_handler_class,
190 logout_handler_class=ipython_app.logout_handler_class,
191 password=ipython_app.password,
191 password=ipython_app.password,
192
192
193 # managers
193 # managers
194 kernel_manager=kernel_manager,
194 kernel_manager=kernel_manager,
195 contents_manager=contents_manager,
195 contents_manager=contents_manager,
196 cluster_manager=cluster_manager,
196 cluster_manager=cluster_manager,
197 session_manager=session_manager,
197 session_manager=session_manager,
198 kernel_spec_manager=kernel_spec_manager,
198 kernel_spec_manager=kernel_spec_manager,
199 config_manager=config_manager,
199 config_manager=config_manager,
200
200
201 # IPython stuff
201 # IPython stuff
202 nbextensions_path=ipython_app.nbextensions_path,
202 nbextensions_path=ipython_app.nbextensions_path,
203 websocket_url=ipython_app.websocket_url,
203 websocket_url=ipython_app.websocket_url,
204 mathjax_url=ipython_app.mathjax_url,
204 mathjax_url=ipython_app.mathjax_url,
205 config=ipython_app.config,
205 config=ipython_app.config,
206 jinja2_env=env,
206 jinja2_env=env,
207 terminals_available=False, # Set later if terminals are available
207 terminals_available=False, # Set later if terminals are available
208 )
208 )
209
209
210 # allow custom overrides for the tornado web app.
210 # allow custom overrides for the tornado web app.
211 settings.update(settings_overrides)
211 settings.update(settings_overrides)
212 return settings
212 return settings
213
213
214 def init_handlers(self, settings):
214 def init_handlers(self, settings):
215 """Load the (URL pattern, handler) tuples for each component."""
215 """Load the (URL pattern, handler) tuples for each component."""
216
216
217 # Order matters. The first handler to match the URL will handle the request.
217 # Order matters. The first handler to match the URL will handle the request.
218 handlers = []
218 handlers = []
219 handlers.extend(load_handlers('tree.handlers'))
219 handlers.extend(load_handlers('tree.handlers'))
220 handlers.extend([(r"/login", settings['login_handler_class'])])
220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 handlers.extend(load_handlers('files.handlers'))
222 handlers.extend(load_handlers('files.handlers'))
223 handlers.extend(load_handlers('notebook.handlers'))
223 handlers.extend(load_handlers('notebook.handlers'))
224 handlers.extend(load_handlers('nbconvert.handlers'))
224 handlers.extend(load_handlers('nbconvert.handlers'))
225 handlers.extend(load_handlers('kernelspecs.handlers'))
225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 handlers.extend(load_handlers('edit.handlers'))
226 handlers.extend(load_handlers('edit.handlers'))
227 handlers.extend(load_handlers('services.config.handlers'))
227 handlers.extend(load_handlers('services.config.handlers'))
228 handlers.extend(load_handlers('services.kernels.handlers'))
228 handlers.extend(load_handlers('services.kernels.handlers'))
229 handlers.extend(load_handlers('services.contents.handlers'))
229 handlers.extend(load_handlers('services.contents.handlers'))
230 handlers.extend(load_handlers('services.clusters.handlers'))
230 handlers.extend(load_handlers('services.clusters.handlers'))
231 handlers.extend(load_handlers('services.sessions.handlers'))
231 handlers.extend(load_handlers('services.sessions.handlers'))
232 handlers.extend(load_handlers('services.nbconvert.handlers'))
232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 handlers.extend(load_handlers('services.security.handlers'))
234 handlers.extend(load_handlers('services.security.handlers'))
235 handlers.append(
235 handlers.append(
236 (r"/nbextensions/(.*)", FileFindHandler, {
236 (r"/nbextensions/(.*)", FileFindHandler, {
237 'path': settings['nbextensions_path'],
237 'path': settings['nbextensions_path'],
238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 }),
239 }),
240 )
240 )
241 # register base handlers last
241 # register base handlers last
242 handlers.extend(load_handlers('base.handlers'))
242 handlers.extend(load_handlers('base.handlers'))
243 # set the URL that will be redirected from `/`
243 # set the URL that will be redirected from `/`
244 handlers.append(
244 handlers.append(
245 (r'/?', web.RedirectHandler, {
245 (r'/?', web.RedirectHandler, {
246 'url' : settings['default_url'],
246 'url' : settings['default_url'],
247 'permanent': False, # want 302, not 301
247 'permanent': False, # want 302, not 301
248 })
248 })
249 )
249 )
250 # prepend base_url onto the patterns that we match
250 # prepend base_url onto the patterns that we match
251 new_handlers = []
251 new_handlers = []
252 for handler in handlers:
252 for handler in handlers:
253 pattern = url_path_join(settings['base_url'], handler[0])
253 pattern = url_path_join(settings['base_url'], handler[0])
254 new_handler = tuple([pattern] + list(handler[1:]))
254 new_handler = tuple([pattern] + list(handler[1:]))
255 new_handlers.append(new_handler)
255 new_handlers.append(new_handler)
256 # add 404 on the end, which will catch everything that falls through
256 # add 404 on the end, which will catch everything that falls through
257 new_handlers.append((r'(.*)', Template404))
257 new_handlers.append((r'(.*)', Template404))
258 return new_handlers
258 return new_handlers
259
259
260
260
261 class NbserverListApp(BaseIPythonApplication):
261 class NbserverListApp(BaseIPythonApplication):
262
262
263 description="List currently running notebook servers in this profile."
263 description="List currently running notebook servers in this profile."
264
264
265 flags = dict(
265 flags = dict(
266 json=({'NbserverListApp': {'json': True}},
266 json=({'NbserverListApp': {'json': True}},
267 "Produce machine-readable JSON output."),
267 "Produce machine-readable JSON output."),
268 )
268 )
269
269
270 json = Bool(False, config=True,
270 json = Bool(False, config=True,
271 help="If True, each line of output will be a JSON object with the "
271 help="If True, each line of output will be a JSON object with the "
272 "details from the server info file.")
272 "details from the server info file.")
273
273
274 def start(self):
274 def start(self):
275 if not self.json:
275 if not self.json:
276 print("Currently running servers:")
276 print("Currently running servers:")
277 for serverinfo in list_running_servers(self.profile):
277 for serverinfo in list_running_servers(self.profile):
278 if self.json:
278 if self.json:
279 print(json.dumps(serverinfo))
279 print(json.dumps(serverinfo))
280 else:
280 else:
281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282
282
283 #-----------------------------------------------------------------------------
283 #-----------------------------------------------------------------------------
284 # Aliases and Flags
284 # Aliases and Flags
285 #-----------------------------------------------------------------------------
285 #-----------------------------------------------------------------------------
286
286
287 flags = dict(base_flags)
287 flags = dict(base_flags)
288 flags['no-browser']=(
288 flags['no-browser']=(
289 {'NotebookApp' : {'open_browser' : False}},
289 {'NotebookApp' : {'open_browser' : False}},
290 "Don't open the notebook in a browser after startup."
290 "Don't open the notebook in a browser after startup."
291 )
291 )
292 flags['pylab']=(
292 flags['pylab']=(
293 {'NotebookApp' : {'pylab' : 'warn'}},
293 {'NotebookApp' : {'pylab' : 'warn'}},
294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 )
295 )
296 flags['no-mathjax']=(
296 flags['no-mathjax']=(
297 {'NotebookApp' : {'enable_mathjax' : False}},
297 {'NotebookApp' : {'enable_mathjax' : False}},
298 """Disable MathJax
298 """Disable MathJax
299
299
300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 very large, so you may want to disable it if you have a slow internet
301 very large, so you may want to disable it if you have a slow internet
302 connection, or for offline use of the notebook.
302 connection, or for offline use of the notebook.
303
303
304 When disabled, equations etc. will appear as their untransformed TeX source.
304 When disabled, equations etc. will appear as their untransformed TeX source.
305 """
305 """
306 )
306 )
307
307
308 # Add notebook manager flags
308 # Add notebook manager flags
309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 'DEPRECATED, IGNORED',
310 'DEPRECATED, IGNORED',
311 'DEPRECATED, IGNORED'))
311 'DEPRECATED, IGNORED'))
312
312
313 aliases = dict(base_aliases)
313 aliases = dict(base_aliases)
314
314
315 aliases.update({
315 aliases.update({
316 'ip': 'NotebookApp.ip',
316 'ip': 'NotebookApp.ip',
317 'port': 'NotebookApp.port',
317 'port': 'NotebookApp.port',
318 'port-retries': 'NotebookApp.port_retries',
318 'port-retries': 'NotebookApp.port_retries',
319 'transport': 'KernelManager.transport',
319 'transport': 'KernelManager.transport',
320 'keyfile': 'NotebookApp.keyfile',
320 'keyfile': 'NotebookApp.keyfile',
321 'certfile': 'NotebookApp.certfile',
321 'certfile': 'NotebookApp.certfile',
322 'notebook-dir': 'NotebookApp.notebook_dir',
322 'notebook-dir': 'NotebookApp.notebook_dir',
323 'browser': 'NotebookApp.browser',
323 'browser': 'NotebookApp.browser',
324 'pylab': 'NotebookApp.pylab',
324 'pylab': 'NotebookApp.pylab',
325 })
325 })
326
326
327 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
328 # NotebookApp
328 # NotebookApp
329 #-----------------------------------------------------------------------------
329 #-----------------------------------------------------------------------------
330
330
331 class NotebookApp(BaseIPythonApplication):
331 class NotebookApp(BaseIPythonApplication):
332
332
333 name = 'ipython-notebook'
333 name = 'ipython-notebook'
334
334
335 description = """
335 description = """
336 The IPython HTML Notebook.
336 The IPython HTML Notebook.
337
337
338 This launches a Tornado based HTML Notebook Server that serves up an
338 This launches a Tornado based HTML Notebook Server that serves up an
339 HTML5/Javascript Notebook client.
339 HTML5/Javascript Notebook client.
340 """
340 """
341 examples = _examples
341 examples = _examples
342 aliases = aliases
342 aliases = aliases
343 flags = flags
343 flags = flags
344
344
345 classes = [
345 classes = [
346 KernelManager, ProfileDir, Session, MappingKernelManager,
346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 ContentsManager, FileContentsManager, NotebookNotary,
347 ContentsManager, FileContentsManager, NotebookNotary,
348 KernelSpecManager,
348 KernelSpecManager,
349 ]
349 ]
350 flags = Dict(flags)
350 flags = Dict(flags)
351 aliases = Dict(aliases)
351 aliases = Dict(aliases)
352
352
353 subcommands = dict(
353 subcommands = dict(
354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 )
355 )
356
356
357 ipython_kernel_argv = List(Unicode)
358
359 _log_formatter_cls = LogFormatter
357 _log_formatter_cls = LogFormatter
360
358
361 def _log_level_default(self):
359 def _log_level_default(self):
362 return logging.INFO
360 return logging.INFO
363
361
364 def _log_datefmt_default(self):
362 def _log_datefmt_default(self):
365 """Exclude date from default date format"""
363 """Exclude date from default date format"""
366 return "%H:%M:%S"
364 return "%H:%M:%S"
367
365
368 def _log_format_default(self):
366 def _log_format_default(self):
369 """override default log format to include time"""
367 """override default log format to include time"""
370 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
371
369
372 # create requested profiles by default, if they don't exist:
370 # create requested profiles by default, if they don't exist:
373 auto_create = Bool(True)
371 auto_create = Bool(True)
374
372
375 # file to be opened in the notebook server
373 # file to be opened in the notebook server
376 file_to_run = Unicode('', config=True)
374 file_to_run = Unicode('', config=True)
377
375
378 # Network related information
376 # Network related information
379
377
380 allow_origin = Unicode('', config=True,
378 allow_origin = Unicode('', config=True,
381 help="""Set the Access-Control-Allow-Origin header
379 help="""Set the Access-Control-Allow-Origin header
382
380
383 Use '*' to allow any origin to access your server.
381 Use '*' to allow any origin to access your server.
384
382
385 Takes precedence over allow_origin_pat.
383 Takes precedence over allow_origin_pat.
386 """
384 """
387 )
385 )
388
386
389 allow_origin_pat = Unicode('', config=True,
387 allow_origin_pat = Unicode('', config=True,
390 help="""Use a regular expression for the Access-Control-Allow-Origin header
388 help="""Use a regular expression for the Access-Control-Allow-Origin header
391
389
392 Requests from an origin matching the expression will get replies with:
390 Requests from an origin matching the expression will get replies with:
393
391
394 Access-Control-Allow-Origin: origin
392 Access-Control-Allow-Origin: origin
395
393
396 where `origin` is the origin of the request.
394 where `origin` is the origin of the request.
397
395
398 Ignored if allow_origin is set.
396 Ignored if allow_origin is set.
399 """
397 """
400 )
398 )
401
399
402 allow_credentials = Bool(False, config=True,
400 allow_credentials = Bool(False, config=True,
403 help="Set the Access-Control-Allow-Credentials: true header"
401 help="Set the Access-Control-Allow-Credentials: true header"
404 )
402 )
405
403
406 default_url = Unicode('/tree', config=True,
404 default_url = Unicode('/tree', config=True,
407 help="The default URL to redirect to from `/`"
405 help="The default URL to redirect to from `/`"
408 )
406 )
409
407
410 ip = Unicode('localhost', config=True,
408 ip = Unicode('localhost', config=True,
411 help="The IP address the notebook server will listen on."
409 help="The IP address the notebook server will listen on."
412 )
410 )
413 def _ip_default(self):
411 def _ip_default(self):
414 """Return localhost if available, 127.0.0.1 otherwise.
412 """Return localhost if available, 127.0.0.1 otherwise.
415
413
416 On some (horribly broken) systems, localhost cannot be bound.
414 On some (horribly broken) systems, localhost cannot be bound.
417 """
415 """
418 s = socket.socket()
416 s = socket.socket()
419 try:
417 try:
420 s.bind(('localhost', 0))
418 s.bind(('localhost', 0))
421 except socket.error as e:
419 except socket.error as e:
422 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
420 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
423 return '127.0.0.1'
421 return '127.0.0.1'
424 else:
422 else:
425 s.close()
423 s.close()
426 return 'localhost'
424 return 'localhost'
427
425
428 def _ip_changed(self, name, old, new):
426 def _ip_changed(self, name, old, new):
429 if new == u'*': self.ip = u''
427 if new == u'*': self.ip = u''
430
428
431 port = Integer(8888, config=True,
429 port = Integer(8888, config=True,
432 help="The port the notebook server will listen on."
430 help="The port the notebook server will listen on."
433 )
431 )
434 port_retries = Integer(50, config=True,
432 port_retries = Integer(50, config=True,
435 help="The number of additional ports to try if the specified port is not available."
433 help="The number of additional ports to try if the specified port is not available."
436 )
434 )
437
435
438 certfile = Unicode(u'', config=True,
436 certfile = Unicode(u'', config=True,
439 help="""The full path to an SSL/TLS certificate file."""
437 help="""The full path to an SSL/TLS certificate file."""
440 )
438 )
441
439
442 keyfile = Unicode(u'', config=True,
440 keyfile = Unicode(u'', config=True,
443 help="""The full path to a private key file for usage with SSL/TLS."""
441 help="""The full path to a private key file for usage with SSL/TLS."""
444 )
442 )
445
443
446 cookie_secret_file = Unicode(config=True,
444 cookie_secret_file = Unicode(config=True,
447 help="""The file where the cookie secret is stored."""
445 help="""The file where the cookie secret is stored."""
448 )
446 )
449 def _cookie_secret_file_default(self):
447 def _cookie_secret_file_default(self):
450 if self.profile_dir is None:
448 if self.profile_dir is None:
451 return ''
449 return ''
452 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
450 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
453
451
454 cookie_secret = Bytes(b'', config=True,
452 cookie_secret = Bytes(b'', config=True,
455 help="""The random bytes used to secure cookies.
453 help="""The random bytes used to secure cookies.
456 By default this is a new random number every time you start the Notebook.
454 By default this is a new random number every time you start the Notebook.
457 Set it to a value in a config file to enable logins to persist across server sessions.
455 Set it to a value in a config file to enable logins to persist across server sessions.
458
456
459 Note: Cookie secrets should be kept private, do not share config files with
457 Note: Cookie secrets should be kept private, do not share config files with
460 cookie_secret stored in plaintext (you can read the value from a file).
458 cookie_secret stored in plaintext (you can read the value from a file).
461 """
459 """
462 )
460 )
463 def _cookie_secret_default(self):
461 def _cookie_secret_default(self):
464 if os.path.exists(self.cookie_secret_file):
462 if os.path.exists(self.cookie_secret_file):
465 with io.open(self.cookie_secret_file, 'rb') as f:
463 with io.open(self.cookie_secret_file, 'rb') as f:
466 return f.read()
464 return f.read()
467 else:
465 else:
468 secret = base64.encodestring(os.urandom(1024))
466 secret = base64.encodestring(os.urandom(1024))
469 self._write_cookie_secret_file(secret)
467 self._write_cookie_secret_file(secret)
470 return secret
468 return secret
471
469
472 def _write_cookie_secret_file(self, secret):
470 def _write_cookie_secret_file(self, secret):
473 """write my secret to my secret_file"""
471 """write my secret to my secret_file"""
474 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
472 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
475 with io.open(self.cookie_secret_file, 'wb') as f:
473 with io.open(self.cookie_secret_file, 'wb') as f:
476 f.write(secret)
474 f.write(secret)
477 try:
475 try:
478 os.chmod(self.cookie_secret_file, 0o600)
476 os.chmod(self.cookie_secret_file, 0o600)
479 except OSError:
477 except OSError:
480 self.log.warn(
478 self.log.warn(
481 "Could not set permissions on %s",
479 "Could not set permissions on %s",
482 self.cookie_secret_file
480 self.cookie_secret_file
483 )
481 )
484
482
485 password = Unicode(u'', config=True,
483 password = Unicode(u'', config=True,
486 help="""Hashed password to use for web authentication.
484 help="""Hashed password to use for web authentication.
487
485
488 To generate, type in a python/IPython shell:
486 To generate, type in a python/IPython shell:
489
487
490 from IPython.lib import passwd; passwd()
488 from IPython.lib import passwd; passwd()
491
489
492 The string should be of the form type:salt:hashed-password.
490 The string should be of the form type:salt:hashed-password.
493 """
491 """
494 )
492 )
495
493
496 open_browser = Bool(True, config=True,
494 open_browser = Bool(True, config=True,
497 help="""Whether to open in a browser after starting.
495 help="""Whether to open in a browser after starting.
498 The specific browser used is platform dependent and
496 The specific browser used is platform dependent and
499 determined by the python standard library `webbrowser`
497 determined by the python standard library `webbrowser`
500 module, unless it is overridden using the --browser
498 module, unless it is overridden using the --browser
501 (NotebookApp.browser) configuration option.
499 (NotebookApp.browser) configuration option.
502 """)
500 """)
503
501
504 browser = Unicode(u'', config=True,
502 browser = Unicode(u'', config=True,
505 help="""Specify what command to use to invoke a web
503 help="""Specify what command to use to invoke a web
506 browser when opening the notebook. If not specified, the
504 browser when opening the notebook. If not specified, the
507 default browser will be determined by the `webbrowser`
505 default browser will be determined by the `webbrowser`
508 standard library module, which allows setting of the
506 standard library module, which allows setting of the
509 BROWSER environment variable to override it.
507 BROWSER environment variable to override it.
510 """)
508 """)
511
509
512 webapp_settings = Dict(config=True,
510 webapp_settings = Dict(config=True,
513 help="DEPRECATED, use tornado_settings"
511 help="DEPRECATED, use tornado_settings"
514 )
512 )
515 def _webapp_settings_changed(self, name, old, new):
513 def _webapp_settings_changed(self, name, old, new):
516 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
514 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
517 self.tornado_settings = new
515 self.tornado_settings = new
518
516
519 tornado_settings = Dict(config=True,
517 tornado_settings = Dict(config=True,
520 help="Supply overrides for the tornado.web.Application that the "
518 help="Supply overrides for the tornado.web.Application that the "
521 "IPython notebook uses.")
519 "IPython notebook uses.")
522
520
523 ssl_options = Dict(config=True,
521 ssl_options = Dict(config=True,
524 help="""Supply SSL options for the tornado HTTPServer.
522 help="""Supply SSL options for the tornado HTTPServer.
525 See the tornado docs for details.""")
523 See the tornado docs for details.""")
526
524
527 jinja_environment_options = Dict(config=True,
525 jinja_environment_options = Dict(config=True,
528 help="Supply extra arguments that will be passed to Jinja environment.")
526 help="Supply extra arguments that will be passed to Jinja environment.")
529
527
530 enable_mathjax = Bool(True, config=True,
528 enable_mathjax = Bool(True, config=True,
531 help="""Whether to enable MathJax for typesetting math/TeX
529 help="""Whether to enable MathJax for typesetting math/TeX
532
530
533 MathJax is the javascript library IPython uses to render math/LaTeX. It is
531 MathJax is the javascript library IPython uses to render math/LaTeX. It is
534 very large, so you may want to disable it if you have a slow internet
532 very large, so you may want to disable it if you have a slow internet
535 connection, or for offline use of the notebook.
533 connection, or for offline use of the notebook.
536
534
537 When disabled, equations etc. will appear as their untransformed TeX source.
535 When disabled, equations etc. will appear as their untransformed TeX source.
538 """
536 """
539 )
537 )
540 def _enable_mathjax_changed(self, name, old, new):
538 def _enable_mathjax_changed(self, name, old, new):
541 """set mathjax url to empty if mathjax is disabled"""
539 """set mathjax url to empty if mathjax is disabled"""
542 if not new:
540 if not new:
543 self.mathjax_url = u''
541 self.mathjax_url = u''
544
542
545 base_url = Unicode('/', config=True,
543 base_url = Unicode('/', config=True,
546 help='''The base URL for the notebook server.
544 help='''The base URL for the notebook server.
547
545
548 Leading and trailing slashes can be omitted,
546 Leading and trailing slashes can be omitted,
549 and will automatically be added.
547 and will automatically be added.
550 ''')
548 ''')
551 def _base_url_changed(self, name, old, new):
549 def _base_url_changed(self, name, old, new):
552 if not new.startswith('/'):
550 if not new.startswith('/'):
553 self.base_url = '/'+new
551 self.base_url = '/'+new
554 elif not new.endswith('/'):
552 elif not new.endswith('/'):
555 self.base_url = new+'/'
553 self.base_url = new+'/'
556
554
557 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
555 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
558 def _base_project_url_changed(self, name, old, new):
556 def _base_project_url_changed(self, name, old, new):
559 self.log.warn("base_project_url is deprecated, use base_url")
557 self.log.warn("base_project_url is deprecated, use base_url")
560 self.base_url = new
558 self.base_url = new
561
559
562 extra_static_paths = List(Unicode, config=True,
560 extra_static_paths = List(Unicode, config=True,
563 help="""Extra paths to search for serving static files.
561 help="""Extra paths to search for serving static files.
564
562
565 This allows adding javascript/css to be available from the notebook server machine,
563 This allows adding javascript/css to be available from the notebook server machine,
566 or overriding individual files in the IPython"""
564 or overriding individual files in the IPython"""
567 )
565 )
568 def _extra_static_paths_default(self):
566 def _extra_static_paths_default(self):
569 return [os.path.join(self.profile_dir.location, 'static')]
567 return [os.path.join(self.profile_dir.location, 'static')]
570
568
571 @property
569 @property
572 def static_file_path(self):
570 def static_file_path(self):
573 """return extra paths + the default location"""
571 """return extra paths + the default location"""
574 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
572 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
575
573
576 extra_template_paths = List(Unicode, config=True,
574 extra_template_paths = List(Unicode, config=True,
577 help="""Extra paths to search for serving jinja templates.
575 help="""Extra paths to search for serving jinja templates.
578
576
579 Can be used to override templates from IPython.html.templates."""
577 Can be used to override templates from IPython.html.templates."""
580 )
578 )
581 def _extra_template_paths_default(self):
579 def _extra_template_paths_default(self):
582 return []
580 return []
583
581
584 @property
582 @property
585 def template_file_path(self):
583 def template_file_path(self):
586 """return extra paths + the default locations"""
584 """return extra paths + the default locations"""
587 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
585 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
588
586
589 extra_nbextensions_path = List(Unicode, config=True,
587 extra_nbextensions_path = List(Unicode, config=True,
590 help="""extra paths to look for Javascript notebook extensions"""
588 help="""extra paths to look for Javascript notebook extensions"""
591 )
589 )
592
590
593 @property
591 @property
594 def nbextensions_path(self):
592 def nbextensions_path(self):
595 """The path to look for Javascript notebook extensions"""
593 """The path to look for Javascript notebook extensions"""
596 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
594 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
597
595
598 websocket_url = Unicode("", config=True,
596 websocket_url = Unicode("", config=True,
599 help="""The base URL for websockets,
597 help="""The base URL for websockets,
600 if it differs from the HTTP server (hint: it almost certainly doesn't).
598 if it differs from the HTTP server (hint: it almost certainly doesn't).
601
599
602 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
600 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
603 """
601 """
604 )
602 )
605 mathjax_url = Unicode("", config=True,
603 mathjax_url = Unicode("", config=True,
606 help="""The url for MathJax.js."""
604 help="""The url for MathJax.js."""
607 )
605 )
608 def _mathjax_url_default(self):
606 def _mathjax_url_default(self):
609 if not self.enable_mathjax:
607 if not self.enable_mathjax:
610 return u''
608 return u''
611 static_url_prefix = self.tornado_settings.get("static_url_prefix",
609 static_url_prefix = self.tornado_settings.get("static_url_prefix",
612 url_path_join(self.base_url, "static")
610 url_path_join(self.base_url, "static")
613 )
611 )
614
612
615 # try local mathjax, either in nbextensions/mathjax or static/mathjax
613 # try local mathjax, either in nbextensions/mathjax or static/mathjax
616 for (url_prefix, search_path) in [
614 for (url_prefix, search_path) in [
617 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
615 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
618 (static_url_prefix, self.static_file_path),
616 (static_url_prefix, self.static_file_path),
619 ]:
617 ]:
620 self.log.debug("searching for local mathjax in %s", search_path)
618 self.log.debug("searching for local mathjax in %s", search_path)
621 try:
619 try:
622 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
620 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
623 except IOError:
621 except IOError:
624 continue
622 continue
625 else:
623 else:
626 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
624 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
627 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
625 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
628 return url
626 return url
629
627
630 # no local mathjax, serve from CDN
628 # no local mathjax, serve from CDN
631 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
629 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
632 self.log.info("Using MathJax from CDN: %s", url)
630 self.log.info("Using MathJax from CDN: %s", url)
633 return url
631 return url
634
632
635 def _mathjax_url_changed(self, name, old, new):
633 def _mathjax_url_changed(self, name, old, new):
636 if new and not self.enable_mathjax:
634 if new and not self.enable_mathjax:
637 # enable_mathjax=False overrides mathjax_url
635 # enable_mathjax=False overrides mathjax_url
638 self.mathjax_url = u''
636 self.mathjax_url = u''
639 else:
637 else:
640 self.log.info("Using MathJax: %s", new)
638 self.log.info("Using MathJax: %s", new)
641
639
642 contents_manager_class = Type(
640 contents_manager_class = Type(
643 default_value=FileContentsManager,
641 default_value=FileContentsManager,
644 klass=ContentsManager,
642 klass=ContentsManager,
645 config=True,
643 config=True,
646 help='The notebook manager class to use.'
644 help='The notebook manager class to use.'
647 )
645 )
648 kernel_manager_class = Type(
646 kernel_manager_class = Type(
649 default_value=MappingKernelManager,
647 default_value=MappingKernelManager,
650 config=True,
648 config=True,
651 help='The kernel manager class to use.'
649 help='The kernel manager class to use.'
652 )
650 )
653 session_manager_class = Type(
651 session_manager_class = Type(
654 default_value=SessionManager,
652 default_value=SessionManager,
655 config=True,
653 config=True,
656 help='The session manager class to use.'
654 help='The session manager class to use.'
657 )
655 )
658 cluster_manager_class = Type(
656 cluster_manager_class = Type(
659 default_value=ClusterManager,
657 default_value=ClusterManager,
660 config=True,
658 config=True,
661 help='The cluster manager class to use.'
659 help='The cluster manager class to use.'
662 )
660 )
663
661
664 config_manager_class = Type(
662 config_manager_class = Type(
665 default_value=ConfigManager,
663 default_value=ConfigManager,
666 config = True,
664 config = True,
667 help='The config manager class to use'
665 help='The config manager class to use'
668 )
666 )
669
667
670 kernel_spec_manager = Instance(KernelSpecManager)
668 kernel_spec_manager = Instance(KernelSpecManager)
671
669
672 kernel_spec_manager_class = Type(
670 kernel_spec_manager_class = Type(
673 default_value=KernelSpecManager,
671 default_value=KernelSpecManager,
674 config=True,
672 config=True,
675 help="""
673 help="""
676 The kernel spec manager class to use. Should be a subclass
674 The kernel spec manager class to use. Should be a subclass
677 of `IPython.kernel.kernelspec.KernelSpecManager`.
675 of `IPython.kernel.kernelspec.KernelSpecManager`.
678
676
679 The Api of KernelSpecManager is provisional and might change
677 The Api of KernelSpecManager is provisional and might change
680 without warning between this version of IPython and the next stable one.
678 without warning between this version of IPython and the next stable one.
681 """
679 """
682 )
680 )
683
681
684 login_handler_class = Type(
682 login_handler_class = Type(
685 default_value=LoginHandler,
683 default_value=LoginHandler,
686 klass=web.RequestHandler,
684 klass=web.RequestHandler,
687 config=True,
685 config=True,
688 help='The login handler class to use.',
686 help='The login handler class to use.',
689 )
687 )
690
688
691 logout_handler_class = Type(
689 logout_handler_class = Type(
692 default_value=LogoutHandler,
690 default_value=LogoutHandler,
693 klass=web.RequestHandler,
691 klass=web.RequestHandler,
694 config=True,
692 config=True,
695 help='The logout handler class to use.',
693 help='The logout handler class to use.',
696 )
694 )
697
695
698 trust_xheaders = Bool(False, config=True,
696 trust_xheaders = Bool(False, config=True,
699 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
697 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
700 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
698 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
701 )
699 )
702
700
703 info_file = Unicode()
701 info_file = Unicode()
704
702
705 def _info_file_default(self):
703 def _info_file_default(self):
706 info_file = "nbserver-%s.json"%os.getpid()
704 info_file = "nbserver-%s.json"%os.getpid()
707 return os.path.join(self.profile_dir.security_dir, info_file)
705 return os.path.join(self.profile_dir.security_dir, info_file)
708
706
709 pylab = Unicode('disabled', config=True,
707 pylab = Unicode('disabled', config=True,
710 help="""
708 help="""
711 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
709 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
712 """
710 """
713 )
711 )
714 def _pylab_changed(self, name, old, new):
712 def _pylab_changed(self, name, old, new):
715 """when --pylab is specified, display a warning and exit"""
713 """when --pylab is specified, display a warning and exit"""
716 if new != 'warn':
714 if new != 'warn':
717 backend = ' %s' % new
715 backend = ' %s' % new
718 else:
716 else:
719 backend = ''
717 backend = ''
720 self.log.error("Support for specifying --pylab on the command line has been removed.")
718 self.log.error("Support for specifying --pylab on the command line has been removed.")
721 self.log.error(
719 self.log.error(
722 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
720 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
723 )
721 )
724 self.exit(1)
722 self.exit(1)
725
723
726 notebook_dir = Unicode(config=True,
724 notebook_dir = Unicode(config=True,
727 help="The directory to use for notebooks and kernels."
725 help="The directory to use for notebooks and kernels."
728 )
726 )
729
727
730 def _notebook_dir_default(self):
728 def _notebook_dir_default(self):
731 if self.file_to_run:
729 if self.file_to_run:
732 return os.path.dirname(os.path.abspath(self.file_to_run))
730 return os.path.dirname(os.path.abspath(self.file_to_run))
733 else:
731 else:
734 return py3compat.getcwd()
732 return py3compat.getcwd()
735
733
736 def _notebook_dir_changed(self, name, old, new):
734 def _notebook_dir_changed(self, name, old, new):
737 """Do a bit of validation of the notebook dir."""
735 """Do a bit of validation of the notebook dir."""
738 if not os.path.isabs(new):
736 if not os.path.isabs(new):
739 # If we receive a non-absolute path, make it absolute.
737 # If we receive a non-absolute path, make it absolute.
740 self.notebook_dir = os.path.abspath(new)
738 self.notebook_dir = os.path.abspath(new)
741 return
739 return
742 if not os.path.isdir(new):
740 if not os.path.isdir(new):
743 raise TraitError("No such notebook dir: %r" % new)
741 raise TraitError("No such notebook dir: %r" % new)
744
742
745 # setting App.notebook_dir implies setting notebook and kernel dirs as well
743 # setting App.notebook_dir implies setting notebook and kernel dirs as well
746 self.config.FileContentsManager.root_dir = new
744 self.config.FileContentsManager.root_dir = new
747 self.config.MappingKernelManager.root_dir = new
745 self.config.MappingKernelManager.root_dir = new
748
746
749 server_extensions = List(Unicode(), config=True,
747 server_extensions = List(Unicode(), config=True,
750 help=("Python modules to load as notebook server extensions. "
748 help=("Python modules to load as notebook server extensions. "
751 "This is an experimental API, and may change in future releases.")
749 "This is an experimental API, and may change in future releases.")
752 )
750 )
753
751
754 reraise_server_extension_failures = Bool(
752 reraise_server_extension_failures = Bool(
755 False,
753 False,
756 config=True,
754 config=True,
757 help="Reraise exceptions encountered loading server extensions?",
755 help="Reraise exceptions encountered loading server extensions?",
758 )
756 )
759
757
760 def parse_command_line(self, argv=None):
758 def parse_command_line(self, argv=None):
761 super(NotebookApp, self).parse_command_line(argv)
759 super(NotebookApp, self).parse_command_line(argv)
762
760
763 if self.extra_args:
761 if self.extra_args:
764 arg0 = self.extra_args[0]
762 arg0 = self.extra_args[0]
765 f = os.path.abspath(arg0)
763 f = os.path.abspath(arg0)
766 self.argv.remove(arg0)
764 self.argv.remove(arg0)
767 if not os.path.exists(f):
765 if not os.path.exists(f):
768 self.log.critical("No such file or directory: %s", f)
766 self.log.critical("No such file or directory: %s", f)
769 self.exit(1)
767 self.exit(1)
770
768
771 # Use config here, to ensure that it takes higher priority than
769 # Use config here, to ensure that it takes higher priority than
772 # anything that comes from the profile.
770 # anything that comes from the profile.
773 c = Config()
771 c = Config()
774 if os.path.isdir(f):
772 if os.path.isdir(f):
775 c.NotebookApp.notebook_dir = f
773 c.NotebookApp.notebook_dir = f
776 elif os.path.isfile(f):
774 elif os.path.isfile(f):
777 c.NotebookApp.file_to_run = f
775 c.NotebookApp.file_to_run = f
778 self.update_config(c)
776 self.update_config(c)
779
777
780 def init_kernel_argv(self):
781 """add the profile-dir to arguments to be passed to IPython kernels"""
782 # FIXME: remove special treatment of IPython kernels
783 # Kernel should get *absolute* path to profile directory
784 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
785
786 def init_configurables(self):
778 def init_configurables(self):
787 self.kernel_spec_manager = self.kernel_spec_manager_class(
779 self.kernel_spec_manager = self.kernel_spec_manager_class(
788 parent=self,
780 parent=self,
789 ipython_dir=self.ipython_dir,
781 ipython_dir=self.ipython_dir,
790 )
782 )
791 self.kernel_manager = self.kernel_manager_class(
783 self.kernel_manager = self.kernel_manager_class(
792 parent=self,
784 parent=self,
793 log=self.log,
785 log=self.log,
794 ipython_kernel_argv=self.ipython_kernel_argv,
795 connection_dir=self.profile_dir.security_dir,
786 connection_dir=self.profile_dir.security_dir,
796 )
787 )
797 self.contents_manager = self.contents_manager_class(
788 self.contents_manager = self.contents_manager_class(
798 parent=self,
789 parent=self,
799 log=self.log,
790 log=self.log,
800 )
791 )
801 self.session_manager = self.session_manager_class(
792 self.session_manager = self.session_manager_class(
802 parent=self,
793 parent=self,
803 log=self.log,
794 log=self.log,
804 kernel_manager=self.kernel_manager,
795 kernel_manager=self.kernel_manager,
805 contents_manager=self.contents_manager,
796 contents_manager=self.contents_manager,
806 )
797 )
807 self.cluster_manager = self.cluster_manager_class(
798 self.cluster_manager = self.cluster_manager_class(
808 parent=self,
799 parent=self,
809 log=self.log,
800 log=self.log,
810 )
801 )
811
802
812 self.config_manager = self.config_manager_class(
803 self.config_manager = self.config_manager_class(
813 parent=self,
804 parent=self,
814 log=self.log,
805 log=self.log,
815 profile_dir=self.profile_dir.location,
806 profile_dir=self.profile_dir.location,
816 )
807 )
817
808
818 def init_logging(self):
809 def init_logging(self):
819 # This prevents double log messages because tornado use a root logger that
810 # This prevents double log messages because tornado use a root logger that
820 # self.log is a child of. The logging module dipatches log messages to a log
811 # self.log is a child of. The logging module dipatches log messages to a log
821 # and all of its ancenstors until propagate is set to False.
812 # and all of its ancenstors until propagate is set to False.
822 self.log.propagate = False
813 self.log.propagate = False
823
814
824 for log in app_log, access_log, gen_log:
815 for log in app_log, access_log, gen_log:
825 # consistent log output name (NotebookApp instead of tornado.access, etc.)
816 # consistent log output name (NotebookApp instead of tornado.access, etc.)
826 log.name = self.log.name
817 log.name = self.log.name
827 # hook up tornado 3's loggers to our app handlers
818 # hook up tornado 3's loggers to our app handlers
828 logger = logging.getLogger('tornado')
819 logger = logging.getLogger('tornado')
829 logger.propagate = True
820 logger.propagate = True
830 logger.parent = self.log
821 logger.parent = self.log
831 logger.setLevel(self.log.level)
822 logger.setLevel(self.log.level)
832
823
833 def init_webapp(self):
824 def init_webapp(self):
834 """initialize tornado webapp and httpserver"""
825 """initialize tornado webapp and httpserver"""
835 self.tornado_settings['allow_origin'] = self.allow_origin
826 self.tornado_settings['allow_origin'] = self.allow_origin
836 if self.allow_origin_pat:
827 if self.allow_origin_pat:
837 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
828 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
838 self.tornado_settings['allow_credentials'] = self.allow_credentials
829 self.tornado_settings['allow_credentials'] = self.allow_credentials
839 # ensure default_url starts with base_url
830 # ensure default_url starts with base_url
840 if not self.default_url.startswith(self.base_url):
831 if not self.default_url.startswith(self.base_url):
841 self.default_url = url_path_join(self.base_url, self.default_url)
832 self.default_url = url_path_join(self.base_url, self.default_url)
842
833
843 self.web_app = NotebookWebApplication(
834 self.web_app = NotebookWebApplication(
844 self, self.kernel_manager, self.contents_manager,
835 self, self.kernel_manager, self.contents_manager,
845 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
836 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
846 self.config_manager,
837 self.config_manager,
847 self.log, self.base_url, self.default_url, self.tornado_settings,
838 self.log, self.base_url, self.default_url, self.tornado_settings,
848 self.jinja_environment_options
839 self.jinja_environment_options
849 )
840 )
850 ssl_options = self.ssl_options
841 ssl_options = self.ssl_options
851 if self.certfile:
842 if self.certfile:
852 ssl_options['certfile'] = self.certfile
843 ssl_options['certfile'] = self.certfile
853 if self.keyfile:
844 if self.keyfile:
854 ssl_options['keyfile'] = self.keyfile
845 ssl_options['keyfile'] = self.keyfile
855 if not ssl_options:
846 if not ssl_options:
856 # None indicates no SSL config
847 # None indicates no SSL config
857 ssl_options = None
848 ssl_options = None
858 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
859 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
860 xheaders=self.trust_xheaders)
851 xheaders=self.trust_xheaders)
861
852
862 success = None
853 success = None
863 for port in random_ports(self.port, self.port_retries+1):
854 for port in random_ports(self.port, self.port_retries+1):
864 try:
855 try:
865 self.http_server.listen(port, self.ip)
856 self.http_server.listen(port, self.ip)
866 except socket.error as e:
857 except socket.error as e:
867 if e.errno == errno.EADDRINUSE:
858 if e.errno == errno.EADDRINUSE:
868 self.log.info('The port %i is already in use, trying another random port.' % port)
859 self.log.info('The port %i is already in use, trying another random port.' % port)
869 continue
860 continue
870 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
871 self.log.warn("Permission to listen on port %i denied" % port)
862 self.log.warn("Permission to listen on port %i denied" % port)
872 continue
863 continue
873 else:
864 else:
874 raise
865 raise
875 else:
866 else:
876 self.port = port
867 self.port = port
877 success = True
868 success = True
878 break
869 break
879 if not success:
870 if not success:
880 self.log.critical('ERROR: the notebook server could not be started because '
871 self.log.critical('ERROR: the notebook server could not be started because '
881 'no available port could be found.')
872 'no available port could be found.')
882 self.exit(1)
873 self.exit(1)
883
874
884 @property
875 @property
885 def display_url(self):
876 def display_url(self):
886 ip = self.ip if self.ip else '[all ip addresses on your system]'
877 ip = self.ip if self.ip else '[all ip addresses on your system]'
887 return self._url(ip)
878 return self._url(ip)
888
879
889 @property
880 @property
890 def connection_url(self):
881 def connection_url(self):
891 ip = self.ip if self.ip else 'localhost'
882 ip = self.ip if self.ip else 'localhost'
892 return self._url(ip)
883 return self._url(ip)
893
884
894 def _url(self, ip):
885 def _url(self, ip):
895 proto = 'https' if self.certfile else 'http'
886 proto = 'https' if self.certfile else 'http'
896 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
897
888
898 def init_terminals(self):
889 def init_terminals(self):
899 try:
890 try:
900 from .terminal import initialize
891 from .terminal import initialize
901 initialize(self.web_app)
892 initialize(self.web_app)
902 self.web_app.settings['terminals_available'] = True
893 self.web_app.settings['terminals_available'] = True
903 except ImportError as e:
894 except ImportError as e:
904 log = self.log.debug if sys.platform == 'win32' else self.log.warn
895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
905 log("Terminals not available (error was %s)", e)
896 log("Terminals not available (error was %s)", e)
906
897
907 def init_signal(self):
898 def init_signal(self):
908 if not sys.platform.startswith('win'):
899 if not sys.platform.startswith('win'):
909 signal.signal(signal.SIGINT, self._handle_sigint)
900 signal.signal(signal.SIGINT, self._handle_sigint)
910 signal.signal(signal.SIGTERM, self._signal_stop)
901 signal.signal(signal.SIGTERM, self._signal_stop)
911 if hasattr(signal, 'SIGUSR1'):
902 if hasattr(signal, 'SIGUSR1'):
912 # Windows doesn't support SIGUSR1
903 # Windows doesn't support SIGUSR1
913 signal.signal(signal.SIGUSR1, self._signal_info)
904 signal.signal(signal.SIGUSR1, self._signal_info)
914 if hasattr(signal, 'SIGINFO'):
905 if hasattr(signal, 'SIGINFO'):
915 # only on BSD-based systems
906 # only on BSD-based systems
916 signal.signal(signal.SIGINFO, self._signal_info)
907 signal.signal(signal.SIGINFO, self._signal_info)
917
908
918 def _handle_sigint(self, sig, frame):
909 def _handle_sigint(self, sig, frame):
919 """SIGINT handler spawns confirmation dialog"""
910 """SIGINT handler spawns confirmation dialog"""
920 # register more forceful signal handler for ^C^C case
911 # register more forceful signal handler for ^C^C case
921 signal.signal(signal.SIGINT, self._signal_stop)
912 signal.signal(signal.SIGINT, self._signal_stop)
922 # request confirmation dialog in bg thread, to avoid
913 # request confirmation dialog in bg thread, to avoid
923 # blocking the App
914 # blocking the App
924 thread = threading.Thread(target=self._confirm_exit)
915 thread = threading.Thread(target=self._confirm_exit)
925 thread.daemon = True
916 thread.daemon = True
926 thread.start()
917 thread.start()
927
918
928 def _restore_sigint_handler(self):
919 def _restore_sigint_handler(self):
929 """callback for restoring original SIGINT handler"""
920 """callback for restoring original SIGINT handler"""
930 signal.signal(signal.SIGINT, self._handle_sigint)
921 signal.signal(signal.SIGINT, self._handle_sigint)
931
922
932 def _confirm_exit(self):
923 def _confirm_exit(self):
933 """confirm shutdown on ^C
924 """confirm shutdown on ^C
934
925
935 A second ^C, or answering 'y' within 5s will cause shutdown,
926 A second ^C, or answering 'y' within 5s will cause shutdown,
936 otherwise original SIGINT handler will be restored.
927 otherwise original SIGINT handler will be restored.
937
928
938 This doesn't work on Windows.
929 This doesn't work on Windows.
939 """
930 """
940 info = self.log.info
931 info = self.log.info
941 info('interrupted')
932 info('interrupted')
942 print(self.notebook_info())
933 print(self.notebook_info())
943 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
944 sys.stdout.flush()
935 sys.stdout.flush()
945 r,w,x = select.select([sys.stdin], [], [], 5)
936 r,w,x = select.select([sys.stdin], [], [], 5)
946 if r:
937 if r:
947 line = sys.stdin.readline()
938 line = sys.stdin.readline()
948 if line.lower().startswith('y') and 'n' not in line.lower():
939 if line.lower().startswith('y') and 'n' not in line.lower():
949 self.log.critical("Shutdown confirmed")
940 self.log.critical("Shutdown confirmed")
950 ioloop.IOLoop.current().stop()
941 ioloop.IOLoop.current().stop()
951 return
942 return
952 else:
943 else:
953 print("No answer for 5s:", end=' ')
944 print("No answer for 5s:", end=' ')
954 print("resuming operation...")
945 print("resuming operation...")
955 # no answer, or answer is no:
946 # no answer, or answer is no:
956 # set it back to original SIGINT handler
947 # set it back to original SIGINT handler
957 # use IOLoop.add_callback because signal.signal must be called
948 # use IOLoop.add_callback because signal.signal must be called
958 # from main thread
949 # from main thread
959 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
960
951
961 def _signal_stop(self, sig, frame):
952 def _signal_stop(self, sig, frame):
962 self.log.critical("received signal %s, stopping", sig)
953 self.log.critical("received signal %s, stopping", sig)
963 ioloop.IOLoop.current().stop()
954 ioloop.IOLoop.current().stop()
964
955
965 def _signal_info(self, sig, frame):
956 def _signal_info(self, sig, frame):
966 print(self.notebook_info())
957 print(self.notebook_info())
967
958
968 def init_components(self):
959 def init_components(self):
969 """Check the components submodule, and warn if it's unclean"""
960 """Check the components submodule, and warn if it's unclean"""
970 status = submodule.check_submodule_status()
961 status = submodule.check_submodule_status()
971 if status == 'missing':
962 if status == 'missing':
972 self.log.warn("components submodule missing, running `git submodule update`")
963 self.log.warn("components submodule missing, running `git submodule update`")
973 submodule.update_submodules(submodule.ipython_parent())
964 submodule.update_submodules(submodule.ipython_parent())
974 elif status == 'unclean':
965 elif status == 'unclean':
975 self.log.warn("components submodule unclean, you may see 404s on static/components")
966 self.log.warn("components submodule unclean, you may see 404s on static/components")
976 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
977
968
978 def init_server_extensions(self):
969 def init_server_extensions(self):
979 """Load any extensions specified by config.
970 """Load any extensions specified by config.
980
971
981 Import the module, then call the load_jupyter_server_extension function,
972 Import the module, then call the load_jupyter_server_extension function,
982 if one exists.
973 if one exists.
983
974
984 The extension API is experimental, and may change in future releases.
975 The extension API is experimental, and may change in future releases.
985 """
976 """
986 for modulename in self.server_extensions:
977 for modulename in self.server_extensions:
987 try:
978 try:
988 mod = importlib.import_module(modulename)
979 mod = importlib.import_module(modulename)
989 func = getattr(mod, 'load_jupyter_server_extension', None)
980 func = getattr(mod, 'load_jupyter_server_extension', None)
990 if func is not None:
981 if func is not None:
991 func(self)
982 func(self)
992 except Exception:
983 except Exception:
993 if self.reraise_server_extension_failures:
984 if self.reraise_server_extension_failures:
994 raise
985 raise
995 self.log.warn("Error loading server extension %s", modulename,
986 self.log.warn("Error loading server extension %s", modulename,
996 exc_info=True)
987 exc_info=True)
997
988
998 @catch_config_error
989 @catch_config_error
999 def initialize(self, argv=None):
990 def initialize(self, argv=None):
1000 super(NotebookApp, self).initialize(argv)
991 super(NotebookApp, self).initialize(argv)
1001 self.init_logging()
992 self.init_logging()
1002 self.init_kernel_argv()
1003 self.init_configurables()
993 self.init_configurables()
1004 self.init_components()
994 self.init_components()
1005 self.init_webapp()
995 self.init_webapp()
1006 self.init_terminals()
996 self.init_terminals()
1007 self.init_signal()
997 self.init_signal()
1008 self.init_server_extensions()
998 self.init_server_extensions()
1009
999
1010 def cleanup_kernels(self):
1000 def cleanup_kernels(self):
1011 """Shutdown all kernels.
1001 """Shutdown all kernels.
1012
1002
1013 The kernels will shutdown themselves when this process no longer exists,
1003 The kernels will shutdown themselves when this process no longer exists,
1014 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1015 """
1005 """
1016 self.log.info('Shutting down kernels')
1006 self.log.info('Shutting down kernels')
1017 self.kernel_manager.shutdown_all()
1007 self.kernel_manager.shutdown_all()
1018
1008
1019 def notebook_info(self):
1009 def notebook_info(self):
1020 "Return the current working directory and the server url information"
1010 "Return the current working directory and the server url information"
1021 info = self.contents_manager.info_string() + "\n"
1011 info = self.contents_manager.info_string() + "\n"
1022 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1023 return info + "The IPython Notebook is running at: %s" % self.display_url
1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1024
1014
1025 def server_info(self):
1015 def server_info(self):
1026 """Return a JSONable dict of information about this server."""
1016 """Return a JSONable dict of information about this server."""
1027 return {'url': self.connection_url,
1017 return {'url': self.connection_url,
1028 'hostname': self.ip if self.ip else 'localhost',
1018 'hostname': self.ip if self.ip else 'localhost',
1029 'port': self.port,
1019 'port': self.port,
1030 'secure': bool(self.certfile),
1020 'secure': bool(self.certfile),
1031 'base_url': self.base_url,
1021 'base_url': self.base_url,
1032 'notebook_dir': os.path.abspath(self.notebook_dir),
1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1033 'pid': os.getpid()
1023 'pid': os.getpid()
1034 }
1024 }
1035
1025
1036 def write_server_info_file(self):
1026 def write_server_info_file(self):
1037 """Write the result of server_info() to the JSON file info_file."""
1027 """Write the result of server_info() to the JSON file info_file."""
1038 with open(self.info_file, 'w') as f:
1028 with open(self.info_file, 'w') as f:
1039 json.dump(self.server_info(), f, indent=2)
1029 json.dump(self.server_info(), f, indent=2)
1040
1030
1041 def remove_server_info_file(self):
1031 def remove_server_info_file(self):
1042 """Remove the nbserver-<pid>.json file created for this server.
1032 """Remove the nbserver-<pid>.json file created for this server.
1043
1033
1044 Ignores the error raised when the file has already been removed.
1034 Ignores the error raised when the file has already been removed.
1045 """
1035 """
1046 try:
1036 try:
1047 os.unlink(self.info_file)
1037 os.unlink(self.info_file)
1048 except OSError as e:
1038 except OSError as e:
1049 if e.errno != errno.ENOENT:
1039 if e.errno != errno.ENOENT:
1050 raise
1040 raise
1051
1041
1052 def start(self):
1042 def start(self):
1053 """ Start the IPython Notebook server app, after initialization
1043 """ Start the IPython Notebook server app, after initialization
1054
1044
1055 This method takes no arguments so all configuration and initialization
1045 This method takes no arguments so all configuration and initialization
1056 must be done prior to calling this method."""
1046 must be done prior to calling this method."""
1057 if self.subapp is not None:
1047 if self.subapp is not None:
1058 return self.subapp.start()
1048 return self.subapp.start()
1059
1049
1060 info = self.log.info
1050 info = self.log.info
1061 for line in self.notebook_info().split("\n"):
1051 for line in self.notebook_info().split("\n"):
1062 info(line)
1052 info(line)
1063 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1064
1054
1065 self.write_server_info_file()
1055 self.write_server_info_file()
1066
1056
1067 if self.open_browser or self.file_to_run:
1057 if self.open_browser or self.file_to_run:
1068 try:
1058 try:
1069 browser = webbrowser.get(self.browser or None)
1059 browser = webbrowser.get(self.browser or None)
1070 except webbrowser.Error as e:
1060 except webbrowser.Error as e:
1071 self.log.warn('No web browser found: %s.' % e)
1061 self.log.warn('No web browser found: %s.' % e)
1072 browser = None
1062 browser = None
1073
1063
1074 if self.file_to_run:
1064 if self.file_to_run:
1075 if not os.path.exists(self.file_to_run):
1065 if not os.path.exists(self.file_to_run):
1076 self.log.critical("%s does not exist" % self.file_to_run)
1066 self.log.critical("%s does not exist" % self.file_to_run)
1077 self.exit(1)
1067 self.exit(1)
1078
1068
1079 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1080 uri = url_path_join('notebooks', *relpath.split(os.sep))
1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1081 else:
1071 else:
1082 uri = 'tree'
1072 uri = 'tree'
1083 if browser:
1073 if browser:
1084 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1085 new=2)
1075 new=2)
1086 threading.Thread(target=b).start()
1076 threading.Thread(target=b).start()
1087
1077
1088 self.io_loop = ioloop.IOLoop.current()
1078 self.io_loop = ioloop.IOLoop.current()
1089 if sys.platform.startswith('win'):
1079 if sys.platform.startswith('win'):
1090 # add no-op to wake every 5s
1080 # add no-op to wake every 5s
1091 # to handle signals that may be ignored by the inner loop
1081 # to handle signals that may be ignored by the inner loop
1092 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1093 pc.start()
1083 pc.start()
1094 try:
1084 try:
1095 self.io_loop.start()
1085 self.io_loop.start()
1096 except KeyboardInterrupt:
1086 except KeyboardInterrupt:
1097 info("Interrupted...")
1087 info("Interrupted...")
1098 finally:
1088 finally:
1099 self.cleanup_kernels()
1089 self.cleanup_kernels()
1100 self.remove_server_info_file()
1090 self.remove_server_info_file()
1101
1091
1102 def stop(self):
1092 def stop(self):
1103 def _stop():
1093 def _stop():
1104 self.http_server.stop()
1094 self.http_server.stop()
1105 self.io_loop.stop()
1095 self.io_loop.stop()
1106 self.io_loop.add_callback(_stop)
1096 self.io_loop.add_callback(_stop)
1107
1097
1108
1098
1109 def list_running_servers(profile='default'):
1099 def list_running_servers(profile='default'):
1110 """Iterate over the server info files of running notebook servers.
1100 """Iterate over the server info files of running notebook servers.
1111
1101
1112 Given a profile name, find nbserver-* files in the security directory of
1102 Given a profile name, find nbserver-* files in the security directory of
1113 that profile, and yield dicts of their information, each one pertaining to
1103 that profile, and yield dicts of their information, each one pertaining to
1114 a currently running notebook server instance.
1104 a currently running notebook server instance.
1115 """
1105 """
1116 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1117 for file in os.listdir(pd.security_dir):
1107 for file in os.listdir(pd.security_dir):
1118 if file.startswith('nbserver-'):
1108 if file.startswith('nbserver-'):
1119 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1120 info = json.load(f)
1110 info = json.load(f)
1121
1111
1122 # Simple check whether that process is really still running
1112 # Simple check whether that process is really still running
1123 # Also remove leftover files from IPython 2.x without a pid field
1113 # Also remove leftover files from IPython 2.x without a pid field
1124 if ('pid' in info) and check_pid(info['pid']):
1114 if ('pid' in info) and check_pid(info['pid']):
1125 yield info
1115 yield info
1126 else:
1116 else:
1127 # If the process has died, try to delete its info file
1117 # If the process has died, try to delete its info file
1128 try:
1118 try:
1129 os.unlink(file)
1119 os.unlink(file)
1130 except OSError:
1120 except OSError:
1131 pass # TODO: This should warn or log or something
1121 pass # TODO: This should warn or log or something
1132 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1133 # Main entry point
1123 # Main entry point
1134 #-----------------------------------------------------------------------------
1124 #-----------------------------------------------------------------------------
1135
1125
1136 launch_new_instance = NotebookApp.launch_instance
1126 launch_new_instance = NotebookApp.launch_instance
1137
1127
@@ -1,395 +1,380 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
7 Authors
8 -------
9
10 * Brian Granger
11 * Fernando Perez
12 * Min Ragan-Kelley
13 """
6 """
14
7
15 #-----------------------------------------------------------------------------
8 # Copyright (c) IPython Development Team.
16 # Copyright (C) 2008-2011 The IPython Development Team
9 # Distributed under the terms of the Modified BSD License.
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
21
22 #-----------------------------------------------------------------------------
23 # Imports
24 #-----------------------------------------------------------------------------
25
10
26 from __future__ import absolute_import
11 from __future__ import absolute_import
27 from __future__ import print_function
12 from __future__ import print_function
28
13
29 import logging
14 import logging
30 import os
15 import os
31 import sys
16 import sys
32
17
33 from IPython.config.loader import Config
18 from IPython.config.loader import Config
34 from IPython.config.application import boolean_flag, catch_config_error, Application
19 from IPython.config.application import boolean_flag, catch_config_error, Application
35 from IPython.core import release
20 from IPython.core import release
36 from IPython.core import usage
21 from IPython.core import usage
37 from IPython.core.completer import IPCompleter
22 from IPython.core.completer import IPCompleter
38 from IPython.core.crashhandler import CrashHandler
23 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.formatters import PlainTextFormatter
24 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.history import HistoryManager
25 from IPython.core.history import HistoryManager
41 from IPython.core.prompts import PromptManager
26 from IPython.core.prompts import PromptManager
42 from IPython.core.application import (
27 from IPython.core.application import (
43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
28 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
44 )
29 )
45 from IPython.core.magics import ScriptMagics
30 from IPython.core.magics import ScriptMagics
46 from IPython.core.shellapp import (
31 from IPython.core.shellapp import (
47 InteractiveShellApp, shell_flags, shell_aliases
32 InteractiveShellApp, shell_flags, shell_aliases
48 )
33 )
49 from IPython.extensions.storemagic import StoreMagics
34 from IPython.extensions.storemagic import StoreMagics
50 from IPython.terminal.interactiveshell import TerminalInteractiveShell
35 from IPython.terminal.interactiveshell import TerminalInteractiveShell
51 from IPython.utils import warn
36 from IPython.utils import warn
52 from IPython.utils.path import get_ipython_dir, check_for_old_config
37 from IPython.utils.path import get_ipython_dir, check_for_old_config
53 from IPython.utils.traitlets import (
38 from IPython.utils.traitlets import (
54 Bool, List, Dict,
39 Bool, List, Dict,
55 )
40 )
56
41
57 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
58 # Globals, utilities and helpers
43 # Globals, utilities and helpers
59 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
60
45
61 _examples = """
46 _examples = """
62 ipython --matplotlib # enable matplotlib integration
47 ipython --matplotlib # enable matplotlib integration
63 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
48 ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
64
49
65 ipython --log-level=DEBUG # set logging to DEBUG
50 ipython --log-level=DEBUG # set logging to DEBUG
66 ipython --profile=foo # start with profile foo
51 ipython --profile=foo # start with profile foo
67
52
68 ipython qtconsole # start the qtconsole GUI application
53 ipython qtconsole # start the qtconsole GUI application
69 ipython help qtconsole # show the help for the qtconsole subcmd
54 ipython help qtconsole # show the help for the qtconsole subcmd
70
55
71 ipython console # start the terminal-based console application
56 ipython console # start the terminal-based console application
72 ipython help console # show the help for the console subcmd
57 ipython help console # show the help for the console subcmd
73
58
74 ipython notebook # start the IPython notebook
59 ipython notebook # start the IPython notebook
75 ipython help notebook # show the help for the notebook subcmd
60 ipython help notebook # show the help for the notebook subcmd
76
61
77 ipython profile create foo # create profile foo w/ default config files
62 ipython profile create foo # create profile foo w/ default config files
78 ipython help profile # show the help for the profile subcmd
63 ipython help profile # show the help for the profile subcmd
79
64
80 ipython locate # print the path to the IPython directory
65 ipython locate # print the path to the IPython directory
81 ipython locate profile foo # print the path to the directory for profile `foo`
66 ipython locate profile foo # print the path to the directory for profile `foo`
82
67
83 ipython nbconvert # convert notebooks to/from other formats
68 ipython nbconvert # convert notebooks to/from other formats
84 """
69 """
85
70
86 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
87 # Crash handler for this application
72 # Crash handler for this application
88 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
89
74
90 class IPAppCrashHandler(CrashHandler):
75 class IPAppCrashHandler(CrashHandler):
91 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
76 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
92
77
93 def __init__(self, app):
78 def __init__(self, app):
94 contact_name = release.author
79 contact_name = release.author
95 contact_email = release.author_email
80 contact_email = release.author_email
96 bug_tracker = 'https://github.com/ipython/ipython/issues'
81 bug_tracker = 'https://github.com/ipython/ipython/issues'
97 super(IPAppCrashHandler,self).__init__(
82 super(IPAppCrashHandler,self).__init__(
98 app, contact_name, contact_email, bug_tracker
83 app, contact_name, contact_email, bug_tracker
99 )
84 )
100
85
101 def make_report(self,traceback):
86 def make_report(self,traceback):
102 """Return a string containing a crash report."""
87 """Return a string containing a crash report."""
103
88
104 sec_sep = self.section_sep
89 sec_sep = self.section_sep
105 # Start with parent report
90 # Start with parent report
106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
91 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 # Add interactive-specific info we may have
92 # Add interactive-specific info we may have
108 rpt_add = report.append
93 rpt_add = report.append
109 try:
94 try:
110 rpt_add(sec_sep+"History of session input:")
95 rpt_add(sec_sep+"History of session input:")
111 for line in self.app.shell.user_ns['_ih']:
96 for line in self.app.shell.user_ns['_ih']:
112 rpt_add(line)
97 rpt_add(line)
113 rpt_add('\n*** Last line of input (may not be in above history):\n')
98 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 rpt_add(self.app.shell._last_input_line+'\n')
99 rpt_add(self.app.shell._last_input_line+'\n')
115 except:
100 except:
116 pass
101 pass
117
102
118 return ''.join(report)
103 return ''.join(report)
119
104
120 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
121 # Aliases and Flags
106 # Aliases and Flags
122 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
123 flags = dict(base_flags)
108 flags = dict(base_flags)
124 flags.update(shell_flags)
109 flags.update(shell_flags)
125 frontend_flags = {}
110 frontend_flags = {}
126 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
111 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
127 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
112 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
128 'Turn on auto editing of files with syntax errors.',
113 'Turn on auto editing of files with syntax errors.',
129 'Turn off auto editing of files with syntax errors.'
114 'Turn off auto editing of files with syntax errors.'
130 )
115 )
131 addflag('banner', 'TerminalIPythonApp.display_banner',
116 addflag('banner', 'TerminalIPythonApp.display_banner',
132 "Display a banner upon starting IPython.",
117 "Display a banner upon starting IPython.",
133 "Don't display a banner upon starting IPython."
118 "Don't display a banner upon starting IPython."
134 )
119 )
135 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
120 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
136 """Set to confirm when you try to exit IPython with an EOF (Control-D
121 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
122 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
138 you can force a direct exit without any confirmation.""",
123 you can force a direct exit without any confirmation.""",
139 "Don't prompt the user when exiting."
124 "Don't prompt the user when exiting."
140 )
125 )
141 addflag('term-title', 'TerminalInteractiveShell.term_title',
126 addflag('term-title', 'TerminalInteractiveShell.term_title',
142 "Enable auto setting the terminal title.",
127 "Enable auto setting the terminal title.",
143 "Disable auto setting the terminal title."
128 "Disable auto setting the terminal title."
144 )
129 )
145 classic_config = Config()
130 classic_config = Config()
146 classic_config.InteractiveShell.cache_size = 0
131 classic_config.InteractiveShell.cache_size = 0
147 classic_config.PlainTextFormatter.pprint = False
132 classic_config.PlainTextFormatter.pprint = False
148 classic_config.PromptManager.in_template = '>>> '
133 classic_config.PromptManager.in_template = '>>> '
149 classic_config.PromptManager.in2_template = '... '
134 classic_config.PromptManager.in2_template = '... '
150 classic_config.PromptManager.out_template = ''
135 classic_config.PromptManager.out_template = ''
151 classic_config.InteractiveShell.separate_in = ''
136 classic_config.InteractiveShell.separate_in = ''
152 classic_config.InteractiveShell.separate_out = ''
137 classic_config.InteractiveShell.separate_out = ''
153 classic_config.InteractiveShell.separate_out2 = ''
138 classic_config.InteractiveShell.separate_out2 = ''
154 classic_config.InteractiveShell.colors = 'NoColor'
139 classic_config.InteractiveShell.colors = 'NoColor'
155 classic_config.InteractiveShell.xmode = 'Plain'
140 classic_config.InteractiveShell.xmode = 'Plain'
156
141
157 frontend_flags['classic']=(
142 frontend_flags['classic']=(
158 classic_config,
143 classic_config,
159 "Gives IPython a similar feel to the classic Python prompt."
144 "Gives IPython a similar feel to the classic Python prompt."
160 )
145 )
161 # # log doesn't make so much sense this way anymore
146 # # log doesn't make so much sense this way anymore
162 # paa('--log','-l',
147 # paa('--log','-l',
163 # action='store_true', dest='InteractiveShell.logstart',
148 # action='store_true', dest='InteractiveShell.logstart',
164 # help="Start logging to the default log file (./ipython_log.py).")
149 # help="Start logging to the default log file (./ipython_log.py).")
165 #
150 #
166 # # quick is harder to implement
151 # # quick is harder to implement
167 frontend_flags['quick']=(
152 frontend_flags['quick']=(
168 {'TerminalIPythonApp' : {'quick' : True}},
153 {'TerminalIPythonApp' : {'quick' : True}},
169 "Enable quick startup with no config files."
154 "Enable quick startup with no config files."
170 )
155 )
171
156
172 frontend_flags['i'] = (
157 frontend_flags['i'] = (
173 {'TerminalIPythonApp' : {'force_interact' : True}},
158 {'TerminalIPythonApp' : {'force_interact' : True}},
174 """If running code from the command line, become interactive afterwards."""
159 """If running code from the command line, become interactive afterwards."""
175 )
160 )
176 flags.update(frontend_flags)
161 flags.update(frontend_flags)
177
162
178 aliases = dict(base_aliases)
163 aliases = dict(base_aliases)
179 aliases.update(shell_aliases)
164 aliases.update(shell_aliases)
180
165
181 #-----------------------------------------------------------------------------
166 #-----------------------------------------------------------------------------
182 # Main classes and functions
167 # Main classes and functions
183 #-----------------------------------------------------------------------------
168 #-----------------------------------------------------------------------------
184
169
185
170
186 class LocateIPythonApp(BaseIPythonApplication):
171 class LocateIPythonApp(BaseIPythonApplication):
187 description = """print the path to the IPython dir"""
172 description = """print the path to the IPython dir"""
188 subcommands = Dict(dict(
173 subcommands = Dict(dict(
189 profile=('IPython.core.profileapp.ProfileLocate',
174 profile=('IPython.core.profileapp.ProfileLocate',
190 "print the path to an IPython profile directory",
175 "print the path to an IPython profile directory",
191 ),
176 ),
192 ))
177 ))
193 def start(self):
178 def start(self):
194 if self.subapp is not None:
179 if self.subapp is not None:
195 return self.subapp.start()
180 return self.subapp.start()
196 else:
181 else:
197 print(self.ipython_dir)
182 print(self.ipython_dir)
198
183
199
184
200 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
185 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
201 name = u'ipython'
186 name = u'ipython'
202 description = usage.cl_usage
187 description = usage.cl_usage
203 crash_handler_class = IPAppCrashHandler
188 crash_handler_class = IPAppCrashHandler
204 examples = _examples
189 examples = _examples
205
190
206 flags = Dict(flags)
191 flags = Dict(flags)
207 aliases = Dict(aliases)
192 aliases = Dict(aliases)
208 classes = List()
193 classes = List()
209 def _classes_default(self):
194 def _classes_default(self):
210 """This has to be in a method, for TerminalIPythonApp to be available."""
195 """This has to be in a method, for TerminalIPythonApp to be available."""
211 return [
196 return [
212 InteractiveShellApp, # ShellApp comes before TerminalApp, because
197 InteractiveShellApp, # ShellApp comes before TerminalApp, because
213 self.__class__, # it will also affect subclasses (e.g. QtConsole)
198 self.__class__, # it will also affect subclasses (e.g. QtConsole)
214 TerminalInteractiveShell,
199 TerminalInteractiveShell,
215 PromptManager,
200 PromptManager,
216 HistoryManager,
201 HistoryManager,
217 ProfileDir,
202 ProfileDir,
218 PlainTextFormatter,
203 PlainTextFormatter,
219 IPCompleter,
204 IPCompleter,
220 ScriptMagics,
205 ScriptMagics,
221 StoreMagics,
206 StoreMagics,
222 ]
207 ]
223
208
224 subcommands = dict(
209 subcommands = dict(
225 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
210 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
226 """Launch the IPython Qt Console."""
211 """Launch the IPython Qt Console."""
227 ),
212 ),
228 notebook=('IPython.html.notebookapp.NotebookApp',
213 notebook=('IPython.html.notebookapp.NotebookApp',
229 """Launch the IPython HTML Notebook Server."""
214 """Launch the IPython HTML Notebook Server."""
230 ),
215 ),
231 profile = ("IPython.core.profileapp.ProfileApp",
216 profile = ("IPython.core.profileapp.ProfileApp",
232 "Create and manage IPython profiles."
217 "Create and manage IPython profiles."
233 ),
218 ),
234 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
219 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
235 "Start a kernel without an attached frontend."
220 "Start a kernel without an attached frontend."
236 ),
221 ),
237 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
222 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
238 """Launch the IPython terminal-based Console."""
223 """Launch the IPython terminal-based Console."""
239 ),
224 ),
240 locate=('IPython.terminal.ipapp.LocateIPythonApp',
225 locate=('IPython.terminal.ipapp.LocateIPythonApp',
241 LocateIPythonApp.description
226 LocateIPythonApp.description
242 ),
227 ),
243 history=('IPython.core.historyapp.HistoryApp',
228 history=('IPython.core.historyapp.HistoryApp',
244 "Manage the IPython history database."
229 "Manage the IPython history database."
245 ),
230 ),
246 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
231 nbconvert=('IPython.nbconvert.nbconvertapp.NbConvertApp',
247 "Convert notebooks to/from other formats."
232 "Convert notebooks to/from other formats."
248 ),
233 ),
249 trust=('IPython.nbformat.sign.TrustNotebookApp',
234 trust=('IPython.nbformat.sign.TrustNotebookApp',
250 "Sign notebooks to trust their potentially unsafe contents at load."
235 "Sign notebooks to trust their potentially unsafe contents at load."
251 ),
236 ),
252 kernelspec=('IPython.kernel.kernelspecapp.KernelSpecApp',
237 kernelspec=('IPython.kernel.kernelspecapp.KernelSpecApp',
253 "Manage IPython kernel specifications."
238 "Manage IPython kernel specifications."
254 ),
239 ),
255 )
240 )
256 subcommands['install-nbextension'] = (
241 subcommands['install-nbextension'] = (
257 "IPython.html.nbextensions.NBExtensionApp",
242 "IPython.html.nbextensions.NBExtensionApp",
258 "Install IPython notebook extension files"
243 "Install IPython notebook extension files"
259 )
244 )
260
245
261 # *do* autocreate requested profile, but don't create the config file.
246 # *do* autocreate requested profile, but don't create the config file.
262 auto_create=Bool(True)
247 auto_create=Bool(True)
263 # configurables
248 # configurables
264 ignore_old_config=Bool(False, config=True,
249 ignore_old_config=Bool(False, config=True,
265 help="Suppress warning messages about legacy config files"
250 help="Suppress warning messages about legacy config files"
266 )
251 )
267 quick = Bool(False, config=True,
252 quick = Bool(False, config=True,
268 help="""Start IPython quickly by skipping the loading of config files."""
253 help="""Start IPython quickly by skipping the loading of config files."""
269 )
254 )
270 def _quick_changed(self, name, old, new):
255 def _quick_changed(self, name, old, new):
271 if new:
256 if new:
272 self.load_config_file = lambda *a, **kw: None
257 self.load_config_file = lambda *a, **kw: None
273 self.ignore_old_config=True
258 self.ignore_old_config=True
274
259
275 display_banner = Bool(True, config=True,
260 display_banner = Bool(True, config=True,
276 help="Whether to display a banner upon starting IPython."
261 help="Whether to display a banner upon starting IPython."
277 )
262 )
278
263
279 # if there is code of files to run from the cmd line, don't interact
264 # if there is code of files to run from the cmd line, don't interact
280 # unless the --i flag (App.force_interact) is true.
265 # unless the --i flag (App.force_interact) is true.
281 force_interact = Bool(False, config=True,
266 force_interact = Bool(False, config=True,
282 help="""If a command or file is given via the command-line,
267 help="""If a command or file is given via the command-line,
283 e.g. 'ipython foo.py', start an interactive shell after executing the
268 e.g. 'ipython foo.py', start an interactive shell after executing the
284 file or command."""
269 file or command."""
285 )
270 )
286 def _force_interact_changed(self, name, old, new):
271 def _force_interact_changed(self, name, old, new):
287 if new:
272 if new:
288 self.interact = True
273 self.interact = True
289
274
290 def _file_to_run_changed(self, name, old, new):
275 def _file_to_run_changed(self, name, old, new):
291 if new:
276 if new:
292 self.something_to_run = True
277 self.something_to_run = True
293 if new and not self.force_interact:
278 if new and not self.force_interact:
294 self.interact = False
279 self.interact = False
295 _code_to_run_changed = _file_to_run_changed
280 _code_to_run_changed = _file_to_run_changed
296 _module_to_run_changed = _file_to_run_changed
281 _module_to_run_changed = _file_to_run_changed
297
282
298 # internal, not-configurable
283 # internal, not-configurable
299 interact=Bool(True)
284 interact=Bool(True)
300 something_to_run=Bool(False)
285 something_to_run=Bool(False)
301
286
302 def parse_command_line(self, argv=None):
287 def parse_command_line(self, argv=None):
303 """override to allow old '-pylab' flag with deprecation warning"""
288 """override to allow old '-pylab' flag with deprecation warning"""
304
289
305 argv = sys.argv[1:] if argv is None else argv
290 argv = sys.argv[1:] if argv is None else argv
306
291
307 if '-pylab' in argv:
292 if '-pylab' in argv:
308 # deprecated `-pylab` given,
293 # deprecated `-pylab` given,
309 # warn and transform into current syntax
294 # warn and transform into current syntax
310 argv = argv[:] # copy, don't clobber
295 argv = argv[:] # copy, don't clobber
311 idx = argv.index('-pylab')
296 idx = argv.index('-pylab')
312 warn.warn("`-pylab` flag has been deprecated.\n"
297 warn.warn("`-pylab` flag has been deprecated.\n"
313 " Use `--matplotlib <backend>` and import pylab manually.")
298 " Use `--matplotlib <backend>` and import pylab manually.")
314 argv[idx] = '--pylab'
299 argv[idx] = '--pylab'
315
300
316 return super(TerminalIPythonApp, self).parse_command_line(argv)
301 return super(TerminalIPythonApp, self).parse_command_line(argv)
317
302
318 @catch_config_error
303 @catch_config_error
319 def initialize(self, argv=None):
304 def initialize(self, argv=None):
320 """Do actions after construct, but before starting the app."""
305 """Do actions after construct, but before starting the app."""
321 super(TerminalIPythonApp, self).initialize(argv)
306 super(TerminalIPythonApp, self).initialize(argv)
322 if self.subapp is not None:
307 if self.subapp is not None:
323 # don't bother initializing further, starting subapp
308 # don't bother initializing further, starting subapp
324 return
309 return
325 if not self.ignore_old_config:
310 if not self.ignore_old_config:
326 check_for_old_config(self.ipython_dir)
311 check_for_old_config(self.ipython_dir)
327 # print self.extra_args
312 # print self.extra_args
328 if self.extra_args and not self.something_to_run:
313 if self.extra_args and not self.something_to_run:
329 self.file_to_run = self.extra_args[0]
314 self.file_to_run = self.extra_args[0]
330 self.init_path()
315 self.init_path()
331 # create the shell
316 # create the shell
332 self.init_shell()
317 self.init_shell()
333 # and draw the banner
318 # and draw the banner
334 self.init_banner()
319 self.init_banner()
335 # Now a variety of things that happen after the banner is printed.
320 # Now a variety of things that happen after the banner is printed.
336 self.init_gui_pylab()
321 self.init_gui_pylab()
337 self.init_extensions()
322 self.init_extensions()
338 self.init_code()
323 self.init_code()
339
324
340 def init_shell(self):
325 def init_shell(self):
341 """initialize the InteractiveShell instance"""
326 """initialize the InteractiveShell instance"""
342 # Create an InteractiveShell instance.
327 # Create an InteractiveShell instance.
343 # shell.display_banner should always be False for the terminal
328 # shell.display_banner should always be False for the terminal
344 # based app, because we call shell.show_banner() by hand below
329 # based app, because we call shell.show_banner() by hand below
345 # so the banner shows *before* all extension loading stuff.
330 # so the banner shows *before* all extension loading stuff.
346 self.shell = TerminalInteractiveShell.instance(parent=self,
331 self.shell = TerminalInteractiveShell.instance(parent=self,
347 display_banner=False, profile_dir=self.profile_dir,
332 display_banner=False, profile_dir=self.profile_dir,
348 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
333 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
349 self.shell.configurables.append(self)
334 self.shell.configurables.append(self)
350
335
351 def init_banner(self):
336 def init_banner(self):
352 """optionally display the banner"""
337 """optionally display the banner"""
353 if self.display_banner and self.interact:
338 if self.display_banner and self.interact:
354 self.shell.show_banner()
339 self.shell.show_banner()
355 # Make sure there is a space below the banner.
340 # Make sure there is a space below the banner.
356 if self.log_level <= logging.INFO: print()
341 if self.log_level <= logging.INFO: print()
357
342
358 def _pylab_changed(self, name, old, new):
343 def _pylab_changed(self, name, old, new):
359 """Replace --pylab='inline' with --pylab='auto'"""
344 """Replace --pylab='inline' with --pylab='auto'"""
360 if new == 'inline':
345 if new == 'inline':
361 warn.warn("'inline' not available as pylab backend, "
346 warn.warn("'inline' not available as pylab backend, "
362 "using 'auto' instead.")
347 "using 'auto' instead.")
363 self.pylab = 'auto'
348 self.pylab = 'auto'
364
349
365 def start(self):
350 def start(self):
366 if self.subapp is not None:
351 if self.subapp is not None:
367 return self.subapp.start()
352 return self.subapp.start()
368 # perform any prexec steps:
353 # perform any prexec steps:
369 if self.interact:
354 if self.interact:
370 self.log.debug("Starting IPython's mainloop...")
355 self.log.debug("Starting IPython's mainloop...")
371 self.shell.mainloop()
356 self.shell.mainloop()
372 else:
357 else:
373 self.log.debug("IPython not interactive...")
358 self.log.debug("IPython not interactive...")
374
359
375 def load_default_config(ipython_dir=None):
360 def load_default_config(ipython_dir=None):
376 """Load the default config file from the default ipython_dir.
361 """Load the default config file from the default ipython_dir.
377
362
378 This is useful for embedded shells.
363 This is useful for embedded shells.
379 """
364 """
380 if ipython_dir is None:
365 if ipython_dir is None:
381 ipython_dir = get_ipython_dir()
366 ipython_dir = get_ipython_dir()
382
367
383 profile_dir = os.path.join(ipython_dir, 'profile_default')
368 profile_dir = os.path.join(ipython_dir, 'profile_default')
384
369
385 config = Config()
370 config = Config()
386 for cf in Application._load_config_files("ipython_config", path=profile_dir):
371 for cf in Application._load_config_files("ipython_config", path=profile_dir):
387 config.update(cf)
372 config.update(cf)
388
373
389 return config
374 return config
390
375
391 launch_new_instance = TerminalIPythonApp.launch_instance
376 launch_new_instance = TerminalIPythonApp.launch_instance
392
377
393
378
394 if __name__ == '__main__':
379 if __name__ == '__main__':
395 launch_new_instance()
380 launch_new_instance()
@@ -1,149 +1,146 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
6 Authors:
7
8 * Min RK
9 * Paul Ivanov
10
11 """
5 """
12
6
13 #-----------------------------------------------------------------------------
7 # Copyright (c) IPython Development Team.
14 # Imports
8 # Distributed under the terms of the Modified BSD License.
15 #-----------------------------------------------------------------------------
9
16 import signal
10 import signal
17
11
18 from IPython.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
12 from IPython.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
19
13
20 from IPython.utils.traitlets import (
14 from IPython.utils.traitlets import (
21 Dict, Any
15 Dict, Any
22 )
16 )
23 from IPython.utils.warn import error
17 from IPython.utils.warn import error
24
18
25 from IPython.consoleapp import (
19 from IPython.consoleapp import (
26 IPythonConsoleApp, app_aliases, app_flags, aliases, flags
20 IPythonConsoleApp, app_aliases, app_flags, aliases, flags
27 )
21 )
28
22
29 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
23 from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
30
24
31 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
32 # Globals
26 # Globals
33 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
34
28
35 _examples = """
29 _examples = """
36 ipython console # start the ZMQ-based console
30 ipython console # start the ZMQ-based console
37 ipython console --existing # connect to an existing ipython session
31 ipython console --existing # connect to an existing ipython session
38 """
32 """
39
33
40 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
41 # Flags and Aliases
35 # Flags and Aliases
42 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
43
37
44 # copy flags from mixin:
38 # copy flags from mixin:
45 flags = dict(flags)
39 flags = dict(flags)
46 # start with mixin frontend flags:
40 # start with mixin frontend flags:
47 frontend_flags = dict(app_flags)
41 frontend_flags = dict(app_flags)
48 # add TerminalIPApp flags:
42 # add TerminalIPApp flags:
49 frontend_flags.update(term_flags)
43 frontend_flags.update(term_flags)
50 # disable quick startup, as it won't propagate to the kernel anyway
44 # disable quick startup, as it won't propagate to the kernel anyway
51 frontend_flags.pop('quick')
45 frontend_flags.pop('quick')
52 # update full dict with frontend flags:
46 # update full dict with frontend flags:
53 flags.update(frontend_flags)
47 flags.update(frontend_flags)
54
48
55 # copy flags from mixin
49 # copy flags from mixin
56 aliases = dict(aliases)
50 aliases = dict(aliases)
57 # start with mixin frontend flags
51 # start with mixin frontend flags
58 frontend_aliases = dict(app_aliases)
52 frontend_aliases = dict(app_aliases)
59 # load updated frontend flags into full dict
53 # load updated frontend flags into full dict
60 aliases.update(frontend_aliases)
54 aliases.update(frontend_aliases)
55 aliases['colors'] = 'InteractiveShell.colors'
61
56
62 # get flags&aliases into sets, and remove a couple that
57 # get flags&aliases into sets, and remove a couple that
63 # shouldn't be scrubbed from backend flags:
58 # shouldn't be scrubbed from backend flags:
64 frontend_aliases = set(frontend_aliases.keys())
59 frontend_aliases = set(frontend_aliases.keys())
65 frontend_flags = set(frontend_flags.keys())
60 frontend_flags = set(frontend_flags.keys())
66
61
67
62
68 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
69 # Classes
64 # Classes
70 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
71
66
72
67
73 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
68 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
74 name = "ipython-console"
69 name = "ipython-console"
75 """Start a terminal frontend to the IPython zmq kernel."""
70 """Start a terminal frontend to the IPython zmq kernel."""
76
71
77 description = """
72 description = """
78 The IPython terminal-based Console.
73 The IPython terminal-based Console.
79
74
80 This launches a Console application inside a terminal.
75 This launches a Console application inside a terminal.
81
76
82 The Console supports various extra features beyond the traditional
77 The Console supports various extra features beyond the traditional
83 single-process Terminal IPython shell, such as connecting to an
78 single-process Terminal IPython shell, such as connecting to an
84 existing ipython session, via:
79 existing ipython session, via:
85
80
86 ipython console --existing
81 ipython console --existing
87
82
88 where the previous session could have been created by another ipython
83 where the previous session could have been created by another ipython
89 console, an ipython qtconsole, or by opening an ipython notebook.
84 console, an ipython qtconsole, or by opening an ipython notebook.
90
85
91 """
86 """
92 examples = _examples
87 examples = _examples
93
88
94 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
89 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
95 flags = Dict(flags)
90 flags = Dict(flags)
96 aliases = Dict(aliases)
91 aliases = Dict(aliases)
97 frontend_aliases = Any(frontend_aliases)
92 frontend_aliases = Any(frontend_aliases)
98 frontend_flags = Any(frontend_flags)
93 frontend_flags = Any(frontend_flags)
99
94
100 subcommands = Dict()
95 subcommands = Dict()
96
97 force_interact = True
101
98
102 def parse_command_line(self, argv=None):
99 def parse_command_line(self, argv=None):
103 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
100 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
104 self.build_kernel_argv(argv)
101 self.build_kernel_argv(self.extra_args)
105
102
106 def init_shell(self):
103 def init_shell(self):
107 IPythonConsoleApp.initialize(self)
104 IPythonConsoleApp.initialize(self)
108 # relay sigint to kernel
105 # relay sigint to kernel
109 signal.signal(signal.SIGINT, self.handle_sigint)
106 signal.signal(signal.SIGINT, self.handle_sigint)
110 self.shell = ZMQTerminalInteractiveShell.instance(parent=self,
107 self.shell = ZMQTerminalInteractiveShell.instance(parent=self,
111 display_banner=False, profile_dir=self.profile_dir,
108 display_banner=False, profile_dir=self.profile_dir,
112 ipython_dir=self.ipython_dir,
109 ipython_dir=self.ipython_dir,
113 manager=self.kernel_manager,
110 manager=self.kernel_manager,
114 client=self.kernel_client,
111 client=self.kernel_client,
115 )
112 )
116
113
117 def init_gui_pylab(self):
114 def init_gui_pylab(self):
118 # no-op, because we don't want to import matplotlib in the frontend.
115 # no-op, because we don't want to import matplotlib in the frontend.
119 pass
116 pass
120
117
121 def handle_sigint(self, *args):
118 def handle_sigint(self, *args):
122 if self.shell._executing:
119 if self.shell._executing:
123 if self.kernel_manager:
120 if self.kernel_manager:
124 # interrupt already gets passed to subprocess by signal handler.
121 # interrupt already gets passed to subprocess by signal handler.
125 # Only if we prevent that should we need to explicitly call
122 # Only if we prevent that should we need to explicitly call
126 # interrupt_kernel, until which time, this would result in a
123 # interrupt_kernel, until which time, this would result in a
127 # double-interrupt:
124 # double-interrupt:
128 # self.kernel_manager.interrupt_kernel()
125 # self.kernel_manager.interrupt_kernel()
129 pass
126 pass
130 else:
127 else:
131 self.shell.write_err('\n')
128 self.shell.write_err('\n')
132 error("Cannot interrupt kernels we didn't start.\n")
129 error("Cannot interrupt kernels we didn't start.\n")
133 else:
130 else:
134 # raise the KeyboardInterrupt if we aren't waiting for execution,
131 # raise the KeyboardInterrupt if we aren't waiting for execution,
135 # so that the interact loop advances, and prompt is redrawn, etc.
132 # so that the interact loop advances, and prompt is redrawn, etc.
136 raise KeyboardInterrupt
133 raise KeyboardInterrupt
137
134
138
135
139 def init_code(self):
136 def init_code(self):
140 # no-op in the frontend, code gets run in the backend
137 # no-op in the frontend, code gets run in the backend
141 pass
138 pass
142
139
143
140
144 launch_new_instance = ZMQTerminalIPythonApp.launch_instance
141 launch_new_instance = ZMQTerminalIPythonApp.launch_instance
145
142
146
143
147 if __name__ == '__main__':
144 if __name__ == '__main__':
148 launch_new_instance()
145 launch_new_instance()
149
146
@@ -1,386 +1,379 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
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # stdlib imports
15 import os
10 import os
16 import signal
11 import signal
17 import sys
12 import sys
18
13
19 # If run on Windows, install an exception hook which pops up a
14 # If run on Windows, install an exception hook which pops up a
20 # message box. Pythonw.exe hides the console, so without this
15 # message box. Pythonw.exe hides the console, so without this
21 # the application silently fails to load.
16 # the application silently fails to load.
22 #
17 #
23 # We always install this handler, because the expectation is for
18 # We always install this handler, because the expectation is for
24 # qtconsole to bring up a GUI even if called from the console.
19 # qtconsole to bring up a GUI even if called from the console.
25 # The old handler is called, so the exception is printed as well.
20 # The old handler is called, so the exception is printed as well.
26 # If desired, check for pythonw with an additional condition
21 # If desired, check for pythonw with an additional condition
27 # (sys.executable.lower().find('pythonw.exe') >= 0).
22 # (sys.executable.lower().find('pythonw.exe') >= 0).
28 if os.name == 'nt':
23 if os.name == 'nt':
29 old_excepthook = sys.excepthook
24 old_excepthook = sys.excepthook
30
25
31 # Exclude this from our autogenerated API docs.
26 # Exclude this from our autogenerated API docs.
32 undoc = lambda func: func
27 undoc = lambda func: func
33
28
34 @undoc
29 @undoc
35 def gui_excepthook(exctype, value, tb):
30 def gui_excepthook(exctype, value, tb):
36 try:
31 try:
37 import ctypes, traceback
32 import ctypes, traceback
38 MB_ICONERROR = 0x00000010
33 MB_ICONERROR = 0x00000010
39 title = u'Error starting IPython QtConsole'
34 title = u'Error starting IPython QtConsole'
40 msg = u''.join(traceback.format_exception(exctype, value, tb))
35 msg = u''.join(traceback.format_exception(exctype, value, tb))
41 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
36 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
42 finally:
37 finally:
43 # Also call the old exception hook to let it do
38 # Also call the old exception hook to let it do
44 # its thing too.
39 # its thing too.
45 old_excepthook(exctype, value, tb)
40 old_excepthook(exctype, value, tb)
46
41
47 sys.excepthook = gui_excepthook
42 sys.excepthook = gui_excepthook
48
43
49 # System library imports
50 from IPython.external.qt import QtCore, QtGui
44 from IPython.external.qt import QtCore, QtGui
51
45
52 # Local imports
53 from IPython.config.application import boolean_flag
46 from IPython.config.application import boolean_flag
54 from IPython.config.application import catch_config_error
47 from IPython.config.application import catch_config_error
55 from IPython.core.application import BaseIPythonApplication
48 from IPython.core.application import BaseIPythonApplication
56 from IPython.qt.console.ipython_widget import IPythonWidget
49 from IPython.qt.console.ipython_widget import IPythonWidget
57 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
50 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
58 from IPython.qt.console import styles
51 from IPython.qt.console import styles
59 from IPython.qt.console.mainwindow import MainWindow
52 from IPython.qt.console.mainwindow import MainWindow
60 from IPython.qt.client import QtKernelClient
53 from IPython.qt.client import QtKernelClient
61 from IPython.qt.manager import QtKernelManager
54 from IPython.qt.manager import QtKernelManager
62 from IPython.utils.traitlets import (
55 from IPython.utils.traitlets import (
63 Dict, Unicode, CBool, Any
56 Dict, Unicode, CBool, Any
64 )
57 )
65
58
66 from IPython.consoleapp import (
59 from IPython.consoleapp import (
67 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
60 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
68 )
61 )
69
62
70 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
71 # Network Constants
64 # Network Constants
72 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
73
66
74 from IPython.utils.localinterfaces import is_local_ip
67 from IPython.utils.localinterfaces import is_local_ip
75
68
76 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
77 # Globals
70 # Globals
78 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
79
72
80 _examples = """
73 _examples = """
81 ipython qtconsole # start the qtconsole
74 ipython qtconsole # start the qtconsole
82 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
75 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
83 """
76 """
84
77
85 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
86 # Aliases and Flags
79 # Aliases and Flags
87 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
88
81
89 # start with copy of flags
82 # start with copy of flags
90 flags = dict(flags)
83 flags = dict(flags)
91 qt_flags = {
84 qt_flags = {
92 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
85 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
93 "Disable rich text support."),
86 "Disable rich text support."),
94 }
87 }
95 qt_flags.update(boolean_flag(
88 qt_flags.update(boolean_flag(
96 'banner', 'IPythonQtConsoleApp.display_banner',
89 'banner', 'IPythonQtConsoleApp.display_banner',
97 "Display a banner upon starting the QtConsole.",
90 "Display a banner upon starting the QtConsole.",
98 "Don't display a banner upon starting the QtConsole."
91 "Don't display a banner upon starting the QtConsole."
99 ))
92 ))
100
93
101 # and app_flags from the Console Mixin
94 # and app_flags from the Console Mixin
102 qt_flags.update(app_flags)
95 qt_flags.update(app_flags)
103 # add frontend flags to the full set
96 # add frontend flags to the full set
104 flags.update(qt_flags)
97 flags.update(qt_flags)
105
98
106 # start with copy of front&backend aliases list
99 # start with copy of front&backend aliases list
107 aliases = dict(aliases)
100 aliases = dict(aliases)
108 qt_aliases = dict(
101 qt_aliases = dict(
109 style = 'IPythonWidget.syntax_style',
102 style = 'IPythonWidget.syntax_style',
110 stylesheet = 'IPythonQtConsoleApp.stylesheet',
103 stylesheet = 'IPythonQtConsoleApp.stylesheet',
111 colors = 'ZMQInteractiveShell.colors',
104 colors = 'ZMQInteractiveShell.colors',
112
105
113 editor = 'IPythonWidget.editor',
106 editor = 'IPythonWidget.editor',
114 paging = 'ConsoleWidget.paging',
107 paging = 'ConsoleWidget.paging',
115 )
108 )
116 # and app_aliases from the Console Mixin
109 # and app_aliases from the Console Mixin
117 qt_aliases.update(app_aliases)
110 qt_aliases.update(app_aliases)
118 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
111 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
119 # add frontend aliases to the full set
112 # add frontend aliases to the full set
120 aliases.update(qt_aliases)
113 aliases.update(qt_aliases)
121
114
122 # get flags&aliases into sets, and remove a couple that
115 # get flags&aliases into sets, and remove a couple that
123 # shouldn't be scrubbed from backend flags:
116 # shouldn't be scrubbed from backend flags:
124 qt_aliases = set(qt_aliases.keys())
117 qt_aliases = set(qt_aliases.keys())
125 qt_aliases.remove('colors')
118 qt_aliases.remove('colors')
126 qt_flags = set(qt_flags.keys())
119 qt_flags = set(qt_flags.keys())
127
120
128 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
129 # Classes
122 # Classes
130 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
131
124
132 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
133 # IPythonQtConsole
126 # IPythonQtConsole
134 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
135
128
136
129
137 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
130 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
138 name = 'ipython-qtconsole'
131 name = 'ipython-qtconsole'
139
132
140 description = """
133 description = """
141 The IPython QtConsole.
134 The IPython QtConsole.
142
135
143 This launches a Console-style application using Qt. It is not a full
136 This launches a Console-style application using Qt. It is not a full
144 console, in that launched terminal subprocesses will not be able to accept
137 console, in that launched terminal subprocesses will not be able to accept
145 input.
138 input.
146
139
147 The QtConsole supports various extra features beyond the Terminal IPython
140 The QtConsole supports various extra features beyond the Terminal IPython
148 shell, such as inline plotting with matplotlib, via:
141 shell, such as inline plotting with matplotlib, via:
149
142
150 ipython qtconsole --matplotlib=inline
143 ipython qtconsole --matplotlib=inline
151
144
152 as well as saving your session as HTML, and printing the output.
145 as well as saving your session as HTML, and printing the output.
153
146
154 """
147 """
155 examples = _examples
148 examples = _examples
156
149
157 classes = [IPythonWidget] + IPythonConsoleApp.classes
150 classes = [IPythonWidget] + IPythonConsoleApp.classes
158 flags = Dict(flags)
151 flags = Dict(flags)
159 aliases = Dict(aliases)
152 aliases = Dict(aliases)
160 frontend_flags = Any(qt_flags)
153 frontend_flags = Any(qt_flags)
161 frontend_aliases = Any(qt_aliases)
154 frontend_aliases = Any(qt_aliases)
162 kernel_client_class = QtKernelClient
155 kernel_client_class = QtKernelClient
163 kernel_manager_class = QtKernelManager
156 kernel_manager_class = QtKernelManager
164
157
165 stylesheet = Unicode('', config=True,
158 stylesheet = Unicode('', config=True,
166 help="path to a custom CSS stylesheet")
159 help="path to a custom CSS stylesheet")
167
160
168 hide_menubar = CBool(False, config=True,
161 hide_menubar = CBool(False, config=True,
169 help="Start the console window with the menu bar hidden.")
162 help="Start the console window with the menu bar hidden.")
170
163
171 maximize = CBool(False, config=True,
164 maximize = CBool(False, config=True,
172 help="Start the console window maximized.")
165 help="Start the console window maximized.")
173
166
174 plain = CBool(False, config=True,
167 plain = CBool(False, config=True,
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
168 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176
169
177 display_banner = CBool(True, config=True,
170 display_banner = CBool(True, config=True,
178 help="Whether to display a banner upon starting the QtConsole."
171 help="Whether to display a banner upon starting the QtConsole."
179 )
172 )
180
173
181 def _plain_changed(self, name, old, new):
174 def _plain_changed(self, name, old, new):
182 kind = 'plain' if new else 'rich'
175 kind = 'plain' if new else 'rich'
183 self.config.ConsoleWidget.kind = kind
176 self.config.ConsoleWidget.kind = kind
184 if new:
177 if new:
185 self.widget_factory = IPythonWidget
178 self.widget_factory = IPythonWidget
186 else:
179 else:
187 self.widget_factory = RichIPythonWidget
180 self.widget_factory = RichIPythonWidget
188
181
189 # the factory for creating a widget
182 # the factory for creating a widget
190 widget_factory = Any(RichIPythonWidget)
183 widget_factory = Any(RichIPythonWidget)
191
184
192 def parse_command_line(self, argv=None):
185 def parse_command_line(self, argv=None):
193 super(IPythonQtConsoleApp, self).parse_command_line(argv)
186 super(IPythonQtConsoleApp, self).parse_command_line(argv)
194 self.build_kernel_argv(argv)
187 self.build_kernel_argv(self.extra_args)
195
188
196
189
197 def new_frontend_master(self):
190 def new_frontend_master(self):
198 """ Create and return new frontend attached to new kernel, launched on localhost.
191 """ Create and return new frontend attached to new kernel, launched on localhost.
199 """
192 """
200 kernel_manager = self.kernel_manager_class(
193 kernel_manager = self.kernel_manager_class(
201 connection_file=self._new_connection_file(),
194 connection_file=self._new_connection_file(),
202 parent=self,
195 parent=self,
203 autorestart=True,
196 autorestart=True,
204 )
197 )
205 # start the kernel
198 # start the kernel
206 kwargs = {}
199 kwargs = {}
207 # FIXME: remove special treatment of IPython kernels
200 # FIXME: remove special treatment of IPython kernels
208 if self.kernel_manager.ipython_kernel:
201 if self.kernel_manager.ipython_kernel:
209 kwargs['extra_arguments'] = self.kernel_argv
202 kwargs['extra_arguments'] = self.kernel_argv
210 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.start_kernel(**kwargs)
211 kernel_manager.client_factory = self.kernel_client_class
204 kernel_manager.client_factory = self.kernel_client_class
212 kernel_client = kernel_manager.client()
205 kernel_client = kernel_manager.client()
213 kernel_client.start_channels(shell=True, iopub=True)
206 kernel_client.start_channels(shell=True, iopub=True)
214 widget = self.widget_factory(config=self.config,
207 widget = self.widget_factory(config=self.config,
215 local_kernel=True)
208 local_kernel=True)
216 self.init_colors(widget)
209 self.init_colors(widget)
217 widget.kernel_manager = kernel_manager
210 widget.kernel_manager = kernel_manager
218 widget.kernel_client = kernel_client
211 widget.kernel_client = kernel_client
219 widget._existing = False
212 widget._existing = False
220 widget._may_close = True
213 widget._may_close = True
221 widget._confirm_exit = self.confirm_exit
214 widget._confirm_exit = self.confirm_exit
222 widget._display_banner = self.display_banner
215 widget._display_banner = self.display_banner
223 return widget
216 return widget
224
217
225 def new_frontend_slave(self, current_widget):
218 def new_frontend_slave(self, current_widget):
226 """Create and return a new frontend attached to an existing kernel.
219 """Create and return a new frontend attached to an existing kernel.
227
220
228 Parameters
221 Parameters
229 ----------
222 ----------
230 current_widget : IPythonWidget
223 current_widget : IPythonWidget
231 The IPythonWidget whose kernel this frontend is to share
224 The IPythonWidget whose kernel this frontend is to share
232 """
225 """
233 kernel_client = self.kernel_client_class(
226 kernel_client = self.kernel_client_class(
234 connection_file=current_widget.kernel_client.connection_file,
227 connection_file=current_widget.kernel_client.connection_file,
235 config = self.config,
228 config = self.config,
236 )
229 )
237 kernel_client.load_connection_file()
230 kernel_client.load_connection_file()
238 kernel_client.start_channels()
231 kernel_client.start_channels()
239 widget = self.widget_factory(config=self.config,
232 widget = self.widget_factory(config=self.config,
240 local_kernel=False)
233 local_kernel=False)
241 self.init_colors(widget)
234 self.init_colors(widget)
242 widget._existing = True
235 widget._existing = True
243 widget._may_close = False
236 widget._may_close = False
244 widget._confirm_exit = False
237 widget._confirm_exit = False
245 widget._display_banner = self.display_banner
238 widget._display_banner = self.display_banner
246 widget.kernel_client = kernel_client
239 widget.kernel_client = kernel_client
247 widget.kernel_manager = current_widget.kernel_manager
240 widget.kernel_manager = current_widget.kernel_manager
248 return widget
241 return widget
249
242
250 def init_qt_app(self):
243 def init_qt_app(self):
251 # separate from qt_elements, because it must run first
244 # separate from qt_elements, because it must run first
252 self.app = QtGui.QApplication([])
245 self.app = QtGui.QApplication([])
253
246
254 def init_qt_elements(self):
247 def init_qt_elements(self):
255 # Create the widget.
248 # Create the widget.
256
249
257 base_path = os.path.abspath(os.path.dirname(__file__))
250 base_path = os.path.abspath(os.path.dirname(__file__))
258 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
251 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
259 self.app.icon = QtGui.QIcon(icon_path)
252 self.app.icon = QtGui.QIcon(icon_path)
260 QtGui.QApplication.setWindowIcon(self.app.icon)
253 QtGui.QApplication.setWindowIcon(self.app.icon)
261
254
262 ip = self.ip
255 ip = self.ip
263 local_kernel = (not self.existing) or is_local_ip(ip)
256 local_kernel = (not self.existing) or is_local_ip(ip)
264 self.widget = self.widget_factory(config=self.config,
257 self.widget = self.widget_factory(config=self.config,
265 local_kernel=local_kernel)
258 local_kernel=local_kernel)
266 self.init_colors(self.widget)
259 self.init_colors(self.widget)
267 self.widget._existing = self.existing
260 self.widget._existing = self.existing
268 self.widget._may_close = not self.existing
261 self.widget._may_close = not self.existing
269 self.widget._confirm_exit = self.confirm_exit
262 self.widget._confirm_exit = self.confirm_exit
270 self.widget._display_banner = self.display_banner
263 self.widget._display_banner = self.display_banner
271
264
272 self.widget.kernel_manager = self.kernel_manager
265 self.widget.kernel_manager = self.kernel_manager
273 self.widget.kernel_client = self.kernel_client
266 self.widget.kernel_client = self.kernel_client
274 self.window = MainWindow(self.app,
267 self.window = MainWindow(self.app,
275 confirm_exit=self.confirm_exit,
268 confirm_exit=self.confirm_exit,
276 new_frontend_factory=self.new_frontend_master,
269 new_frontend_factory=self.new_frontend_master,
277 slave_frontend_factory=self.new_frontend_slave,
270 slave_frontend_factory=self.new_frontend_slave,
278 )
271 )
279 self.window.log = self.log
272 self.window.log = self.log
280 self.window.add_tab_with_frontend(self.widget)
273 self.window.add_tab_with_frontend(self.widget)
281 self.window.init_magic_helper()
274 self.window.init_magic_helper()
282 self.window.init_menu_bar()
275 self.window.init_menu_bar()
283
276
284 # Ignore on OSX, where there is always a menu bar
277 # Ignore on OSX, where there is always a menu bar
285 if sys.platform != 'darwin' and self.hide_menubar:
278 if sys.platform != 'darwin' and self.hide_menubar:
286 self.window.menuBar().setVisible(False)
279 self.window.menuBar().setVisible(False)
287
280
288 self.window.setWindowTitle('IPython')
281 self.window.setWindowTitle('IPython')
289
282
290 def init_colors(self, widget):
283 def init_colors(self, widget):
291 """Configure the coloring of the widget"""
284 """Configure the coloring of the widget"""
292 # Note: This will be dramatically simplified when colors
285 # Note: This will be dramatically simplified when colors
293 # are removed from the backend.
286 # are removed from the backend.
294
287
295 # parse the colors arg down to current known labels
288 # parse the colors arg down to current known labels
296 cfg = self.config
289 cfg = self.config
297 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
290 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
298 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
291 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
299 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
292 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
300
293
301 # find the value for colors:
294 # find the value for colors:
302 if colors:
295 if colors:
303 colors=colors.lower()
296 colors=colors.lower()
304 if colors in ('lightbg', 'light'):
297 if colors in ('lightbg', 'light'):
305 colors='lightbg'
298 colors='lightbg'
306 elif colors in ('dark', 'linux'):
299 elif colors in ('dark', 'linux'):
307 colors='linux'
300 colors='linux'
308 else:
301 else:
309 colors='nocolor'
302 colors='nocolor'
310 elif style:
303 elif style:
311 if style=='bw':
304 if style=='bw':
312 colors='nocolor'
305 colors='nocolor'
313 elif styles.dark_style(style):
306 elif styles.dark_style(style):
314 colors='linux'
307 colors='linux'
315 else:
308 else:
316 colors='lightbg'
309 colors='lightbg'
317 else:
310 else:
318 colors=None
311 colors=None
319
312
320 # Configure the style
313 # Configure the style
321 if style:
314 if style:
322 widget.style_sheet = styles.sheet_from_template(style, colors)
315 widget.style_sheet = styles.sheet_from_template(style, colors)
323 widget.syntax_style = style
316 widget.syntax_style = style
324 widget._syntax_style_changed()
317 widget._syntax_style_changed()
325 widget._style_sheet_changed()
318 widget._style_sheet_changed()
326 elif colors:
319 elif colors:
327 # use a default dark/light/bw style
320 # use a default dark/light/bw style
328 widget.set_default_style(colors=colors)
321 widget.set_default_style(colors=colors)
329
322
330 if self.stylesheet:
323 if self.stylesheet:
331 # we got an explicit stylesheet
324 # we got an explicit stylesheet
332 if os.path.isfile(self.stylesheet):
325 if os.path.isfile(self.stylesheet):
333 with open(self.stylesheet) as f:
326 with open(self.stylesheet) as f:
334 sheet = f.read()
327 sheet = f.read()
335 else:
328 else:
336 raise IOError("Stylesheet %r not found." % self.stylesheet)
329 raise IOError("Stylesheet %r not found." % self.stylesheet)
337 if sheet:
330 if sheet:
338 widget.style_sheet = sheet
331 widget.style_sheet = sheet
339 widget._style_sheet_changed()
332 widget._style_sheet_changed()
340
333
341
334
342 def init_signal(self):
335 def init_signal(self):
343 """allow clean shutdown on sigint"""
336 """allow clean shutdown on sigint"""
344 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
337 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
345 # need a timer, so that QApplication doesn't block until a real
338 # need a timer, so that QApplication doesn't block until a real
346 # Qt event fires (can require mouse movement)
339 # Qt event fires (can require mouse movement)
347 # timer trick from http://stackoverflow.com/q/4938723/938949
340 # timer trick from http://stackoverflow.com/q/4938723/938949
348 timer = QtCore.QTimer()
341 timer = QtCore.QTimer()
349 # Let the interpreter run each 200 ms:
342 # Let the interpreter run each 200 ms:
350 timer.timeout.connect(lambda: None)
343 timer.timeout.connect(lambda: None)
351 timer.start(200)
344 timer.start(200)
352 # hold onto ref, so the timer doesn't get cleaned up
345 # hold onto ref, so the timer doesn't get cleaned up
353 self._sigint_timer = timer
346 self._sigint_timer = timer
354
347
355 @catch_config_error
348 @catch_config_error
356 def initialize(self, argv=None):
349 def initialize(self, argv=None):
357 self.init_qt_app()
350 self.init_qt_app()
358 super(IPythonQtConsoleApp, self).initialize(argv)
351 super(IPythonQtConsoleApp, self).initialize(argv)
359 IPythonConsoleApp.initialize(self,argv)
352 IPythonConsoleApp.initialize(self,argv)
360 self.init_qt_elements()
353 self.init_qt_elements()
361 self.init_signal()
354 self.init_signal()
362
355
363 def start(self):
356 def start(self):
364
357
365 # draw the window
358 # draw the window
366 if self.maximize:
359 if self.maximize:
367 self.window.showMaximized()
360 self.window.showMaximized()
368 else:
361 else:
369 self.window.show()
362 self.window.show()
370 self.window.raise_()
363 self.window.raise_()
371
364
372 # Start the application main loop.
365 # Start the application main loop.
373 self.app.exec_()
366 self.app.exec_()
374
367
375 #-----------------------------------------------------------------------------
368 #-----------------------------------------------------------------------------
376 # Main entry point
369 # Main entry point
377 #-----------------------------------------------------------------------------
370 #-----------------------------------------------------------------------------
378
371
379 def main():
372 def main():
380 app = IPythonQtConsoleApp()
373 app = IPythonQtConsoleApp()
381 app.initialize()
374 app.initialize()
382 app.start()
375 app.start()
383
376
384
377
385 if __name__ == '__main__':
378 if __name__ == '__main__':
386 main()
379 main()
General Comments 0
You need to be logged in to leave comments. Login now