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