##// END OF EJS Templates
don't relay notebook dir to kernel from command-line
MinRK -
Show More
@@ -1,769 +1,771 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
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 f = os.path.abspath(self.extra_args[0])
503 arg0 = self.extra_args[0]
504 f = os.path.abspath(arg0)
505 self.argv.remove(arg0)
504 if not os.path.exists(f):
506 if not os.path.exists(f):
505 self.log.critical("No such file or directory: %s", f)
507 self.log.critical("No such file or directory: %s", f)
506 self.exit(1)
508 self.exit(1)
507 if os.path.isdir(f):
509 if os.path.isdir(f):
508 self.config.FileNotebookManager.notebook_dir = f
510 self.config.FileNotebookManager.notebook_dir = f
509 elif os.path.isfile(f):
511 elif os.path.isfile(f):
510 self.file_to_run = f
512 self.file_to_run = f
511
513
512 def init_kernel_argv(self):
514 def init_kernel_argv(self):
513 """construct the kernel arguments"""
515 """construct the kernel arguments"""
514 # Scrub frontend-specific flags
516 # Scrub frontend-specific flags
515 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
517 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
516 # Kernel should inherit default config file from frontend
518 # Kernel should inherit default config file from frontend
517 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
519 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
518 # Kernel should get *absolute* path to profile directory
520 # Kernel should get *absolute* path to profile directory
519 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
521 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
520
522
521 def init_configurables(self):
523 def init_configurables(self):
522 # force Session default to be secure
524 # force Session default to be secure
523 default_secure(self.config)
525 default_secure(self.config)
524 self.kernel_manager = MappingKernelManager(
526 self.kernel_manager = MappingKernelManager(
525 parent=self, log=self.log, kernel_argv=self.kernel_argv,
527 parent=self, log=self.log, kernel_argv=self.kernel_argv,
526 connection_dir = self.profile_dir.security_dir,
528 connection_dir = self.profile_dir.security_dir,
527 )
529 )
528 kls = import_item(self.notebook_manager_class)
530 kls = import_item(self.notebook_manager_class)
529 self.notebook_manager = kls(parent=self, log=self.log)
531 self.notebook_manager = kls(parent=self, log=self.log)
530 self.session_manager = SessionManager(parent=self, log=self.log)
532 self.session_manager = SessionManager(parent=self, log=self.log)
531 self.cluster_manager = ClusterManager(parent=self, log=self.log)
533 self.cluster_manager = ClusterManager(parent=self, log=self.log)
532 self.cluster_manager.update_profiles()
534 self.cluster_manager.update_profiles()
533
535
534 def init_logging(self):
536 def init_logging(self):
535 # 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
536 # 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
537 # and all of its ancenstors until propagate is set to False.
539 # and all of its ancenstors until propagate is set to False.
538 self.log.propagate = False
540 self.log.propagate = False
539
541
540 # hook up tornado 3's loggers to our app handlers
542 # hook up tornado 3's loggers to our app handlers
541 for name in ('access', 'application', 'general'):
543 for name in ('access', 'application', 'general'):
542 logger = logging.getLogger('tornado.%s' % name)
544 logger = logging.getLogger('tornado.%s' % name)
543 logger.propagate = False
545 logger.propagate = False
544 logger.setLevel(self.log.level)
546 logger.setLevel(self.log.level)
545 logger.handlers = self.log.handlers
547 logger.handlers = self.log.handlers
546
548
547 def init_webapp(self):
549 def init_webapp(self):
548 """initialize tornado webapp and httpserver"""
550 """initialize tornado webapp and httpserver"""
549 self.web_app = NotebookWebApplication(
551 self.web_app = NotebookWebApplication(
550 self, self.kernel_manager, self.notebook_manager,
552 self, self.kernel_manager, self.notebook_manager,
551 self.cluster_manager, self.session_manager,
553 self.cluster_manager, self.session_manager,
552 self.log, self.base_project_url, self.webapp_settings
554 self.log, self.base_project_url, self.webapp_settings
553 )
555 )
554 if self.certfile:
556 if self.certfile:
555 ssl_options = dict(certfile=self.certfile)
557 ssl_options = dict(certfile=self.certfile)
556 if self.keyfile:
558 if self.keyfile:
557 ssl_options['keyfile'] = self.keyfile
559 ssl_options['keyfile'] = self.keyfile
558 else:
560 else:
559 ssl_options = None
561 ssl_options = None
560 self.web_app.password = self.password
562 self.web_app.password = self.password
561 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,
562 xheaders=self.trust_xheaders)
564 xheaders=self.trust_xheaders)
563 if not self.ip:
565 if not self.ip:
564 warning = "WARNING: The notebook server is listening on all IP addresses"
566 warning = "WARNING: The notebook server is listening on all IP addresses"
565 if ssl_options is None:
567 if ssl_options is None:
566 self.log.critical(warning + " and not using encryption. This "
568 self.log.critical(warning + " and not using encryption. This "
567 "is not recommended.")
569 "is not recommended.")
568 if not self.password:
570 if not self.password:
569 self.log.critical(warning + " and not using authentication. "
571 self.log.critical(warning + " and not using authentication. "
570 "This is highly insecure and not recommended.")
572 "This is highly insecure and not recommended.")
571 success = None
573 success = None
572 for port in random_ports(self.port, self.port_retries+1):
574 for port in random_ports(self.port, self.port_retries+1):
573 try:
575 try:
574 self.http_server.listen(port, self.ip)
576 self.http_server.listen(port, self.ip)
575 except socket.error as e:
577 except socket.error as e:
576 # XXX: remove the e.errno == -9 block when we require
578 # XXX: remove the e.errno == -9 block when we require
577 # tornado >= 3.0
579 # tornado >= 3.0
578 if e.errno == -9 and tornado.version_info[0] < 3:
580 if e.errno == -9 and tornado.version_info[0] < 3:
579 # The flags passed to socket.getaddrinfo from
581 # The flags passed to socket.getaddrinfo from
580 # tornado.netutils.bind_sockets can cause "gaierror:
582 # tornado.netutils.bind_sockets can cause "gaierror:
581 # [Errno -9] Address family for hostname not supported"
583 # [Errno -9] Address family for hostname not supported"
582 # when the interface is not associated, for example.
584 # when the interface is not associated, for example.
583 # Changing the flags to exclude socket.AI_ADDRCONFIG does
585 # Changing the flags to exclude socket.AI_ADDRCONFIG does
584 # 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
585 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
587 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
586 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
588 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
587 self.log.warn('Monkeypatching socket to fix tornado bug')
589 self.log.warn('Monkeypatching socket to fix tornado bug')
588 del(socket.AI_ADDRCONFIG)
590 del(socket.AI_ADDRCONFIG)
589 try:
591 try:
590 # retry the tornado call without AI_ADDRCONFIG flags
592 # retry the tornado call without AI_ADDRCONFIG flags
591 self.http_server.listen(port, self.ip)
593 self.http_server.listen(port, self.ip)
592 except socket.error as e2:
594 except socket.error as e2:
593 e = e2
595 e = e2
594 else:
596 else:
595 self.port = port
597 self.port = port
596 success = True
598 success = True
597 break
599 break
598 # restore the monekypatch
600 # restore the monekypatch
599 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
601 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
600 if e.errno == errno.EADDRINUSE:
602 if e.errno == errno.EADDRINUSE:
601 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)
602 continue
604 continue
603 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
605 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
604 self.log.warn("Permission to listen on port %i denied" % port)
606 self.log.warn("Permission to listen on port %i denied" % port)
605 continue
607 continue
606 else:
608 else:
607 raise
609 raise
608 else:
610 else:
609 self.port = port
611 self.port = port
610 success = True
612 success = True
611 break
613 break
612 if not success:
614 if not success:
613 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 '
614 'no available port could be found.')
616 'no available port could be found.')
615 self.exit(1)
617 self.exit(1)
616
618
617 def init_signal(self):
619 def init_signal(self):
618 if not sys.platform.startswith('win'):
620 if not sys.platform.startswith('win'):
619 signal.signal(signal.SIGINT, self._handle_sigint)
621 signal.signal(signal.SIGINT, self._handle_sigint)
620 signal.signal(signal.SIGTERM, self._signal_stop)
622 signal.signal(signal.SIGTERM, self._signal_stop)
621 if hasattr(signal, 'SIGUSR1'):
623 if hasattr(signal, 'SIGUSR1'):
622 # Windows doesn't support SIGUSR1
624 # Windows doesn't support SIGUSR1
623 signal.signal(signal.SIGUSR1, self._signal_info)
625 signal.signal(signal.SIGUSR1, self._signal_info)
624 if hasattr(signal, 'SIGINFO'):
626 if hasattr(signal, 'SIGINFO'):
625 # only on BSD-based systems
627 # only on BSD-based systems
626 signal.signal(signal.SIGINFO, self._signal_info)
628 signal.signal(signal.SIGINFO, self._signal_info)
627
629
628 def _handle_sigint(self, sig, frame):
630 def _handle_sigint(self, sig, frame):
629 """SIGINT handler spawns confirmation dialog"""
631 """SIGINT handler spawns confirmation dialog"""
630 # register more forceful signal handler for ^C^C case
632 # register more forceful signal handler for ^C^C case
631 signal.signal(signal.SIGINT, self._signal_stop)
633 signal.signal(signal.SIGINT, self._signal_stop)
632 # request confirmation dialog in bg thread, to avoid
634 # request confirmation dialog in bg thread, to avoid
633 # blocking the App
635 # blocking the App
634 thread = threading.Thread(target=self._confirm_exit)
636 thread = threading.Thread(target=self._confirm_exit)
635 thread.daemon = True
637 thread.daemon = True
636 thread.start()
638 thread.start()
637
639
638 def _restore_sigint_handler(self):
640 def _restore_sigint_handler(self):
639 """callback for restoring original SIGINT handler"""
641 """callback for restoring original SIGINT handler"""
640 signal.signal(signal.SIGINT, self._handle_sigint)
642 signal.signal(signal.SIGINT, self._handle_sigint)
641
643
642 def _confirm_exit(self):
644 def _confirm_exit(self):
643 """confirm shutdown on ^C
645 """confirm shutdown on ^C
644
646
645 A second ^C, or answering 'y' within 5s will cause shutdown,
647 A second ^C, or answering 'y' within 5s will cause shutdown,
646 otherwise original SIGINT handler will be restored.
648 otherwise original SIGINT handler will be restored.
647
649
648 This doesn't work on Windows.
650 This doesn't work on Windows.
649 """
651 """
650 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
652 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
651 time.sleep(0.1)
653 time.sleep(0.1)
652 info = self.log.info
654 info = self.log.info
653 info('interrupted')
655 info('interrupted')
654 print self.notebook_info()
656 print self.notebook_info()
655 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
657 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
656 sys.stdout.flush()
658 sys.stdout.flush()
657 r,w,x = select.select([sys.stdin], [], [], 5)
659 r,w,x = select.select([sys.stdin], [], [], 5)
658 if r:
660 if r:
659 line = sys.stdin.readline()
661 line = sys.stdin.readline()
660 if line.lower().startswith('y'):
662 if line.lower().startswith('y'):
661 self.log.critical("Shutdown confirmed")
663 self.log.critical("Shutdown confirmed")
662 ioloop.IOLoop.instance().stop()
664 ioloop.IOLoop.instance().stop()
663 return
665 return
664 else:
666 else:
665 print "No answer for 5s:",
667 print "No answer for 5s:",
666 print "resuming operation..."
668 print "resuming operation..."
667 # no answer, or answer is no:
669 # no answer, or answer is no:
668 # set it back to original SIGINT handler
670 # set it back to original SIGINT handler
669 # use IOLoop.add_callback because signal.signal must be called
671 # use IOLoop.add_callback because signal.signal must be called
670 # from main thread
672 # from main thread
671 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
673 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
672
674
673 def _signal_stop(self, sig, frame):
675 def _signal_stop(self, sig, frame):
674 self.log.critical("received signal %s, stopping", sig)
676 self.log.critical("received signal %s, stopping", sig)
675 ioloop.IOLoop.instance().stop()
677 ioloop.IOLoop.instance().stop()
676
678
677 def _signal_info(self, sig, frame):
679 def _signal_info(self, sig, frame):
678 print self.notebook_info()
680 print self.notebook_info()
679
681
680 def init_components(self):
682 def init_components(self):
681 """Check the components submodule, and warn if it's unclean"""
683 """Check the components submodule, and warn if it's unclean"""
682 status = submodule.check_submodule_status()
684 status = submodule.check_submodule_status()
683 if status == 'missing':
685 if status == 'missing':
684 self.log.warn("components submodule missing, running `git submodule update`")
686 self.log.warn("components submodule missing, running `git submodule update`")
685 submodule.update_submodules(submodule.ipython_parent())
687 submodule.update_submodules(submodule.ipython_parent())
686 elif status == 'unclean':
688 elif status == 'unclean':
687 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")
688 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")
689
691
690
692
691 @catch_config_error
693 @catch_config_error
692 def initialize(self, argv=None):
694 def initialize(self, argv=None):
693 self.init_logging()
695 self.init_logging()
694 super(NotebookApp, self).initialize(argv)
696 super(NotebookApp, self).initialize(argv)
695 self.init_kernel_argv()
697 self.init_kernel_argv()
696 self.init_configurables()
698 self.init_configurables()
697 self.init_components()
699 self.init_components()
698 self.init_webapp()
700 self.init_webapp()
699 self.init_signal()
701 self.init_signal()
700
702
701 def cleanup_kernels(self):
703 def cleanup_kernels(self):
702 """Shutdown all kernels.
704 """Shutdown all kernels.
703
705
704 The kernels will shutdown themselves when this process no longer exists,
706 The kernels will shutdown themselves when this process no longer exists,
705 but explicit shutdown allows the KernelManagers to cleanup the connection files.
707 but explicit shutdown allows the KernelManagers to cleanup the connection files.
706 """
708 """
707 self.log.info('Shutting down kernels')
709 self.log.info('Shutting down kernels')
708 self.kernel_manager.shutdown_all()
710 self.kernel_manager.shutdown_all()
709
711
710 def notebook_info(self):
712 def notebook_info(self):
711 "Return the current working directory and the server url information"
713 "Return the current working directory and the server url information"
712 mgr_info = self.notebook_manager.info_string() + "\n"
714 mgr_info = self.notebook_manager.info_string() + "\n"
713 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
714
716
715 def start(self):
717 def start(self):
716 """ Start the IPython Notebook server app, after initialization
718 """ Start the IPython Notebook server app, after initialization
717
719
718 This method takes no arguments so all configuration and initialization
720 This method takes no arguments so all configuration and initialization
719 must be done prior to calling this method."""
721 must be done prior to calling this method."""
720 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]'
721 proto = 'https' if self.certfile else 'http'
723 proto = 'https' if self.certfile else 'http'
722 info = self.log.info
724 info = self.log.info
723 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
725 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
724 self.base_project_url)
726 self.base_project_url)
725 for line in self.notebook_info().split("\n"):
727 for line in self.notebook_info().split("\n"):
726 info(line)
728 info(line)
727 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).")
728
730
729 if self.open_browser or self.file_to_run:
731 if self.open_browser or self.file_to_run:
730 ip = self.ip or localhost()
732 ip = self.ip or localhost()
731 try:
733 try:
732 browser = webbrowser.get(self.browser or None)
734 browser = webbrowser.get(self.browser or None)
733 except webbrowser.Error as e:
735 except webbrowser.Error as e:
734 self.log.warn('No web browser found: %s.' % e)
736 self.log.warn('No web browser found: %s.' % e)
735 browser = None
737 browser = None
736
738
737 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
739 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
738 f = self.file_to_run
740 f = self.file_to_run
739 if f.startswith(nbdir):
741 if f.startswith(nbdir):
740 f = f[len(nbdir):]
742 f = f[len(nbdir):]
741 else:
743 else:
742 self.log.warn(
744 self.log.warn(
743 "Probably won't be able to open notebook %s "
745 "Probably won't be able to open notebook %s "
744 "because it is not in notebook_dir %s",
746 "because it is not in notebook_dir %s",
745 f, nbdir,
747 f, nbdir,
746 )
748 )
747
749
748 if os.path.isfile(self.file_to_run):
750 if os.path.isfile(self.file_to_run):
749 url = url_path_join('notebooks', f)
751 url = url_path_join('notebooks', f)
750 else:
752 else:
751 url = url_path_join('tree', f)
753 url = url_path_join('tree', f)
752 if browser:
754 if browser:
753 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
755 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
754 self.port, self.base_project_url, url), new=2)
756 self.port, self.base_project_url, url), new=2)
755 threading.Thread(target=b).start()
757 threading.Thread(target=b).start()
756 try:
758 try:
757 ioloop.IOLoop.instance().start()
759 ioloop.IOLoop.instance().start()
758 except KeyboardInterrupt:
760 except KeyboardInterrupt:
759 info("Interrupted...")
761 info("Interrupted...")
760 finally:
762 finally:
761 self.cleanup_kernels()
763 self.cleanup_kernels()
762
764
763
765
764 #-----------------------------------------------------------------------------
766 #-----------------------------------------------------------------------------
765 # Main entry point
767 # Main entry point
766 #-----------------------------------------------------------------------------
768 #-----------------------------------------------------------------------------
767
769
768 launch_new_instance = NotebookApp.launch_instance
770 launch_new_instance = NotebookApp.launch_instance
769
771
General Comments 0
You need to be logged in to leave comments. Login now