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