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