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