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