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