##// END OF EJS Templates
fix local mathjax with custom base_project_url...
MinRK -
Show More
@@ -1,753 +1,754
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.html')
37 check_for_zmq('2.1.11', 'IPython.html')
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.html import DEFAULT_STATIC_FILES_PATH
64 from IPython.html 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.consoleapp import IPythonConsoleApp
75 from IPython.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.html.' + name
123 name = 'IPython.html.' + 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
346
347 Note: Cookie secrets should be kept private, do not share config files with
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).
348 cookie_secret stored in plaintext (you can read the value from a file).
349 """
349 """
350 )
350 )
351 def _cookie_secret_default(self):
351 def _cookie_secret_default(self):
352 return os.urandom(1024)
352 return os.urandom(1024)
353
353
354 password = Unicode(u'', config=True,
354 password = Unicode(u'', config=True,
355 help="""Hashed password to use for web authentication.
355 help="""Hashed password to use for web authentication.
356
356
357 To generate, type in a python/IPython shell:
357 To generate, type in a python/IPython shell:
358
358
359 from IPython.lib import passwd; passwd()
359 from IPython.lib import passwd; passwd()
360
360
361 The string should be of the form type:salt:hashed-password.
361 The string should be of the form type:salt:hashed-password.
362 """
362 """
363 )
363 )
364
364
365 open_browser = Bool(True, config=True,
365 open_browser = Bool(True, config=True,
366 help="""Whether to open in a browser after starting.
366 help="""Whether to open in a browser after starting.
367 The specific browser used is platform dependent and
367 The specific browser used is platform dependent and
368 determined by the python standard library `webbrowser`
368 determined by the python standard library `webbrowser`
369 module, unless it is overridden using the --browser
369 module, unless it is overridden using the --browser
370 (NotebookApp.browser) configuration option.
370 (NotebookApp.browser) configuration option.
371 """)
371 """)
372
372
373 browser = Unicode(u'', config=True,
373 browser = Unicode(u'', config=True,
374 help="""Specify what command to use to invoke a web
374 help="""Specify what command to use to invoke a web
375 browser when opening the notebook. If not specified, the
375 browser when opening the notebook. If not specified, the
376 default browser will be determined by the `webbrowser`
376 default browser will be determined by the `webbrowser`
377 standard library module, which allows setting of the
377 standard library module, which allows setting of the
378 BROWSER environment variable to override it.
378 BROWSER environment variable to override it.
379 """)
379 """)
380
380
381 read_only = Bool(False, config=True,
381 read_only = Bool(False, config=True,
382 help="Whether to prevent editing/execution of notebooks."
382 help="Whether to prevent editing/execution of notebooks."
383 )
383 )
384
384
385 use_less = Bool(False, config=True,
385 use_less = Bool(False, config=True,
386 help="""Wether to use Browser Side less-css parsing
386 help="""Wether to use Browser Side less-css parsing
387 instead of compiled css version in templates that allows
387 instead of compiled css version in templates that allows
388 it. This is mainly convenient when working on the less
388 it. This is mainly convenient when working on the less
389 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
390 some of the less variables without having to recompile
390 some of the less variables without having to recompile
391 everything.
391 everything.
392
392
393 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
394 either in the source tree or in your profile folder.
394 either in the source tree or in your profile folder.
395 """)
395 """)
396
396
397 webapp_settings = Dict(config=True,
397 webapp_settings = Dict(config=True,
398 help="Supply overrides for the tornado.web.Application that the "
398 help="Supply overrides for the tornado.web.Application that the "
399 "IPython notebook uses.")
399 "IPython notebook uses.")
400
400
401 enable_mathjax = Bool(True, config=True,
401 enable_mathjax = Bool(True, config=True,
402 help="""Whether to enable MathJax for typesetting math/TeX
402 help="""Whether to enable MathJax for typesetting math/TeX
403
403
404 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
405 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
406 connection, or for offline use of the notebook.
406 connection, or for offline use of the notebook.
407
407
408 When disabled, equations etc. will appear as their untransformed TeX source.
408 When disabled, equations etc. will appear as their untransformed TeX source.
409 """
409 """
410 )
410 )
411 def _enable_mathjax_changed(self, name, old, new):
411 def _enable_mathjax_changed(self, name, old, new):
412 """set mathjax url to empty if mathjax is disabled"""
412 """set mathjax url to empty if mathjax is disabled"""
413 if not new:
413 if not new:
414 self.mathjax_url = u''
414 self.mathjax_url = u''
415
415
416 base_project_url = Unicode('/', config=True,
416 base_project_url = Unicode('/', config=True,
417 help='''The base URL for the notebook server.
417 help='''The base URL for the notebook server.
418
418
419 Leading and trailing slashes can be omitted,
419 Leading and trailing slashes can be omitted,
420 and will automatically be added.
420 and will automatically be added.
421 ''')
421 ''')
422 def _base_project_url_changed(self, name, old, new):
422 def _base_project_url_changed(self, name, old, new):
423 if not new.startswith('/'):
423 if not new.startswith('/'):
424 self.base_project_url = '/'+new
424 self.base_project_url = '/'+new
425 elif not new.endswith('/'):
425 elif not new.endswith('/'):
426 self.base_project_url = new+'/'
426 self.base_project_url = new+'/'
427
427
428 base_kernel_url = Unicode('/', config=True,
428 base_kernel_url = Unicode('/', config=True,
429 help='''The base URL for the kernel server
429 help='''The base URL for the kernel server
430
430
431 Leading and trailing slashes can be omitted,
431 Leading and trailing slashes can be omitted,
432 and will automatically be added.
432 and will automatically be added.
433 ''')
433 ''')
434 def _base_kernel_url_changed(self, name, old, new):
434 def _base_kernel_url_changed(self, name, old, new):
435 if not new.startswith('/'):
435 if not new.startswith('/'):
436 self.base_kernel_url = '/'+new
436 self.base_kernel_url = '/'+new
437 elif not new.endswith('/'):
437 elif not new.endswith('/'):
438 self.base_kernel_url = new+'/'
438 self.base_kernel_url = new+'/'
439
439
440 websocket_url = Unicode("", config=True,
440 websocket_url = Unicode("", config=True,
441 help="""The base URL for the websocket server,
441 help="""The base URL for the websocket server,
442 if it differs from the HTTP server (hint: it almost certainly doesn't).
442 if it differs from the HTTP server (hint: it almost certainly doesn't).
443
443
444 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
444 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
445 """
445 """
446 )
446 )
447
447
448 extra_static_paths = List(Unicode, config=True,
448 extra_static_paths = List(Unicode, config=True,
449 help="""Extra paths to search for serving static files.
449 help="""Extra paths to search for serving static files.
450
450
451 This allows adding javascript/css to be available from the notebook server machine,
451 This allows adding javascript/css to be available from the notebook server machine,
452 or overriding individual files in the IPython"""
452 or overriding individual files in the IPython"""
453 )
453 )
454 def _extra_static_paths_default(self):
454 def _extra_static_paths_default(self):
455 return [os.path.join(self.profile_dir.location, 'static')]
455 return [os.path.join(self.profile_dir.location, 'static')]
456
456
457 @property
457 @property
458 def static_file_path(self):
458 def static_file_path(self):
459 """return extra paths + the default location"""
459 """return extra paths + the default location"""
460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461
461
462 mathjax_url = Unicode("", config=True,
462 mathjax_url = Unicode("", config=True,
463 help="""The url for MathJax.js."""
463 help="""The url for MathJax.js."""
464 )
464 )
465 def _mathjax_url_default(self):
465 def _mathjax_url_default(self):
466 if not self.enable_mathjax:
466 if not self.enable_mathjax:
467 return u''
467 return u''
468 static_url_prefix = self.webapp_settings.get("static_url_prefix",
468 static_url_prefix = self.webapp_settings.get("static_url_prefix",
469 "/static/")
469 url_path_join(self.base_project_url, "static")
470 )
470 try:
471 try:
471 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
472 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
472 except IOError:
473 except IOError:
473 if self.certfile:
474 if self.certfile:
474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 base = u"https://c328740.ssl.cf1.rackcdn.com"
476 base = u"https://c328740.ssl.cf1.rackcdn.com"
476 else:
477 else:
477 base = u"http://cdn.mathjax.org"
478 base = u"http://cdn.mathjax.org"
478
479
479 url = base + u"/mathjax/latest/MathJax.js"
480 url = base + u"/mathjax/latest/MathJax.js"
480 self.log.info("Using MathJax from CDN: %s", url)
481 self.log.info("Using MathJax from CDN: %s", url)
481 return url
482 return url
482 else:
483 else:
483 self.log.info("Using local MathJax from %s" % mathjax)
484 self.log.info("Using local MathJax from %s" % mathjax)
484 return static_url_prefix+u"mathjax/MathJax.js"
485 return url_path_join(static_url_prefix, u"mathjax/MathJax.js")
485
486
486 def _mathjax_url_changed(self, name, old, new):
487 def _mathjax_url_changed(self, name, old, new):
487 if new and not self.enable_mathjax:
488 if new and not self.enable_mathjax:
488 # enable_mathjax=False overrides mathjax_url
489 # enable_mathjax=False overrides mathjax_url
489 self.mathjax_url = u''
490 self.mathjax_url = u''
490 else:
491 else:
491 self.log.info("Using MathJax: %s", new)
492 self.log.info("Using MathJax: %s", new)
492
493
493 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
494 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
494 config=True,
495 config=True,
495 help='The notebook manager class to use.')
496 help='The notebook manager class to use.')
496
497
497 trust_xheaders = Bool(False, config=True,
498 trust_xheaders = Bool(False, config=True,
498 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
499 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
499 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
500 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
500 )
501 )
501
502
502 def parse_command_line(self, argv=None):
503 def parse_command_line(self, argv=None):
503 super(NotebookApp, self).parse_command_line(argv)
504 super(NotebookApp, self).parse_command_line(argv)
504 if argv is None:
505 if argv is None:
505 argv = sys.argv[1:]
506 argv = sys.argv[1:]
506
507
507 # Scrub frontend-specific flags
508 # Scrub frontend-specific flags
508 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
509 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
509 # Kernel should inherit default config file from frontend
510 # Kernel should inherit default config file from frontend
510 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
511 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
511
512
512 if self.extra_args:
513 if self.extra_args:
513 f = os.path.abspath(self.extra_args[0])
514 f = os.path.abspath(self.extra_args[0])
514 if os.path.isdir(f):
515 if os.path.isdir(f):
515 nbdir = f
516 nbdir = f
516 else:
517 else:
517 self.file_to_run = f
518 self.file_to_run = f
518 nbdir = os.path.dirname(f)
519 nbdir = os.path.dirname(f)
519 self.config.NotebookManager.notebook_dir = nbdir
520 self.config.NotebookManager.notebook_dir = nbdir
520
521
521 def init_configurables(self):
522 def init_configurables(self):
522 # force Session default to be secure
523 # force Session default to be secure
523 default_secure(self.config)
524 default_secure(self.config)
524 self.kernel_manager = MappingKernelManager(
525 self.kernel_manager = MappingKernelManager(
525 parent=self, log=self.log, kernel_argv=self.kernel_argv,
526 parent=self, log=self.log, kernel_argv=self.kernel_argv,
526 connection_dir = self.profile_dir.security_dir,
527 connection_dir = self.profile_dir.security_dir,
527 )
528 )
528 kls = import_item(self.notebook_manager_class)
529 kls = import_item(self.notebook_manager_class)
529 self.notebook_manager = kls(parent=self, log=self.log)
530 self.notebook_manager = kls(parent=self, log=self.log)
530 self.notebook_manager.load_notebook_names()
531 self.notebook_manager.load_notebook_names()
531 self.cluster_manager = ClusterManager(parent=self, log=self.log)
532 self.cluster_manager = ClusterManager(parent=self, log=self.log)
532 self.cluster_manager.update_profiles()
533 self.cluster_manager.update_profiles()
533
534
534 def init_logging(self):
535 def init_logging(self):
535 # This prevents double log messages because tornado use a root logger that
536 # This prevents double log messages because tornado use a root logger that
536 # self.log is a child of. The logging module dipatches log messages to a log
537 # self.log is a child of. The logging module dipatches log messages to a log
537 # and all of its ancenstors until propagate is set to False.
538 # and all of its ancenstors until propagate is set to False.
538 self.log.propagate = False
539 self.log.propagate = False
539
540
540 # hook up tornado 3's loggers to our app handlers
541 # hook up tornado 3's loggers to our app handlers
541 for name in ('access', 'application', 'general'):
542 for name in ('access', 'application', 'general'):
542 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543
544
544 def init_webapp(self):
545 def init_webapp(self):
545 """initialize tornado webapp and httpserver"""
546 """initialize tornado webapp and httpserver"""
546 self.web_app = NotebookWebApplication(
547 self.web_app = NotebookWebApplication(
547 self, self.kernel_manager, self.notebook_manager,
548 self, self.kernel_manager, self.notebook_manager,
548 self.cluster_manager, self.log,
549 self.cluster_manager, self.log,
549 self.base_project_url, self.webapp_settings
550 self.base_project_url, self.webapp_settings
550 )
551 )
551 if self.certfile:
552 if self.certfile:
552 ssl_options = dict(certfile=self.certfile)
553 ssl_options = dict(certfile=self.certfile)
553 if self.keyfile:
554 if self.keyfile:
554 ssl_options['keyfile'] = self.keyfile
555 ssl_options['keyfile'] = self.keyfile
555 else:
556 else:
556 ssl_options = None
557 ssl_options = None
557 self.web_app.password = self.password
558 self.web_app.password = self.password
558 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
559 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
559 xheaders=self.trust_xheaders)
560 xheaders=self.trust_xheaders)
560 if not self.ip:
561 if not self.ip:
561 warning = "WARNING: The notebook server is listening on all IP addresses"
562 warning = "WARNING: The notebook server is listening on all IP addresses"
562 if ssl_options is None:
563 if ssl_options is None:
563 self.log.critical(warning + " and not using encryption. This "
564 self.log.critical(warning + " and not using encryption. This "
564 "is not recommended.")
565 "is not recommended.")
565 if not self.password and not self.read_only:
566 if not self.password and not self.read_only:
566 self.log.critical(warning + " and not using authentication. "
567 self.log.critical(warning + " and not using authentication. "
567 "This is highly insecure and not recommended.")
568 "This is highly insecure and not recommended.")
568 success = None
569 success = None
569 for port in random_ports(self.port, self.port_retries+1):
570 for port in random_ports(self.port, self.port_retries+1):
570 try:
571 try:
571 self.http_server.listen(port, self.ip)
572 self.http_server.listen(port, self.ip)
572 except socket.error as e:
573 except socket.error as e:
573 # XXX: remove the e.errno == -9 block when we require
574 # XXX: remove the e.errno == -9 block when we require
574 # tornado >= 3.0
575 # tornado >= 3.0
575 if e.errno == -9 and tornado.version_info[0] < 3:
576 if e.errno == -9 and tornado.version_info[0] < 3:
576 # The flags passed to socket.getaddrinfo from
577 # The flags passed to socket.getaddrinfo from
577 # tornado.netutils.bind_sockets can cause "gaierror:
578 # tornado.netutils.bind_sockets can cause "gaierror:
578 # [Errno -9] Address family for hostname not supported"
579 # [Errno -9] Address family for hostname not supported"
579 # when the interface is not associated, for example.
580 # when the interface is not associated, for example.
580 # Changing the flags to exclude socket.AI_ADDRCONFIG does
581 # Changing the flags to exclude socket.AI_ADDRCONFIG does
581 # not cause this error, but the only way to do this is to
582 # not cause this error, but the only way to do this is to
582 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
583 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
583 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
584 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
584 self.log.warn('Monkeypatching socket to fix tornado bug')
585 self.log.warn('Monkeypatching socket to fix tornado bug')
585 del(socket.AI_ADDRCONFIG)
586 del(socket.AI_ADDRCONFIG)
586 try:
587 try:
587 # retry the tornado call without AI_ADDRCONFIG flags
588 # retry the tornado call without AI_ADDRCONFIG flags
588 self.http_server.listen(port, self.ip)
589 self.http_server.listen(port, self.ip)
589 except socket.error as e2:
590 except socket.error as e2:
590 e = e2
591 e = e2
591 else:
592 else:
592 self.port = port
593 self.port = port
593 success = True
594 success = True
594 break
595 break
595 # restore the monekypatch
596 # restore the monekypatch
596 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
597 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
597 if e.errno != errno.EADDRINUSE:
598 if e.errno != errno.EADDRINUSE:
598 raise
599 raise
599 self.log.info('The port %i is already in use, trying another random port.' % port)
600 self.log.info('The port %i is already in use, trying another random port.' % port)
600 else:
601 else:
601 self.port = port
602 self.port = port
602 success = True
603 success = True
603 break
604 break
604 if not success:
605 if not success:
605 self.log.critical('ERROR: the notebook server could not be started because '
606 self.log.critical('ERROR: the notebook server could not be started because '
606 'no available port could be found.')
607 'no available port could be found.')
607 self.exit(1)
608 self.exit(1)
608
609
609 def init_signal(self):
610 def init_signal(self):
610 if not sys.platform.startswith('win'):
611 if not sys.platform.startswith('win'):
611 signal.signal(signal.SIGINT, self._handle_sigint)
612 signal.signal(signal.SIGINT, self._handle_sigint)
612 signal.signal(signal.SIGTERM, self._signal_stop)
613 signal.signal(signal.SIGTERM, self._signal_stop)
613 if hasattr(signal, 'SIGUSR1'):
614 if hasattr(signal, 'SIGUSR1'):
614 # Windows doesn't support SIGUSR1
615 # Windows doesn't support SIGUSR1
615 signal.signal(signal.SIGUSR1, self._signal_info)
616 signal.signal(signal.SIGUSR1, self._signal_info)
616 if hasattr(signal, 'SIGINFO'):
617 if hasattr(signal, 'SIGINFO'):
617 # only on BSD-based systems
618 # only on BSD-based systems
618 signal.signal(signal.SIGINFO, self._signal_info)
619 signal.signal(signal.SIGINFO, self._signal_info)
619
620
620 def _handle_sigint(self, sig, frame):
621 def _handle_sigint(self, sig, frame):
621 """SIGINT handler spawns confirmation dialog"""
622 """SIGINT handler spawns confirmation dialog"""
622 # register more forceful signal handler for ^C^C case
623 # register more forceful signal handler for ^C^C case
623 signal.signal(signal.SIGINT, self._signal_stop)
624 signal.signal(signal.SIGINT, self._signal_stop)
624 # request confirmation dialog in bg thread, to avoid
625 # request confirmation dialog in bg thread, to avoid
625 # blocking the App
626 # blocking the App
626 thread = threading.Thread(target=self._confirm_exit)
627 thread = threading.Thread(target=self._confirm_exit)
627 thread.daemon = True
628 thread.daemon = True
628 thread.start()
629 thread.start()
629
630
630 def _restore_sigint_handler(self):
631 def _restore_sigint_handler(self):
631 """callback for restoring original SIGINT handler"""
632 """callback for restoring original SIGINT handler"""
632 signal.signal(signal.SIGINT, self._handle_sigint)
633 signal.signal(signal.SIGINT, self._handle_sigint)
633
634
634 def _confirm_exit(self):
635 def _confirm_exit(self):
635 """confirm shutdown on ^C
636 """confirm shutdown on ^C
636
637
637 A second ^C, or answering 'y' within 5s will cause shutdown,
638 A second ^C, or answering 'y' within 5s will cause shutdown,
638 otherwise original SIGINT handler will be restored.
639 otherwise original SIGINT handler will be restored.
639
640
640 This doesn't work on Windows.
641 This doesn't work on Windows.
641 """
642 """
642 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
643 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
643 time.sleep(0.1)
644 time.sleep(0.1)
644 info = self.log.info
645 info = self.log.info
645 info('interrupted')
646 info('interrupted')
646 print self.notebook_info()
647 print self.notebook_info()
647 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
648 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
648 sys.stdout.flush()
649 sys.stdout.flush()
649 r,w,x = select.select([sys.stdin], [], [], 5)
650 r,w,x = select.select([sys.stdin], [], [], 5)
650 if r:
651 if r:
651 line = sys.stdin.readline()
652 line = sys.stdin.readline()
652 if line.lower().startswith('y'):
653 if line.lower().startswith('y'):
653 self.log.critical("Shutdown confirmed")
654 self.log.critical("Shutdown confirmed")
654 ioloop.IOLoop.instance().stop()
655 ioloop.IOLoop.instance().stop()
655 return
656 return
656 else:
657 else:
657 print "No answer for 5s:",
658 print "No answer for 5s:",
658 print "resuming operation..."
659 print "resuming operation..."
659 # no answer, or answer is no:
660 # no answer, or answer is no:
660 # set it back to original SIGINT handler
661 # set it back to original SIGINT handler
661 # use IOLoop.add_callback because signal.signal must be called
662 # use IOLoop.add_callback because signal.signal must be called
662 # from main thread
663 # from main thread
663 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
664 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
664
665
665 def _signal_stop(self, sig, frame):
666 def _signal_stop(self, sig, frame):
666 self.log.critical("received signal %s, stopping", sig)
667 self.log.critical("received signal %s, stopping", sig)
667 ioloop.IOLoop.instance().stop()
668 ioloop.IOLoop.instance().stop()
668
669
669 def _signal_info(self, sig, frame):
670 def _signal_info(self, sig, frame):
670 print self.notebook_info()
671 print self.notebook_info()
671
672
672 def init_components(self):
673 def init_components(self):
673 """Check the components submodule, and warn if it's unclean"""
674 """Check the components submodule, and warn if it's unclean"""
674 status = submodule.check_submodule_status()
675 status = submodule.check_submodule_status()
675 if status == 'missing':
676 if status == 'missing':
676 self.log.warn("components submodule missing, running `git submodule update`")
677 self.log.warn("components submodule missing, running `git submodule update`")
677 submodule.update_submodules(submodule.ipython_parent())
678 submodule.update_submodules(submodule.ipython_parent())
678 elif status == 'unclean':
679 elif status == 'unclean':
679 self.log.warn("components submodule unclean, you may see 404s on static/components")
680 self.log.warn("components submodule unclean, you may see 404s on static/components")
680 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
681 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
681
682
682
683
683 @catch_config_error
684 @catch_config_error
684 def initialize(self, argv=None):
685 def initialize(self, argv=None):
685 self.init_logging()
686 self.init_logging()
686 super(NotebookApp, self).initialize(argv)
687 super(NotebookApp, self).initialize(argv)
687 self.init_configurables()
688 self.init_configurables()
688 self.init_components()
689 self.init_components()
689 self.init_webapp()
690 self.init_webapp()
690 self.init_signal()
691 self.init_signal()
691
692
692 def cleanup_kernels(self):
693 def cleanup_kernels(self):
693 """Shutdown all kernels.
694 """Shutdown all kernels.
694
695
695 The kernels will shutdown themselves when this process no longer exists,
696 The kernels will shutdown themselves when this process no longer exists,
696 but explicit shutdown allows the KernelManagers to cleanup the connection files.
697 but explicit shutdown allows the KernelManagers to cleanup the connection files.
697 """
698 """
698 self.log.info('Shutting down kernels')
699 self.log.info('Shutting down kernels')
699 self.kernel_manager.shutdown_all()
700 self.kernel_manager.shutdown_all()
700
701
701 def notebook_info(self):
702 def notebook_info(self):
702 "Return the current working directory and the server url information"
703 "Return the current working directory and the server url information"
703 mgr_info = self.notebook_manager.info_string() + "\n"
704 mgr_info = self.notebook_manager.info_string() + "\n"
704 return mgr_info +"The IPython Notebook is running at: %s" % self._url
705 return mgr_info +"The IPython Notebook is running at: %s" % self._url
705
706
706 def start(self):
707 def start(self):
707 """ Start the IPython Notebook server app, after initialization
708 """ Start the IPython Notebook server app, after initialization
708
709
709 This method takes no arguments so all configuration and initialization
710 This method takes no arguments so all configuration and initialization
710 must be done prior to calling this method."""
711 must be done prior to calling this method."""
711 ip = self.ip if self.ip else '[all ip addresses on your system]'
712 ip = self.ip if self.ip else '[all ip addresses on your system]'
712 proto = 'https' if self.certfile else 'http'
713 proto = 'https' if self.certfile else 'http'
713 info = self.log.info
714 info = self.log.info
714 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
715 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
715 self.base_project_url)
716 self.base_project_url)
716 for line in self.notebook_info().split("\n"):
717 for line in self.notebook_info().split("\n"):
717 info(line)
718 info(line)
718 info("Use Control-C to stop this server and shut down all kernels.")
719 info("Use Control-C to stop this server and shut down all kernels.")
719
720
720 if self.open_browser or self.file_to_run:
721 if self.open_browser or self.file_to_run:
721 ip = self.ip or LOCALHOST
722 ip = self.ip or LOCALHOST
722 try:
723 try:
723 browser = webbrowser.get(self.browser or None)
724 browser = webbrowser.get(self.browser or None)
724 except webbrowser.Error as e:
725 except webbrowser.Error as e:
725 self.log.warn('No web browser found: %s.' % e)
726 self.log.warn('No web browser found: %s.' % e)
726 browser = None
727 browser = None
727
728
728 if self.file_to_run:
729 if self.file_to_run:
729 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
730 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
730 url = self.notebook_manager.rev_mapping.get(name, '')
731 url = self.notebook_manager.rev_mapping.get(name, '')
731 else:
732 else:
732 url = ''
733 url = ''
733 if browser:
734 if browser:
734 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 self.port, self.base_project_url, url), new=2)
736 self.port, self.base_project_url, url), new=2)
736 threading.Thread(target=b).start()
737 threading.Thread(target=b).start()
737 try:
738 try:
738 ioloop.IOLoop.instance().start()
739 ioloop.IOLoop.instance().start()
739 except KeyboardInterrupt:
740 except KeyboardInterrupt:
740 info("Interrupted...")
741 info("Interrupted...")
741 finally:
742 finally:
742 self.cleanup_kernels()
743 self.cleanup_kernels()
743
744
744
745
745 #-----------------------------------------------------------------------------
746 #-----------------------------------------------------------------------------
746 # Main entry point
747 # Main entry point
747 #-----------------------------------------------------------------------------
748 #-----------------------------------------------------------------------------
748
749
749 def launch_new_instance():
750 def launch_new_instance():
750 app = NotebookApp.instance()
751 app = NotebookApp.instance()
751 app.initialize()
752 app.initialize()
752 app.start()
753 app.start()
753
754
General Comments 0
You need to be logged in to leave comments. Login now