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