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