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