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