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