##// END OF EJS Templates
properly hook up tornado loggers
MinRK -
Show More
@@ -1,757 +1,760 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import select
24 import select
25 import signal
25 import signal
26 import socket
26 import socket
27 import sys
27 import sys
28 import threading
28 import threading
29 import time
29 import time
30 import webbrowser
30 import webbrowser
31
31
32
32
33 # Third party
33 # Third party
34 # check for pyzmq 2.1.11
34 # check for pyzmq 2.1.11
35 from IPython.utils.zmqrelated import check_for_zmq
35 from IPython.utils.zmqrelated import check_for_zmq
36 check_for_zmq('2.1.11', 'IPython.html')
36 check_for_zmq('2.1.11', 'IPython.html')
37
37
38 from jinja2 import Environment, FileSystemLoader
38 from jinja2 import Environment, FileSystemLoader
39
39
40 # Install the pyzmq ioloop. This has to be done before anything else from
40 # Install the pyzmq ioloop. This has to be done before anything else from
41 # tornado is imported.
41 # tornado is imported.
42 from zmq.eventloop import ioloop
42 from zmq.eventloop import ioloop
43 ioloop.install()
43 ioloop.install()
44
44
45 # check for tornado 2.1.0
45 # check for tornado 2.1.0
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
47 try:
47 try:
48 import tornado
48 import tornado
49 except ImportError:
49 except ImportError:
50 raise ImportError(msg)
50 raise ImportError(msg)
51 try:
51 try:
52 version_info = tornado.version_info
52 version_info = tornado.version_info
53 except AttributeError:
53 except AttributeError:
54 raise ImportError(msg + ", but you have < 1.1.0")
54 raise ImportError(msg + ", but you have < 1.1.0")
55 if version_info < (2,1,0):
55 if version_info < (2,1,0):
56 raise ImportError(msg + ", but you have %s" % tornado.version)
56 raise ImportError(msg + ", but you have %s" % tornado.version)
57
57
58 from tornado import httpserver
58 from tornado import httpserver
59 from tornado import web
59 from tornado import web
60
60
61 # Our own libraries
61 # Our own libraries
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
63
63
64 from .services.kernels.kernelmanager import MappingKernelManager
64 from .services.kernels.kernelmanager import MappingKernelManager
65 from .services.notebooks.nbmanager import NotebookManager
65 from .services.notebooks.nbmanager import NotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
67 from .services.clusters.clustermanager import ClusterManager
67 from .services.clusters.clustermanager import ClusterManager
68 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 entry_path = Unicode('')
298 entry_path = 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 f = os.path.abspath(self.extra_args[0])
504 f = os.path.abspath(self.extra_args[0])
505 if os.path.isdir(f):
505 if os.path.isdir(f):
506 self.entry_path = self.extra_args[0]
506 self.entry_path = self.extra_args[0]
507 elif os.path.isfile(f):
507 elif os.path.isfile(f):
508 self.file_to_run = f
508 self.file_to_run = f
509 path = os.path.split(self.extra_args[0])
509 path = os.path.split(self.extra_args[0])
510 if path[0] != '':
510 if path[0] != '':
511 self.entry_path = path[0]+'/'
511 self.entry_path = path[0]+'/'
512
512
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 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
544 logger = logging.getLogger('tornado.%s' % name)
545 logger.propagate = False
546 logger.setLevel(self.log.level)
547 logger.handlers = self.log.handlers
545
548
546 def init_webapp(self):
549 def init_webapp(self):
547 """initialize tornado webapp and httpserver"""
550 """initialize tornado webapp and httpserver"""
548 self.web_app = NotebookWebApplication(
551 self.web_app = NotebookWebApplication(
549 self, self.kernel_manager, self.notebook_manager,
552 self, self.kernel_manager, self.notebook_manager,
550 self.cluster_manager, self.session_manager,
553 self.cluster_manager, self.session_manager,
551 self.log, self.base_project_url, self.webapp_settings
554 self.log, self.base_project_url, self.webapp_settings
552 )
555 )
553 if self.certfile:
556 if self.certfile:
554 ssl_options = dict(certfile=self.certfile)
557 ssl_options = dict(certfile=self.certfile)
555 if self.keyfile:
558 if self.keyfile:
556 ssl_options['keyfile'] = self.keyfile
559 ssl_options['keyfile'] = self.keyfile
557 else:
560 else:
558 ssl_options = None
561 ssl_options = None
559 self.web_app.password = self.password
562 self.web_app.password = self.password
560 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,
561 xheaders=self.trust_xheaders)
564 xheaders=self.trust_xheaders)
562 if not self.ip:
565 if not self.ip:
563 warning = "WARNING: The notebook server is listening on all IP addresses"
566 warning = "WARNING: The notebook server is listening on all IP addresses"
564 if ssl_options is None:
567 if ssl_options is None:
565 self.log.critical(warning + " and not using encryption. This "
568 self.log.critical(warning + " and not using encryption. This "
566 "is not recommended.")
569 "is not recommended.")
567 if not self.password:
570 if not self.password:
568 self.log.critical(warning + " and not using authentication. "
571 self.log.critical(warning + " and not using authentication. "
569 "This is highly insecure and not recommended.")
572 "This is highly insecure and not recommended.")
570 success = None
573 success = None
571 for port in random_ports(self.port, self.port_retries+1):
574 for port in random_ports(self.port, self.port_retries+1):
572 try:
575 try:
573 self.http_server.listen(port, self.ip)
576 self.http_server.listen(port, self.ip)
574 except socket.error as e:
577 except socket.error as e:
575 # XXX: remove the e.errno == -9 block when we require
578 # XXX: remove the e.errno == -9 block when we require
576 # tornado >= 3.0
579 # tornado >= 3.0
577 if e.errno == -9 and tornado.version_info[0] < 3:
580 if e.errno == -9 and tornado.version_info[0] < 3:
578 # The flags passed to socket.getaddrinfo from
581 # The flags passed to socket.getaddrinfo from
579 # tornado.netutils.bind_sockets can cause "gaierror:
582 # tornado.netutils.bind_sockets can cause "gaierror:
580 # [Errno -9] Address family for hostname not supported"
583 # [Errno -9] Address family for hostname not supported"
581 # when the interface is not associated, for example.
584 # when the interface is not associated, for example.
582 # Changing the flags to exclude socket.AI_ADDRCONFIG does
585 # Changing the flags to exclude socket.AI_ADDRCONFIG does
583 # not cause this error, but the only way to do this is to
586 # not cause this error, but the only way to do this is to
584 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
587 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
585 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
588 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
586 self.log.warn('Monkeypatching socket to fix tornado bug')
589 self.log.warn('Monkeypatching socket to fix tornado bug')
587 del(socket.AI_ADDRCONFIG)
590 del(socket.AI_ADDRCONFIG)
588 try:
591 try:
589 # retry the tornado call without AI_ADDRCONFIG flags
592 # retry the tornado call without AI_ADDRCONFIG flags
590 self.http_server.listen(port, self.ip)
593 self.http_server.listen(port, self.ip)
591 except socket.error as e2:
594 except socket.error as e2:
592 e = e2
595 e = e2
593 else:
596 else:
594 self.port = port
597 self.port = port
595 success = True
598 success = True
596 break
599 break
597 # restore the monekypatch
600 # restore the monekypatch
598 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
601 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
599 if e.errno == errno.EADDRINUSE:
602 if e.errno == errno.EADDRINUSE:
600 self.log.info('The port %i is already in use, trying another random port.' % port)
603 self.log.info('The port %i is already in use, trying another random port.' % port)
601 continue
604 continue
602 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
605 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
603 self.log.warn("Permission to listen on port %i denied" % port)
606 self.log.warn("Permission to listen on port %i denied" % port)
604 continue
607 continue
605 else:
608 else:
606 raise
609 raise
607 else:
610 else:
608 self.port = port
611 self.port = port
609 success = True
612 success = True
610 break
613 break
611 if not success:
614 if not success:
612 self.log.critical('ERROR: the notebook server could not be started because '
615 self.log.critical('ERROR: the notebook server could not be started because '
613 'no available port could be found.')
616 'no available port could be found.')
614 self.exit(1)
617 self.exit(1)
615
618
616 def init_signal(self):
619 def init_signal(self):
617 if not sys.platform.startswith('win'):
620 if not sys.platform.startswith('win'):
618 signal.signal(signal.SIGINT, self._handle_sigint)
621 signal.signal(signal.SIGINT, self._handle_sigint)
619 signal.signal(signal.SIGTERM, self._signal_stop)
622 signal.signal(signal.SIGTERM, self._signal_stop)
620 if hasattr(signal, 'SIGUSR1'):
623 if hasattr(signal, 'SIGUSR1'):
621 # Windows doesn't support SIGUSR1
624 # Windows doesn't support SIGUSR1
622 signal.signal(signal.SIGUSR1, self._signal_info)
625 signal.signal(signal.SIGUSR1, self._signal_info)
623 if hasattr(signal, 'SIGINFO'):
626 if hasattr(signal, 'SIGINFO'):
624 # only on BSD-based systems
627 # only on BSD-based systems
625 signal.signal(signal.SIGINFO, self._signal_info)
628 signal.signal(signal.SIGINFO, self._signal_info)
626
629
627 def _handle_sigint(self, sig, frame):
630 def _handle_sigint(self, sig, frame):
628 """SIGINT handler spawns confirmation dialog"""
631 """SIGINT handler spawns confirmation dialog"""
629 # register more forceful signal handler for ^C^C case
632 # register more forceful signal handler for ^C^C case
630 signal.signal(signal.SIGINT, self._signal_stop)
633 signal.signal(signal.SIGINT, self._signal_stop)
631 # request confirmation dialog in bg thread, to avoid
634 # request confirmation dialog in bg thread, to avoid
632 # blocking the App
635 # blocking the App
633 thread = threading.Thread(target=self._confirm_exit)
636 thread = threading.Thread(target=self._confirm_exit)
634 thread.daemon = True
637 thread.daemon = True
635 thread.start()
638 thread.start()
636
639
637 def _restore_sigint_handler(self):
640 def _restore_sigint_handler(self):
638 """callback for restoring original SIGINT handler"""
641 """callback for restoring original SIGINT handler"""
639 signal.signal(signal.SIGINT, self._handle_sigint)
642 signal.signal(signal.SIGINT, self._handle_sigint)
640
643
641 def _confirm_exit(self):
644 def _confirm_exit(self):
642 """confirm shutdown on ^C
645 """confirm shutdown on ^C
643
646
644 A second ^C, or answering 'y' within 5s will cause shutdown,
647 A second ^C, or answering 'y' within 5s will cause shutdown,
645 otherwise original SIGINT handler will be restored.
648 otherwise original SIGINT handler will be restored.
646
649
647 This doesn't work on Windows.
650 This doesn't work on Windows.
648 """
651 """
649 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
652 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
650 time.sleep(0.1)
653 time.sleep(0.1)
651 info = self.log.info
654 info = self.log.info
652 info('interrupted')
655 info('interrupted')
653 print self.notebook_info()
656 print self.notebook_info()
654 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
657 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
655 sys.stdout.flush()
658 sys.stdout.flush()
656 r,w,x = select.select([sys.stdin], [], [], 5)
659 r,w,x = select.select([sys.stdin], [], [], 5)
657 if r:
660 if r:
658 line = sys.stdin.readline()
661 line = sys.stdin.readline()
659 if line.lower().startswith('y'):
662 if line.lower().startswith('y'):
660 self.log.critical("Shutdown confirmed")
663 self.log.critical("Shutdown confirmed")
661 ioloop.IOLoop.instance().stop()
664 ioloop.IOLoop.instance().stop()
662 return
665 return
663 else:
666 else:
664 print "No answer for 5s:",
667 print "No answer for 5s:",
665 print "resuming operation..."
668 print "resuming operation..."
666 # no answer, or answer is no:
669 # no answer, or answer is no:
667 # set it back to original SIGINT handler
670 # set it back to original SIGINT handler
668 # use IOLoop.add_callback because signal.signal must be called
671 # use IOLoop.add_callback because signal.signal must be called
669 # from main thread
672 # from main thread
670 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
673 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
671
674
672 def _signal_stop(self, sig, frame):
675 def _signal_stop(self, sig, frame):
673 self.log.critical("received signal %s, stopping", sig)
676 self.log.critical("received signal %s, stopping", sig)
674 ioloop.IOLoop.instance().stop()
677 ioloop.IOLoop.instance().stop()
675
678
676 def _signal_info(self, sig, frame):
679 def _signal_info(self, sig, frame):
677 print self.notebook_info()
680 print self.notebook_info()
678
681
679 def init_components(self):
682 def init_components(self):
680 """Check the components submodule, and warn if it's unclean"""
683 """Check the components submodule, and warn if it's unclean"""
681 status = submodule.check_submodule_status()
684 status = submodule.check_submodule_status()
682 if status == 'missing':
685 if status == 'missing':
683 self.log.warn("components submodule missing, running `git submodule update`")
686 self.log.warn("components submodule missing, running `git submodule update`")
684 submodule.update_submodules(submodule.ipython_parent())
687 submodule.update_submodules(submodule.ipython_parent())
685 elif status == 'unclean':
688 elif status == 'unclean':
686 self.log.warn("components submodule unclean, you may see 404s on static/components")
689 self.log.warn("components submodule unclean, you may see 404s on static/components")
687 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
690 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
688
691
689
692
690 @catch_config_error
693 @catch_config_error
691 def initialize(self, argv=None):
694 def initialize(self, argv=None):
692 self.init_logging()
695 self.init_logging()
693 super(NotebookApp, self).initialize(argv)
696 super(NotebookApp, self).initialize(argv)
694 self.init_kernel_argv()
697 self.init_kernel_argv()
695 self.init_configurables()
698 self.init_configurables()
696 self.init_components()
699 self.init_components()
697 self.init_webapp()
700 self.init_webapp()
698 self.init_signal()
701 self.init_signal()
699
702
700 def cleanup_kernels(self):
703 def cleanup_kernels(self):
701 """Shutdown all kernels.
704 """Shutdown all kernels.
702
705
703 The kernels will shutdown themselves when this process no longer exists,
706 The kernels will shutdown themselves when this process no longer exists,
704 but explicit shutdown allows the KernelManagers to cleanup the connection files.
707 but explicit shutdown allows the KernelManagers to cleanup the connection files.
705 """
708 """
706 self.log.info('Shutting down kernels')
709 self.log.info('Shutting down kernels')
707 self.kernel_manager.shutdown_all()
710 self.kernel_manager.shutdown_all()
708
711
709 def notebook_info(self):
712 def notebook_info(self):
710 "Return the current working directory and the server url information"
713 "Return the current working directory and the server url information"
711 mgr_info = self.notebook_manager.info_string() + "\n"
714 mgr_info = self.notebook_manager.info_string() + "\n"
712 return mgr_info +"The IPython Notebook is running at: %s" % self._url
715 return mgr_info +"The IPython Notebook is running at: %s" % self._url
713
716
714 def start(self):
717 def start(self):
715 """ Start the IPython Notebook server app, after initialization
718 """ Start the IPython Notebook server app, after initialization
716
719
717 This method takes no arguments so all configuration and initialization
720 This method takes no arguments so all configuration and initialization
718 must be done prior to calling this method."""
721 must be done prior to calling this method."""
719 ip = self.ip if self.ip else '[all ip addresses on your system]'
722 ip = self.ip if self.ip else '[all ip addresses on your system]'
720 proto = 'https' if self.certfile else 'http'
723 proto = 'https' if self.certfile else 'http'
721 info = self.log.info
724 info = self.log.info
722 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
725 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
723 self.base_project_url)
726 self.base_project_url)
724 for line in self.notebook_info().split("\n"):
727 for line in self.notebook_info().split("\n"):
725 info(line)
728 info(line)
726 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
729 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
727
730
728 if self.open_browser or self.file_to_run:
731 if self.open_browser or self.file_to_run:
729 ip = self.ip or localhost()
732 ip = self.ip or localhost()
730 try:
733 try:
731 browser = webbrowser.get(self.browser or None)
734 browser = webbrowser.get(self.browser or None)
732 except webbrowser.Error as e:
735 except webbrowser.Error as e:
733 self.log.warn('No web browser found: %s.' % e)
736 self.log.warn('No web browser found: %s.' % e)
734 browser = None
737 browser = None
735
738
736 if self.file_to_run:
739 if self.file_to_run:
737 url = url_path_join('notebooks', self.entry_path, self.file_to_run)
740 url = url_path_join('notebooks', self.entry_path, self.file_to_run)
738 else:
741 else:
739 url = url_path_join('tree', self.entry_path)
742 url = url_path_join('tree', self.entry_path)
740 if browser:
743 if browser:
741 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
744 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
742 self.port, self.base_project_url, url), new=2)
745 self.port, self.base_project_url, url), new=2)
743 threading.Thread(target=b).start()
746 threading.Thread(target=b).start()
744 try:
747 try:
745 ioloop.IOLoop.instance().start()
748 ioloop.IOLoop.instance().start()
746 except KeyboardInterrupt:
749 except KeyboardInterrupt:
747 info("Interrupted...")
750 info("Interrupted...")
748 finally:
751 finally:
749 self.cleanup_kernels()
752 self.cleanup_kernels()
750
753
751
754
752 #-----------------------------------------------------------------------------
755 #-----------------------------------------------------------------------------
753 # Main entry point
756 # Main entry point
754 #-----------------------------------------------------------------------------
757 #-----------------------------------------------------------------------------
755
758
756 launch_new_instance = NotebookApp.launch_instance
759 launch_new_instance = NotebookApp.launch_instance
757
760
General Comments 0
You need to be logged in to leave comments. Login now