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