##// END OF EJS Templates
Made changes as per discussion in PR thread.
dkua -
Show More
@@ -1,621 +1,622 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) 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 # Third party
34 # Third party
35 import zmq
35 import zmq
36
36
37 # Install the pyzmq ioloop. This has to be done before anything else from
37 # Install the pyzmq ioloop. This has to be done before anything else from
38 # tornado is imported.
38 # tornado is imported.
39 from zmq.eventloop import ioloop
39 from zmq.eventloop import ioloop
40 ioloop.install()
40 ioloop.install()
41
41
42 from tornado import httpserver
42 from tornado import httpserver
43 from tornado import web
43 from tornado import web
44
44
45 # Our own libraries
45 # Our own libraries
46 from .kernelmanager import MappingKernelManager
46 from .kernelmanager import MappingKernelManager
47 from .handlers import (LoginHandler, LogoutHandler,
47 from .handlers import (LoginHandler, LogoutHandler,
48 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
48 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
49 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
49 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
50 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
50 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
51 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
51 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
52 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
52 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
53 FileFindHandler,
53 FileFindHandler,
54 )
54 )
55 from .nbmanager import NotebookManager
55 from .nbmanager import NotebookManager
56 from .filenbmanager import FileNotebookManager
56 from .filenbmanager import FileNotebookManager
57 from .clustermanager import ClusterManager
57 from .clustermanager import ClusterManager
58
58
59 from IPython.config.application import catch_config_error, boolean_flag
59 from IPython.config.application import catch_config_error, boolean_flag
60 from IPython.core.application import BaseIPythonApplication
60 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.profiledir import ProfileDir
61 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.consoleapp import IPythonConsoleApp
62 from IPython.frontend.consoleapp import IPythonConsoleApp
63 from IPython.lib.kernel import swallow_argv
63 from IPython.lib.kernel import swallow_argv
64 from IPython.zmq.session import Session, default_secure
64 from IPython.zmq.session import Session, default_secure
65 from IPython.zmq.zmqshell import ZMQInteractiveShell
65 from IPython.zmq.zmqshell import ZMQInteractiveShell
66 from IPython.zmq.ipkernel import (
66 from IPython.zmq.ipkernel import (
67 flags as ipkernel_flags,
67 flags as ipkernel_flags,
68 aliases as ipkernel_aliases,
68 aliases as ipkernel_aliases,
69 IPKernelApp
69 IPKernelApp
70 )
70 )
71 from IPython.utils.importstring import import_item
71 from IPython.utils.importstring import import_item
72 from IPython.utils.traitlets import (
72 from IPython.utils.traitlets import (
73 Dict, Unicode, Integer, List, Enum, Bool,
73 Dict, Unicode, Integer, List, Enum, Bool,
74 DottedObjectName
74 DottedObjectName
75 )
75 )
76 from IPython.utils import py3compat
76 from IPython.utils import py3compat
77 from IPython.utils.path import filefind
77 from IPython.utils.path import filefind
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Module globals
80 # Module globals
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82
82
83 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
83 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
84 _kernel_action_regex = r"(?P<action>restart|interrupt)"
84 _kernel_action_regex = r"(?P<action>restart|interrupt)"
85 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
85 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
86 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
86 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
87 _cluster_action_regex = r"(?P<action>start|stop)"
87 _cluster_action_regex = r"(?P<action>start|stop)"
88
88
89
89
90 LOCALHOST = '127.0.0.1'
90 LOCALHOST = '127.0.0.1'
91
91
92 _examples = """
92 _examples = """
93 ipython notebook # start the notebook
93 ipython notebook # start the notebook
94 ipython notebook --profile=sympy # use the sympy profile
94 ipython notebook --profile=sympy # use the sympy profile
95 ipython notebook --pylab=inline # pylab in inline plotting mode
95 ipython notebook --pylab=inline # pylab in inline plotting mode
96 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
96 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
97 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
97 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
98 """
98 """
99
99
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101 # Helper functions
101 # Helper functions
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103
103
104 def url_path_join(a,b):
104 def url_path_join(a,b):
105 if a.endswith('/') and b.startswith('/'):
105 if a.endswith('/') and b.startswith('/'):
106 return a[:-1]+b
106 return a[:-1]+b
107 else:
107 else:
108 return a+b
108 return a+b
109
109
110 def random_ports(port, n):
110 def random_ports(port, n):
111 """Generate a list of n random ports near the given port.
111 """Generate a list of n random ports near the given port.
112
112
113 The first 5 ports will be sequential, and the remaining n-5 will be
113 The first 5 ports will be sequential, and the remaining n-5 will be
114 randomly selected in the range [port-2*n, port+2*n].
114 randomly selected in the range [port-2*n, port+2*n].
115 """
115 """
116 for i in range(min(5, n)):
116 for i in range(min(5, n)):
117 yield port + i
117 yield port + i
118 for i in range(n-5):
118 for i in range(n-5):
119 yield port + random.randint(-2*n, 2*n)
119 yield port + random.randint(-2*n, 2*n)
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # The Tornado web application
122 # The Tornado web application
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124
124
125 class NotebookWebApplication(web.Application):
125 class NotebookWebApplication(web.Application):
126
126
127 def __init__(self, ipython_app, kernel_manager, notebook_manager,
127 def __init__(self, ipython_app, kernel_manager, notebook_manager,
128 cluster_manager, log,
128 cluster_manager, log,
129 base_project_url, settings_overrides):
129 base_project_url, settings_overrides):
130 handlers = [
130 handlers = [
131 (r"/", ProjectDashboardHandler),
131 (r"/", ProjectDashboardHandler),
132 (r"/login", LoginHandler),
132 (r"/login", LoginHandler),
133 (r"/logout", LogoutHandler),
133 (r"/logout", LogoutHandler),
134 (r"/new", NewHandler),
134 (r"/new", NewHandler),
135 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
135 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
136 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
136 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
137 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
137 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
138 (r"/kernels", MainKernelHandler),
138 (r"/kernels", MainKernelHandler),
139 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
139 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
140 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
140 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
141 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
141 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
142 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
142 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
143 (r"/notebooks", NotebookRootHandler),
143 (r"/notebooks", NotebookRootHandler),
144 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
144 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
145 (r"/rstservice/render", RSTHandler),
145 (r"/rstservice/render", RSTHandler),
146 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
146 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
147 (r"/clusters", MainClusterHandler),
147 (r"/clusters", MainClusterHandler),
148 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
148 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
149 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
149 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
150 ]
150 ]
151
151
152 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
153 # base_project_url will always be unicode, which will in turn
153 # base_project_url will always be unicode, which will in turn
154 # make the patterns unicode, and ultimately result in unicode
154 # make the patterns unicode, and ultimately result in unicode
155 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 # keys in kwargs to handler._execute(**kwargs) in tornado.
156 # This enforces that base_project_url be ascii in that situation.
156 # This enforces that base_project_url be ascii in that situation.
157 #
157 #
158 # Note that the URLs these patterns check against are escaped,
158 # Note that the URLs these patterns check against are escaped,
159 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
160 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
160 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
161
161
162 settings = dict(
162 settings = dict(
163 template_path=os.path.join(os.path.dirname(__file__), "templates"),
163 template_path=os.path.join(os.path.dirname(__file__), "templates"),
164 static_path=ipython_app.static_file_path,
164 static_path=ipython_app.static_file_path,
165 static_handler_class = FileFindHandler,
165 static_handler_class = FileFindHandler,
166 cookie_secret=os.urandom(1024),
166 cookie_secret=os.urandom(1024),
167 login_url="%s/login"%(base_project_url.rstrip('/')),
167 login_url="%s/login"%(base_project_url.rstrip('/')),
168 cookie_name='username-%s' % uuid.uuid4(),
168 cookie_name='username-%s' % uuid.uuid4(),
169 )
169 )
170
170
171 # allow custom overrides for the tornado web app.
171 # allow custom overrides for the tornado web app.
172 settings.update(settings_overrides)
172 settings.update(settings_overrides)
173
173
174 # prepend base_project_url onto the patterns that we match
174 # prepend base_project_url onto the patterns that we match
175 new_handlers = []
175 new_handlers = []
176 for handler in handlers:
176 for handler in handlers:
177 pattern = url_path_join(base_project_url, handler[0])
177 pattern = url_path_join(base_project_url, handler[0])
178 new_handler = tuple([pattern]+list(handler[1:]))
178 new_handler = tuple([pattern]+list(handler[1:]))
179 new_handlers.append( new_handler )
179 new_handlers.append( new_handler )
180
180
181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
182
182
183 self.kernel_manager = kernel_manager
183 self.kernel_manager = kernel_manager
184 self.notebook_manager = notebook_manager
184 self.notebook_manager = notebook_manager
185 self.cluster_manager = cluster_manager
185 self.cluster_manager = cluster_manager
186 self.ipython_app = ipython_app
186 self.ipython_app = ipython_app
187 self.read_only = self.ipython_app.read_only
187 self.read_only = self.ipython_app.read_only
188 self.log = log
188 self.log = log
189
189
190
190
191 #-----------------------------------------------------------------------------
191 #-----------------------------------------------------------------------------
192 # Aliases and Flags
192 # Aliases and Flags
193 #-----------------------------------------------------------------------------
193 #-----------------------------------------------------------------------------
194
194
195 flags = dict(ipkernel_flags)
195 flags = dict(ipkernel_flags)
196 flags['no-browser']=(
196 flags['no-browser']=(
197 {'NotebookApp' : {'open_browser' : False}},
197 {'NotebookApp' : {'open_browser' : False}},
198 "Don't open the notebook in a browser after startup."
198 "Don't open the notebook in a browser after startup."
199 )
199 )
200 flags['no-mathjax']=(
200 flags['no-mathjax']=(
201 {'NotebookApp' : {'enable_mathjax' : False}},
201 {'NotebookApp' : {'enable_mathjax' : False}},
202 """Disable MathJax
202 """Disable MathJax
203
203
204 MathJax is the javascript library IPython uses to render math/LaTeX. It is
204 MathJax is the javascript library IPython uses to render math/LaTeX. It is
205 very large, so you may want to disable it if you have a slow internet
205 very large, so you may want to disable it if you have a slow internet
206 connection, or for offline use of the notebook.
206 connection, or for offline use of the notebook.
207
207
208 When disabled, equations etc. will appear as their untransformed TeX source.
208 When disabled, equations etc. will appear as their untransformed TeX source.
209 """
209 """
210 )
210 )
211 flags['read-only'] = (
211 flags['read-only'] = (
212 {'NotebookApp' : {'read_only' : True}},
212 {'NotebookApp' : {'read_only' : True}},
213 """Allow read-only access to notebooks.
213 """Allow read-only access to notebooks.
214
214
215 When using a password to protect the notebook server, this flag
215 When using a password to protect the notebook server, this flag
216 allows unauthenticated clients to view the notebook list, and
216 allows unauthenticated clients to view the notebook list, and
217 individual notebooks, but not edit them, start kernels, or run
217 individual notebooks, but not edit them, start kernels, or run
218 code.
218 code.
219
219
220 If no password is set, the server will be entirely read-only.
220 If no password is set, the server will be entirely read-only.
221 """
221 """
222 )
222 )
223
223
224 # Add notebook manager flags
224 # Add notebook manager flags
225 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
225 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
226 'Auto-save a .py script everytime the .ipynb notebook is saved',
226 'Auto-save a .py script everytime the .ipynb notebook is saved',
227 'Do not auto-save .py scripts for every notebook'))
227 'Do not auto-save .py scripts for every notebook'))
228
228
229 # the flags that are specific to the frontend
229 # the flags that are specific to the frontend
230 # these must be scrubbed before being passed to the kernel,
230 # these must be scrubbed before being passed to the kernel,
231 # or it will raise an error on unrecognized flags
231 # or it will raise an error on unrecognized flags
232 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
232 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
233
233
234 aliases = dict(ipkernel_aliases)
234 aliases = dict(ipkernel_aliases)
235
235
236 aliases.update({
236 aliases.update({
237 'ip': 'NotebookApp.ip',
237 'ip': 'NotebookApp.ip',
238 'port': 'NotebookApp.port',
238 'port': 'NotebookApp.port',
239 'port-retries': 'NotebookApp.port_retries',
239 'port-retries': 'NotebookApp.port_retries',
240 'keyfile': 'NotebookApp.keyfile',
240 'keyfile': 'NotebookApp.keyfile',
241 'certfile': 'NotebookApp.certfile',
241 'certfile': 'NotebookApp.certfile',
242 'notebook-dir': 'NotebookManager.notebook_dir',
242 'notebook-dir': 'NotebookManager.notebook_dir',
243 'browser': 'NotebookApp.browser',
243 'browser': 'NotebookApp.browser',
244 })
244 })
245
245
246 # remove ipkernel flags that are singletons, and don't make sense in
246 # remove ipkernel flags that are singletons, and don't make sense in
247 # multi-kernel evironment:
247 # multi-kernel evironment:
248 aliases.pop('f', None)
248 aliases.pop('f', None)
249
249
250 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
250 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
251 u'notebook-dir']
251 u'notebook-dir']
252
252
253 #-----------------------------------------------------------------------------
253 #-----------------------------------------------------------------------------
254 # NotebookApp
254 # NotebookApp
255 #-----------------------------------------------------------------------------
255 #-----------------------------------------------------------------------------
256
256
257 class NotebookApp(BaseIPythonApplication):
257 class NotebookApp(BaseIPythonApplication):
258
258
259 name = 'ipython-notebook'
259 name = 'ipython-notebook'
260 default_config_file_name='ipython_notebook_config.py'
260 default_config_file_name='ipython_notebook_config.py'
261
261
262 description = """
262 description = """
263 The IPython HTML Notebook.
263 The IPython HTML Notebook.
264
264
265 This launches a Tornado based HTML Notebook Server that serves up an
265 This launches a Tornado based HTML Notebook Server that serves up an
266 HTML5/Javascript Notebook client.
266 HTML5/Javascript Notebook client.
267 """
267 """
268 examples = _examples
268 examples = _examples
269
269
270 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
270 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
271 FileNotebookManager]
271 FileNotebookManager]
272 flags = Dict(flags)
272 flags = Dict(flags)
273 aliases = Dict(aliases)
273 aliases = Dict(aliases)
274
274
275 kernel_argv = List(Unicode)
275 kernel_argv = List(Unicode)
276
276
277 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
277 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
278 default_value=logging.INFO,
278 default_value=logging.INFO,
279 config=True,
279 config=True,
280 help="Set the log level by value or name.")
280 help="Set the log level by value or name.")
281
281
282 # create requested profiles by default, if they don't exist:
282 # create requested profiles by default, if they don't exist:
283 auto_create = Bool(True)
283 auto_create = Bool(True)
284
284
285 # file to be opened in the notebook server
285 # file to be opened in the notebook server
286 file_to_run = Unicode('')
286 file_to_run = Unicode('')
287
287
288 # Network related information.
288 # Network related information.
289
289
290 ip = Unicode(LOCALHOST, config=True,
290 ip = Unicode(LOCALHOST, config=True,
291 help="The IP address the notebook server will listen on."
291 help="The IP address the notebook server will listen on."
292 )
292 )
293
293
294 def _ip_changed(self, name, old, new):
294 def _ip_changed(self, name, old, new):
295 if new == u'*': self.ip = u''
295 if new == u'*': self.ip = u''
296
296
297 port = Integer(8888, config=True,
297 port = Integer(8888, config=True,
298 help="The port the notebook server will listen on."
298 help="The port the notebook server will listen on."
299 )
299 )
300 port_retries = Integer(50, config=True,
300 port_retries = Integer(50, config=True,
301 help="The number of additional ports to try if the specified port is not available."
301 help="The number of additional ports to try if the specified port is not available."
302 )
302 )
303
303
304 certfile = Unicode(u'', config=True,
304 certfile = Unicode(u'', config=True,
305 help="""The full path to an SSL/TLS certificate file."""
305 help="""The full path to an SSL/TLS certificate file."""
306 )
306 )
307
307
308 keyfile = Unicode(u'', config=True,
308 keyfile = Unicode(u'', config=True,
309 help="""The full path to a private key file for usage with SSL/TLS."""
309 help="""The full path to a private key file for usage with SSL/TLS."""
310 )
310 )
311
311
312 password = Unicode(u'', config=True,
312 password = Unicode(u'', config=True,
313 help="""Hashed password to use for web authentication.
313 help="""Hashed password to use for web authentication.
314
314
315 To generate, type in a python/IPython shell:
315 To generate, type in a python/IPython shell:
316
316
317 from IPython.lib import passwd; passwd()
317 from IPython.lib import passwd; passwd()
318
318
319 The string should be of the form type:salt:hashed-password.
319 The string should be of the form type:salt:hashed-password.
320 """
320 """
321 )
321 )
322
322
323 open_browser = Bool(True, config=True,
323 open_browser = Bool(True, config=True,
324 help="""Whether to open in a browser after starting.
324 help="""Whether to open in a browser after starting.
325 The specific browser used is platform dependent and
325 The specific browser used is platform dependent and
326 determined by the python standard library `webbrowser`
326 determined by the python standard library `webbrowser`
327 module, unless it is overridden using the --browser
327 module, unless it is overridden using the --browser
328 (NotebookApp.browser) configuration option.
328 (NotebookApp.browser) configuration option.
329 """)
329 """)
330
330
331 browser = Unicode(u'', config=True,
331 browser = Unicode(u'', config=True,
332 help="""Specify what command to use to invoke a web
332 help="""Specify what command to use to invoke a web
333 browser when opening the notebook. If not specified, the
333 browser when opening the notebook. If not specified, the
334 default browser will be determined by the `webbrowser`
334 default browser will be determined by the `webbrowser`
335 standard library module, which allows setting of the
335 standard library module, which allows setting of the
336 BROWSER environment variable to override it.
336 BROWSER environment variable to override it.
337 """)
337 """)
338
338
339 read_only = Bool(False, config=True,
339 read_only = Bool(False, config=True,
340 help="Whether to prevent editing/execution of notebooks."
340 help="Whether to prevent editing/execution of notebooks."
341 )
341 )
342
342
343 webapp_settings = Dict(config=True,
343 webapp_settings = Dict(config=True,
344 help="Supply overrides for the tornado.web.Application that the "
344 help="Supply overrides for the tornado.web.Application that the "
345 "IPython notebook uses.")
345 "IPython notebook uses.")
346
346
347 enable_mathjax = Bool(True, config=True,
347 enable_mathjax = Bool(True, config=True,
348 help="""Whether to enable MathJax for typesetting math/TeX
348 help="""Whether to enable MathJax for typesetting math/TeX
349
349
350 MathJax is the javascript library IPython uses to render math/LaTeX. It is
350 MathJax is the javascript library IPython uses to render math/LaTeX. It is
351 very large, so you may want to disable it if you have a slow internet
351 very large, so you may want to disable it if you have a slow internet
352 connection, or for offline use of the notebook.
352 connection, or for offline use of the notebook.
353
353
354 When disabled, equations etc. will appear as their untransformed TeX source.
354 When disabled, equations etc. will appear as their untransformed TeX source.
355 """
355 """
356 )
356 )
357 def _enable_mathjax_changed(self, name, old, new):
357 def _enable_mathjax_changed(self, name, old, new):
358 """set mathjax url to empty if mathjax is disabled"""
358 """set mathjax url to empty if mathjax is disabled"""
359 if not new:
359 if not new:
360 self.mathjax_url = u''
360 self.mathjax_url = u''
361
361
362 base_project_url = Unicode('/', config=True,
362 base_project_url = Unicode('/', config=True,
363 help='''The base URL for the notebook server''')
363 help='''The base URL for the notebook server''')
364 base_kernel_url = Unicode('/', config=True,
364 base_kernel_url = Unicode('/', config=True,
365 help='''The base URL for the kernel server''')
365 help='''The base URL for the kernel server''')
366 websocket_host = Unicode("", config=True,
366 websocket_host = Unicode("", config=True,
367 help="""The hostname for the websocket server."""
367 help="""The hostname for the websocket server."""
368 )
368 )
369
369
370 extra_static_paths = List(Unicode, config=True,
370 extra_static_paths = List(Unicode, config=True,
371 help="""Extra paths to search for serving static files.
371 help="""Extra paths to search for serving static files.
372
372
373 This allows adding javascript/css to be available from the notebook server machine,
373 This allows adding javascript/css to be available from the notebook server machine,
374 or overriding individual files in the IPython"""
374 or overriding individual files in the IPython"""
375 )
375 )
376 def _extra_static_paths_default(self):
376 def _extra_static_paths_default(self):
377 return [os.path.join(self.profile_dir.location, 'static')]
377 return [os.path.join(self.profile_dir.location, 'static')]
378
378
379 @property
379 @property
380 def static_file_path(self):
380 def static_file_path(self):
381 """return extra paths + the default location"""
381 """return extra paths + the default location"""
382 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
382 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
383
383
384 mathjax_url = Unicode("", config=True,
384 mathjax_url = Unicode("", config=True,
385 help="""The url for MathJax.js."""
385 help="""The url for MathJax.js."""
386 )
386 )
387 def _mathjax_url_default(self):
387 def _mathjax_url_default(self):
388 if not self.enable_mathjax:
388 if not self.enable_mathjax:
389 return u''
389 return u''
390 static_url_prefix = self.webapp_settings.get("static_url_prefix",
390 static_url_prefix = self.webapp_settings.get("static_url_prefix",
391 "/static/")
391 "/static/")
392 try:
392 try:
393 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
393 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
394 except IOError:
394 except IOError:
395 if self.certfile:
395 if self.certfile:
396 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
396 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
397 base = u"https://c328740.ssl.cf1.rackcdn.com"
397 base = u"https://c328740.ssl.cf1.rackcdn.com"
398 else:
398 else:
399 base = u"http://cdn.mathjax.org"
399 base = u"http://cdn.mathjax.org"
400
400
401 url = base + u"/mathjax/latest/MathJax.js"
401 url = base + u"/mathjax/latest/MathJax.js"
402 self.log.info("Using MathJax from CDN: %s", url)
402 self.log.info("Using MathJax from CDN: %s", url)
403 return url
403 return url
404 else:
404 else:
405 self.log.info("Using local MathJax from %s" % mathjax)
405 self.log.info("Using local MathJax from %s" % mathjax)
406 return static_url_prefix+u"mathjax/MathJax.js"
406 return static_url_prefix+u"mathjax/MathJax.js"
407
407
408 def _mathjax_url_changed(self, name, old, new):
408 def _mathjax_url_changed(self, name, old, new):
409 if new and not self.enable_mathjax:
409 if new and not self.enable_mathjax:
410 # enable_mathjax=False overrides mathjax_url
410 # enable_mathjax=False overrides mathjax_url
411 self.mathjax_url = u''
411 self.mathjax_url = u''
412 else:
412 else:
413 self.log.info("Using MathJax: %s", new)
413 self.log.info("Using MathJax: %s", new)
414
414
415 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
415 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
416 config=True,
416 config=True,
417 help='The notebook manager class to use.')
417 help='The notebook manager class to use.')
418
418
419 def parse_command_line(self, argv=None):
419 def parse_command_line(self, argv=None):
420 super(NotebookApp, self).parse_command_line(argv)
420 super(NotebookApp, self).parse_command_line(argv)
421 if argv is None:
421 if argv is None:
422 argv = sys.argv[1:]
422 argv = sys.argv[1:]
423
423
424 # Scrub frontend-specific flags
424 # Scrub frontend-specific flags
425 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
425 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
426 # Kernel should inherit default config file from frontend
426 # Kernel should inherit default config file from frontend
427 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
427 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
428
428
429 if self.extra_args:
429 if self.extra_args:
430 f = os.path.abspath(self.extra_args[0])
430 f = os.path.abspath(self.extra_args[0])
431 if os.path.isdir(f):
431 if os.path.isdir(f):
432 nbdir = f
432 nbdir = f
433 else:
433 else:
434 self.file_to_run = f
434 self.file_to_run = f
435 nbdir = os.path.dirname(f)
435 nbdir = os.path.dirname(f)
436 self.config.NotebookManager.notebook_dir = nbdir
436 self.config.NotebookManager.notebook_dir = nbdir
437
437
438 def init_configurables(self):
438 def init_configurables(self):
439 # force Session default to be secure
439 # force Session default to be secure
440 default_secure(self.config)
440 default_secure(self.config)
441 self.kernel_manager = MappingKernelManager(
441 self.kernel_manager = MappingKernelManager(
442 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
442 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
443 connection_dir = self.profile_dir.security_dir,
443 connection_dir = self.profile_dir.security_dir,
444 )
444 )
445 kls = import_item(self.notebook_manager_class)
445 kls = import_item(self.notebook_manager_class)
446 self.notebook_manager = kls(config=self.config, log=self.log)
446 self.notebook_manager = kls(config=self.config, log=self.log)
447 self.notebook_manager.log_info()
447 self.notebook_manager.log_info()
448 self.notebook_manager.load_notebook_names()
448 self.notebook_manager.load_notebook_names()
449 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
449 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
450 self.cluster_manager.update_profiles()
450 self.cluster_manager.update_profiles()
451
451
452 def init_logging(self):
452 def init_logging(self):
453 # This prevents double log messages because tornado use a root logger that
453 # This prevents double log messages because tornado use a root logger that
454 # self.log is a child of. The logging module dipatches log messages to a log
454 # self.log is a child of. The logging module dipatches log messages to a log
455 # and all of its ancenstors until propagate is set to False.
455 # and all of its ancenstors until propagate is set to False.
456 self.log.propagate = False
456 self.log.propagate = False
457
457
458 def init_webapp(self):
458 def init_webapp(self):
459 """initialize tornado webapp and httpserver"""
459 """initialize tornado webapp and httpserver"""
460 self.web_app = NotebookWebApplication(
460 self.web_app = NotebookWebApplication(
461 self, self.kernel_manager, self.notebook_manager,
461 self, self.kernel_manager, self.notebook_manager,
462 self.cluster_manager, self.log,
462 self.cluster_manager, self.log,
463 self.base_project_url, self.webapp_settings
463 self.base_project_url, self.webapp_settings
464 )
464 )
465 if self.certfile:
465 if self.certfile:
466 ssl_options = dict(certfile=self.certfile)
466 ssl_options = dict(certfile=self.certfile)
467 if self.keyfile:
467 if self.keyfile:
468 ssl_options['keyfile'] = self.keyfile
468 ssl_options['keyfile'] = self.keyfile
469 else:
469 else:
470 ssl_options = None
470 ssl_options = None
471 self.web_app.password = self.password
471 self.web_app.password = self.password
472 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
472 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
473 if not self.ip:
473 if not self.ip:
474 warning = "WARNING: The notebook server is listening on all IP addresses"
474 warning = "WARNING: The notebook server is listening on all IP addresses"
475 if ssl_options is None:
475 if ssl_options is None:
476 warning += ", and not using any encryption"
476 self.log.critical(warning + " and not using encryption. This"
477 "is not recommended.")
477 if not self.password and not self.read_only:
478 if not self.password and not self.read_only:
478 warning += ", and not using authentication"
479 self.log.critical(warning + "and not using authentication."
479 self.log.critical(warning + ". This is highly insecure and not recommended.")
480 "This is highly insecure and not recommended.")
480 success = None
481 success = None
481 for port in random_ports(self.port, self.port_retries+1):
482 for port in random_ports(self.port, self.port_retries+1):
482 try:
483 try:
483 self.http_server.listen(port, self.ip)
484 self.http_server.listen(port, self.ip)
484 except socket.error as e:
485 except socket.error as e:
485 if e.errno != errno.EADDRINUSE:
486 if e.errno != errno.EADDRINUSE:
486 raise
487 raise
487 self.log.info('The port %i is already in use, trying another random port.' % port)
488 self.log.info('The port %i is already in use, trying another random port.' % port)
488 else:
489 else:
489 self.port = port
490 self.port = port
490 success = True
491 success = True
491 break
492 break
492 if not success:
493 if not success:
493 self.log.critical('ERROR: the notebook server could not be started because '
494 self.log.critical('ERROR: the notebook server could not be started because '
494 'no available port could be found.')
495 'no available port could be found.')
495 self.exit(1)
496 self.exit(1)
496
497
497 def init_signal(self):
498 def init_signal(self):
498 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
499 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
499 # safely extract zmq version info:
500 # safely extract zmq version info:
500 try:
501 try:
501 zmq_v = zmq.pyzmq_version_info()
502 zmq_v = zmq.pyzmq_version_info()
502 except AttributeError:
503 except AttributeError:
503 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
504 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
504 if 'dev' in zmq.__version__:
505 if 'dev' in zmq.__version__:
505 zmq_v.append(999)
506 zmq_v.append(999)
506 zmq_v = tuple(zmq_v)
507 zmq_v = tuple(zmq_v)
507 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
508 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
508 # This won't work with 2.1.7 and
509 # This won't work with 2.1.7 and
509 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
510 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
510 # but it will work
511 # but it will work
511 signal.signal(signal.SIGINT, self._handle_sigint)
512 signal.signal(signal.SIGINT, self._handle_sigint)
512 signal.signal(signal.SIGTERM, self._signal_stop)
513 signal.signal(signal.SIGTERM, self._signal_stop)
513
514
514 def _handle_sigint(self, sig, frame):
515 def _handle_sigint(self, sig, frame):
515 """SIGINT handler spawns confirmation dialog"""
516 """SIGINT handler spawns confirmation dialog"""
516 # register more forceful signal handler for ^C^C case
517 # register more forceful signal handler for ^C^C case
517 signal.signal(signal.SIGINT, self._signal_stop)
518 signal.signal(signal.SIGINT, self._signal_stop)
518 # request confirmation dialog in bg thread, to avoid
519 # request confirmation dialog in bg thread, to avoid
519 # blocking the App
520 # blocking the App
520 thread = threading.Thread(target=self._confirm_exit)
521 thread = threading.Thread(target=self._confirm_exit)
521 thread.daemon = True
522 thread.daemon = True
522 thread.start()
523 thread.start()
523
524
524 def _restore_sigint_handler(self):
525 def _restore_sigint_handler(self):
525 """callback for restoring original SIGINT handler"""
526 """callback for restoring original SIGINT handler"""
526 signal.signal(signal.SIGINT, self._handle_sigint)
527 signal.signal(signal.SIGINT, self._handle_sigint)
527
528
528 def _confirm_exit(self):
529 def _confirm_exit(self):
529 """confirm shutdown on ^C
530 """confirm shutdown on ^C
530
531
531 A second ^C, or answering 'y' within 5s will cause shutdown,
532 A second ^C, or answering 'y' within 5s will cause shutdown,
532 otherwise original SIGINT handler will be restored.
533 otherwise original SIGINT handler will be restored.
533
534
534 This doesn't work on Windows.
535 This doesn't work on Windows.
535 """
536 """
536 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
537 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
537 time.sleep(0.1)
538 time.sleep(0.1)
538 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
539 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
539 sys.stdout.flush()
540 sys.stdout.flush()
540 r,w,x = select.select([sys.stdin], [], [], 5)
541 r,w,x = select.select([sys.stdin], [], [], 5)
541 if r:
542 if r:
542 line = sys.stdin.readline()
543 line = sys.stdin.readline()
543 if line.lower().startswith('y'):
544 if line.lower().startswith('y'):
544 self.log.critical("Shutdown confirmed")
545 self.log.critical("Shutdown confirmed")
545 ioloop.IOLoop.instance().stop()
546 ioloop.IOLoop.instance().stop()
546 return
547 return
547 else:
548 else:
548 print "No answer for 5s:",
549 print "No answer for 5s:",
549 print "resuming operation..."
550 print "resuming operation..."
550 # no answer, or answer is no:
551 # no answer, or answer is no:
551 # set it back to original SIGINT handler
552 # set it back to original SIGINT handler
552 # use IOLoop.add_callback because signal.signal must be called
553 # use IOLoop.add_callback because signal.signal must be called
553 # from main thread
554 # from main thread
554 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
555 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
555
556
556 def _signal_stop(self, sig, frame):
557 def _signal_stop(self, sig, frame):
557 self.log.critical("received signal %s, stopping", sig)
558 self.log.critical("received signal %s, stopping", sig)
558 ioloop.IOLoop.instance().stop()
559 ioloop.IOLoop.instance().stop()
559
560
560 @catch_config_error
561 @catch_config_error
561 def initialize(self, argv=None):
562 def initialize(self, argv=None):
562 self.init_logging()
563 self.init_logging()
563 super(NotebookApp, self).initialize(argv)
564 super(NotebookApp, self).initialize(argv)
564 self.init_configurables()
565 self.init_configurables()
565 self.init_webapp()
566 self.init_webapp()
566 self.init_signal()
567 self.init_signal()
567
568
568 def cleanup_kernels(self):
569 def cleanup_kernels(self):
569 """shutdown all kernels
570 """shutdown all kernels
570
571
571 The kernels will shutdown themselves when this process no longer exists,
572 The kernels will shutdown themselves when this process no longer exists,
572 but explicit shutdown allows the KernelManagers to cleanup the connection files.
573 but explicit shutdown allows the KernelManagers to cleanup the connection files.
573 """
574 """
574 self.log.info('Shutting down kernels')
575 self.log.info('Shutting down kernels')
575 km = self.kernel_manager
576 km = self.kernel_manager
576 # copy list, since shutdown_kernel deletes keys
577 # copy list, since shutdown_kernel deletes keys
577 for kid in list(km.kernel_ids):
578 for kid in list(km.kernel_ids):
578 km.shutdown_kernel(kid)
579 km.shutdown_kernel(kid)
579
580
580 def start(self):
581 def start(self):
581 ip = self.ip if self.ip else '[all ip addresses on your system]'
582 ip = self.ip if self.ip else '[all ip addresses on your system]'
582 proto = 'https' if self.certfile else 'http'
583 proto = 'https' if self.certfile else 'http'
583 info = self.log.info
584 info = self.log.info
584 info("The IPython Notebook is running at: %s://%s:%i%s" %
585 info("The IPython Notebook is running at: %s://%s:%i%s" %
585 (proto, ip, self.port,self.base_project_url) )
586 (proto, ip, self.port,self.base_project_url) )
586 info("Use Control-C to stop this server and shut down all kernels.")
587 info("Use Control-C to stop this server and shut down all kernels.")
587
588
588 if self.open_browser or self.file_to_run:
589 if self.open_browser or self.file_to_run:
589 ip = self.ip or '127.0.0.1'
590 ip = self.ip or '127.0.0.1'
590 try:
591 try:
591 browser = webbrowser.get(self.browser or None)
592 browser = webbrowser.get(self.browser or None)
592 except webbrowser.Error as e:
593 except webbrowser.Error as e:
593 self.log.warn('No web browser found: %s.' % e)
594 self.log.warn('No web browser found: %s.' % e)
594 browser = None
595 browser = None
595
596
596 if self.file_to_run:
597 if self.file_to_run:
597 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
598 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
598 url = self.notebook_manager.rev_mapping.get(name, '')
599 url = self.notebook_manager.rev_mapping.get(name, '')
599 else:
600 else:
600 url = ''
601 url = ''
601 if browser:
602 if browser:
602 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
603 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
603 self.port, self.base_project_url, url), new=2)
604 self.port, self.base_project_url, url), new=2)
604 threading.Thread(target=b).start()
605 threading.Thread(target=b).start()
605 try:
606 try:
606 ioloop.IOLoop.instance().start()
607 ioloop.IOLoop.instance().start()
607 except KeyboardInterrupt:
608 except KeyboardInterrupt:
608 info("Interrupted...")
609 info("Interrupted...")
609 finally:
610 finally:
610 self.cleanup_kernels()
611 self.cleanup_kernels()
611
612
612
613
613 #-----------------------------------------------------------------------------
614 #-----------------------------------------------------------------------------
614 # Main entry point
615 # Main entry point
615 #-----------------------------------------------------------------------------
616 #-----------------------------------------------------------------------------
616
617
617 def launch_new_instance():
618 def launch_new_instance():
618 app = NotebookApp.instance()
619 app = NotebookApp.instance()
619 app.initialize()
620 app.initialize()
620 app.start()
621 app.start()
621
622
General Comments 0
You need to be logged in to leave comments. Login now