##// END OF EJS Templates
manual rebase notebookapp.py
Zachary Sailer -
Show More
@@ -1,752 +1,756
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import select
24 import select
25 import signal
25 import signal
26 import socket
26 import socket
27 import sys
27 import sys
28 import threading
28 import threading
29 import time
29 import time
30 import webbrowser
30 import webbrowser
31
31
32
32
33 # Third party
33 # Third party
34 # check for pyzmq 2.1.11
34 # check for pyzmq 2.1.11
35 from IPython.utils.zmqrelated import check_for_zmq
35 from IPython.utils.zmqrelated import check_for_zmq
36 check_for_zmq('2.1.11', 'IPython.html')
36 check_for_zmq('2.1.11', 'IPython.html')
37
37
38 from jinja2 import Environment, FileSystemLoader
38 from jinja2 import Environment, FileSystemLoader
39
39
40 # Install the pyzmq ioloop. This has to be done before anything else from
40 # Install the pyzmq ioloop. This has to be done before anything else from
41 # tornado is imported.
41 # tornado is imported.
42 from zmq.eventloop import ioloop
42 from zmq.eventloop import ioloop
43 ioloop.install()
43 ioloop.install()
44
44
45 # check for tornado 2.1.0
45 # check for tornado 2.1.0
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
47 try:
47 try:
48 import tornado
48 import tornado
49 except ImportError:
49 except ImportError:
50 raise ImportError(msg)
50 raise ImportError(msg)
51 try:
51 try:
52 version_info = tornado.version_info
52 version_info = tornado.version_info
53 except AttributeError:
53 except AttributeError:
54 raise ImportError(msg + ", but you have < 1.1.0")
54 raise ImportError(msg + ", but you have < 1.1.0")
55 if version_info < (2,1,0):
55 if version_info < (2,1,0):
56 raise ImportError(msg + ", but you have %s" % tornado.version)
56 raise ImportError(msg + ", but you have %s" % tornado.version)
57
57
58 from tornado import httpserver
58 from tornado import httpserver
59 from tornado import web
59 from tornado import web
60
60
61 # Our own libraries
61 # Our own libraries
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
63
63
64 from .services.kernels.kernelmanager import MappingKernelManager
64 from .services.kernels.kernelmanager import MappingKernelManager
65 from .services.notebooks.nbmanager import NotebookManager
65 from .services.notebooks.nbmanager import NotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
67 from .services.clusters.clustermanager import ClusterManager
67 from .services.clusters.clustermanager import ClusterManager
68 from .services.sessions.sessionmanager import SessionManager
68
69
69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
70 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
70
71
71 from IPython.config.application import catch_config_error, boolean_flag
72 from IPython.config.application import catch_config_error, boolean_flag
72 from IPython.core.application import BaseIPythonApplication
73 from IPython.core.application import BaseIPythonApplication
73 from IPython.consoleapp import IPythonConsoleApp
74 from IPython.consoleapp import IPythonConsoleApp
74 from IPython.kernel import swallow_argv
75 from IPython.kernel import swallow_argv
75 from IPython.kernel.zmq.session import default_secure
76 from IPython.kernel.zmq.session import default_secure
76 from IPython.kernel.zmq.kernelapp import (
77 from IPython.kernel.zmq.kernelapp import (
77 kernel_flags,
78 kernel_flags,
78 kernel_aliases,
79 kernel_aliases,
79 )
80 )
80 from IPython.utils.importstring import import_item
81 from IPython.utils.importstring import import_item
81 from IPython.utils.localinterfaces import localhost
82 from IPython.utils.localinterfaces import localhost
82 from IPython.utils import submodule
83 from IPython.utils import submodule
83 from IPython.utils.traitlets import (
84 from IPython.utils.traitlets import (
84 Dict, Unicode, Integer, List, Bool, Bytes,
85 Dict, Unicode, Integer, List, Bool, Bytes,
85 DottedObjectName
86 DottedObjectName
86 )
87 )
87 from IPython.utils import py3compat
88 from IPython.utils import py3compat
88 from IPython.utils.path import filefind, get_ipython_dir
89 from IPython.utils.path import filefind, get_ipython_dir
89
90
90 from .utils import url_path_join
91 from .utils import url_path_join
91
92
92 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
93 # Module globals
94 # Module globals
94 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
95
96
96 _examples = """
97 _examples = """
97 ipython notebook # start the notebook
98 ipython notebook # start the notebook
98 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 """
101 """
101
102
102 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
103 # Helper functions
104 # Helper functions
104 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
105
106
106 def random_ports(port, n):
107 def random_ports(port, n):
107 """Generate a list of n random ports near the given port.
108 """Generate a list of n random ports near the given port.
108
109
109 The first 5 ports will be sequential, and the remaining n-5 will be
110 The first 5 ports will be sequential, and the remaining n-5 will be
110 randomly selected in the range [port-2*n, port+2*n].
111 randomly selected in the range [port-2*n, port+2*n].
111 """
112 """
112 for i in range(min(5, n)):
113 for i in range(min(5, n)):
113 yield port + i
114 yield port + i
114 for i in range(n-5):
115 for i in range(n-5):
115 yield max(1, port + random.randint(-2*n, 2*n))
116 yield max(1, port + random.randint(-2*n, 2*n))
116
117
117 def load_handlers(name):
118 def load_handlers(name):
118 """Load the (URL pattern, handler) tuples for each component."""
119 """Load the (URL pattern, handler) tuples for each component."""
119 name = 'IPython.html.' + name
120 name = 'IPython.html.' + name
120 mod = __import__(name, fromlist=['default_handlers'])
121 mod = __import__(name, fromlist=['default_handlers'])
121 return mod.default_handlers
122 return mod.default_handlers
122
123
123 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
124 # The Tornado web application
125 # The Tornado web application
125 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
126
127
127 class NotebookWebApplication(web.Application):
128 class NotebookWebApplication(web.Application):
128
129
129 def __init__(self, ipython_app, kernel_manager, notebook_manager,
130 def __init__(self, ipython_app, kernel_manager, notebook_manager,
130 cluster_manager, log,
131 cluster_manager, session_manager, log,
131 base_project_url, settings_overrides):
132 base_project_url, settings_overrides):
132
133
133 settings = self.init_settings(
134 settings = self.init_settings(
134 ipython_app, kernel_manager, notebook_manager, cluster_manager,
135 ipython_app, kernel_manager, notebook_manager, cluster_manager,
135 log, base_project_url, settings_overrides)
136 session_manager, log, base_project_url, settings_overrides)
136 handlers = self.init_handlers(settings)
137 handlers = self.init_handlers(settings)
137
138
138 super(NotebookWebApplication, self).__init__(handlers, **settings)
139 super(NotebookWebApplication, self).__init__(handlers, **settings)
139
140
140 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
141 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
141 cluster_manager, log,
142 cluster_manager, session_manager, log,
142 base_project_url, settings_overrides):
143 base_project_url, settings_overrides):
143 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
144 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
144 # base_project_url will always be unicode, which will in turn
145 # base_project_url will always be unicode, which will in turn
145 # make the patterns unicode, and ultimately result in unicode
146 # make the patterns unicode, and ultimately result in unicode
146 # keys in kwargs to handler._execute(**kwargs) in tornado.
147 # keys in kwargs to handler._execute(**kwargs) in tornado.
147 # This enforces that base_project_url be ascii in that situation.
148 # This enforces that base_project_url be ascii in that situation.
148 #
149 #
149 # Note that the URLs these patterns check against are escaped,
150 # Note that the URLs these patterns check against are escaped,
150 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
151 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
151 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
152 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
152 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
153 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
153 settings = dict(
154 settings = dict(
154 # basics
155 # basics
155 base_project_url=base_project_url,
156 base_project_url=base_project_url,
156 base_kernel_url=ipython_app.base_kernel_url,
157 base_kernel_url=ipython_app.base_kernel_url,
157 template_path=template_path,
158 template_path=template_path,
158 static_path=ipython_app.static_file_path,
159 static_path=ipython_app.static_file_path,
159 static_handler_class = FileFindHandler,
160 static_handler_class = FileFindHandler,
160 static_url_prefix = url_path_join(base_project_url,'/static/'),
161 static_url_prefix = url_path_join(base_project_url,'/static/'),
161
162
162 # authentication
163 # authentication
163 cookie_secret=ipython_app.cookie_secret,
164 cookie_secret=ipython_app.cookie_secret,
164 login_url=url_path_join(base_project_url,'/login'),
165 login_url=url_path_join(base_project_url,'/login'),
165 password=ipython_app.password,
166 password=ipython_app.password,
166
167
167 # managers
168 # managers
168 kernel_manager=kernel_manager,
169 kernel_manager=kernel_manager,
169 notebook_manager=notebook_manager,
170 notebook_manager=notebook_manager,
170 cluster_manager=cluster_manager,
171 cluster_manager=cluster_manager,
172 session_manager=session_manager,
171
173
172 # IPython stuff
174 # IPython stuff
173 nbextensions_path = ipython_app.nbextensions_path,
175 nbextensions_path = ipython_app.nbextensions_path,
174 mathjax_url=ipython_app.mathjax_url,
176 mathjax_url=ipython_app.mathjax_url,
175 config=ipython_app.config,
177 config=ipython_app.config,
176 use_less=ipython_app.use_less,
178 use_less=ipython_app.use_less,
177 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
179 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
178 )
180 )
179
181
180 # allow custom overrides for the tornado web app.
182 # allow custom overrides for the tornado web app.
181 settings.update(settings_overrides)
183 settings.update(settings_overrides)
182 return settings
184 return settings
183
185
184 def init_handlers(self, settings):
186 def init_handlers(self, settings):
185 # Load the (URL pattern, handler) tuples for each component.
187 # Load the (URL pattern, handler) tuples for each component.
186 handlers = []
188 handlers = []
187 handlers.extend(load_handlers('base.handlers'))
189 handlers.extend(load_handlers('base.handlers'))
188 handlers.extend(load_handlers('tree.handlers'))
190 handlers.extend(load_handlers('tree.handlers'))
189 handlers.extend(load_handlers('auth.login'))
191 handlers.extend(load_handlers('auth.login'))
190 handlers.extend(load_handlers('auth.logout'))
192 handlers.extend(load_handlers('auth.logout'))
191 handlers.extend(load_handlers('notebook.handlers'))
193 handlers.extend(load_handlers('notebook.handlers'))
192 handlers.extend(load_handlers('services.kernels.handlers'))
194 handlers.extend(load_handlers('services.kernels.handlers'))
193 handlers.extend(load_handlers('services.notebooks.handlers'))
195 handlers.extend(load_handlers('services.notebooks.handlers'))
194 handlers.extend(load_handlers('services.clusters.handlers'))
196 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend(load_handlers('services.sessions.handlers'))
195 handlers.extend([
198 handlers.extend([
196 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
199 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
197 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
200 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
198 ])
201 ])
199 # prepend base_project_url onto the patterns that we match
202 # prepend base_project_url onto the patterns that we match
200 new_handlers = []
203 new_handlers = []
201 for handler in handlers:
204 for handler in handlers:
202 pattern = url_path_join(settings['base_project_url'], handler[0])
205 pattern = url_path_join(settings['base_project_url'], handler[0])
203 new_handler = tuple([pattern] + list(handler[1:]))
206 new_handler = tuple([pattern] + list(handler[1:]))
204 new_handlers.append(new_handler)
207 new_handlers.append(new_handler)
205 return new_handlers
208 return new_handlers
206
209
207
210
208
211
209 #-----------------------------------------------------------------------------
212 #-----------------------------------------------------------------------------
210 # Aliases and Flags
213 # Aliases and Flags
211 #-----------------------------------------------------------------------------
214 #-----------------------------------------------------------------------------
212
215
213 flags = dict(kernel_flags)
216 flags = dict(kernel_flags)
214 flags['no-browser']=(
217 flags['no-browser']=(
215 {'NotebookApp' : {'open_browser' : False}},
218 {'NotebookApp' : {'open_browser' : False}},
216 "Don't open the notebook in a browser after startup."
219 "Don't open the notebook in a browser after startup."
217 )
220 )
218 flags['no-mathjax']=(
221 flags['no-mathjax']=(
219 {'NotebookApp' : {'enable_mathjax' : False}},
222 {'NotebookApp' : {'enable_mathjax' : False}},
220 """Disable MathJax
223 """Disable MathJax
221
224
222 MathJax is the javascript library IPython uses to render math/LaTeX. It is
225 MathJax is the javascript library IPython uses to render math/LaTeX. It is
223 very large, so you may want to disable it if you have a slow internet
226 very large, so you may want to disable it if you have a slow internet
224 connection, or for offline use of the notebook.
227 connection, or for offline use of the notebook.
225
228
226 When disabled, equations etc. will appear as their untransformed TeX source.
229 When disabled, equations etc. will appear as their untransformed TeX source.
227 """
230 """
228 )
231 )
229
232
230 # Add notebook manager flags
233 # Add notebook manager flags
231 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
234 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
232 'Auto-save a .py script everytime the .ipynb notebook is saved',
235 'Auto-save a .py script everytime the .ipynb notebook is saved',
233 'Do not auto-save .py scripts for every notebook'))
236 'Do not auto-save .py scripts for every notebook'))
234
237
235 # the flags that are specific to the frontend
238 # the flags that are specific to the frontend
236 # these must be scrubbed before being passed to the kernel,
239 # these must be scrubbed before being passed to the kernel,
237 # or it will raise an error on unrecognized flags
240 # or it will raise an error on unrecognized flags
238 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
241 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
239
242
240 aliases = dict(kernel_aliases)
243 aliases = dict(kernel_aliases)
241
244
242 aliases.update({
245 aliases.update({
243 'ip': 'NotebookApp.ip',
246 'ip': 'NotebookApp.ip',
244 'port': 'NotebookApp.port',
247 'port': 'NotebookApp.port',
245 'port-retries': 'NotebookApp.port_retries',
248 'port-retries': 'NotebookApp.port_retries',
246 'transport': 'KernelManager.transport',
249 'transport': 'KernelManager.transport',
247 'keyfile': 'NotebookApp.keyfile',
250 'keyfile': 'NotebookApp.keyfile',
248 'certfile': 'NotebookApp.certfile',
251 'certfile': 'NotebookApp.certfile',
249 'notebook-dir': 'NotebookManager.notebook_dir',
252 'notebook-dir': 'NotebookManager.notebook_dir',
250 'browser': 'NotebookApp.browser',
253 'browser': 'NotebookApp.browser',
251 })
254 })
252
255
253 # remove ipkernel flags that are singletons, and don't make sense in
256 # remove ipkernel flags that are singletons, and don't make sense in
254 # multi-kernel evironment:
257 # multi-kernel evironment:
255 aliases.pop('f', None)
258 aliases.pop('f', None)
256
259
257 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
260 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
258 u'notebook-dir', u'profile', u'profile-dir']
261 u'notebook-dir', u'profile', u'profile-dir']
259
262
260 #-----------------------------------------------------------------------------
263 #-----------------------------------------------------------------------------
261 # NotebookApp
264 # NotebookApp
262 #-----------------------------------------------------------------------------
265 #-----------------------------------------------------------------------------
263
266
264 class NotebookApp(BaseIPythonApplication):
267 class NotebookApp(BaseIPythonApplication):
265
268
266 name = 'ipython-notebook'
269 name = 'ipython-notebook'
267
270
268 description = """
271 description = """
269 The IPython HTML Notebook.
272 The IPython HTML Notebook.
270
273
271 This launches a Tornado based HTML Notebook Server that serves up an
274 This launches a Tornado based HTML Notebook Server that serves up an
272 HTML5/Javascript Notebook client.
275 HTML5/Javascript Notebook client.
273 """
276 """
274 examples = _examples
277 examples = _examples
275
278
276 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
279 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
277 FileNotebookManager]
280 FileNotebookManager]
278 flags = Dict(flags)
281 flags = Dict(flags)
279 aliases = Dict(aliases)
282 aliases = Dict(aliases)
280
283
281 kernel_argv = List(Unicode)
284 kernel_argv = List(Unicode)
282
285
283 def _log_level_default(self):
286 def _log_level_default(self):
284 return logging.INFO
287 return logging.INFO
285
288
286 def _log_format_default(self):
289 def _log_format_default(self):
287 """override default log format to include time"""
290 """override default log format to include time"""
288 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
291 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
289
292
290 # create requested profiles by default, if they don't exist:
293 # create requested profiles by default, if they don't exist:
291 auto_create = Bool(True)
294 auto_create = Bool(True)
292
295
293 # file to be opened in the notebook server
296 # file to be opened in the notebook server
294 file_to_run = Unicode('')
297 file_to_run = Unicode('')
295
298
296 # Network related information.
299 # Network related information.
297
300
298 ip = Unicode(config=True,
301 ip = Unicode(config=True,
299 help="The IP address the notebook server will listen on."
302 help="The IP address the notebook server will listen on."
300 )
303 )
301 def _ip_default(self):
304 def _ip_default(self):
302 return localhost()
305 return localhost()
303
306
304 def _ip_changed(self, name, old, new):
307 def _ip_changed(self, name, old, new):
305 if new == u'*': self.ip = u''
308 if new == u'*': self.ip = u''
306
309
307 port = Integer(8888, config=True,
310 port = Integer(8888, config=True,
308 help="The port the notebook server will listen on."
311 help="The port the notebook server will listen on."
309 )
312 )
310 port_retries = Integer(50, config=True,
313 port_retries = Integer(50, config=True,
311 help="The number of additional ports to try if the specified port is not available."
314 help="The number of additional ports to try if the specified port is not available."
312 )
315 )
313
316
314 certfile = Unicode(u'', config=True,
317 certfile = Unicode(u'', config=True,
315 help="""The full path to an SSL/TLS certificate file."""
318 help="""The full path to an SSL/TLS certificate file."""
316 )
319 )
317
320
318 keyfile = Unicode(u'', config=True,
321 keyfile = Unicode(u'', config=True,
319 help="""The full path to a private key file for usage with SSL/TLS."""
322 help="""The full path to a private key file for usage with SSL/TLS."""
320 )
323 )
321
324
322 cookie_secret = Bytes(b'', config=True,
325 cookie_secret = Bytes(b'', config=True,
323 help="""The random bytes used to secure cookies.
326 help="""The random bytes used to secure cookies.
324 By default this is a new random number every time you start the Notebook.
327 By default this is a new random number every time you start the Notebook.
325 Set it to a value in a config file to enable logins to persist across server sessions.
328 Set it to a value in a config file to enable logins to persist across server sessions.
326
329
327 Note: Cookie secrets should be kept private, do not share config files with
330 Note: Cookie secrets should be kept private, do not share config files with
328 cookie_secret stored in plaintext (you can read the value from a file).
331 cookie_secret stored in plaintext (you can read the value from a file).
329 """
332 """
330 )
333 )
331 def _cookie_secret_default(self):
334 def _cookie_secret_default(self):
332 return os.urandom(1024)
335 return os.urandom(1024)
333
336
334 password = Unicode(u'', config=True,
337 password = Unicode(u'', config=True,
335 help="""Hashed password to use for web authentication.
338 help="""Hashed password to use for web authentication.
336
339
337 To generate, type in a python/IPython shell:
340 To generate, type in a python/IPython shell:
338
341
339 from IPython.lib import passwd; passwd()
342 from IPython.lib import passwd; passwd()
340
343
341 The string should be of the form type:salt:hashed-password.
344 The string should be of the form type:salt:hashed-password.
342 """
345 """
343 )
346 )
344
347
345 open_browser = Bool(True, config=True,
348 open_browser = Bool(True, config=True,
346 help="""Whether to open in a browser after starting.
349 help="""Whether to open in a browser after starting.
347 The specific browser used is platform dependent and
350 The specific browser used is platform dependent and
348 determined by the python standard library `webbrowser`
351 determined by the python standard library `webbrowser`
349 module, unless it is overridden using the --browser
352 module, unless it is overridden using the --browser
350 (NotebookApp.browser) configuration option.
353 (NotebookApp.browser) configuration option.
351 """)
354 """)
352
355
353 browser = Unicode(u'', config=True,
356 browser = Unicode(u'', config=True,
354 help="""Specify what command to use to invoke a web
357 help="""Specify what command to use to invoke a web
355 browser when opening the notebook. If not specified, the
358 browser when opening the notebook. If not specified, the
356 default browser will be determined by the `webbrowser`
359 default browser will be determined by the `webbrowser`
357 standard library module, which allows setting of the
360 standard library module, which allows setting of the
358 BROWSER environment variable to override it.
361 BROWSER environment variable to override it.
359 """)
362 """)
360
363
361 use_less = Bool(False, config=True,
364 use_less = Bool(False, config=True,
362 help="""Wether to use Browser Side less-css parsing
365 help="""Wether to use Browser Side less-css parsing
363 instead of compiled css version in templates that allows
366 instead of compiled css version in templates that allows
364 it. This is mainly convenient when working on the less
367 it. This is mainly convenient when working on the less
365 file to avoid a build step, or if user want to overwrite
368 file to avoid a build step, or if user want to overwrite
366 some of the less variables without having to recompile
369 some of the less variables without having to recompile
367 everything.
370 everything.
368
371
369 You will need to install the less.js component in the static directory
372 You will need to install the less.js component in the static directory
370 either in the source tree or in your profile folder.
373 either in the source tree or in your profile folder.
371 """)
374 """)
372
375
373 webapp_settings = Dict(config=True,
376 webapp_settings = Dict(config=True,
374 help="Supply overrides for the tornado.web.Application that the "
377 help="Supply overrides for the tornado.web.Application that the "
375 "IPython notebook uses.")
378 "IPython notebook uses.")
376
379
377 enable_mathjax = Bool(True, config=True,
380 enable_mathjax = Bool(True, config=True,
378 help="""Whether to enable MathJax for typesetting math/TeX
381 help="""Whether to enable MathJax for typesetting math/TeX
379
382
380 MathJax is the javascript library IPython uses to render math/LaTeX. It is
383 MathJax is the javascript library IPython uses to render math/LaTeX. It is
381 very large, so you may want to disable it if you have a slow internet
384 very large, so you may want to disable it if you have a slow internet
382 connection, or for offline use of the notebook.
385 connection, or for offline use of the notebook.
383
386
384 When disabled, equations etc. will appear as their untransformed TeX source.
387 When disabled, equations etc. will appear as their untransformed TeX source.
385 """
388 """
386 )
389 )
387 def _enable_mathjax_changed(self, name, old, new):
390 def _enable_mathjax_changed(self, name, old, new):
388 """set mathjax url to empty if mathjax is disabled"""
391 """set mathjax url to empty if mathjax is disabled"""
389 if not new:
392 if not new:
390 self.mathjax_url = u''
393 self.mathjax_url = u''
391
394
392 base_project_url = Unicode('/', config=True,
395 base_project_url = Unicode('/', config=True,
393 help='''The base URL for the notebook server.
396 help='''The base URL for the notebook server.
394
397
395 Leading and trailing slashes can be omitted,
398 Leading and trailing slashes can be omitted,
396 and will automatically be added.
399 and will automatically be added.
397 ''')
400 ''')
398 def _base_project_url_changed(self, name, old, new):
401 def _base_project_url_changed(self, name, old, new):
399 if not new.startswith('/'):
402 if not new.startswith('/'):
400 self.base_project_url = '/'+new
403 self.base_project_url = '/'+new
401 elif not new.endswith('/'):
404 elif not new.endswith('/'):
402 self.base_project_url = new+'/'
405 self.base_project_url = new+'/'
403
406
404 base_kernel_url = Unicode('/', config=True,
407 base_kernel_url = Unicode('/', config=True,
405 help='''The base URL for the kernel server
408 help='''The base URL for the kernel server
406
409
407 Leading and trailing slashes can be omitted,
410 Leading and trailing slashes can be omitted,
408 and will automatically be added.
411 and will automatically be added.
409 ''')
412 ''')
410 def _base_kernel_url_changed(self, name, old, new):
413 def _base_kernel_url_changed(self, name, old, new):
411 if not new.startswith('/'):
414 if not new.startswith('/'):
412 self.base_kernel_url = '/'+new
415 self.base_kernel_url = '/'+new
413 elif not new.endswith('/'):
416 elif not new.endswith('/'):
414 self.base_kernel_url = new+'/'
417 self.base_kernel_url = new+'/'
415
418
416 websocket_url = Unicode("", config=True,
419 websocket_url = Unicode("", config=True,
417 help="""The base URL for the websocket server,
420 help="""The base URL for the websocket server,
418 if it differs from the HTTP server (hint: it almost certainly doesn't).
421 if it differs from the HTTP server (hint: it almost certainly doesn't).
419
422
420 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
423 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
421 """
424 """
422 )
425 )
423
426
424 extra_static_paths = List(Unicode, config=True,
427 extra_static_paths = List(Unicode, config=True,
425 help="""Extra paths to search for serving static files.
428 help="""Extra paths to search for serving static files.
426
429
427 This allows adding javascript/css to be available from the notebook server machine,
430 This allows adding javascript/css to be available from the notebook server machine,
428 or overriding individual files in the IPython"""
431 or overriding individual files in the IPython"""
429 )
432 )
430 def _extra_static_paths_default(self):
433 def _extra_static_paths_default(self):
431 return [os.path.join(self.profile_dir.location, 'static')]
434 return [os.path.join(self.profile_dir.location, 'static')]
432
435
433 @property
436 @property
434 def static_file_path(self):
437 def static_file_path(self):
435 """return extra paths + the default location"""
438 """return extra paths + the default location"""
436 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
439 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
437
440
438 nbextensions_path = List(Unicode, config=True,
441 nbextensions_path = List(Unicode, config=True,
439 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
442 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
440 )
443 )
441 def _nbextensions_path_default(self):
444 def _nbextensions_path_default(self):
442 return [os.path.join(get_ipython_dir(), 'nbextensions')]
445 return [os.path.join(get_ipython_dir(), 'nbextensions')]
443
446
444 mathjax_url = Unicode("", config=True,
447 mathjax_url = Unicode("", config=True,
445 help="""The url for MathJax.js."""
448 help="""The url for MathJax.js."""
446 )
449 )
447 def _mathjax_url_default(self):
450 def _mathjax_url_default(self):
448 if not self.enable_mathjax:
451 if not self.enable_mathjax:
449 return u''
452 return u''
450 static_url_prefix = self.webapp_settings.get("static_url_prefix",
453 static_url_prefix = self.webapp_settings.get("static_url_prefix",
451 url_path_join(self.base_project_url, "static")
454 url_path_join(self.base_project_url, "static")
452 )
455 )
453
456
454 # try local mathjax, either in nbextensions/mathjax or static/mathjax
457 # try local mathjax, either in nbextensions/mathjax or static/mathjax
455 for (url_prefix, search_path) in [
458 for (url_prefix, search_path) in [
456 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
459 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
457 (static_url_prefix, self.static_file_path),
460 (static_url_prefix, self.static_file_path),
458 ]:
461 ]:
459 self.log.debug("searching for local mathjax in %s", search_path)
462 self.log.debug("searching for local mathjax in %s", search_path)
460 try:
463 try:
461 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
464 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
462 except IOError:
465 except IOError:
463 continue
466 continue
464 else:
467 else:
465 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
468 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
466 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
469 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
467 return url
470 return url
468
471
469 # no local mathjax, serve from CDN
472 # no local mathjax, serve from CDN
470 if self.certfile:
473 if self.certfile:
471 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
472 host = u"https://c328740.ssl.cf1.rackcdn.com"
475 host = u"https://c328740.ssl.cf1.rackcdn.com"
473 else:
476 else:
474 host = u"http://cdn.mathjax.org"
477 host = u"http://cdn.mathjax.org"
475
478
476 url = host + u"/mathjax/latest/MathJax.js"
479 url = host + u"/mathjax/latest/MathJax.js"
477 self.log.info("Using MathJax from CDN: %s", url)
480 self.log.info("Using MathJax from CDN: %s", url)
478 return url
481 return url
479
482
480 def _mathjax_url_changed(self, name, old, new):
483 def _mathjax_url_changed(self, name, old, new):
481 if new and not self.enable_mathjax:
484 if new and not self.enable_mathjax:
482 # enable_mathjax=False overrides mathjax_url
485 # enable_mathjax=False overrides mathjax_url
483 self.mathjax_url = u''
486 self.mathjax_url = u''
484 else:
487 else:
485 self.log.info("Using MathJax: %s", new)
488 self.log.info("Using MathJax: %s", new)
486
489
487 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
490 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
488 config=True,
491 config=True,
489 help='The notebook manager class to use.')
492 help='The notebook manager class to use.')
490
493
491 trust_xheaders = Bool(False, config=True,
494 trust_xheaders = Bool(False, config=True,
492 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
495 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
493 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
496 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
494 )
497 )
495
498
496 def parse_command_line(self, argv=None):
499 def parse_command_line(self, argv=None):
497 super(NotebookApp, self).parse_command_line(argv)
500 super(NotebookApp, self).parse_command_line(argv)
498
501
499 if self.extra_args:
502 if self.extra_args:
500 f = os.path.abspath(self.extra_args[0])
503 f = os.path.abspath(self.extra_args[0])
501 if os.path.isdir(f):
504 if os.path.isdir(f):
502 nbdir = f
505 nbdir = f
503 else:
506 else:
504 self.file_to_run = f
507 self.file_to_run = f
505 nbdir = os.path.dirname(f)
508 nbdir = os.path.dirname(f)
506 self.config.NotebookManager.notebook_dir = nbdir
509 self.config.NotebookManager.notebook_dir = nbdir
507
510
508 def init_kernel_argv(self):
511 def init_kernel_argv(self):
509 """construct the kernel arguments"""
512 """construct the kernel arguments"""
510 # Scrub frontend-specific flags
513 # Scrub frontend-specific flags
511 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
514 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
512 # Kernel should inherit default config file from frontend
515 # Kernel should inherit default config file from frontend
513 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
516 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
514 # Kernel should get *absolute* path to profile directory
517 # Kernel should get *absolute* path to profile directory
515 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
518 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
516
519
517 def init_configurables(self):
520 def init_configurables(self):
518 # force Session default to be secure
521 # force Session default to be secure
519 default_secure(self.config)
522 default_secure(self.config)
520 self.kernel_manager = MappingKernelManager(
523 self.kernel_manager = MappingKernelManager(
521 parent=self, log=self.log, kernel_argv=self.kernel_argv,
524 parent=self, log=self.log, kernel_argv=self.kernel_argv,
522 connection_dir = self.profile_dir.security_dir,
525 connection_dir = self.profile_dir.security_dir,
523 )
526 )
524 kls = import_item(self.notebook_manager_class)
527 kls = import_item(self.notebook_manager_class)
525 self.notebook_manager = kls(parent=self, log=self.log)
528 self.notebook_manager = kls(parent=self, log=self.log)
526 self.notebook_manager.load_notebook_names()
529 self.notebook_manager.load_notebook_names('')
530 self.session_manager = SessionManager(parent=self, log=self.log)
527 self.cluster_manager = ClusterManager(parent=self, log=self.log)
531 self.cluster_manager = ClusterManager(parent=self, log=self.log)
528 self.cluster_manager.update_profiles()
532 self.cluster_manager.update_profiles()
529
533
530 def init_logging(self):
534 def init_logging(self):
531 # This prevents double log messages because tornado use a root logger that
535 # This prevents double log messages because tornado use a root logger that
532 # self.log is a child of. The logging module dipatches log messages to a log
536 # self.log is a child of. The logging module dipatches log messages to a log
533 # and all of its ancenstors until propagate is set to False.
537 # and all of its ancenstors until propagate is set to False.
534 self.log.propagate = False
538 self.log.propagate = False
535
539
536 # hook up tornado 3's loggers to our app handlers
540 # hook up tornado 3's loggers to our app handlers
537 for name in ('access', 'application', 'general'):
541 for name in ('access', 'application', 'general'):
538 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
542 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
539
543
540 def init_webapp(self):
544 def init_webapp(self):
541 """initialize tornado webapp and httpserver"""
545 """initialize tornado webapp and httpserver"""
542 self.web_app = NotebookWebApplication(
546 self.web_app = NotebookWebApplication(
543 self, self.kernel_manager, self.notebook_manager,
547 self, self.kernel_manager, self.notebook_manager,
544 self.cluster_manager, self.log,
548 self.cluster_manager, self.session_manager, self.log,
545 self.base_project_url, self.webapp_settings,
549 self.base_project_url, self.webapp_settings
546 )
550 )
547 if self.certfile:
551 if self.certfile:
548 ssl_options = dict(certfile=self.certfile)
552 ssl_options = dict(certfile=self.certfile)
549 if self.keyfile:
553 if self.keyfile:
550 ssl_options['keyfile'] = self.keyfile
554 ssl_options['keyfile'] = self.keyfile
551 else:
555 else:
552 ssl_options = None
556 ssl_options = None
553 self.web_app.password = self.password
557 self.web_app.password = self.password
554 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
558 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
555 xheaders=self.trust_xheaders)
559 xheaders=self.trust_xheaders)
556 if not self.ip:
560 if not self.ip:
557 warning = "WARNING: The notebook server is listening on all IP addresses"
561 warning = "WARNING: The notebook server is listening on all IP addresses"
558 if ssl_options is None:
562 if ssl_options is None:
559 self.log.critical(warning + " and not using encryption. This "
563 self.log.critical(warning + " and not using encryption. This "
560 "is not recommended.")
564 "is not recommended.")
561 if not self.password:
565 if not self.password:
562 self.log.critical(warning + " and not using authentication. "
566 self.log.critical(warning + " and not using authentication. "
563 "This is highly insecure and not recommended.")
567 "This is highly insecure and not recommended.")
564 success = None
568 success = None
565 for port in random_ports(self.port, self.port_retries+1):
569 for port in random_ports(self.port, self.port_retries+1):
566 try:
570 try:
567 self.http_server.listen(port, self.ip)
571 self.http_server.listen(port, self.ip)
568 except socket.error as e:
572 except socket.error as e:
569 # XXX: remove the e.errno == -9 block when we require
573 # XXX: remove the e.errno == -9 block when we require
570 # tornado >= 3.0
574 # tornado >= 3.0
571 if e.errno == -9 and tornado.version_info[0] < 3:
575 if e.errno == -9 and tornado.version_info[0] < 3:
572 # The flags passed to socket.getaddrinfo from
576 # The flags passed to socket.getaddrinfo from
573 # tornado.netutils.bind_sockets can cause "gaierror:
577 # tornado.netutils.bind_sockets can cause "gaierror:
574 # [Errno -9] Address family for hostname not supported"
578 # [Errno -9] Address family for hostname not supported"
575 # when the interface is not associated, for example.
579 # when the interface is not associated, for example.
576 # Changing the flags to exclude socket.AI_ADDRCONFIG does
580 # Changing the flags to exclude socket.AI_ADDRCONFIG does
577 # not cause this error, but the only way to do this is to
581 # not cause this error, but the only way to do this is to
578 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
582 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
579 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
583 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
580 self.log.warn('Monkeypatching socket to fix tornado bug')
584 self.log.warn('Monkeypatching socket to fix tornado bug')
581 del(socket.AI_ADDRCONFIG)
585 del(socket.AI_ADDRCONFIG)
582 try:
586 try:
583 # retry the tornado call without AI_ADDRCONFIG flags
587 # retry the tornado call without AI_ADDRCONFIG flags
584 self.http_server.listen(port, self.ip)
588 self.http_server.listen(port, self.ip)
585 except socket.error as e2:
589 except socket.error as e2:
586 e = e2
590 e = e2
587 else:
591 else:
588 self.port = port
592 self.port = port
589 success = True
593 success = True
590 break
594 break
591 # restore the monekypatch
595 # restore the monekypatch
592 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
596 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
593 if e.errno == errno.EADDRINUSE:
597 if e.errno == errno.EADDRINUSE:
594 self.log.info('The port %i is already in use, trying another random port.' % port)
598 self.log.info('The port %i is already in use, trying another random port.' % port)
595 continue
599 continue
596 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
600 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
597 self.log.warn("Permission to listen on port %i denied" % port)
601 self.log.warn("Permission to listen on port %i denied" % port)
598 continue
602 continue
599 else:
603 else:
600 raise
604 raise
601 else:
605 else:
602 self.port = port
606 self.port = port
603 success = True
607 success = True
604 break
608 break
605 if not success:
609 if not success:
606 self.log.critical('ERROR: the notebook server could not be started because '
610 self.log.critical('ERROR: the notebook server could not be started because '
607 'no available port could be found.')
611 'no available port could be found.')
608 self.exit(1)
612 self.exit(1)
609
613
610 def init_signal(self):
614 def init_signal(self):
611 if not sys.platform.startswith('win'):
615 if not sys.platform.startswith('win'):
612 signal.signal(signal.SIGINT, self._handle_sigint)
616 signal.signal(signal.SIGINT, self._handle_sigint)
613 signal.signal(signal.SIGTERM, self._signal_stop)
617 signal.signal(signal.SIGTERM, self._signal_stop)
614 if hasattr(signal, 'SIGUSR1'):
618 if hasattr(signal, 'SIGUSR1'):
615 # Windows doesn't support SIGUSR1
619 # Windows doesn't support SIGUSR1
616 signal.signal(signal.SIGUSR1, self._signal_info)
620 signal.signal(signal.SIGUSR1, self._signal_info)
617 if hasattr(signal, 'SIGINFO'):
621 if hasattr(signal, 'SIGINFO'):
618 # only on BSD-based systems
622 # only on BSD-based systems
619 signal.signal(signal.SIGINFO, self._signal_info)
623 signal.signal(signal.SIGINFO, self._signal_info)
620
624
621 def _handle_sigint(self, sig, frame):
625 def _handle_sigint(self, sig, frame):
622 """SIGINT handler spawns confirmation dialog"""
626 """SIGINT handler spawns confirmation dialog"""
623 # register more forceful signal handler for ^C^C case
627 # register more forceful signal handler for ^C^C case
624 signal.signal(signal.SIGINT, self._signal_stop)
628 signal.signal(signal.SIGINT, self._signal_stop)
625 # request confirmation dialog in bg thread, to avoid
629 # request confirmation dialog in bg thread, to avoid
626 # blocking the App
630 # blocking the App
627 thread = threading.Thread(target=self._confirm_exit)
631 thread = threading.Thread(target=self._confirm_exit)
628 thread.daemon = True
632 thread.daemon = True
629 thread.start()
633 thread.start()
630
634
631 def _restore_sigint_handler(self):
635 def _restore_sigint_handler(self):
632 """callback for restoring original SIGINT handler"""
636 """callback for restoring original SIGINT handler"""
633 signal.signal(signal.SIGINT, self._handle_sigint)
637 signal.signal(signal.SIGINT, self._handle_sigint)
634
638
635 def _confirm_exit(self):
639 def _confirm_exit(self):
636 """confirm shutdown on ^C
640 """confirm shutdown on ^C
637
641
638 A second ^C, or answering 'y' within 5s will cause shutdown,
642 A second ^C, or answering 'y' within 5s will cause shutdown,
639 otherwise original SIGINT handler will be restored.
643 otherwise original SIGINT handler will be restored.
640
644
641 This doesn't work on Windows.
645 This doesn't work on Windows.
642 """
646 """
643 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
647 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
644 time.sleep(0.1)
648 time.sleep(0.1)
645 info = self.log.info
649 info = self.log.info
646 info('interrupted')
650 info('interrupted')
647 print self.notebook_info()
651 print self.notebook_info()
648 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
652 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
649 sys.stdout.flush()
653 sys.stdout.flush()
650 r,w,x = select.select([sys.stdin], [], [], 5)
654 r,w,x = select.select([sys.stdin], [], [], 5)
651 if r:
655 if r:
652 line = sys.stdin.readline()
656 line = sys.stdin.readline()
653 if line.lower().startswith('y'):
657 if line.lower().startswith('y'):
654 self.log.critical("Shutdown confirmed")
658 self.log.critical("Shutdown confirmed")
655 ioloop.IOLoop.instance().stop()
659 ioloop.IOLoop.instance().stop()
656 return
660 return
657 else:
661 else:
658 print "No answer for 5s:",
662 print "No answer for 5s:",
659 print "resuming operation..."
663 print "resuming operation..."
660 # no answer, or answer is no:
664 # no answer, or answer is no:
661 # set it back to original SIGINT handler
665 # set it back to original SIGINT handler
662 # use IOLoop.add_callback because signal.signal must be called
666 # use IOLoop.add_callback because signal.signal must be called
663 # from main thread
667 # from main thread
664 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
668 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
665
669
666 def _signal_stop(self, sig, frame):
670 def _signal_stop(self, sig, frame):
667 self.log.critical("received signal %s, stopping", sig)
671 self.log.critical("received signal %s, stopping", sig)
668 ioloop.IOLoop.instance().stop()
672 ioloop.IOLoop.instance().stop()
669
673
670 def _signal_info(self, sig, frame):
674 def _signal_info(self, sig, frame):
671 print self.notebook_info()
675 print self.notebook_info()
672
676
673 def init_components(self):
677 def init_components(self):
674 """Check the components submodule, and warn if it's unclean"""
678 """Check the components submodule, and warn if it's unclean"""
675 status = submodule.check_submodule_status()
679 status = submodule.check_submodule_status()
676 if status == 'missing':
680 if status == 'missing':
677 self.log.warn("components submodule missing, running `git submodule update`")
681 self.log.warn("components submodule missing, running `git submodule update`")
678 submodule.update_submodules(submodule.ipython_parent())
682 submodule.update_submodules(submodule.ipython_parent())
679 elif status == 'unclean':
683 elif status == 'unclean':
680 self.log.warn("components submodule unclean, you may see 404s on static/components")
684 self.log.warn("components submodule unclean, you may see 404s on static/components")
681 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
685 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
682
686
683
687
684 @catch_config_error
688 @catch_config_error
685 def initialize(self, argv=None):
689 def initialize(self, argv=None):
686 self.init_logging()
690 self.init_logging()
687 super(NotebookApp, self).initialize(argv)
691 super(NotebookApp, self).initialize(argv)
688 self.init_kernel_argv()
692 self.init_kernel_argv()
689 self.init_configurables()
693 self.init_configurables()
690 self.init_components()
694 self.init_components()
691 self.init_webapp()
695 self.init_webapp()
692 self.init_signal()
696 self.init_signal()
693
697
694 def cleanup_kernels(self):
698 def cleanup_kernels(self):
695 """Shutdown all kernels.
699 """Shutdown all kernels.
696
700
697 The kernels will shutdown themselves when this process no longer exists,
701 The kernels will shutdown themselves when this process no longer exists,
698 but explicit shutdown allows the KernelManagers to cleanup the connection files.
702 but explicit shutdown allows the KernelManagers to cleanup the connection files.
699 """
703 """
700 self.log.info('Shutting down kernels')
704 self.log.info('Shutting down kernels')
701 self.kernel_manager.shutdown_all()
705 self.kernel_manager.shutdown_all()
702
706
703 def notebook_info(self):
707 def notebook_info(self):
704 "Return the current working directory and the server url information"
708 "Return the current working directory and the server url information"
705 mgr_info = self.notebook_manager.info_string() + "\n"
709 mgr_info = self.notebook_manager.info_string() + "\n"
706 return mgr_info +"The IPython Notebook is running at: %s" % self._url
710 return mgr_info +"The IPython Notebook is running at: %s" % self._url
707
711
708 def start(self):
712 def start(self):
709 """ Start the IPython Notebook server app, after initialization
713 """ Start the IPython Notebook server app, after initialization
710
714
711 This method takes no arguments so all configuration and initialization
715 This method takes no arguments so all configuration and initialization
712 must be done prior to calling this method."""
716 must be done prior to calling this method."""
713 ip = self.ip if self.ip else '[all ip addresses on your system]'
717 ip = self.ip if self.ip else '[all ip addresses on your system]'
714 proto = 'https' if self.certfile else 'http'
718 proto = 'https' if self.certfile else 'http'
715 info = self.log.info
719 info = self.log.info
716 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
720 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
717 self.base_project_url)
721 self.base_project_url)
718 for line in self.notebook_info().split("\n"):
722 for line in self.notebook_info().split("\n"):
719 info(line)
723 info(line)
720 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
724 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
721
725
722 if self.open_browser or self.file_to_run:
726 if self.open_browser or self.file_to_run:
723 ip = self.ip or localhost()
727 ip = self.ip or localhost()
724 try:
728 try:
725 browser = webbrowser.get(self.browser or None)
729 browser = webbrowser.get(self.browser or None)
726 except webbrowser.Error as e:
730 except webbrowser.Error as e:
727 self.log.warn('No web browser found: %s.' % e)
731 self.log.warn('No web browser found: %s.' % e)
728 browser = None
732 browser = None
729
733
730 if self.file_to_run:
734 if self.file_to_run:
731 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
735 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
732 url = self.notebook_manager.rev_mapping.get(name, '')
736 url = self.notebook_manager.rev_mapping.get(name, '')
733 else:
737 else:
734 url = ''
738 url = ''
735 if browser:
739 if browser:
736 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
740 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
737 self.port, self.base_project_url, url), new=2)
741 self.port, self.base_project_url, url), new=2)
738 threading.Thread(target=b).start()
742 threading.Thread(target=b).start()
739 try:
743 try:
740 ioloop.IOLoop.instance().start()
744 ioloop.IOLoop.instance().start()
741 except KeyboardInterrupt:
745 except KeyboardInterrupt:
742 info("Interrupted...")
746 info("Interrupted...")
743 finally:
747 finally:
744 self.cleanup_kernels()
748 self.cleanup_kernels()
745
749
746
750
747 #-----------------------------------------------------------------------------
751 #-----------------------------------------------------------------------------
748 # Main entry point
752 # Main entry point
749 #-----------------------------------------------------------------------------
753 #-----------------------------------------------------------------------------
750
754
751 launch_new_instance = NotebookApp.launch_instance
755 launch_new_instance = NotebookApp.launch_instance
752
756
General Comments 0
You need to be logged in to leave comments. Login now