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