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