##// END OF EJS Templates
Remove unused imports in IPython.html
Thomas Kluyver -
Show More
@@ -1,753 +1,751 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) 2013 The IPython Development Team
9 # Copyright (C) 2013 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 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 uuid
31 import webbrowser
30 import webbrowser
32
31
33
32
34 # Third party
33 # Third party
35 # check for pyzmq 2.1.11
34 # check for pyzmq 2.1.11
36 from IPython.utils.zmqrelated import check_for_zmq
35 from IPython.utils.zmqrelated import check_for_zmq
37 check_for_zmq('2.1.11', 'IPython.html')
36 check_for_zmq('2.1.11', 'IPython.html')
38
37
39 import zmq
40 from jinja2 import Environment, FileSystemLoader
38 from jinja2 import Environment, FileSystemLoader
41
39
42 # Install the pyzmq ioloop. This has to be done before anything else from
40 # Install the pyzmq ioloop. This has to be done before anything else from
43 # tornado is imported.
41 # tornado is imported.
44 from zmq.eventloop import ioloop
42 from zmq.eventloop import ioloop
45 ioloop.install()
43 ioloop.install()
46
44
47 # check for tornado 2.1.0
45 # check for tornado 2.1.0
48 msg = "The IPython Notebook requires tornado >= 2.1.0"
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
49 try:
47 try:
50 import tornado
48 import tornado
51 except ImportError:
49 except ImportError:
52 raise ImportError(msg)
50 raise ImportError(msg)
53 try:
51 try:
54 version_info = tornado.version_info
52 version_info = tornado.version_info
55 except AttributeError:
53 except AttributeError:
56 raise ImportError(msg + ", but you have < 1.1.0")
54 raise ImportError(msg + ", but you have < 1.1.0")
57 if version_info < (2,1,0):
55 if version_info < (2,1,0):
58 raise ImportError(msg + ", but you have %s" % tornado.version)
56 raise ImportError(msg + ", but you have %s" % tornado.version)
59
57
60 from tornado import httpserver
58 from tornado import httpserver
61 from tornado import web
59 from tornado import web
62
60
63 # Our own libraries
61 # Our own libraries
64 from IPython.html import DEFAULT_STATIC_FILES_PATH
62 from IPython.html import DEFAULT_STATIC_FILES_PATH
65
63
66 from .services.kernels.kernelmanager import MappingKernelManager
64 from .services.kernels.kernelmanager import MappingKernelManager
67 from .services.notebooks.nbmanager import NotebookManager
65 from .services.notebooks.nbmanager import NotebookManager
68 from .services.notebooks.filenbmanager import FileNotebookManager
66 from .services.notebooks.filenbmanager import FileNotebookManager
69 from .services.clusters.clustermanager import ClusterManager
67 from .services.clusters.clustermanager import ClusterManager
70
68
71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
72
70
73 from IPython.config.application import catch_config_error, boolean_flag
71 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import BaseIPythonApplication
72 from IPython.core.application import BaseIPythonApplication
75 from IPython.consoleapp import IPythonConsoleApp
73 from IPython.consoleapp import IPythonConsoleApp
76 from IPython.kernel import swallow_argv
74 from IPython.kernel import swallow_argv
77 from IPython.kernel.zmq.session import default_secure
75 from IPython.kernel.zmq.session import default_secure
78 from IPython.kernel.zmq.kernelapp import (
76 from IPython.kernel.zmq.kernelapp import (
79 kernel_flags,
77 kernel_flags,
80 kernel_aliases,
78 kernel_aliases,
81 )
79 )
82 from IPython.utils.importstring import import_item
80 from IPython.utils.importstring import import_item
83 from IPython.utils.localinterfaces import LOCALHOST
81 from IPython.utils.localinterfaces import LOCALHOST
84 from IPython.utils import submodule
82 from IPython.utils import submodule
85 from IPython.utils.traitlets import (
83 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes,
84 Dict, Unicode, Integer, List, Bool, Bytes,
87 DottedObjectName
85 DottedObjectName
88 )
86 )
89 from IPython.utils import py3compat
87 from IPython.utils import py3compat
90 from IPython.utils.path import filefind
88 from IPython.utils.path import filefind
91
89
92 from .utils import url_path_join
90 from .utils import url_path_join
93
91
94 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
95 # Module globals
93 # Module globals
96 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
97
95
98 _examples = """
96 _examples = """
99 ipython notebook # start the notebook
97 ipython notebook # start the notebook
100 ipython notebook --profile=sympy # use the sympy profile
98 ipython notebook --profile=sympy # use the sympy profile
101 ipython notebook --pylab=inline # pylab in inline plotting mode
99 ipython notebook --pylab=inline # pylab in inline plotting mode
102 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
101 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
104 """
102 """
105
103
106 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
107 # Helper functions
105 # Helper functions
108 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
109
107
110 def random_ports(port, n):
108 def random_ports(port, n):
111 """Generate a list of n random ports near the given port.
109 """Generate a list of n random ports near the given port.
112
110
113 The first 5 ports will be sequential, and the remaining n-5 will be
111 The first 5 ports will be sequential, and the remaining n-5 will be
114 randomly selected in the range [port-2*n, port+2*n].
112 randomly selected in the range [port-2*n, port+2*n].
115 """
113 """
116 for i in range(min(5, n)):
114 for i in range(min(5, n)):
117 yield port + i
115 yield port + i
118 for i in range(n-5):
116 for i in range(n-5):
119 yield port + random.randint(-2*n, 2*n)
117 yield port + random.randint(-2*n, 2*n)
120
118
121 def load_handlers(name):
119 def load_handlers(name):
122 """Load the (URL pattern, handler) tuples for each component."""
120 """Load the (URL pattern, handler) tuples for each component."""
123 name = 'IPython.html.' + name
121 name = 'IPython.html.' + name
124 mod = __import__(name, fromlist=['default_handlers'])
122 mod = __import__(name, fromlist=['default_handlers'])
125 return mod.default_handlers
123 return mod.default_handlers
126
124
127 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
128 # The Tornado web application
126 # The Tornado web application
129 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
130
128
131 class NotebookWebApplication(web.Application):
129 class NotebookWebApplication(web.Application):
132
130
133 def __init__(self, ipython_app, kernel_manager, notebook_manager,
131 def __init__(self, ipython_app, kernel_manager, notebook_manager,
134 cluster_manager, log,
132 cluster_manager, log,
135 base_project_url, settings_overrides):
133 base_project_url, settings_overrides):
136
134
137 settings = self.init_settings(
135 settings = self.init_settings(
138 ipython_app, kernel_manager, notebook_manager, cluster_manager,
136 ipython_app, kernel_manager, notebook_manager, cluster_manager,
139 log, base_project_url, settings_overrides)
137 log, base_project_url, settings_overrides)
140 handlers = self.init_handlers(settings)
138 handlers = self.init_handlers(settings)
141
139
142 super(NotebookWebApplication, self).__init__(handlers, **settings)
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
143
141
144 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
142 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
145 cluster_manager, log,
143 cluster_manager, log,
146 base_project_url, settings_overrides):
144 base_project_url, settings_overrides):
147 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
145 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
148 # base_project_url will always be unicode, which will in turn
146 # base_project_url will always be unicode, which will in turn
149 # make the patterns unicode, and ultimately result in unicode
147 # make the patterns unicode, and ultimately result in unicode
150 # keys in kwargs to handler._execute(**kwargs) in tornado.
148 # keys in kwargs to handler._execute(**kwargs) in tornado.
151 # This enforces that base_project_url be ascii in that situation.
149 # This enforces that base_project_url be ascii in that situation.
152 #
150 #
153 # Note that the URLs these patterns check against are escaped,
151 # Note that the URLs these patterns check against are escaped,
154 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
152 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
155 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
153 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
156 template_path = os.path.join(os.path.dirname(__file__), "templates")
154 template_path = os.path.join(os.path.dirname(__file__), "templates")
157 settings = dict(
155 settings = dict(
158 # basics
156 # basics
159 base_project_url=base_project_url,
157 base_project_url=base_project_url,
160 base_kernel_url=ipython_app.base_kernel_url,
158 base_kernel_url=ipython_app.base_kernel_url,
161 template_path=template_path,
159 template_path=template_path,
162 static_path=ipython_app.static_file_path,
160 static_path=ipython_app.static_file_path,
163 static_handler_class = FileFindHandler,
161 static_handler_class = FileFindHandler,
164 static_url_prefix = url_path_join(base_project_url,'/static/'),
162 static_url_prefix = url_path_join(base_project_url,'/static/'),
165
163
166 # authentication
164 # authentication
167 cookie_secret=ipython_app.cookie_secret,
165 cookie_secret=ipython_app.cookie_secret,
168 login_url=url_path_join(base_project_url,'/login'),
166 login_url=url_path_join(base_project_url,'/login'),
169 read_only=ipython_app.read_only,
167 read_only=ipython_app.read_only,
170 password=ipython_app.password,
168 password=ipython_app.password,
171
169
172 # managers
170 # managers
173 kernel_manager=kernel_manager,
171 kernel_manager=kernel_manager,
174 notebook_manager=notebook_manager,
172 notebook_manager=notebook_manager,
175 cluster_manager=cluster_manager,
173 cluster_manager=cluster_manager,
176
174
177 # IPython stuff
175 # IPython stuff
178 mathjax_url=ipython_app.mathjax_url,
176 mathjax_url=ipython_app.mathjax_url,
179 max_msg_size=ipython_app.max_msg_size,
177 max_msg_size=ipython_app.max_msg_size,
180 config=ipython_app.config,
178 config=ipython_app.config,
181 use_less=ipython_app.use_less,
179 use_less=ipython_app.use_less,
182 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
180 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
183 )
181 )
184
182
185 # allow custom overrides for the tornado web app.
183 # allow custom overrides for the tornado web app.
186 settings.update(settings_overrides)
184 settings.update(settings_overrides)
187 return settings
185 return settings
188
186
189 def init_handlers(self, settings):
187 def init_handlers(self, settings):
190 # Load the (URL pattern, handler) tuples for each component.
188 # Load the (URL pattern, handler) tuples for each component.
191 handlers = []
189 handlers = []
192 handlers.extend(load_handlers('base.handlers'))
190 handlers.extend(load_handlers('base.handlers'))
193 handlers.extend(load_handlers('tree.handlers'))
191 handlers.extend(load_handlers('tree.handlers'))
194 handlers.extend(load_handlers('auth.login'))
192 handlers.extend(load_handlers('auth.login'))
195 handlers.extend(load_handlers('auth.logout'))
193 handlers.extend(load_handlers('auth.logout'))
196 handlers.extend(load_handlers('notebook.handlers'))
194 handlers.extend(load_handlers('notebook.handlers'))
197 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.kernels.handlers'))
198 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.notebooks.handlers'))
199 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend(load_handlers('services.clusters.handlers'))
200 handlers.extend([
198 handlers.extend([
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
199 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
202 ])
200 ])
203 # prepend base_project_url onto the patterns that we match
201 # prepend base_project_url onto the patterns that we match
204 new_handlers = []
202 new_handlers = []
205 for handler in handlers:
203 for handler in handlers:
206 pattern = url_path_join(settings['base_project_url'], handler[0])
204 pattern = url_path_join(settings['base_project_url'], handler[0])
207 new_handler = tuple([pattern] + list(handler[1:]))
205 new_handler = tuple([pattern] + list(handler[1:]))
208 new_handlers.append(new_handler)
206 new_handlers.append(new_handler)
209 return new_handlers
207 return new_handlers
210
208
211
209
212
210
213 #-----------------------------------------------------------------------------
211 #-----------------------------------------------------------------------------
214 # Aliases and Flags
212 # Aliases and Flags
215 #-----------------------------------------------------------------------------
213 #-----------------------------------------------------------------------------
216
214
217 flags = dict(kernel_flags)
215 flags = dict(kernel_flags)
218 flags['no-browser']=(
216 flags['no-browser']=(
219 {'NotebookApp' : {'open_browser' : False}},
217 {'NotebookApp' : {'open_browser' : False}},
220 "Don't open the notebook in a browser after startup."
218 "Don't open the notebook in a browser after startup."
221 )
219 )
222 flags['no-mathjax']=(
220 flags['no-mathjax']=(
223 {'NotebookApp' : {'enable_mathjax' : False}},
221 {'NotebookApp' : {'enable_mathjax' : False}},
224 """Disable MathJax
222 """Disable MathJax
225
223
226 MathJax is the javascript library IPython uses to render math/LaTeX. It is
224 MathJax is the javascript library IPython uses to render math/LaTeX. It is
227 very large, so you may want to disable it if you have a slow internet
225 very large, so you may want to disable it if you have a slow internet
228 connection, or for offline use of the notebook.
226 connection, or for offline use of the notebook.
229
227
230 When disabled, equations etc. will appear as their untransformed TeX source.
228 When disabled, equations etc. will appear as their untransformed TeX source.
231 """
229 """
232 )
230 )
233 flags['read-only'] = (
231 flags['read-only'] = (
234 {'NotebookApp' : {'read_only' : True}},
232 {'NotebookApp' : {'read_only' : True}},
235 """Allow read-only access to notebooks.
233 """Allow read-only access to notebooks.
236
234
237 When using a password to protect the notebook server, this flag
235 When using a password to protect the notebook server, this flag
238 allows unauthenticated clients to view the notebook list, and
236 allows unauthenticated clients to view the notebook list, and
239 individual notebooks, but not edit them, start kernels, or run
237 individual notebooks, but not edit them, start kernels, or run
240 code.
238 code.
241
239
242 If no password is set, the server will be entirely read-only.
240 If no password is set, the server will be entirely read-only.
243 """
241 """
244 )
242 )
245
243
246 # Add notebook manager flags
244 # Add notebook manager flags
247 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
245 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
248 'Auto-save a .py script everytime the .ipynb notebook is saved',
246 'Auto-save a .py script everytime the .ipynb notebook is saved',
249 'Do not auto-save .py scripts for every notebook'))
247 'Do not auto-save .py scripts for every notebook'))
250
248
251 # the flags that are specific to the frontend
249 # the flags that are specific to the frontend
252 # these must be scrubbed before being passed to the kernel,
250 # these must be scrubbed before being passed to the kernel,
253 # or it will raise an error on unrecognized flags
251 # or it will raise an error on unrecognized flags
254 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
252 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
255
253
256 aliases = dict(kernel_aliases)
254 aliases = dict(kernel_aliases)
257
255
258 aliases.update({
256 aliases.update({
259 'ip': 'NotebookApp.ip',
257 'ip': 'NotebookApp.ip',
260 'port': 'NotebookApp.port',
258 'port': 'NotebookApp.port',
261 'port-retries': 'NotebookApp.port_retries',
259 'port-retries': 'NotebookApp.port_retries',
262 'transport': 'KernelManager.transport',
260 'transport': 'KernelManager.transport',
263 'keyfile': 'NotebookApp.keyfile',
261 'keyfile': 'NotebookApp.keyfile',
264 'certfile': 'NotebookApp.certfile',
262 'certfile': 'NotebookApp.certfile',
265 'notebook-dir': 'NotebookManager.notebook_dir',
263 'notebook-dir': 'NotebookManager.notebook_dir',
266 'browser': 'NotebookApp.browser',
264 'browser': 'NotebookApp.browser',
267 })
265 })
268
266
269 # remove ipkernel flags that are singletons, and don't make sense in
267 # remove ipkernel flags that are singletons, and don't make sense in
270 # multi-kernel evironment:
268 # multi-kernel evironment:
271 aliases.pop('f', None)
269 aliases.pop('f', None)
272
270
273 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
271 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
274 u'notebook-dir']
272 u'notebook-dir']
275
273
276 #-----------------------------------------------------------------------------
274 #-----------------------------------------------------------------------------
277 # NotebookApp
275 # NotebookApp
278 #-----------------------------------------------------------------------------
276 #-----------------------------------------------------------------------------
279
277
280 class NotebookApp(BaseIPythonApplication):
278 class NotebookApp(BaseIPythonApplication):
281
279
282 name = 'ipython-notebook'
280 name = 'ipython-notebook'
283 default_config_file_name='ipython_notebook_config.py'
281 default_config_file_name='ipython_notebook_config.py'
284
282
285 description = """
283 description = """
286 The IPython HTML Notebook.
284 The IPython HTML Notebook.
287
285
288 This launches a Tornado based HTML Notebook Server that serves up an
286 This launches a Tornado based HTML Notebook Server that serves up an
289 HTML5/Javascript Notebook client.
287 HTML5/Javascript Notebook client.
290 """
288 """
291 examples = _examples
289 examples = _examples
292
290
293 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
291 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
294 FileNotebookManager]
292 FileNotebookManager]
295 flags = Dict(flags)
293 flags = Dict(flags)
296 aliases = Dict(aliases)
294 aliases = Dict(aliases)
297
295
298 kernel_argv = List(Unicode)
296 kernel_argv = List(Unicode)
299
297
300 max_msg_size = Integer(65536, config=True, help="""
298 max_msg_size = Integer(65536, config=True, help="""
301 The max raw message size accepted from the browser
299 The max raw message size accepted from the browser
302 over a WebSocket connection.
300 over a WebSocket connection.
303 """)
301 """)
304
302
305 def _log_level_default(self):
303 def _log_level_default(self):
306 return logging.INFO
304 return logging.INFO
307
305
308 def _log_format_default(self):
306 def _log_format_default(self):
309 """override default log format to include time"""
307 """override default log format to include time"""
310 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
308 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
311
309
312 # create requested profiles by default, if they don't exist:
310 # create requested profiles by default, if they don't exist:
313 auto_create = Bool(True)
311 auto_create = Bool(True)
314
312
315 # file to be opened in the notebook server
313 # file to be opened in the notebook server
316 file_to_run = Unicode('')
314 file_to_run = Unicode('')
317
315
318 # Network related information.
316 # Network related information.
319
317
320 ip = Unicode(LOCALHOST, config=True,
318 ip = Unicode(LOCALHOST, config=True,
321 help="The IP address the notebook server will listen on."
319 help="The IP address the notebook server will listen on."
322 )
320 )
323
321
324 def _ip_changed(self, name, old, new):
322 def _ip_changed(self, name, old, new):
325 if new == u'*': self.ip = u''
323 if new == u'*': self.ip = u''
326
324
327 port = Integer(8888, config=True,
325 port = Integer(8888, config=True,
328 help="The port the notebook server will listen on."
326 help="The port the notebook server will listen on."
329 )
327 )
330 port_retries = Integer(50, config=True,
328 port_retries = Integer(50, config=True,
331 help="The number of additional ports to try if the specified port is not available."
329 help="The number of additional ports to try if the specified port is not available."
332 )
330 )
333
331
334 certfile = Unicode(u'', config=True,
332 certfile = Unicode(u'', config=True,
335 help="""The full path to an SSL/TLS certificate file."""
333 help="""The full path to an SSL/TLS certificate file."""
336 )
334 )
337
335
338 keyfile = Unicode(u'', config=True,
336 keyfile = Unicode(u'', config=True,
339 help="""The full path to a private key file for usage with SSL/TLS."""
337 help="""The full path to a private key file for usage with SSL/TLS."""
340 )
338 )
341
339
342 cookie_secret = Bytes(b'', config=True,
340 cookie_secret = Bytes(b'', config=True,
343 help="""The random bytes used to secure cookies.
341 help="""The random bytes used to secure cookies.
344 By default this is a new random number every time you start the Notebook.
342 By default this is a new random number every time you start the Notebook.
345 Set it to a value in a config file to enable logins to persist across server sessions.
343 Set it to a value in a config file to enable logins to persist across server sessions.
346
344
347 Note: Cookie secrets should be kept private, do not share config files with
345 Note: Cookie secrets should be kept private, do not share config files with
348 cookie_secret stored in plaintext (you can read the value from a file).
346 cookie_secret stored in plaintext (you can read the value from a file).
349 """
347 """
350 )
348 )
351 def _cookie_secret_default(self):
349 def _cookie_secret_default(self):
352 return os.urandom(1024)
350 return os.urandom(1024)
353
351
354 password = Unicode(u'', config=True,
352 password = Unicode(u'', config=True,
355 help="""Hashed password to use for web authentication.
353 help="""Hashed password to use for web authentication.
356
354
357 To generate, type in a python/IPython shell:
355 To generate, type in a python/IPython shell:
358
356
359 from IPython.lib import passwd; passwd()
357 from IPython.lib import passwd; passwd()
360
358
361 The string should be of the form type:salt:hashed-password.
359 The string should be of the form type:salt:hashed-password.
362 """
360 """
363 )
361 )
364
362
365 open_browser = Bool(True, config=True,
363 open_browser = Bool(True, config=True,
366 help="""Whether to open in a browser after starting.
364 help="""Whether to open in a browser after starting.
367 The specific browser used is platform dependent and
365 The specific browser used is platform dependent and
368 determined by the python standard library `webbrowser`
366 determined by the python standard library `webbrowser`
369 module, unless it is overridden using the --browser
367 module, unless it is overridden using the --browser
370 (NotebookApp.browser) configuration option.
368 (NotebookApp.browser) configuration option.
371 """)
369 """)
372
370
373 browser = Unicode(u'', config=True,
371 browser = Unicode(u'', config=True,
374 help="""Specify what command to use to invoke a web
372 help="""Specify what command to use to invoke a web
375 browser when opening the notebook. If not specified, the
373 browser when opening the notebook. If not specified, the
376 default browser will be determined by the `webbrowser`
374 default browser will be determined by the `webbrowser`
377 standard library module, which allows setting of the
375 standard library module, which allows setting of the
378 BROWSER environment variable to override it.
376 BROWSER environment variable to override it.
379 """)
377 """)
380
378
381 read_only = Bool(False, config=True,
379 read_only = Bool(False, config=True,
382 help="Whether to prevent editing/execution of notebooks."
380 help="Whether to prevent editing/execution of notebooks."
383 )
381 )
384
382
385 use_less = Bool(False, config=True,
383 use_less = Bool(False, config=True,
386 help="""Wether to use Browser Side less-css parsing
384 help="""Wether to use Browser Side less-css parsing
387 instead of compiled css version in templates that allows
385 instead of compiled css version in templates that allows
388 it. This is mainly convenient when working on the less
386 it. This is mainly convenient when working on the less
389 file to avoid a build step, or if user want to overwrite
387 file to avoid a build step, or if user want to overwrite
390 some of the less variables without having to recompile
388 some of the less variables without having to recompile
391 everything.
389 everything.
392
390
393 You will need to install the less.js component in the static directory
391 You will need to install the less.js component in the static directory
394 either in the source tree or in your profile folder.
392 either in the source tree or in your profile folder.
395 """)
393 """)
396
394
397 webapp_settings = Dict(config=True,
395 webapp_settings = Dict(config=True,
398 help="Supply overrides for the tornado.web.Application that the "
396 help="Supply overrides for the tornado.web.Application that the "
399 "IPython notebook uses.")
397 "IPython notebook uses.")
400
398
401 enable_mathjax = Bool(True, config=True,
399 enable_mathjax = Bool(True, config=True,
402 help="""Whether to enable MathJax for typesetting math/TeX
400 help="""Whether to enable MathJax for typesetting math/TeX
403
401
404 MathJax is the javascript library IPython uses to render math/LaTeX. It is
402 MathJax is the javascript library IPython uses to render math/LaTeX. It is
405 very large, so you may want to disable it if you have a slow internet
403 very large, so you may want to disable it if you have a slow internet
406 connection, or for offline use of the notebook.
404 connection, or for offline use of the notebook.
407
405
408 When disabled, equations etc. will appear as their untransformed TeX source.
406 When disabled, equations etc. will appear as their untransformed TeX source.
409 """
407 """
410 )
408 )
411 def _enable_mathjax_changed(self, name, old, new):
409 def _enable_mathjax_changed(self, name, old, new):
412 """set mathjax url to empty if mathjax is disabled"""
410 """set mathjax url to empty if mathjax is disabled"""
413 if not new:
411 if not new:
414 self.mathjax_url = u''
412 self.mathjax_url = u''
415
413
416 base_project_url = Unicode('/', config=True,
414 base_project_url = Unicode('/', config=True,
417 help='''The base URL for the notebook server.
415 help='''The base URL for the notebook server.
418
416
419 Leading and trailing slashes can be omitted,
417 Leading and trailing slashes can be omitted,
420 and will automatically be added.
418 and will automatically be added.
421 ''')
419 ''')
422 def _base_project_url_changed(self, name, old, new):
420 def _base_project_url_changed(self, name, old, new):
423 if not new.startswith('/'):
421 if not new.startswith('/'):
424 self.base_project_url = '/'+new
422 self.base_project_url = '/'+new
425 elif not new.endswith('/'):
423 elif not new.endswith('/'):
426 self.base_project_url = new+'/'
424 self.base_project_url = new+'/'
427
425
428 base_kernel_url = Unicode('/', config=True,
426 base_kernel_url = Unicode('/', config=True,
429 help='''The base URL for the kernel server
427 help='''The base URL for the kernel server
430
428
431 Leading and trailing slashes can be omitted,
429 Leading and trailing slashes can be omitted,
432 and will automatically be added.
430 and will automatically be added.
433 ''')
431 ''')
434 def _base_kernel_url_changed(self, name, old, new):
432 def _base_kernel_url_changed(self, name, old, new):
435 if not new.startswith('/'):
433 if not new.startswith('/'):
436 self.base_kernel_url = '/'+new
434 self.base_kernel_url = '/'+new
437 elif not new.endswith('/'):
435 elif not new.endswith('/'):
438 self.base_kernel_url = new+'/'
436 self.base_kernel_url = new+'/'
439
437
440 websocket_url = Unicode("", config=True,
438 websocket_url = Unicode("", config=True,
441 help="""The base URL for the websocket server,
439 help="""The base URL for the websocket server,
442 if it differs from the HTTP server (hint: it almost certainly doesn't).
440 if it differs from the HTTP server (hint: it almost certainly doesn't).
443
441
444 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
442 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
445 """
443 """
446 )
444 )
447
445
448 extra_static_paths = List(Unicode, config=True,
446 extra_static_paths = List(Unicode, config=True,
449 help="""Extra paths to search for serving static files.
447 help="""Extra paths to search for serving static files.
450
448
451 This allows adding javascript/css to be available from the notebook server machine,
449 This allows adding javascript/css to be available from the notebook server machine,
452 or overriding individual files in the IPython"""
450 or overriding individual files in the IPython"""
453 )
451 )
454 def _extra_static_paths_default(self):
452 def _extra_static_paths_default(self):
455 return [os.path.join(self.profile_dir.location, 'static')]
453 return [os.path.join(self.profile_dir.location, 'static')]
456
454
457 @property
455 @property
458 def static_file_path(self):
456 def static_file_path(self):
459 """return extra paths + the default location"""
457 """return extra paths + the default location"""
460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
458 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461
459
462 mathjax_url = Unicode("", config=True,
460 mathjax_url = Unicode("", config=True,
463 help="""The url for MathJax.js."""
461 help="""The url for MathJax.js."""
464 )
462 )
465 def _mathjax_url_default(self):
463 def _mathjax_url_default(self):
466 if not self.enable_mathjax:
464 if not self.enable_mathjax:
467 return u''
465 return u''
468 static_url_prefix = self.webapp_settings.get("static_url_prefix",
466 static_url_prefix = self.webapp_settings.get("static_url_prefix",
469 "/static/")
467 "/static/")
470 try:
468 try:
471 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
469 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
472 except IOError:
470 except IOError:
473 if self.certfile:
471 if self.certfile:
474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
472 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 base = u"https://c328740.ssl.cf1.rackcdn.com"
473 base = u"https://c328740.ssl.cf1.rackcdn.com"
476 else:
474 else:
477 base = u"http://cdn.mathjax.org"
475 base = u"http://cdn.mathjax.org"
478
476
479 url = base + u"/mathjax/latest/MathJax.js"
477 url = base + u"/mathjax/latest/MathJax.js"
480 self.log.info("Using MathJax from CDN: %s", url)
478 self.log.info("Using MathJax from CDN: %s", url)
481 return url
479 return url
482 else:
480 else:
483 self.log.info("Using local MathJax from %s" % mathjax)
481 self.log.info("Using local MathJax from %s" % mathjax)
484 return static_url_prefix+u"mathjax/MathJax.js"
482 return static_url_prefix+u"mathjax/MathJax.js"
485
483
486 def _mathjax_url_changed(self, name, old, new):
484 def _mathjax_url_changed(self, name, old, new):
487 if new and not self.enable_mathjax:
485 if new and not self.enable_mathjax:
488 # enable_mathjax=False overrides mathjax_url
486 # enable_mathjax=False overrides mathjax_url
489 self.mathjax_url = u''
487 self.mathjax_url = u''
490 else:
488 else:
491 self.log.info("Using MathJax: %s", new)
489 self.log.info("Using MathJax: %s", new)
492
490
493 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
491 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
494 config=True,
492 config=True,
495 help='The notebook manager class to use.')
493 help='The notebook manager class to use.')
496
494
497 trust_xheaders = Bool(False, config=True,
495 trust_xheaders = Bool(False, config=True,
498 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
496 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
499 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
497 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
500 )
498 )
501
499
502 def parse_command_line(self, argv=None):
500 def parse_command_line(self, argv=None):
503 super(NotebookApp, self).parse_command_line(argv)
501 super(NotebookApp, self).parse_command_line(argv)
504 if argv is None:
502 if argv is None:
505 argv = sys.argv[1:]
503 argv = sys.argv[1:]
506
504
507 # Scrub frontend-specific flags
505 # Scrub frontend-specific flags
508 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
506 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
509 # Kernel should inherit default config file from frontend
507 # Kernel should inherit default config file from frontend
510 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
508 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
511
509
512 if self.extra_args:
510 if self.extra_args:
513 f = os.path.abspath(self.extra_args[0])
511 f = os.path.abspath(self.extra_args[0])
514 if os.path.isdir(f):
512 if os.path.isdir(f):
515 nbdir = f
513 nbdir = f
516 else:
514 else:
517 self.file_to_run = f
515 self.file_to_run = f
518 nbdir = os.path.dirname(f)
516 nbdir = os.path.dirname(f)
519 self.config.NotebookManager.notebook_dir = nbdir
517 self.config.NotebookManager.notebook_dir = nbdir
520
518
521 def init_configurables(self):
519 def init_configurables(self):
522 # force Session default to be secure
520 # force Session default to be secure
523 default_secure(self.config)
521 default_secure(self.config)
524 self.kernel_manager = MappingKernelManager(
522 self.kernel_manager = MappingKernelManager(
525 parent=self, log=self.log, kernel_argv=self.kernel_argv,
523 parent=self, log=self.log, kernel_argv=self.kernel_argv,
526 connection_dir = self.profile_dir.security_dir,
524 connection_dir = self.profile_dir.security_dir,
527 )
525 )
528 kls = import_item(self.notebook_manager_class)
526 kls = import_item(self.notebook_manager_class)
529 self.notebook_manager = kls(parent=self, log=self.log)
527 self.notebook_manager = kls(parent=self, log=self.log)
530 self.notebook_manager.load_notebook_names()
528 self.notebook_manager.load_notebook_names()
531 self.cluster_manager = ClusterManager(parent=self, log=self.log)
529 self.cluster_manager = ClusterManager(parent=self, log=self.log)
532 self.cluster_manager.update_profiles()
530 self.cluster_manager.update_profiles()
533
531
534 def init_logging(self):
532 def init_logging(self):
535 # This prevents double log messages because tornado use a root logger that
533 # This prevents double log messages because tornado use a root logger that
536 # self.log is a child of. The logging module dipatches log messages to a log
534 # self.log is a child of. The logging module dipatches log messages to a log
537 # and all of its ancenstors until propagate is set to False.
535 # and all of its ancenstors until propagate is set to False.
538 self.log.propagate = False
536 self.log.propagate = False
539
537
540 # hook up tornado 3's loggers to our app handlers
538 # hook up tornado 3's loggers to our app handlers
541 for name in ('access', 'application', 'general'):
539 for name in ('access', 'application', 'general'):
542 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
540 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543
541
544 def init_webapp(self):
542 def init_webapp(self):
545 """initialize tornado webapp and httpserver"""
543 """initialize tornado webapp and httpserver"""
546 self.web_app = NotebookWebApplication(
544 self.web_app = NotebookWebApplication(
547 self, self.kernel_manager, self.notebook_manager,
545 self, self.kernel_manager, self.notebook_manager,
548 self.cluster_manager, self.log,
546 self.cluster_manager, self.log,
549 self.base_project_url, self.webapp_settings
547 self.base_project_url, self.webapp_settings
550 )
548 )
551 if self.certfile:
549 if self.certfile:
552 ssl_options = dict(certfile=self.certfile)
550 ssl_options = dict(certfile=self.certfile)
553 if self.keyfile:
551 if self.keyfile:
554 ssl_options['keyfile'] = self.keyfile
552 ssl_options['keyfile'] = self.keyfile
555 else:
553 else:
556 ssl_options = None
554 ssl_options = None
557 self.web_app.password = self.password
555 self.web_app.password = self.password
558 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
556 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
559 xheaders=self.trust_xheaders)
557 xheaders=self.trust_xheaders)
560 if not self.ip:
558 if not self.ip:
561 warning = "WARNING: The notebook server is listening on all IP addresses"
559 warning = "WARNING: The notebook server is listening on all IP addresses"
562 if ssl_options is None:
560 if ssl_options is None:
563 self.log.critical(warning + " and not using encryption. This "
561 self.log.critical(warning + " and not using encryption. This "
564 "is not recommended.")
562 "is not recommended.")
565 if not self.password and not self.read_only:
563 if not self.password and not self.read_only:
566 self.log.critical(warning + " and not using authentication. "
564 self.log.critical(warning + " and not using authentication. "
567 "This is highly insecure and not recommended.")
565 "This is highly insecure and not recommended.")
568 success = None
566 success = None
569 for port in random_ports(self.port, self.port_retries+1):
567 for port in random_ports(self.port, self.port_retries+1):
570 try:
568 try:
571 self.http_server.listen(port, self.ip)
569 self.http_server.listen(port, self.ip)
572 except socket.error as e:
570 except socket.error as e:
573 # XXX: remove the e.errno == -9 block when we require
571 # XXX: remove the e.errno == -9 block when we require
574 # tornado >= 3.0
572 # tornado >= 3.0
575 if e.errno == -9 and tornado.version_info[0] < 3:
573 if e.errno == -9 and tornado.version_info[0] < 3:
576 # The flags passed to socket.getaddrinfo from
574 # The flags passed to socket.getaddrinfo from
577 # tornado.netutils.bind_sockets can cause "gaierror:
575 # tornado.netutils.bind_sockets can cause "gaierror:
578 # [Errno -9] Address family for hostname not supported"
576 # [Errno -9] Address family for hostname not supported"
579 # when the interface is not associated, for example.
577 # when the interface is not associated, for example.
580 # Changing the flags to exclude socket.AI_ADDRCONFIG does
578 # Changing the flags to exclude socket.AI_ADDRCONFIG does
581 # not cause this error, but the only way to do this is to
579 # not cause this error, but the only way to do this is to
582 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
580 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
583 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
581 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
584 self.log.warn('Monkeypatching socket to fix tornado bug')
582 self.log.warn('Monkeypatching socket to fix tornado bug')
585 del(socket.AI_ADDRCONFIG)
583 del(socket.AI_ADDRCONFIG)
586 try:
584 try:
587 # retry the tornado call without AI_ADDRCONFIG flags
585 # retry the tornado call without AI_ADDRCONFIG flags
588 self.http_server.listen(port, self.ip)
586 self.http_server.listen(port, self.ip)
589 except socket.error as e2:
587 except socket.error as e2:
590 e = e2
588 e = e2
591 else:
589 else:
592 self.port = port
590 self.port = port
593 success = True
591 success = True
594 break
592 break
595 # restore the monekypatch
593 # restore the monekypatch
596 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
594 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
597 if e.errno != errno.EADDRINUSE:
595 if e.errno != errno.EADDRINUSE:
598 raise
596 raise
599 self.log.info('The port %i is already in use, trying another random port.' % port)
597 self.log.info('The port %i is already in use, trying another random port.' % port)
600 else:
598 else:
601 self.port = port
599 self.port = port
602 success = True
600 success = True
603 break
601 break
604 if not success:
602 if not success:
605 self.log.critical('ERROR: the notebook server could not be started because '
603 self.log.critical('ERROR: the notebook server could not be started because '
606 'no available port could be found.')
604 'no available port could be found.')
607 self.exit(1)
605 self.exit(1)
608
606
609 def init_signal(self):
607 def init_signal(self):
610 if not sys.platform.startswith('win'):
608 if not sys.platform.startswith('win'):
611 signal.signal(signal.SIGINT, self._handle_sigint)
609 signal.signal(signal.SIGINT, self._handle_sigint)
612 signal.signal(signal.SIGTERM, self._signal_stop)
610 signal.signal(signal.SIGTERM, self._signal_stop)
613 if hasattr(signal, 'SIGUSR1'):
611 if hasattr(signal, 'SIGUSR1'):
614 # Windows doesn't support SIGUSR1
612 # Windows doesn't support SIGUSR1
615 signal.signal(signal.SIGUSR1, self._signal_info)
613 signal.signal(signal.SIGUSR1, self._signal_info)
616 if hasattr(signal, 'SIGINFO'):
614 if hasattr(signal, 'SIGINFO'):
617 # only on BSD-based systems
615 # only on BSD-based systems
618 signal.signal(signal.SIGINFO, self._signal_info)
616 signal.signal(signal.SIGINFO, self._signal_info)
619
617
620 def _handle_sigint(self, sig, frame):
618 def _handle_sigint(self, sig, frame):
621 """SIGINT handler spawns confirmation dialog"""
619 """SIGINT handler spawns confirmation dialog"""
622 # register more forceful signal handler for ^C^C case
620 # register more forceful signal handler for ^C^C case
623 signal.signal(signal.SIGINT, self._signal_stop)
621 signal.signal(signal.SIGINT, self._signal_stop)
624 # request confirmation dialog in bg thread, to avoid
622 # request confirmation dialog in bg thread, to avoid
625 # blocking the App
623 # blocking the App
626 thread = threading.Thread(target=self._confirm_exit)
624 thread = threading.Thread(target=self._confirm_exit)
627 thread.daemon = True
625 thread.daemon = True
628 thread.start()
626 thread.start()
629
627
630 def _restore_sigint_handler(self):
628 def _restore_sigint_handler(self):
631 """callback for restoring original SIGINT handler"""
629 """callback for restoring original SIGINT handler"""
632 signal.signal(signal.SIGINT, self._handle_sigint)
630 signal.signal(signal.SIGINT, self._handle_sigint)
633
631
634 def _confirm_exit(self):
632 def _confirm_exit(self):
635 """confirm shutdown on ^C
633 """confirm shutdown on ^C
636
634
637 A second ^C, or answering 'y' within 5s will cause shutdown,
635 A second ^C, or answering 'y' within 5s will cause shutdown,
638 otherwise original SIGINT handler will be restored.
636 otherwise original SIGINT handler will be restored.
639
637
640 This doesn't work on Windows.
638 This doesn't work on Windows.
641 """
639 """
642 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
640 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
643 time.sleep(0.1)
641 time.sleep(0.1)
644 info = self.log.info
642 info = self.log.info
645 info('interrupted')
643 info('interrupted')
646 print self.notebook_info()
644 print self.notebook_info()
647 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
645 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
648 sys.stdout.flush()
646 sys.stdout.flush()
649 r,w,x = select.select([sys.stdin], [], [], 5)
647 r,w,x = select.select([sys.stdin], [], [], 5)
650 if r:
648 if r:
651 line = sys.stdin.readline()
649 line = sys.stdin.readline()
652 if line.lower().startswith('y'):
650 if line.lower().startswith('y'):
653 self.log.critical("Shutdown confirmed")
651 self.log.critical("Shutdown confirmed")
654 ioloop.IOLoop.instance().stop()
652 ioloop.IOLoop.instance().stop()
655 return
653 return
656 else:
654 else:
657 print "No answer for 5s:",
655 print "No answer for 5s:",
658 print "resuming operation..."
656 print "resuming operation..."
659 # no answer, or answer is no:
657 # no answer, or answer is no:
660 # set it back to original SIGINT handler
658 # set it back to original SIGINT handler
661 # use IOLoop.add_callback because signal.signal must be called
659 # use IOLoop.add_callback because signal.signal must be called
662 # from main thread
660 # from main thread
663 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
661 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
664
662
665 def _signal_stop(self, sig, frame):
663 def _signal_stop(self, sig, frame):
666 self.log.critical("received signal %s, stopping", sig)
664 self.log.critical("received signal %s, stopping", sig)
667 ioloop.IOLoop.instance().stop()
665 ioloop.IOLoop.instance().stop()
668
666
669 def _signal_info(self, sig, frame):
667 def _signal_info(self, sig, frame):
670 print self.notebook_info()
668 print self.notebook_info()
671
669
672 def init_components(self):
670 def init_components(self):
673 """Check the components submodule, and warn if it's unclean"""
671 """Check the components submodule, and warn if it's unclean"""
674 status = submodule.check_submodule_status()
672 status = submodule.check_submodule_status()
675 if status == 'missing':
673 if status == 'missing':
676 self.log.warn("components submodule missing, running `git submodule update`")
674 self.log.warn("components submodule missing, running `git submodule update`")
677 submodule.update_submodules(submodule.ipython_parent())
675 submodule.update_submodules(submodule.ipython_parent())
678 elif status == 'unclean':
676 elif status == 'unclean':
679 self.log.warn("components submodule unclean, you may see 404s on static/components")
677 self.log.warn("components submodule unclean, you may see 404s on static/components")
680 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
678 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
681
679
682
680
683 @catch_config_error
681 @catch_config_error
684 def initialize(self, argv=None):
682 def initialize(self, argv=None):
685 self.init_logging()
683 self.init_logging()
686 super(NotebookApp, self).initialize(argv)
684 super(NotebookApp, self).initialize(argv)
687 self.init_configurables()
685 self.init_configurables()
688 self.init_components()
686 self.init_components()
689 self.init_webapp()
687 self.init_webapp()
690 self.init_signal()
688 self.init_signal()
691
689
692 def cleanup_kernels(self):
690 def cleanup_kernels(self):
693 """Shutdown all kernels.
691 """Shutdown all kernels.
694
692
695 The kernels will shutdown themselves when this process no longer exists,
693 The kernels will shutdown themselves when this process no longer exists,
696 but explicit shutdown allows the KernelManagers to cleanup the connection files.
694 but explicit shutdown allows the KernelManagers to cleanup the connection files.
697 """
695 """
698 self.log.info('Shutting down kernels')
696 self.log.info('Shutting down kernels')
699 self.kernel_manager.shutdown_all()
697 self.kernel_manager.shutdown_all()
700
698
701 def notebook_info(self):
699 def notebook_info(self):
702 "Return the current working directory and the server url information"
700 "Return the current working directory and the server url information"
703 mgr_info = self.notebook_manager.info_string() + "\n"
701 mgr_info = self.notebook_manager.info_string() + "\n"
704 return mgr_info +"The IPython Notebook is running at: %s" % self._url
702 return mgr_info +"The IPython Notebook is running at: %s" % self._url
705
703
706 def start(self):
704 def start(self):
707 """ Start the IPython Notebook server app, after initialization
705 """ Start the IPython Notebook server app, after initialization
708
706
709 This method takes no arguments so all configuration and initialization
707 This method takes no arguments so all configuration and initialization
710 must be done prior to calling this method."""
708 must be done prior to calling this method."""
711 ip = self.ip if self.ip else '[all ip addresses on your system]'
709 ip = self.ip if self.ip else '[all ip addresses on your system]'
712 proto = 'https' if self.certfile else 'http'
710 proto = 'https' if self.certfile else 'http'
713 info = self.log.info
711 info = self.log.info
714 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
712 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
715 self.base_project_url)
713 self.base_project_url)
716 for line in self.notebook_info().split("\n"):
714 for line in self.notebook_info().split("\n"):
717 info(line)
715 info(line)
718 info("Use Control-C to stop this server and shut down all kernels.")
716 info("Use Control-C to stop this server and shut down all kernels.")
719
717
720 if self.open_browser or self.file_to_run:
718 if self.open_browser or self.file_to_run:
721 ip = self.ip or LOCALHOST
719 ip = self.ip or LOCALHOST
722 try:
720 try:
723 browser = webbrowser.get(self.browser or None)
721 browser = webbrowser.get(self.browser or None)
724 except webbrowser.Error as e:
722 except webbrowser.Error as e:
725 self.log.warn('No web browser found: %s.' % e)
723 self.log.warn('No web browser found: %s.' % e)
726 browser = None
724 browser = None
727
725
728 if self.file_to_run:
726 if self.file_to_run:
729 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
727 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
730 url = self.notebook_manager.rev_mapping.get(name, '')
728 url = self.notebook_manager.rev_mapping.get(name, '')
731 else:
729 else:
732 url = ''
730 url = ''
733 if browser:
731 if browser:
734 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
732 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 self.port, self.base_project_url, url), new=2)
733 self.port, self.base_project_url, url), new=2)
736 threading.Thread(target=b).start()
734 threading.Thread(target=b).start()
737 try:
735 try:
738 ioloop.IOLoop.instance().start()
736 ioloop.IOLoop.instance().start()
739 except KeyboardInterrupt:
737 except KeyboardInterrupt:
740 info("Interrupted...")
738 info("Interrupted...")
741 finally:
739 finally:
742 self.cleanup_kernels()
740 self.cleanup_kernels()
743
741
744
742
745 #-----------------------------------------------------------------------------
743 #-----------------------------------------------------------------------------
746 # Main entry point
744 # Main entry point
747 #-----------------------------------------------------------------------------
745 #-----------------------------------------------------------------------------
748
746
749 def launch_new_instance():
747 def launch_new_instance():
750 app = NotebookApp.instance()
748 app = NotebookApp.instance()
751 app.initialize()
749 app.initialize()
752 app.start()
750 app.start()
753
751
@@ -1,172 +1,170 b''
1 """Manage IPython.parallel clusters in the notebook.
1 """Manage IPython.parallel clusters in the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
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 import os
19 import os
20
20
21 from tornado import web
21 from tornado import web
22 from zmq.eventloop import ioloop
22 from zmq.eventloop import ioloop
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.config.loader import load_pyconfig_files
26 from IPython.utils.traitlets import Dict, Instance, CFloat
25 from IPython.utils.traitlets import Dict, Instance, CFloat
27 from IPython.parallel.apps.ipclusterapp import IPClusterStart
26 from IPython.parallel.apps.ipclusterapp import IPClusterStart
28 from IPython.core.profileapp import list_profiles_in
27 from IPython.core.profileapp import list_profiles_in
29 from IPython.core.profiledir import ProfileDir
28 from IPython.core.profiledir import ProfileDir
30 from IPython.utils.path import get_ipython_dir
29 from IPython.utils.path import get_ipython_dir
31 from IPython.utils.sysinfo import num_cpus
32
30
33
31
34 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
35 # Classes
33 # Classes
36 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
37
35
38
36
39 class DummyIPClusterStart(IPClusterStart):
37 class DummyIPClusterStart(IPClusterStart):
40 """Dummy subclass to skip init steps that conflict with global app.
38 """Dummy subclass to skip init steps that conflict with global app.
41
39
42 Instantiating and initializing this class should result in fully configured
40 Instantiating and initializing this class should result in fully configured
43 launchers, but no other side effects or state.
41 launchers, but no other side effects or state.
44 """
42 """
45
43
46 def init_signal(self):
44 def init_signal(self):
47 pass
45 pass
48 def reinit_logging(self):
46 def reinit_logging(self):
49 pass
47 pass
50
48
51
49
52 class ClusterManager(LoggingConfigurable):
50 class ClusterManager(LoggingConfigurable):
53
51
54 profiles = Dict()
52 profiles = Dict()
55
53
56 delay = CFloat(1., config=True,
54 delay = CFloat(1., config=True,
57 help="delay (in s) between starting the controller and the engines")
55 help="delay (in s) between starting the controller and the engines")
58
56
59 loop = Instance('zmq.eventloop.ioloop.IOLoop')
57 loop = Instance('zmq.eventloop.ioloop.IOLoop')
60 def _loop_default(self):
58 def _loop_default(self):
61 from zmq.eventloop.ioloop import IOLoop
59 from zmq.eventloop.ioloop import IOLoop
62 return IOLoop.instance()
60 return IOLoop.instance()
63
61
64 def build_launchers(self, profile_dir):
62 def build_launchers(self, profile_dir):
65 starter = DummyIPClusterStart(log=self.log)
63 starter = DummyIPClusterStart(log=self.log)
66 starter.initialize(['--profile-dir', profile_dir])
64 starter.initialize(['--profile-dir', profile_dir])
67 cl = starter.controller_launcher
65 cl = starter.controller_launcher
68 esl = starter.engine_launcher
66 esl = starter.engine_launcher
69 n = starter.n
67 n = starter.n
70 return cl, esl, n
68 return cl, esl, n
71
69
72 def get_profile_dir(self, name, path):
70 def get_profile_dir(self, name, path):
73 p = ProfileDir.find_profile_dir_by_name(path,name=name)
71 p = ProfileDir.find_profile_dir_by_name(path,name=name)
74 return p.location
72 return p.location
75
73
76 def update_profiles(self):
74 def update_profiles(self):
77 """List all profiles in the ipython_dir and cwd.
75 """List all profiles in the ipython_dir and cwd.
78 """
76 """
79 for path in [get_ipython_dir(), os.getcwdu()]:
77 for path in [get_ipython_dir(), os.getcwdu()]:
80 for profile in list_profiles_in(path):
78 for profile in list_profiles_in(path):
81 pd = self.get_profile_dir(profile, path)
79 pd = self.get_profile_dir(profile, path)
82 if profile not in self.profiles:
80 if profile not in self.profiles:
83 self.log.debug("Adding cluster profile '%s'" % profile)
81 self.log.debug("Adding cluster profile '%s'" % profile)
84 self.profiles[profile] = {
82 self.profiles[profile] = {
85 'profile': profile,
83 'profile': profile,
86 'profile_dir': pd,
84 'profile_dir': pd,
87 'status': 'stopped'
85 'status': 'stopped'
88 }
86 }
89
87
90 def list_profiles(self):
88 def list_profiles(self):
91 self.update_profiles()
89 self.update_profiles()
92 result = [self.profile_info(p) for p in sorted(self.profiles.keys())]
90 result = [self.profile_info(p) for p in sorted(self.profiles.keys())]
93 return result
91 return result
94
92
95 def check_profile(self, profile):
93 def check_profile(self, profile):
96 if profile not in self.profiles:
94 if profile not in self.profiles:
97 raise web.HTTPError(404, u'profile not found')
95 raise web.HTTPError(404, u'profile not found')
98
96
99 def profile_info(self, profile):
97 def profile_info(self, profile):
100 self.check_profile(profile)
98 self.check_profile(profile)
101 result = {}
99 result = {}
102 data = self.profiles.get(profile)
100 data = self.profiles.get(profile)
103 result['profile'] = profile
101 result['profile'] = profile
104 result['profile_dir'] = data['profile_dir']
102 result['profile_dir'] = data['profile_dir']
105 result['status'] = data['status']
103 result['status'] = data['status']
106 if 'n' in data:
104 if 'n' in data:
107 result['n'] = data['n']
105 result['n'] = data['n']
108 return result
106 return result
109
107
110 def start_cluster(self, profile, n=None):
108 def start_cluster(self, profile, n=None):
111 """Start a cluster for a given profile."""
109 """Start a cluster for a given profile."""
112 self.check_profile(profile)
110 self.check_profile(profile)
113 data = self.profiles[profile]
111 data = self.profiles[profile]
114 if data['status'] == 'running':
112 if data['status'] == 'running':
115 raise web.HTTPError(409, u'cluster already running')
113 raise web.HTTPError(409, u'cluster already running')
116 cl, esl, default_n = self.build_launchers(data['profile_dir'])
114 cl, esl, default_n = self.build_launchers(data['profile_dir'])
117 n = n if n is not None else default_n
115 n = n if n is not None else default_n
118 def clean_data():
116 def clean_data():
119 data.pop('controller_launcher',None)
117 data.pop('controller_launcher',None)
120 data.pop('engine_set_launcher',None)
118 data.pop('engine_set_launcher',None)
121 data.pop('n',None)
119 data.pop('n',None)
122 data['status'] = 'stopped'
120 data['status'] = 'stopped'
123 def engines_stopped(r):
121 def engines_stopped(r):
124 self.log.debug('Engines stopped')
122 self.log.debug('Engines stopped')
125 if cl.running:
123 if cl.running:
126 cl.stop()
124 cl.stop()
127 clean_data()
125 clean_data()
128 esl.on_stop(engines_stopped)
126 esl.on_stop(engines_stopped)
129 def controller_stopped(r):
127 def controller_stopped(r):
130 self.log.debug('Controller stopped')
128 self.log.debug('Controller stopped')
131 if esl.running:
129 if esl.running:
132 esl.stop()
130 esl.stop()
133 clean_data()
131 clean_data()
134 cl.on_stop(controller_stopped)
132 cl.on_stop(controller_stopped)
135
133
136 dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop)
134 dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop)
137 dc.start()
135 dc.start()
138 dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop)
136 dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop)
139 dc.start()
137 dc.start()
140
138
141 self.log.debug('Cluster started')
139 self.log.debug('Cluster started')
142 data['controller_launcher'] = cl
140 data['controller_launcher'] = cl
143 data['engine_set_launcher'] = esl
141 data['engine_set_launcher'] = esl
144 data['n'] = n
142 data['n'] = n
145 data['status'] = 'running'
143 data['status'] = 'running'
146 return self.profile_info(profile)
144 return self.profile_info(profile)
147
145
148 def stop_cluster(self, profile):
146 def stop_cluster(self, profile):
149 """Stop a cluster for a given profile."""
147 """Stop a cluster for a given profile."""
150 self.check_profile(profile)
148 self.check_profile(profile)
151 data = self.profiles[profile]
149 data = self.profiles[profile]
152 if data['status'] == 'stopped':
150 if data['status'] == 'stopped':
153 raise web.HTTPError(409, u'cluster not running')
151 raise web.HTTPError(409, u'cluster not running')
154 data = self.profiles[profile]
152 data = self.profiles[profile]
155 cl = data['controller_launcher']
153 cl = data['controller_launcher']
156 esl = data['engine_set_launcher']
154 esl = data['engine_set_launcher']
157 if cl.running:
155 if cl.running:
158 cl.stop()
156 cl.stop()
159 if esl.running:
157 if esl.running:
160 esl.stop()
158 esl.stop()
161 # Return a temp info dict, the real one is updated in the on_stop
159 # Return a temp info dict, the real one is updated in the on_stop
162 # logic above.
160 # logic above.
163 result = {
161 result = {
164 'profile': data['profile'],
162 'profile': data['profile'],
165 'profile_dir': data['profile_dir'],
163 'profile_dir': data['profile_dir'],
166 'status': 'stopped'
164 'status': 'stopped'
167 }
165 }
168 return result
166 return result
169
167
170 def stop_all_clusters(self):
168 def stop_all_clusters(self):
171 for p in self.profiles.keys():
169 for p in self.profiles.keys():
172 self.stop_cluster(p)
170 self.stop_cluster(p)
@@ -1,110 +1,110 b''
1 """A kernel manager relating notebooks and kernels
1 """A kernel manager relating notebooks and kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 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 from tornado import web
19 from tornado import web
20
20
21 from IPython.kernel.multikernelmanager import MultiKernelManager
21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 from IPython.utils.traitlets import (
22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Integer,
23 Dict, List, Unicode,
24 )
24 )
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Classes
27 # Classes
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30
30
31 class MappingKernelManager(MultiKernelManager):
31 class MappingKernelManager(MultiKernelManager):
32 """A KernelManager that handles notebook mapping and HTTP error handling"""
32 """A KernelManager that handles notebook mapping and HTTP error handling"""
33
33
34 def _kernel_manager_class_default(self):
34 def _kernel_manager_class_default(self):
35 return "IPython.kernel.ioloop.IOLoopKernelManager"
35 return "IPython.kernel.ioloop.IOLoopKernelManager"
36
36
37 kernel_argv = List(Unicode)
37 kernel_argv = List(Unicode)
38
38
39 _notebook_mapping = Dict()
39 _notebook_mapping = Dict()
40
40
41 #-------------------------------------------------------------------------
41 #-------------------------------------------------------------------------
42 # Methods for managing kernels and sessions
42 # Methods for managing kernels and sessions
43 #-------------------------------------------------------------------------
43 #-------------------------------------------------------------------------
44
44
45 def kernel_for_notebook(self, notebook_id):
45 def kernel_for_notebook(self, notebook_id):
46 """Return the kernel_id for a notebook_id or None."""
46 """Return the kernel_id for a notebook_id or None."""
47 return self._notebook_mapping.get(notebook_id)
47 return self._notebook_mapping.get(notebook_id)
48
48
49 def set_kernel_for_notebook(self, notebook_id, kernel_id):
49 def set_kernel_for_notebook(self, notebook_id, kernel_id):
50 """Associate a notebook with a kernel."""
50 """Associate a notebook with a kernel."""
51 if notebook_id is not None:
51 if notebook_id is not None:
52 self._notebook_mapping[notebook_id] = kernel_id
52 self._notebook_mapping[notebook_id] = kernel_id
53
53
54 def notebook_for_kernel(self, kernel_id):
54 def notebook_for_kernel(self, kernel_id):
55 """Return the notebook_id for a kernel_id or None."""
55 """Return the notebook_id for a kernel_id or None."""
56 for notebook_id, kid in self._notebook_mapping.iteritems():
56 for notebook_id, kid in self._notebook_mapping.iteritems():
57 if kernel_id == kid:
57 if kernel_id == kid:
58 return notebook_id
58 return notebook_id
59 return None
59 return None
60
60
61 def delete_mapping_for_kernel(self, kernel_id):
61 def delete_mapping_for_kernel(self, kernel_id):
62 """Remove the kernel/notebook mapping for kernel_id."""
62 """Remove the kernel/notebook mapping for kernel_id."""
63 notebook_id = self.notebook_for_kernel(kernel_id)
63 notebook_id = self.notebook_for_kernel(kernel_id)
64 if notebook_id is not None:
64 if notebook_id is not None:
65 del self._notebook_mapping[notebook_id]
65 del self._notebook_mapping[notebook_id]
66
66
67 def _handle_kernel_died(self, kernel_id):
67 def _handle_kernel_died(self, kernel_id):
68 """notice that a kernel died"""
68 """notice that a kernel died"""
69 self.log.warn("Kernel %s died, removing from map.", kernel_id)
69 self.log.warn("Kernel %s died, removing from map.", kernel_id)
70 self.delete_mapping_for_kernel(kernel_id)
70 self.delete_mapping_for_kernel(kernel_id)
71 self.remove_kernel(kernel_id)
71 self.remove_kernel(kernel_id)
72
72
73 def start_kernel(self, notebook_id=None, **kwargs):
73 def start_kernel(self, notebook_id=None, **kwargs):
74 """Start a kernel for a notebook an return its kernel_id.
74 """Start a kernel for a notebook an return its kernel_id.
75
75
76 Parameters
76 Parameters
77 ----------
77 ----------
78 notebook_id : uuid
78 notebook_id : uuid
79 The uuid of the notebook to associate the new kernel with. If this
79 The uuid of the notebook to associate the new kernel with. If this
80 is not None, this kernel will be persistent whenever the notebook
80 is not None, this kernel will be persistent whenever the notebook
81 requests a kernel.
81 requests a kernel.
82 """
82 """
83 kernel_id = self.kernel_for_notebook(notebook_id)
83 kernel_id = self.kernel_for_notebook(notebook_id)
84 if kernel_id is None:
84 if kernel_id is None:
85 kwargs['extra_arguments'] = self.kernel_argv
85 kwargs['extra_arguments'] = self.kernel_argv
86 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
86 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
87 self.set_kernel_for_notebook(notebook_id, kernel_id)
87 self.set_kernel_for_notebook(notebook_id, kernel_id)
88 self.log.info("Kernel started: %s" % kernel_id)
88 self.log.info("Kernel started: %s" % kernel_id)
89 self.log.debug("Kernel args: %r" % kwargs)
89 self.log.debug("Kernel args: %r" % kwargs)
90 # register callback for failed auto-restart
90 # register callback for failed auto-restart
91 self.add_restart_callback(kernel_id,
91 self.add_restart_callback(kernel_id,
92 lambda : self._handle_kernel_died(kernel_id),
92 lambda : self._handle_kernel_died(kernel_id),
93 'dead',
93 'dead',
94 )
94 )
95 else:
95 else:
96 self.log.info("Using existing kernel: %s" % kernel_id)
96 self.log.info("Using existing kernel: %s" % kernel_id)
97
97
98 return kernel_id
98 return kernel_id
99
99
100 def shutdown_kernel(self, kernel_id, now=False):
100 def shutdown_kernel(self, kernel_id, now=False):
101 """Shutdown a kernel by kernel_id"""
101 """Shutdown a kernel by kernel_id"""
102 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
102 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
103 self.delete_mapping_for_kernel(kernel_id)
103 self.delete_mapping_for_kernel(kernel_id)
104
104
105 # override _check_kernel_id to raise 404 instead of KeyError
105 # override _check_kernel_id to raise 404 instead of KeyError
106 def _check_kernel_id(self, kernel_id):
106 def _check_kernel_id(self, kernel_id):
107 """Check a that a kernel_id exists and raise 404 if not."""
107 """Check a that a kernel_id exists and raise 404 if not."""
108 if kernel_id not in self:
108 if kernel_id not in self:
109 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
109 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
110
110
General Comments 0
You need to be logged in to leave comments. Login now