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