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