##// END OF EJS Templates
allow system-wide paths for nbextensions...
Min RK -
Show More
@@ -1,265 +1,327 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Utilities for installing Javascript extensions for the notebook"""
2 """Utilities for installing Javascript extensions for the notebook"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import os
9 import os
10 import shutil
10 import shutil
11 import sys
11 import tarfile
12 import tarfile
12 import zipfile
13 import zipfile
13 from os.path import basename, join as pjoin
14 from os.path import basename, join as pjoin
14
15
15 # Deferred imports
16 # Deferred imports
16 try:
17 try:
17 from urllib.parse import urlparse # Py3
18 from urllib.parse import urlparse # Py3
18 from urllib.request import urlretrieve
19 from urllib.request import urlretrieve
19 except ImportError:
20 except ImportError:
20 from urlparse import urlparse
21 from urlparse import urlparse
21 from urllib import urlretrieve
22 from urllib import urlretrieve
22
23
23 from IPython.utils.path import get_ipython_dir, ensure_dir_exists
24 from IPython.utils.path import get_ipython_dir, ensure_dir_exists
24 from IPython.utils.py3compat import string_types, cast_unicode_py2
25 from IPython.utils.py3compat import string_types, cast_unicode_py2
25 from IPython.utils.tempdir import TemporaryDirectory
26 from IPython.utils.tempdir import TemporaryDirectory
26
27
27
28
29 # Packagers: modify the next block if you store system-installed nbextensions elsewhere (unlikely)
30 SYSTEM_NBEXTENSIONS_DIRS = []
31
32 if os.name == 'nt':
33 programdata = os.environ.get('PROGRAMDATA', None)
34 if programdata: # PROGRAMDATA is not defined by default on XP.
35 SYSTEM_NBEXTENSIONS_DIRS = [pjoin(programdata, 'jupyter', 'nbextensions')]
36 prefixes = []
37 else:
38 prefixes = ['/usr/local', '/usr']
39
40 # add sys.prefix at the front
41 if sys.prefix not in prefixes:
42 prefixes.insert(0, sys.prefix)
43
44 for prefix in prefixes:
45 nbext = os.path.join(prefix, 'share', 'jupyter', 'nbextensions')
46 if nbext not in SYSTEM_NBEXTENSIONS_DIRS:
47 SYSTEM_NBEXTENSIONS_DIRS.append(nbext)
48
49 if os.name == 'nt':
50 # PROGRAMDATA
51 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-1]
52 else:
53 # /usr/local
54 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-2]
55
56
28 def _should_copy(src, dest, verbose=1):
57 def _should_copy(src, dest, verbose=1):
29 """should a file be copied?"""
58 """should a file be copied?"""
30 if not os.path.exists(dest):
59 if not os.path.exists(dest):
31 return True
60 return True
32 if os.stat(dest).st_mtime < os.stat(src).st_mtime:
61 if os.stat(dest).st_mtime < os.stat(src).st_mtime:
33 if verbose >= 2:
62 if verbose >= 2:
34 print("%s is out of date" % dest)
63 print("%s is out of date" % dest)
35 return True
64 return True
36 if verbose >= 2:
65 if verbose >= 2:
37 print("%s is up to date" % dest)
66 print("%s is up to date" % dest)
38 return False
67 return False
39
68
40
69
41 def _maybe_copy(src, dest, verbose=1):
70 def _maybe_copy(src, dest, verbose=1):
42 """copy a file if it needs updating"""
71 """copy a file if it needs updating"""
43 if _should_copy(src, dest, verbose):
72 if _should_copy(src, dest, verbose):
44 if verbose >= 1:
73 if verbose >= 1:
45 print("copying %s -> %s" % (src, dest))
74 print("copying %s -> %s" % (src, dest))
46 shutil.copy2(src, dest)
75 shutil.copy2(src, dest)
47
76
48
77
49 def _safe_is_tarfile(path):
78 def _safe_is_tarfile(path):
50 """safe version of is_tarfile, return False on IOError"""
79 """safe version of is_tarfile, return False on IOError"""
51 try:
80 try:
52 return tarfile.is_tarfile(path)
81 return tarfile.is_tarfile(path)
53 except IOError:
82 except IOError:
54 return False
83 return False
55
84
56
85
57 def check_nbextension(files, ipython_dir=None):
86 def check_nbextension(files, nbextensions=None):
58 """Check whether nbextension files have been installed
87 """Check whether nbextension files have been installed
59
88
60 files should be a list of relative paths within nbextensions.
89 files should be a list of relative paths within nbextensions.
61
90
62 Returns True if all files are found, False if any are missing.
91 Returns True if all files are found, False if any are missing.
63 """
92 """
64 ipython_dir = ipython_dir or get_ipython_dir()
93 if nbextensions:
65 nbext = pjoin(ipython_dir, u'nbextensions')
94 nbext = nbextensions
95 else:
96 nbext = pjoin(get_ipython_dir(), u'nbextensions')
66 # make sure nbextensions dir exists
97 # make sure nbextensions dir exists
67 if not os.path.exists(nbext):
98 if not os.path.exists(nbext):
68 return False
99 return False
69
100
70 if isinstance(files, string_types):
101 if isinstance(files, string_types):
71 # one file given, turn it into a list
102 # one file given, turn it into a list
72 files = [files]
103 files = [files]
73
104
74 return all(os.path.exists(pjoin(nbext, f)) for f in files)
105 return all(os.path.exists(pjoin(nbext, f)) for f in files)
75
106
76
107
77 def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1):
108 def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions=None, verbose=1):
78 """Install a Javascript extension for the notebook
109 """Install a Javascript extension for the notebook
79
110
80 Stages files and/or directories into IPYTHONDIR/nbextensions.
111 Stages files and/or directories into IPYTHONDIR/nbextensions.
81 By default, this compares modification time, and only stages files that need updating.
112 By default, this compares modification time, and only stages files that need updating.
82 If `overwrite` is specified, matching files are purged before proceeding.
113 If `overwrite` is specified, matching files are purged before proceeding.
83
114
84 Parameters
115 Parameters
85 ----------
116 ----------
86
117
87 files : list(paths or URLs)
118 files : list(paths or URLs)
88 One or more paths or URLs to existing files directories to install.
119 One or more paths or URLs to existing files directories to install.
89 These will be installed with their base name, so '/path/to/foo'
120 These will be installed with their base name, so '/path/to/foo'
90 will install to 'nbextensions/foo'.
121 will install to 'nbextensions/foo'.
91 Archives (zip or tarballs) will be extracted into the nbextensions directory.
122 Archives (zip or tarballs) will be extracted into the nbextensions directory.
92 overwrite : bool [default: False]
123 overwrite : bool [default: False]
93 If True, always install the files, regardless of what may already be installed.
124 If True, always install the files, regardless of what may already be installed.
94 symlink : bool [default: False]
125 symlink : bool [default: False]
95 If True, create a symlink in nbextensions, rather than copying files.
126 If True, create a symlink in nbextensions, rather than copying files.
96 Not allowed with URLs or archives. Windows support for symlinks requires
127 Not allowed with URLs or archives. Windows support for symlinks requires
97 Vista or above, Python 3, and a permission bit which only admin users
128 Vista or above, Python 3, and a permission bit which only admin users
98 have by default, so don't rely on it.
129 have by default, so don't rely on it.
99 ipython_dir : str [optional]
130 user : bool [default: False]
100 The path to an IPython directory, if the default value is not desired.
131 Whether to install to the user's .ipython/nbextensions directory.
101 get_ipython_dir() is used by default.
132 Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
133 prefix : str [optional]
134 Specify install prefix, if it should differ from default (e.g. /usr/local).
135 Will install to prefix/share/jupyter/nbextensions
136 nbextensions : str [optional]
137 Specify absolute path of nbextensions directory explicitly.
102 verbose : int [default: 1]
138 verbose : int [default: 1]
103 Set verbosity level. The default is 1, where file actions are printed.
139 Set verbosity level. The default is 1, where file actions are printed.
104 set verbose=2 for more output, or verbose=0 for silence.
140 set verbose=2 for more output, or verbose=0 for silence.
105 """
141 """
106
142 if sum(map(bool, [user, prefix, nbextensions])) > 1:
107 ipython_dir = ipython_dir or get_ipython_dir()
143 raise ValueError("Cannot specify more than one of user, prefix, or nbextensions.")
108 nbext = pjoin(ipython_dir, u'nbextensions')
144 if user:
145 nbext = pjoin(get_ipython_dir(), u'nbextensions')
146 else:
147 if prefix:
148 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
149 elif nbextensions:
150 nbext = nbextensions
151 else:
152 nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
109 # make sure nbextensions dir exists
153 # make sure nbextensions dir exists
110 ensure_dir_exists(nbext)
154 ensure_dir_exists(nbext)
111
155
112 if isinstance(files, string_types):
156 if isinstance(files, string_types):
113 # one file given, turn it into a list
157 # one file given, turn it into a list
114 files = [files]
158 files = [files]
115
159
116 for path in map(cast_unicode_py2, files):
160 for path in map(cast_unicode_py2, files):
117
161
118 if path.startswith(('https://', 'http://')):
162 if path.startswith(('https://', 'http://')):
119 if symlink:
163 if symlink:
120 raise ValueError("Cannot symlink from URLs")
164 raise ValueError("Cannot symlink from URLs")
121 # Given a URL, download it
165 # Given a URL, download it
122 with TemporaryDirectory() as td:
166 with TemporaryDirectory() as td:
123 filename = urlparse(path).path.split('/')[-1]
167 filename = urlparse(path).path.split('/')[-1]
124 local_path = os.path.join(td, filename)
168 local_path = os.path.join(td, filename)
125 if verbose >= 1:
169 if verbose >= 1:
126 print("downloading %s to %s" % (path, local_path))
170 print("downloading %s to %s" % (path, local_path))
127 urlretrieve(path, local_path)
171 urlretrieve(path, local_path)
128 # now install from the local copy
172 # now install from the local copy
129 install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose)
173 install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions=nbext, verbose=verbose)
130 continue
174 continue
131
175
132 # handle archives
176 # handle archives
133 archive = None
177 archive = None
134 if path.endswith('.zip'):
178 if path.endswith('.zip'):
135 archive = zipfile.ZipFile(path)
179 archive = zipfile.ZipFile(path)
136 elif _safe_is_tarfile(path):
180 elif _safe_is_tarfile(path):
137 archive = tarfile.open(path)
181 archive = tarfile.open(path)
138
182
139 if archive:
183 if archive:
140 if symlink:
184 if symlink:
141 raise ValueError("Cannot symlink from archives")
185 raise ValueError("Cannot symlink from archives")
142 if verbose >= 1:
186 if verbose >= 1:
143 print("extracting %s to %s" % (path, nbext))
187 print("extracting %s to %s" % (path, nbext))
144 archive.extractall(nbext)
188 archive.extractall(nbext)
145 archive.close()
189 archive.close()
146 continue
190 continue
147
191
148 dest = pjoin(nbext, basename(path))
192 dest = pjoin(nbext, basename(path))
149 if overwrite and os.path.exists(dest):
193 if overwrite and os.path.exists(dest):
150 if verbose >= 1:
194 if verbose >= 1:
151 print("removing %s" % dest)
195 print("removing %s" % dest)
152 if os.path.isdir(dest) and not os.path.islink(dest):
196 if os.path.isdir(dest) and not os.path.islink(dest):
153 shutil.rmtree(dest)
197 shutil.rmtree(dest)
154 else:
198 else:
155 os.remove(dest)
199 os.remove(dest)
156
200
157 if symlink:
201 if symlink:
158 path = os.path.abspath(path)
202 path = os.path.abspath(path)
159 if not os.path.exists(dest):
203 if not os.path.exists(dest):
160 if verbose >= 1:
204 if verbose >= 1:
161 print("symlink %s -> %s" % (dest, path))
205 print("symlink %s -> %s" % (dest, path))
162 os.symlink(path, dest)
206 os.symlink(path, dest)
163 continue
207 continue
164
208
165 if os.path.isdir(path):
209 if os.path.isdir(path):
166 strip_prefix_len = len(path) - len(basename(path))
210 strip_prefix_len = len(path) - len(basename(path))
167 for parent, dirs, files in os.walk(path):
211 for parent, dirs, files in os.walk(path):
168 dest_dir = pjoin(nbext, parent[strip_prefix_len:])
212 dest_dir = pjoin(nbext, parent[strip_prefix_len:])
169 if not os.path.exists(dest_dir):
213 if not os.path.exists(dest_dir):
170 if verbose >= 2:
214 if verbose >= 2:
171 print("making directory %s" % dest_dir)
215 print("making directory %s" % dest_dir)
172 os.makedirs(dest_dir)
216 os.makedirs(dest_dir)
173 for file in files:
217 for file in files:
174 src = pjoin(parent, file)
218 src = pjoin(parent, file)
175 # print("%r, %r" % (dest_dir, file))
219 # print("%r, %r" % (dest_dir, file))
176 dest = pjoin(dest_dir, file)
220 dest = pjoin(dest_dir, file)
177 _maybe_copy(src, dest, verbose)
221 _maybe_copy(src, dest, verbose)
178 else:
222 else:
179 src = path
223 src = path
180 _maybe_copy(src, dest, verbose)
224 _maybe_copy(src, dest, verbose)
181
225
182 #----------------------------------------------------------------------
226 #----------------------------------------------------------------------
183 # install nbextension app
227 # install nbextension app
184 #----------------------------------------------------------------------
228 #----------------------------------------------------------------------
185
229
186 from IPython.utils.traitlets import Bool, Enum
230 from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
187 from IPython.core.application import BaseIPythonApplication
231 from IPython.core.application import BaseIPythonApplication
188
232
189 flags = {
233 flags = {
190 "overwrite" : ({
234 "overwrite" : ({
191 "NBExtensionApp" : {
235 "NBExtensionApp" : {
192 "overwrite" : True,
236 "overwrite" : True,
193 }}, "Force overwrite of existing files"
237 }}, "Force overwrite of existing files"
194 ),
238 ),
195 "debug" : ({
239 "debug" : ({
196 "NBExtensionApp" : {
240 "NBExtensionApp" : {
197 "verbose" : 2,
241 "verbose" : 2,
198 }}, "Extra output"
242 }}, "Extra output"
199 ),
243 ),
200 "quiet" : ({
244 "quiet" : ({
201 "NBExtensionApp" : {
245 "NBExtensionApp" : {
202 "verbose" : 0,
246 "verbose" : 0,
203 }}, "Minimal output"
247 }}, "Minimal output"
204 ),
248 ),
205 "symlink" : ({
249 "symlink" : ({
206 "NBExtensionApp" : {
250 "NBExtensionApp" : {
207 "symlink" : True,
251 "symlink" : True,
208 }}, "Create symlinks instead of copying files"
252 }}, "Create symlinks instead of copying files"
209 ),
253 ),
254 "user" : ({
255 "NBExtensionApp" : {
256 "user" : True,
257 }}, "Install to the user's IPython directory"
258 ),
210 }
259 }
211 flags['s'] = flags['symlink']
260 flags['s'] = flags['symlink']
212
261
213 aliases = {
262 aliases = {
214 "ipython-dir" : "NBExtensionApp.ipython_dir"
263 "ipython-dir" : "NBExtensionApp.ipython_dir",
264 "prefix" : "NBExtensionApp.prefix",
265 "nbextensions" : "NBExtensionApp.nbextensions",
215 }
266 }
216
267
217 class NBExtensionApp(BaseIPythonApplication):
268 class NBExtensionApp(BaseIPythonApplication):
218 """Entry point for installing notebook extensions"""
269 """Entry point for installing notebook extensions"""
219
270
220 description = """Install IPython notebook extensions
271 description = """Install IPython notebook extensions
221
272
222 Usage
273 Usage
223
274
224 ipython install-nbextension file [more files, folders, archives or urls]
275 ipython install-nbextension file [more files, folders, archives or urls]
225
276
226 This copies files and/or folders into the IPython nbextensions directory.
277 This copies files and/or folders into the IPython nbextensions directory.
227 If a URL is given, it will be downloaded.
278 If a URL is given, it will be downloaded.
228 If an archive is given, it will be extracted into nbextensions.
279 If an archive is given, it will be extracted into nbextensions.
229 If the requested files are already up to date, no action is taken
280 If the requested files are already up to date, no action is taken
230 unless --overwrite is specified.
281 unless --overwrite is specified.
231 """
282 """
232
283
233 examples = """
284 examples = """
234 ipython install-nbextension /path/to/d3.js /path/to/myextension
285 ipython install-nbextension /path/to/d3.js /path/to/myextension
235 """
286 """
236 aliases = aliases
287 aliases = aliases
237 flags = flags
288 flags = flags
238
289
239 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
290 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
240 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
291 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
292 user = Bool(False, config=True, help="Whether to do a user install")
293 prefix = Unicode('', config=True, help="Installation prefix")
294 nbextensions = Unicode('', config=True, help="Full path to nbextensions (probably use prefix or user)")
241 verbose = Enum((0,1,2), default_value=1, config=True,
295 verbose = Enum((0,1,2), default_value=1, config=True,
242 help="Verbosity level"
296 help="Verbosity level"
243 )
297 )
244
298
299 def check_install():
300 if sum(map(bool, [user, prefix, nbextensions])) > 1:
301 raise TraitError("Cannot specify more than one of user, prefix, or nbextensions.")
302
245 def install_extensions(self):
303 def install_extensions(self):
246 install_nbextension(self.extra_args,
304 install_nbextension(self.extra_args,
247 overwrite=self.overwrite,
305 overwrite=self.overwrite,
248 symlink=self.symlink,
306 symlink=self.symlink,
249 verbose=self.verbose,
307 verbose=self.verbose,
250 ipython_dir=self.ipython_dir,
308 user=self.user,
309 prefix=self.prefix,
310 nbextensions=self.nbextensions,
251 )
311 )
252
312
253 def start(self):
313 def start(self):
254 if not self.extra_args:
314 if not self.extra_args:
255 nbext = pjoin(self.ipython_dir, u'nbextensions')
315 for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS:
256 print("Notebook extensions in %s:" % nbext)
316 if os.path.exists(nbext):
257 for ext in os.listdir(nbext):
317 print("Notebook extensions in %s:" % nbext)
258 print(u" %s" % ext)
318 for ext in os.listdir(nbext):
319 print(u" %s" % ext)
259 else:
320 else:
321 self.check_install()
260 self.install_extensions()
322 self.install_extensions()
261
323
262
324
263 if __name__ == '__main__':
325 if __name__ == '__main__':
264 NBExtensionApp.launch_instance()
326 NBExtensionApp.launch_instance()
265 No newline at end of file
327
@@ -1,1100 +1,1104 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server."""
2 """A tornado based IPython notebook server."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import importlib
12 import importlib
13 import io
13 import io
14 import json
14 import json
15 import logging
15 import logging
16 import os
16 import os
17 import random
17 import random
18 import re
18 import re
19 import select
19 import select
20 import signal
20 import signal
21 import socket
21 import socket
22 import sys
22 import sys
23 import threading
23 import threading
24 import webbrowser
24 import webbrowser
25
25
26
26
27 # check for pyzmq 2.1.11
27 # check for pyzmq 2.1.11
28 from IPython.utils.zmqrelated import check_for_zmq
28 from IPython.utils.zmqrelated import check_for_zmq
29 check_for_zmq('2.1.11', 'IPython.html')
29 check_for_zmq('2.1.11', 'IPython.html')
30
30
31 from jinja2 import Environment, FileSystemLoader
31 from jinja2 import Environment, FileSystemLoader
32
32
33 # Install the pyzmq ioloop. This has to be done before anything else from
33 # Install the pyzmq ioloop. This has to be done before anything else from
34 # tornado is imported.
34 # tornado is imported.
35 from zmq.eventloop import ioloop
35 from zmq.eventloop import ioloop
36 ioloop.install()
36 ioloop.install()
37
37
38 # check for tornado 3.1.0
38 # check for tornado 3.1.0
39 msg = "The IPython Notebook requires tornado >= 4.0"
39 msg = "The IPython Notebook requires tornado >= 4.0"
40 try:
40 try:
41 import tornado
41 import tornado
42 except ImportError:
42 except ImportError:
43 raise ImportError(msg)
43 raise ImportError(msg)
44 try:
44 try:
45 version_info = tornado.version_info
45 version_info = tornado.version_info
46 except AttributeError:
46 except AttributeError:
47 raise ImportError(msg + ", but you have < 1.1.0")
47 raise ImportError(msg + ", but you have < 1.1.0")
48 if version_info < (4,0):
48 if version_info < (4,0):
49 raise ImportError(msg + ", but you have %s" % tornado.version)
49 raise ImportError(msg + ", but you have %s" % tornado.version)
50
50
51 from tornado import httpserver
51 from tornado import httpserver
52 from tornado import web
52 from tornado import web
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54
54
55 from IPython.html import (
55 from IPython.html import (
56 DEFAULT_STATIC_FILES_PATH,
56 DEFAULT_STATIC_FILES_PATH,
57 DEFAULT_TEMPLATE_PATH_LIST,
57 DEFAULT_TEMPLATE_PATH_LIST,
58 )
58 )
59 from .base.handlers import Template404
59 from .base.handlers import Template404
60 from .log import log_request
60 from .log import log_request
61 from .services.kernels.kernelmanager import MappingKernelManager
61 from .services.kernels.kernelmanager import MappingKernelManager
62 from .services.config import ConfigManager
62 from .services.config import ConfigManager
63 from .services.contents.manager import ContentsManager
63 from .services.contents.manager import ContentsManager
64 from .services.contents.filemanager import FileContentsManager
64 from .services.contents.filemanager import FileContentsManager
65 from .services.clusters.clustermanager import ClusterManager
65 from .services.clusters.clustermanager import ClusterManager
66 from .services.sessions.sessionmanager import SessionManager
66 from .services.sessions.sessionmanager import SessionManager
67
67
68 from .auth.login import LoginHandler
68 from .auth.login import LoginHandler
69 from .auth.logout import LogoutHandler
69 from .auth.logout import LogoutHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
70 from .base.handlers import IPythonHandler, FileFindHandler
71
71
72 from IPython.config import Config
72 from IPython.config import Config
73 from IPython.config.application import catch_config_error, boolean_flag
73 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import (
74 from IPython.core.application import (
75 BaseIPythonApplication, base_flags, base_aliases,
75 BaseIPythonApplication, base_flags, base_aliases,
76 )
76 )
77 from IPython.core.profiledir import ProfileDir
77 from IPython.core.profiledir import ProfileDir
78 from IPython.kernel import KernelManager
78 from IPython.kernel import KernelManager
79 from IPython.kernel.kernelspec import KernelSpecManager
79 from IPython.kernel.kernelspec import KernelSpecManager
80 from IPython.kernel.zmq.session import default_secure, Session
80 from IPython.kernel.zmq.session import default_secure, Session
81 from IPython.nbformat.sign import NotebookNotary
81 from IPython.nbformat.sign import NotebookNotary
82 from IPython.utils.importstring import import_item
82 from IPython.utils.importstring import import_item
83 from IPython.utils import submodule
83 from IPython.utils import submodule
84 from IPython.utils.process import check_pid
84 from IPython.utils.process import check_pid
85 from IPython.utils.traitlets import (
85 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 TraitError, Type,
87 TraitError, Type,
88 )
88 )
89 from IPython.utils import py3compat
89 from IPython.utils import py3compat
90 from IPython.utils.path import filefind, get_ipython_dir
90 from IPython.utils.path import filefind, get_ipython_dir
91 from IPython.utils.sysinfo import get_sys_info
91 from IPython.utils.sysinfo import get_sys_info
92
92
93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
93 from .utils import url_path_join
94 from .utils import url_path_join
94
95
95 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
96 # Module globals
97 # Module globals
97 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
98
99
99 _examples = """
100 _examples = """
100 ipython notebook # start the notebook
101 ipython notebook # start the notebook
101 ipython notebook --profile=sympy # use the sympy profile
102 ipython notebook --profile=sympy # use the sympy profile
102 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 """
104 """
104
105
105 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
106 # Helper functions
107 # Helper functions
107 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
108
109
109 def random_ports(port, n):
110 def random_ports(port, n):
110 """Generate a list of n random ports near the given port.
111 """Generate a list of n random ports near the given port.
111
112
112 The first 5 ports will be sequential, and the remaining n-5 will be
113 The first 5 ports will be sequential, and the remaining n-5 will be
113 randomly selected in the range [port-2*n, port+2*n].
114 randomly selected in the range [port-2*n, port+2*n].
114 """
115 """
115 for i in range(min(5, n)):
116 for i in range(min(5, n)):
116 yield port + i
117 yield port + i
117 for i in range(n-5):
118 for i in range(n-5):
118 yield max(1, port + random.randint(-2*n, 2*n))
119 yield max(1, port + random.randint(-2*n, 2*n))
119
120
120 def load_handlers(name):
121 def load_handlers(name):
121 """Load the (URL pattern, handler) tuples for each component."""
122 """Load the (URL pattern, handler) tuples for each component."""
122 name = 'IPython.html.' + name
123 name = 'IPython.html.' + name
123 mod = __import__(name, fromlist=['default_handlers'])
124 mod = __import__(name, fromlist=['default_handlers'])
124 return mod.default_handlers
125 return mod.default_handlers
125
126
126 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
127 # The Tornado web application
128 # The Tornado web application
128 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
129
130
130 class NotebookWebApplication(web.Application):
131 class NotebookWebApplication(web.Application):
131
132
132 def __init__(self, ipython_app, kernel_manager, contents_manager,
133 def __init__(self, ipython_app, kernel_manager, contents_manager,
133 cluster_manager, session_manager, kernel_spec_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
134 config_manager, log,
135 config_manager, log,
135 base_url, default_url, settings_overrides, jinja_env_options):
136 base_url, default_url, settings_overrides, jinja_env_options):
136
137
137 settings = self.init_settings(
138 settings = self.init_settings(
138 ipython_app, kernel_manager, contents_manager, cluster_manager,
139 ipython_app, kernel_manager, contents_manager, cluster_manager,
139 session_manager, kernel_spec_manager, config_manager, log, base_url,
140 session_manager, kernel_spec_manager, config_manager, log, base_url,
140 default_url, settings_overrides, jinja_env_options)
141 default_url, settings_overrides, jinja_env_options)
141 handlers = self.init_handlers(settings)
142 handlers = self.init_handlers(settings)
142
143
143 super(NotebookWebApplication, self).__init__(handlers, **settings)
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
144
145
145 def init_settings(self, ipython_app, kernel_manager, contents_manager,
146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
146 cluster_manager, session_manager, kernel_spec_manager,
147 cluster_manager, session_manager, kernel_spec_manager,
147 config_manager,
148 config_manager,
148 log, base_url, default_url, settings_overrides,
149 log, base_url, default_url, settings_overrides,
149 jinja_env_options=None):
150 jinja_env_options=None):
150
151
151 _template_path = settings_overrides.get(
152 _template_path = settings_overrides.get(
152 "template_path",
153 "template_path",
153 ipython_app.template_file_path,
154 ipython_app.template_file_path,
154 )
155 )
155 if isinstance(_template_path, str):
156 if isinstance(_template_path, str):
156 _template_path = (_template_path,)
157 _template_path = (_template_path,)
157 template_path = [os.path.expanduser(path) for path in _template_path]
158 template_path = [os.path.expanduser(path) for path in _template_path]
158
159
159 jenv_opt = jinja_env_options if jinja_env_options else {}
160 jenv_opt = jinja_env_options if jinja_env_options else {}
160 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
161
162
162 sys_info = get_sys_info()
163 sys_info = get_sys_info()
163 if sys_info['commit_source'] == 'repository':
164 if sys_info['commit_source'] == 'repository':
164 # don't cache (rely on 304) when working from master
165 # don't cache (rely on 304) when working from master
165 version_hash = ''
166 version_hash = ''
166 else:
167 else:
167 # reset the cache on server restart
168 # reset the cache on server restart
168 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
169
170
170 settings = dict(
171 settings = dict(
171 # basics
172 # basics
172 log_function=log_request,
173 log_function=log_request,
173 base_url=base_url,
174 base_url=base_url,
174 default_url=default_url,
175 default_url=default_url,
175 template_path=template_path,
176 template_path=template_path,
176 static_path=ipython_app.static_file_path,
177 static_path=ipython_app.static_file_path,
177 static_handler_class = FileFindHandler,
178 static_handler_class = FileFindHandler,
178 static_url_prefix = url_path_join(base_url,'/static/'),
179 static_url_prefix = url_path_join(base_url,'/static/'),
179 static_handler_args = {
180 static_handler_args = {
180 # don't cache custom.js
181 # don't cache custom.js
181 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
182 },
183 },
183 version_hash=version_hash,
184 version_hash=version_hash,
184
185
185 # authentication
186 # authentication
186 cookie_secret=ipython_app.cookie_secret,
187 cookie_secret=ipython_app.cookie_secret,
187 login_url=url_path_join(base_url,'/login'),
188 login_url=url_path_join(base_url,'/login'),
188 login_handler_class=ipython_app.login_handler_class,
189 login_handler_class=ipython_app.login_handler_class,
189 logout_handler_class=ipython_app.logout_handler_class,
190 logout_handler_class=ipython_app.logout_handler_class,
190 password=ipython_app.password,
191 password=ipython_app.password,
191
192
192 # managers
193 # managers
193 kernel_manager=kernel_manager,
194 kernel_manager=kernel_manager,
194 contents_manager=contents_manager,
195 contents_manager=contents_manager,
195 cluster_manager=cluster_manager,
196 cluster_manager=cluster_manager,
196 session_manager=session_manager,
197 session_manager=session_manager,
197 kernel_spec_manager=kernel_spec_manager,
198 kernel_spec_manager=kernel_spec_manager,
198 config_manager=config_manager,
199 config_manager=config_manager,
199
200
200 # IPython stuff
201 # IPython stuff
201 nbextensions_path=ipython_app.nbextensions_path,
202 nbextensions_path=ipython_app.nbextensions_path,
202 websocket_url=ipython_app.websocket_url,
203 websocket_url=ipython_app.websocket_url,
203 mathjax_url=ipython_app.mathjax_url,
204 mathjax_url=ipython_app.mathjax_url,
204 config=ipython_app.config,
205 config=ipython_app.config,
205 jinja2_env=env,
206 jinja2_env=env,
206 terminals_available=False, # Set later if terminals are available
207 terminals_available=False, # Set later if terminals are available
207 )
208 )
208
209
209 # allow custom overrides for the tornado web app.
210 # allow custom overrides for the tornado web app.
210 settings.update(settings_overrides)
211 settings.update(settings_overrides)
211 return settings
212 return settings
212
213
213 def init_handlers(self, settings):
214 def init_handlers(self, settings):
214 """Load the (URL pattern, handler) tuples for each component."""
215 """Load the (URL pattern, handler) tuples for each component."""
215
216
216 # Order matters. The first handler to match the URL will handle the request.
217 # Order matters. The first handler to match the URL will handle the request.
217 handlers = []
218 handlers = []
218 handlers.extend(load_handlers('tree.handlers'))
219 handlers.extend(load_handlers('tree.handlers'))
219 handlers.extend([(r"/login", settings['login_handler_class'])])
220 handlers.extend([(r"/login", settings['login_handler_class'])])
220 handlers.extend([(r"/logout", settings['logout_handler_class'])])
221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
221 handlers.extend(load_handlers('files.handlers'))
222 handlers.extend(load_handlers('files.handlers'))
222 handlers.extend(load_handlers('notebook.handlers'))
223 handlers.extend(load_handlers('notebook.handlers'))
223 handlers.extend(load_handlers('nbconvert.handlers'))
224 handlers.extend(load_handlers('nbconvert.handlers'))
224 handlers.extend(load_handlers('kernelspecs.handlers'))
225 handlers.extend(load_handlers('kernelspecs.handlers'))
225 handlers.extend(load_handlers('edit.handlers'))
226 handlers.extend(load_handlers('edit.handlers'))
226 handlers.extend(load_handlers('services.config.handlers'))
227 handlers.extend(load_handlers('services.config.handlers'))
227 handlers.extend(load_handlers('services.kernels.handlers'))
228 handlers.extend(load_handlers('services.kernels.handlers'))
228 handlers.extend(load_handlers('services.contents.handlers'))
229 handlers.extend(load_handlers('services.contents.handlers'))
229 handlers.extend(load_handlers('services.clusters.handlers'))
230 handlers.extend(load_handlers('services.clusters.handlers'))
230 handlers.extend(load_handlers('services.sessions.handlers'))
231 handlers.extend(load_handlers('services.sessions.handlers'))
231 handlers.extend(load_handlers('services.nbconvert.handlers'))
232 handlers.extend(load_handlers('services.nbconvert.handlers'))
232 handlers.extend(load_handlers('services.kernelspecs.handlers'))
233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
233 handlers.extend(load_handlers('services.security.handlers'))
234 handlers.extend(load_handlers('services.security.handlers'))
234 handlers.append(
235 handlers.append(
235 (r"/nbextensions/(.*)", FileFindHandler, {
236 (r"/nbextensions/(.*)", FileFindHandler, {
236 'path': settings['nbextensions_path'],
237 'path': settings['nbextensions_path'],
237 'no_cache_paths': ['/'], # don't cache anything in nbextensions
238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
238 }),
239 }),
239 )
240 )
240 # register base handlers last
241 # register base handlers last
241 handlers.extend(load_handlers('base.handlers'))
242 handlers.extend(load_handlers('base.handlers'))
242 # set the URL that will be redirected from `/`
243 # set the URL that will be redirected from `/`
243 handlers.append(
244 handlers.append(
244 (r'/?', web.RedirectHandler, {
245 (r'/?', web.RedirectHandler, {
245 'url' : url_path_join(settings['base_url'], settings['default_url']),
246 'url' : url_path_join(settings['base_url'], settings['default_url']),
246 'permanent': False, # want 302, not 301
247 'permanent': False, # want 302, not 301
247 })
248 })
248 )
249 )
249 # prepend base_url onto the patterns that we match
250 # prepend base_url onto the patterns that we match
250 new_handlers = []
251 new_handlers = []
251 for handler in handlers:
252 for handler in handlers:
252 pattern = url_path_join(settings['base_url'], handler[0])
253 pattern = url_path_join(settings['base_url'], handler[0])
253 new_handler = tuple([pattern] + list(handler[1:]))
254 new_handler = tuple([pattern] + list(handler[1:]))
254 new_handlers.append(new_handler)
255 new_handlers.append(new_handler)
255 # add 404 on the end, which will catch everything that falls through
256 # add 404 on the end, which will catch everything that falls through
256 new_handlers.append((r'(.*)', Template404))
257 new_handlers.append((r'(.*)', Template404))
257 return new_handlers
258 return new_handlers
258
259
259
260
260 class NbserverListApp(BaseIPythonApplication):
261 class NbserverListApp(BaseIPythonApplication):
261
262
262 description="List currently running notebook servers in this profile."
263 description="List currently running notebook servers in this profile."
263
264
264 flags = dict(
265 flags = dict(
265 json=({'NbserverListApp': {'json': True}},
266 json=({'NbserverListApp': {'json': True}},
266 "Produce machine-readable JSON output."),
267 "Produce machine-readable JSON output."),
267 )
268 )
268
269
269 json = Bool(False, config=True,
270 json = Bool(False, config=True,
270 help="If True, each line of output will be a JSON object with the "
271 help="If True, each line of output will be a JSON object with the "
271 "details from the server info file.")
272 "details from the server info file.")
272
273
273 def start(self):
274 def start(self):
274 if not self.json:
275 if not self.json:
275 print("Currently running servers:")
276 print("Currently running servers:")
276 for serverinfo in list_running_servers(self.profile):
277 for serverinfo in list_running_servers(self.profile):
277 if self.json:
278 if self.json:
278 print(json.dumps(serverinfo))
279 print(json.dumps(serverinfo))
279 else:
280 else:
280 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
281
282
282 #-----------------------------------------------------------------------------
283 #-----------------------------------------------------------------------------
283 # Aliases and Flags
284 # Aliases and Flags
284 #-----------------------------------------------------------------------------
285 #-----------------------------------------------------------------------------
285
286
286 flags = dict(base_flags)
287 flags = dict(base_flags)
287 flags['no-browser']=(
288 flags['no-browser']=(
288 {'NotebookApp' : {'open_browser' : False}},
289 {'NotebookApp' : {'open_browser' : False}},
289 "Don't open the notebook in a browser after startup."
290 "Don't open the notebook in a browser after startup."
290 )
291 )
291 flags['pylab']=(
292 flags['pylab']=(
292 {'NotebookApp' : {'pylab' : 'warn'}},
293 {'NotebookApp' : {'pylab' : 'warn'}},
293 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
294 )
295 )
295 flags['no-mathjax']=(
296 flags['no-mathjax']=(
296 {'NotebookApp' : {'enable_mathjax' : False}},
297 {'NotebookApp' : {'enable_mathjax' : False}},
297 """Disable MathJax
298 """Disable MathJax
298
299
299 MathJax is the javascript library IPython uses to render math/LaTeX. It is
300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
300 very large, so you may want to disable it if you have a slow internet
301 very large, so you may want to disable it if you have a slow internet
301 connection, or for offline use of the notebook.
302 connection, or for offline use of the notebook.
302
303
303 When disabled, equations etc. will appear as their untransformed TeX source.
304 When disabled, equations etc. will appear as their untransformed TeX source.
304 """
305 """
305 )
306 )
306
307
307 # Add notebook manager flags
308 # Add notebook manager flags
308 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
309 'DEPRECATED, IGNORED',
310 'DEPRECATED, IGNORED',
310 'DEPRECATED, IGNORED'))
311 'DEPRECATED, IGNORED'))
311
312
312 aliases = dict(base_aliases)
313 aliases = dict(base_aliases)
313
314
314 aliases.update({
315 aliases.update({
315 'ip': 'NotebookApp.ip',
316 'ip': 'NotebookApp.ip',
316 'port': 'NotebookApp.port',
317 'port': 'NotebookApp.port',
317 'port-retries': 'NotebookApp.port_retries',
318 'port-retries': 'NotebookApp.port_retries',
318 'transport': 'KernelManager.transport',
319 'transport': 'KernelManager.transport',
319 'keyfile': 'NotebookApp.keyfile',
320 'keyfile': 'NotebookApp.keyfile',
320 'certfile': 'NotebookApp.certfile',
321 'certfile': 'NotebookApp.certfile',
321 'notebook-dir': 'NotebookApp.notebook_dir',
322 'notebook-dir': 'NotebookApp.notebook_dir',
322 'browser': 'NotebookApp.browser',
323 'browser': 'NotebookApp.browser',
323 'pylab': 'NotebookApp.pylab',
324 'pylab': 'NotebookApp.pylab',
324 })
325 })
325
326
326 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
327 # NotebookApp
328 # NotebookApp
328 #-----------------------------------------------------------------------------
329 #-----------------------------------------------------------------------------
329
330
330 class NotebookApp(BaseIPythonApplication):
331 class NotebookApp(BaseIPythonApplication):
331
332
332 name = 'ipython-notebook'
333 name = 'ipython-notebook'
333
334
334 description = """
335 description = """
335 The IPython HTML Notebook.
336 The IPython HTML Notebook.
336
337
337 This launches a Tornado based HTML Notebook Server that serves up an
338 This launches a Tornado based HTML Notebook Server that serves up an
338 HTML5/Javascript Notebook client.
339 HTML5/Javascript Notebook client.
339 """
340 """
340 examples = _examples
341 examples = _examples
341 aliases = aliases
342 aliases = aliases
342 flags = flags
343 flags = flags
343
344
344 classes = [
345 classes = [
345 KernelManager, ProfileDir, Session, MappingKernelManager,
346 KernelManager, ProfileDir, Session, MappingKernelManager,
346 ContentsManager, FileContentsManager, NotebookNotary,
347 ContentsManager, FileContentsManager, NotebookNotary,
347 ]
348 ]
348 flags = Dict(flags)
349 flags = Dict(flags)
349 aliases = Dict(aliases)
350 aliases = Dict(aliases)
350
351
351 subcommands = dict(
352 subcommands = dict(
352 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
353 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
353 )
354 )
354
355
355 ipython_kernel_argv = List(Unicode)
356 ipython_kernel_argv = List(Unicode)
356
357
357 _log_formatter_cls = LogFormatter
358 _log_formatter_cls = LogFormatter
358
359
359 def _log_level_default(self):
360 def _log_level_default(self):
360 return logging.INFO
361 return logging.INFO
361
362
362 def _log_datefmt_default(self):
363 def _log_datefmt_default(self):
363 """Exclude date from default date format"""
364 """Exclude date from default date format"""
364 return "%H:%M:%S"
365 return "%H:%M:%S"
365
366
366 def _log_format_default(self):
367 def _log_format_default(self):
367 """override default log format to include time"""
368 """override default log format to include time"""
368 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
369 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
369
370
370 # create requested profiles by default, if they don't exist:
371 # create requested profiles by default, if they don't exist:
371 auto_create = Bool(True)
372 auto_create = Bool(True)
372
373
373 # file to be opened in the notebook server
374 # file to be opened in the notebook server
374 file_to_run = Unicode('', config=True)
375 file_to_run = Unicode('', config=True)
375
376
376 # Network related information
377 # Network related information
377
378
378 allow_origin = Unicode('', config=True,
379 allow_origin = Unicode('', config=True,
379 help="""Set the Access-Control-Allow-Origin header
380 help="""Set the Access-Control-Allow-Origin header
380
381
381 Use '*' to allow any origin to access your server.
382 Use '*' to allow any origin to access your server.
382
383
383 Takes precedence over allow_origin_pat.
384 Takes precedence over allow_origin_pat.
384 """
385 """
385 )
386 )
386
387
387 allow_origin_pat = Unicode('', config=True,
388 allow_origin_pat = Unicode('', config=True,
388 help="""Use a regular expression for the Access-Control-Allow-Origin header
389 help="""Use a regular expression for the Access-Control-Allow-Origin header
389
390
390 Requests from an origin matching the expression will get replies with:
391 Requests from an origin matching the expression will get replies with:
391
392
392 Access-Control-Allow-Origin: origin
393 Access-Control-Allow-Origin: origin
393
394
394 where `origin` is the origin of the request.
395 where `origin` is the origin of the request.
395
396
396 Ignored if allow_origin is set.
397 Ignored if allow_origin is set.
397 """
398 """
398 )
399 )
399
400
400 allow_credentials = Bool(False, config=True,
401 allow_credentials = Bool(False, config=True,
401 help="Set the Access-Control-Allow-Credentials: true header"
402 help="Set the Access-Control-Allow-Credentials: true header"
402 )
403 )
403
404
404 default_url = Unicode('/tree', config=True,
405 default_url = Unicode('/tree', config=True,
405 help="The default URL to redirect to from `/`"
406 help="The default URL to redirect to from `/`"
406 )
407 )
407
408
408 ip = Unicode('localhost', config=True,
409 ip = Unicode('localhost', config=True,
409 help="The IP address the notebook server will listen on."
410 help="The IP address the notebook server will listen on."
410 )
411 )
411
412
412 def _ip_changed(self, name, old, new):
413 def _ip_changed(self, name, old, new):
413 if new == u'*': self.ip = u''
414 if new == u'*': self.ip = u''
414
415
415 port = Integer(8888, config=True,
416 port = Integer(8888, config=True,
416 help="The port the notebook server will listen on."
417 help="The port the notebook server will listen on."
417 )
418 )
418 port_retries = Integer(50, config=True,
419 port_retries = Integer(50, config=True,
419 help="The number of additional ports to try if the specified port is not available."
420 help="The number of additional ports to try if the specified port is not available."
420 )
421 )
421
422
422 certfile = Unicode(u'', config=True,
423 certfile = Unicode(u'', config=True,
423 help="""The full path to an SSL/TLS certificate file."""
424 help="""The full path to an SSL/TLS certificate file."""
424 )
425 )
425
426
426 keyfile = Unicode(u'', config=True,
427 keyfile = Unicode(u'', config=True,
427 help="""The full path to a private key file for usage with SSL/TLS."""
428 help="""The full path to a private key file for usage with SSL/TLS."""
428 )
429 )
429
430
430 cookie_secret_file = Unicode(config=True,
431 cookie_secret_file = Unicode(config=True,
431 help="""The file where the cookie secret is stored."""
432 help="""The file where the cookie secret is stored."""
432 )
433 )
433 def _cookie_secret_file_default(self):
434 def _cookie_secret_file_default(self):
434 if self.profile_dir is None:
435 if self.profile_dir is None:
435 return ''
436 return ''
436 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
437 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
437
438
438 cookie_secret = Bytes(b'', config=True,
439 cookie_secret = Bytes(b'', config=True,
439 help="""The random bytes used to secure cookies.
440 help="""The random bytes used to secure cookies.
440 By default this is a new random number every time you start the Notebook.
441 By default this is a new random number every time you start the Notebook.
441 Set it to a value in a config file to enable logins to persist across server sessions.
442 Set it to a value in a config file to enable logins to persist across server sessions.
442
443
443 Note: Cookie secrets should be kept private, do not share config files with
444 Note: Cookie secrets should be kept private, do not share config files with
444 cookie_secret stored in plaintext (you can read the value from a file).
445 cookie_secret stored in plaintext (you can read the value from a file).
445 """
446 """
446 )
447 )
447 def _cookie_secret_default(self):
448 def _cookie_secret_default(self):
448 if os.path.exists(self.cookie_secret_file):
449 if os.path.exists(self.cookie_secret_file):
449 with io.open(self.cookie_secret_file, 'rb') as f:
450 with io.open(self.cookie_secret_file, 'rb') as f:
450 return f.read()
451 return f.read()
451 else:
452 else:
452 secret = base64.encodestring(os.urandom(1024))
453 secret = base64.encodestring(os.urandom(1024))
453 self._write_cookie_secret_file(secret)
454 self._write_cookie_secret_file(secret)
454 return secret
455 return secret
455
456
456 def _write_cookie_secret_file(self, secret):
457 def _write_cookie_secret_file(self, secret):
457 """write my secret to my secret_file"""
458 """write my secret to my secret_file"""
458 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
459 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
459 with io.open(self.cookie_secret_file, 'wb') as f:
460 with io.open(self.cookie_secret_file, 'wb') as f:
460 f.write(secret)
461 f.write(secret)
461 try:
462 try:
462 os.chmod(self.cookie_secret_file, 0o600)
463 os.chmod(self.cookie_secret_file, 0o600)
463 except OSError:
464 except OSError:
464 self.log.warn(
465 self.log.warn(
465 "Could not set permissions on %s",
466 "Could not set permissions on %s",
466 self.cookie_secret_file
467 self.cookie_secret_file
467 )
468 )
468
469
469 password = Unicode(u'', config=True,
470 password = Unicode(u'', config=True,
470 help="""Hashed password to use for web authentication.
471 help="""Hashed password to use for web authentication.
471
472
472 To generate, type in a python/IPython shell:
473 To generate, type in a python/IPython shell:
473
474
474 from IPython.lib import passwd; passwd()
475 from IPython.lib import passwd; passwd()
475
476
476 The string should be of the form type:salt:hashed-password.
477 The string should be of the form type:salt:hashed-password.
477 """
478 """
478 )
479 )
479
480
480 open_browser = Bool(True, config=True,
481 open_browser = Bool(True, config=True,
481 help="""Whether to open in a browser after starting.
482 help="""Whether to open in a browser after starting.
482 The specific browser used is platform dependent and
483 The specific browser used is platform dependent and
483 determined by the python standard library `webbrowser`
484 determined by the python standard library `webbrowser`
484 module, unless it is overridden using the --browser
485 module, unless it is overridden using the --browser
485 (NotebookApp.browser) configuration option.
486 (NotebookApp.browser) configuration option.
486 """)
487 """)
487
488
488 browser = Unicode(u'', config=True,
489 browser = Unicode(u'', config=True,
489 help="""Specify what command to use to invoke a web
490 help="""Specify what command to use to invoke a web
490 browser when opening the notebook. If not specified, the
491 browser when opening the notebook. If not specified, the
491 default browser will be determined by the `webbrowser`
492 default browser will be determined by the `webbrowser`
492 standard library module, which allows setting of the
493 standard library module, which allows setting of the
493 BROWSER environment variable to override it.
494 BROWSER environment variable to override it.
494 """)
495 """)
495
496
496 webapp_settings = Dict(config=True,
497 webapp_settings = Dict(config=True,
497 help="DEPRECATED, use tornado_settings"
498 help="DEPRECATED, use tornado_settings"
498 )
499 )
499 def _webapp_settings_changed(self, name, old, new):
500 def _webapp_settings_changed(self, name, old, new):
500 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
501 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
501 self.tornado_settings = new
502 self.tornado_settings = new
502
503
503 tornado_settings = Dict(config=True,
504 tornado_settings = Dict(config=True,
504 help="Supply overrides for the tornado.web.Application that the "
505 help="Supply overrides for the tornado.web.Application that the "
505 "IPython notebook uses.")
506 "IPython notebook uses.")
506
507
507 jinja_environment_options = Dict(config=True,
508 jinja_environment_options = Dict(config=True,
508 help="Supply extra arguments that will be passed to Jinja environment.")
509 help="Supply extra arguments that will be passed to Jinja environment.")
509
510
510 enable_mathjax = Bool(True, config=True,
511 enable_mathjax = Bool(True, config=True,
511 help="""Whether to enable MathJax for typesetting math/TeX
512 help="""Whether to enable MathJax for typesetting math/TeX
512
513
513 MathJax is the javascript library IPython uses to render math/LaTeX. It is
514 MathJax is the javascript library IPython uses to render math/LaTeX. It is
514 very large, so you may want to disable it if you have a slow internet
515 very large, so you may want to disable it if you have a slow internet
515 connection, or for offline use of the notebook.
516 connection, or for offline use of the notebook.
516
517
517 When disabled, equations etc. will appear as their untransformed TeX source.
518 When disabled, equations etc. will appear as their untransformed TeX source.
518 """
519 """
519 )
520 )
520 def _enable_mathjax_changed(self, name, old, new):
521 def _enable_mathjax_changed(self, name, old, new):
521 """set mathjax url to empty if mathjax is disabled"""
522 """set mathjax url to empty if mathjax is disabled"""
522 if not new:
523 if not new:
523 self.mathjax_url = u''
524 self.mathjax_url = u''
524
525
525 base_url = Unicode('/', config=True,
526 base_url = Unicode('/', config=True,
526 help='''The base URL for the notebook server.
527 help='''The base URL for the notebook server.
527
528
528 Leading and trailing slashes can be omitted,
529 Leading and trailing slashes can be omitted,
529 and will automatically be added.
530 and will automatically be added.
530 ''')
531 ''')
531 def _base_url_changed(self, name, old, new):
532 def _base_url_changed(self, name, old, new):
532 if not new.startswith('/'):
533 if not new.startswith('/'):
533 self.base_url = '/'+new
534 self.base_url = '/'+new
534 elif not new.endswith('/'):
535 elif not new.endswith('/'):
535 self.base_url = new+'/'
536 self.base_url = new+'/'
536
537
537 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
538 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
538 def _base_project_url_changed(self, name, old, new):
539 def _base_project_url_changed(self, name, old, new):
539 self.log.warn("base_project_url is deprecated, use base_url")
540 self.log.warn("base_project_url is deprecated, use base_url")
540 self.base_url = new
541 self.base_url = new
541
542
542 extra_static_paths = List(Unicode, config=True,
543 extra_static_paths = List(Unicode, config=True,
543 help="""Extra paths to search for serving static files.
544 help="""Extra paths to search for serving static files.
544
545
545 This allows adding javascript/css to be available from the notebook server machine,
546 This allows adding javascript/css to be available from the notebook server machine,
546 or overriding individual files in the IPython"""
547 or overriding individual files in the IPython"""
547 )
548 )
548 def _extra_static_paths_default(self):
549 def _extra_static_paths_default(self):
549 return [os.path.join(self.profile_dir.location, 'static')]
550 return [os.path.join(self.profile_dir.location, 'static')]
550
551
551 @property
552 @property
552 def static_file_path(self):
553 def static_file_path(self):
553 """return extra paths + the default location"""
554 """return extra paths + the default location"""
554 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
555 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
555
556
556 extra_template_paths = List(Unicode, config=True,
557 extra_template_paths = List(Unicode, config=True,
557 help="""Extra paths to search for serving jinja templates.
558 help="""Extra paths to search for serving jinja templates.
558
559
559 Can be used to override templates from IPython.html.templates."""
560 Can be used to override templates from IPython.html.templates."""
560 )
561 )
561 def _extra_template_paths_default(self):
562 def _extra_template_paths_default(self):
562 return []
563 return []
563
564
564 @property
565 @property
565 def template_file_path(self):
566 def template_file_path(self):
566 """return extra paths + the default locations"""
567 """return extra paths + the default locations"""
567 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
568 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
568
569
569 nbextensions_path = List(Unicode, config=True,
570 extra_nbextensions_path = List(Unicode, config=True,
570 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
571 help="""extra paths to look for Javascript notebook extensions"""
571 )
572 )
572 def _nbextensions_path_default(self):
573
573 return [os.path.join(get_ipython_dir(), 'nbextensions')]
574 @property
575 def nbextensions_path(self):
576 """The path to look for Javascript notebook extensions"""
577 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
574
578
575 websocket_url = Unicode("", config=True,
579 websocket_url = Unicode("", config=True,
576 help="""The base URL for websockets,
580 help="""The base URL for websockets,
577 if it differs from the HTTP server (hint: it almost certainly doesn't).
581 if it differs from the HTTP server (hint: it almost certainly doesn't).
578
582
579 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
583 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
580 """
584 """
581 )
585 )
582 mathjax_url = Unicode("", config=True,
586 mathjax_url = Unicode("", config=True,
583 help="""The url for MathJax.js."""
587 help="""The url for MathJax.js."""
584 )
588 )
585 def _mathjax_url_default(self):
589 def _mathjax_url_default(self):
586 if not self.enable_mathjax:
590 if not self.enable_mathjax:
587 return u''
591 return u''
588 static_url_prefix = self.tornado_settings.get("static_url_prefix",
592 static_url_prefix = self.tornado_settings.get("static_url_prefix",
589 url_path_join(self.base_url, "static")
593 url_path_join(self.base_url, "static")
590 )
594 )
591
595
592 # try local mathjax, either in nbextensions/mathjax or static/mathjax
596 # try local mathjax, either in nbextensions/mathjax or static/mathjax
593 for (url_prefix, search_path) in [
597 for (url_prefix, search_path) in [
594 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
598 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
595 (static_url_prefix, self.static_file_path),
599 (static_url_prefix, self.static_file_path),
596 ]:
600 ]:
597 self.log.debug("searching for local mathjax in %s", search_path)
601 self.log.debug("searching for local mathjax in %s", search_path)
598 try:
602 try:
599 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
603 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
600 except IOError:
604 except IOError:
601 continue
605 continue
602 else:
606 else:
603 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
607 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
604 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
608 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
605 return url
609 return url
606
610
607 # no local mathjax, serve from CDN
611 # no local mathjax, serve from CDN
608 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
612 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
609 self.log.info("Using MathJax from CDN: %s", url)
613 self.log.info("Using MathJax from CDN: %s", url)
610 return url
614 return url
611
615
612 def _mathjax_url_changed(self, name, old, new):
616 def _mathjax_url_changed(self, name, old, new):
613 if new and not self.enable_mathjax:
617 if new and not self.enable_mathjax:
614 # enable_mathjax=False overrides mathjax_url
618 # enable_mathjax=False overrides mathjax_url
615 self.mathjax_url = u''
619 self.mathjax_url = u''
616 else:
620 else:
617 self.log.info("Using MathJax: %s", new)
621 self.log.info("Using MathJax: %s", new)
618
622
619 contents_manager_class = Type(
623 contents_manager_class = Type(
620 default_value=FileContentsManager,
624 default_value=FileContentsManager,
621 klass=ContentsManager,
625 klass=ContentsManager,
622 config=True,
626 config=True,
623 help='The notebook manager class to use.'
627 help='The notebook manager class to use.'
624 )
628 )
625 kernel_manager_class = Type(
629 kernel_manager_class = Type(
626 default_value=MappingKernelManager,
630 default_value=MappingKernelManager,
627 config=True,
631 config=True,
628 help='The kernel manager class to use.'
632 help='The kernel manager class to use.'
629 )
633 )
630 session_manager_class = Type(
634 session_manager_class = Type(
631 default_value=SessionManager,
635 default_value=SessionManager,
632 config=True,
636 config=True,
633 help='The session manager class to use.'
637 help='The session manager class to use.'
634 )
638 )
635 cluster_manager_class = Type(
639 cluster_manager_class = Type(
636 default_value=ClusterManager,
640 default_value=ClusterManager,
637 config=True,
641 config=True,
638 help='The cluster manager class to use.'
642 help='The cluster manager class to use.'
639 )
643 )
640
644
641 config_manager_class = Type(
645 config_manager_class = Type(
642 default_value=ConfigManager,
646 default_value=ConfigManager,
643 config = True,
647 config = True,
644 help='The config manager class to use'
648 help='The config manager class to use'
645 )
649 )
646
650
647 kernel_spec_manager = Instance(KernelSpecManager)
651 kernel_spec_manager = Instance(KernelSpecManager)
648
652
649 def _kernel_spec_manager_default(self):
653 def _kernel_spec_manager_default(self):
650 return KernelSpecManager(ipython_dir=self.ipython_dir)
654 return KernelSpecManager(ipython_dir=self.ipython_dir)
651
655
652 kernel_spec_manager_class = Type(
656 kernel_spec_manager_class = Type(
653 default_value=KernelSpecManager,
657 default_value=KernelSpecManager,
654 config=True,
658 config=True,
655 help="""
659 help="""
656 The kernel spec manager class to use. Should be a subclass
660 The kernel spec manager class to use. Should be a subclass
657 of `IPython.kernel.kernelspec.KernelSpecManager`.
661 of `IPython.kernel.kernelspec.KernelSpecManager`.
658
662
659 The Api of KernelSpecManager is provisional and might change
663 The Api of KernelSpecManager is provisional and might change
660 without warning between this version of IPython and the next stable one.
664 without warning between this version of IPython and the next stable one.
661 """
665 """
662 )
666 )
663
667
664 login_handler_class = Type(
668 login_handler_class = Type(
665 default_value=LoginHandler,
669 default_value=LoginHandler,
666 klass=web.RequestHandler,
670 klass=web.RequestHandler,
667 config=True,
671 config=True,
668 help='The login handler class to use.',
672 help='The login handler class to use.',
669 )
673 )
670
674
671 logout_handler_class = Type(
675 logout_handler_class = Type(
672 default_value=LogoutHandler,
676 default_value=LogoutHandler,
673 klass=web.RequestHandler,
677 klass=web.RequestHandler,
674 config=True,
678 config=True,
675 help='The logout handler class to use.',
679 help='The logout handler class to use.',
676 )
680 )
677
681
678 trust_xheaders = Bool(False, config=True,
682 trust_xheaders = Bool(False, config=True,
679 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
683 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
680 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
684 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
681 )
685 )
682
686
683 info_file = Unicode()
687 info_file = Unicode()
684
688
685 def _info_file_default(self):
689 def _info_file_default(self):
686 info_file = "nbserver-%s.json"%os.getpid()
690 info_file = "nbserver-%s.json"%os.getpid()
687 return os.path.join(self.profile_dir.security_dir, info_file)
691 return os.path.join(self.profile_dir.security_dir, info_file)
688
692
689 pylab = Unicode('disabled', config=True,
693 pylab = Unicode('disabled', config=True,
690 help="""
694 help="""
691 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
695 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
692 """
696 """
693 )
697 )
694 def _pylab_changed(self, name, old, new):
698 def _pylab_changed(self, name, old, new):
695 """when --pylab is specified, display a warning and exit"""
699 """when --pylab is specified, display a warning and exit"""
696 if new != 'warn':
700 if new != 'warn':
697 backend = ' %s' % new
701 backend = ' %s' % new
698 else:
702 else:
699 backend = ''
703 backend = ''
700 self.log.error("Support for specifying --pylab on the command line has been removed.")
704 self.log.error("Support for specifying --pylab on the command line has been removed.")
701 self.log.error(
705 self.log.error(
702 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
706 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
703 )
707 )
704 self.exit(1)
708 self.exit(1)
705
709
706 notebook_dir = Unicode(config=True,
710 notebook_dir = Unicode(config=True,
707 help="The directory to use for notebooks and kernels."
711 help="The directory to use for notebooks and kernels."
708 )
712 )
709
713
710 def _notebook_dir_default(self):
714 def _notebook_dir_default(self):
711 if self.file_to_run:
715 if self.file_to_run:
712 return os.path.dirname(os.path.abspath(self.file_to_run))
716 return os.path.dirname(os.path.abspath(self.file_to_run))
713 else:
717 else:
714 return py3compat.getcwd()
718 return py3compat.getcwd()
715
719
716 def _notebook_dir_changed(self, name, old, new):
720 def _notebook_dir_changed(self, name, old, new):
717 """Do a bit of validation of the notebook dir."""
721 """Do a bit of validation of the notebook dir."""
718 if not os.path.isabs(new):
722 if not os.path.isabs(new):
719 # If we receive a non-absolute path, make it absolute.
723 # If we receive a non-absolute path, make it absolute.
720 self.notebook_dir = os.path.abspath(new)
724 self.notebook_dir = os.path.abspath(new)
721 return
725 return
722 if not os.path.isdir(new):
726 if not os.path.isdir(new):
723 raise TraitError("No such notebook dir: %r" % new)
727 raise TraitError("No such notebook dir: %r" % new)
724
728
725 # setting App.notebook_dir implies setting notebook and kernel dirs as well
729 # setting App.notebook_dir implies setting notebook and kernel dirs as well
726 self.config.FileContentsManager.root_dir = new
730 self.config.FileContentsManager.root_dir = new
727 self.config.MappingKernelManager.root_dir = new
731 self.config.MappingKernelManager.root_dir = new
728
732
729 server_extensions = List(Unicode(), config=True,
733 server_extensions = List(Unicode(), config=True,
730 help=("Python modules to load as notebook server extensions. "
734 help=("Python modules to load as notebook server extensions. "
731 "This is an experimental API, and may change in future releases.")
735 "This is an experimental API, and may change in future releases.")
732 )
736 )
733
737
734 def parse_command_line(self, argv=None):
738 def parse_command_line(self, argv=None):
735 super(NotebookApp, self).parse_command_line(argv)
739 super(NotebookApp, self).parse_command_line(argv)
736
740
737 if self.extra_args:
741 if self.extra_args:
738 arg0 = self.extra_args[0]
742 arg0 = self.extra_args[0]
739 f = os.path.abspath(arg0)
743 f = os.path.abspath(arg0)
740 self.argv.remove(arg0)
744 self.argv.remove(arg0)
741 if not os.path.exists(f):
745 if not os.path.exists(f):
742 self.log.critical("No such file or directory: %s", f)
746 self.log.critical("No such file or directory: %s", f)
743 self.exit(1)
747 self.exit(1)
744
748
745 # Use config here, to ensure that it takes higher priority than
749 # Use config here, to ensure that it takes higher priority than
746 # anything that comes from the profile.
750 # anything that comes from the profile.
747 c = Config()
751 c = Config()
748 if os.path.isdir(f):
752 if os.path.isdir(f):
749 c.NotebookApp.notebook_dir = f
753 c.NotebookApp.notebook_dir = f
750 elif os.path.isfile(f):
754 elif os.path.isfile(f):
751 c.NotebookApp.file_to_run = f
755 c.NotebookApp.file_to_run = f
752 self.update_config(c)
756 self.update_config(c)
753
757
754 def init_kernel_argv(self):
758 def init_kernel_argv(self):
755 """add the profile-dir to arguments to be passed to IPython kernels"""
759 """add the profile-dir to arguments to be passed to IPython kernels"""
756 # FIXME: remove special treatment of IPython kernels
760 # FIXME: remove special treatment of IPython kernels
757 # Kernel should get *absolute* path to profile directory
761 # Kernel should get *absolute* path to profile directory
758 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
762 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
759
763
760 def init_configurables(self):
764 def init_configurables(self):
761 # force Session default to be secure
765 # force Session default to be secure
762 default_secure(self.config)
766 default_secure(self.config)
763
767
764 self.kernel_spec_manager = self.kernel_spec_manager_class(
768 self.kernel_spec_manager = self.kernel_spec_manager_class(
765 ipython_dir=self.ipython_dir,
769 ipython_dir=self.ipython_dir,
766 )
770 )
767 self.kernel_manager = self.kernel_manager_class(
771 self.kernel_manager = self.kernel_manager_class(
768 parent=self,
772 parent=self,
769 log=self.log,
773 log=self.log,
770 ipython_kernel_argv=self.ipython_kernel_argv,
774 ipython_kernel_argv=self.ipython_kernel_argv,
771 connection_dir=self.profile_dir.security_dir,
775 connection_dir=self.profile_dir.security_dir,
772 )
776 )
773 self.contents_manager = self.contents_manager_class(
777 self.contents_manager = self.contents_manager_class(
774 parent=self,
778 parent=self,
775 log=self.log,
779 log=self.log,
776 )
780 )
777 self.session_manager = self.session_manager_class(
781 self.session_manager = self.session_manager_class(
778 parent=self,
782 parent=self,
779 log=self.log,
783 log=self.log,
780 kernel_manager=self.kernel_manager,
784 kernel_manager=self.kernel_manager,
781 contents_manager=self.contents_manager,
785 contents_manager=self.contents_manager,
782 )
786 )
783 self.cluster_manager = self.cluster_manager_class(
787 self.cluster_manager = self.cluster_manager_class(
784 parent=self,
788 parent=self,
785 log=self.log,
789 log=self.log,
786 )
790 )
787
791
788 self.config_manager = self.config_manager_class(
792 self.config_manager = self.config_manager_class(
789 parent=self,
793 parent=self,
790 log=self.log,
794 log=self.log,
791 profile_dir=self.profile_dir.location,
795 profile_dir=self.profile_dir.location,
792 )
796 )
793
797
794 def init_logging(self):
798 def init_logging(self):
795 # This prevents double log messages because tornado use a root logger that
799 # This prevents double log messages because tornado use a root logger that
796 # self.log is a child of. The logging module dipatches log messages to a log
800 # self.log is a child of. The logging module dipatches log messages to a log
797 # and all of its ancenstors until propagate is set to False.
801 # and all of its ancenstors until propagate is set to False.
798 self.log.propagate = False
802 self.log.propagate = False
799
803
800 for log in app_log, access_log, gen_log:
804 for log in app_log, access_log, gen_log:
801 # consistent log output name (NotebookApp instead of tornado.access, etc.)
805 # consistent log output name (NotebookApp instead of tornado.access, etc.)
802 log.name = self.log.name
806 log.name = self.log.name
803 # hook up tornado 3's loggers to our app handlers
807 # hook up tornado 3's loggers to our app handlers
804 logger = logging.getLogger('tornado')
808 logger = logging.getLogger('tornado')
805 logger.propagate = True
809 logger.propagate = True
806 logger.parent = self.log
810 logger.parent = self.log
807 logger.setLevel(self.log.level)
811 logger.setLevel(self.log.level)
808
812
809 def init_webapp(self):
813 def init_webapp(self):
810 """initialize tornado webapp and httpserver"""
814 """initialize tornado webapp and httpserver"""
811 self.tornado_settings['allow_origin'] = self.allow_origin
815 self.tornado_settings['allow_origin'] = self.allow_origin
812 if self.allow_origin_pat:
816 if self.allow_origin_pat:
813 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
817 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
814 self.tornado_settings['allow_credentials'] = self.allow_credentials
818 self.tornado_settings['allow_credentials'] = self.allow_credentials
815
819
816 self.web_app = NotebookWebApplication(
820 self.web_app = NotebookWebApplication(
817 self, self.kernel_manager, self.contents_manager,
821 self, self.kernel_manager, self.contents_manager,
818 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
822 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
819 self.config_manager,
823 self.config_manager,
820 self.log, self.base_url, self.default_url, self.tornado_settings,
824 self.log, self.base_url, self.default_url, self.tornado_settings,
821 self.jinja_environment_options
825 self.jinja_environment_options
822 )
826 )
823 if self.certfile:
827 if self.certfile:
824 ssl_options = dict(certfile=self.certfile)
828 ssl_options = dict(certfile=self.certfile)
825 if self.keyfile:
829 if self.keyfile:
826 ssl_options['keyfile'] = self.keyfile
830 ssl_options['keyfile'] = self.keyfile
827 else:
831 else:
828 ssl_options = None
832 ssl_options = None
829 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
833 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
830 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
834 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
831 xheaders=self.trust_xheaders)
835 xheaders=self.trust_xheaders)
832
836
833 success = None
837 success = None
834 for port in random_ports(self.port, self.port_retries+1):
838 for port in random_ports(self.port, self.port_retries+1):
835 try:
839 try:
836 self.http_server.listen(port, self.ip)
840 self.http_server.listen(port, self.ip)
837 except socket.error as e:
841 except socket.error as e:
838 if e.errno == errno.EADDRINUSE:
842 if e.errno == errno.EADDRINUSE:
839 self.log.info('The port %i is already in use, trying another random port.' % port)
843 self.log.info('The port %i is already in use, trying another random port.' % port)
840 continue
844 continue
841 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
845 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
842 self.log.warn("Permission to listen on port %i denied" % port)
846 self.log.warn("Permission to listen on port %i denied" % port)
843 continue
847 continue
844 else:
848 else:
845 raise
849 raise
846 else:
850 else:
847 self.port = port
851 self.port = port
848 success = True
852 success = True
849 break
853 break
850 if not success:
854 if not success:
851 self.log.critical('ERROR: the notebook server could not be started because '
855 self.log.critical('ERROR: the notebook server could not be started because '
852 'no available port could be found.')
856 'no available port could be found.')
853 self.exit(1)
857 self.exit(1)
854
858
855 @property
859 @property
856 def display_url(self):
860 def display_url(self):
857 ip = self.ip if self.ip else '[all ip addresses on your system]'
861 ip = self.ip if self.ip else '[all ip addresses on your system]'
858 return self._url(ip)
862 return self._url(ip)
859
863
860 @property
864 @property
861 def connection_url(self):
865 def connection_url(self):
862 ip = self.ip if self.ip else 'localhost'
866 ip = self.ip if self.ip else 'localhost'
863 return self._url(ip)
867 return self._url(ip)
864
868
865 def _url(self, ip):
869 def _url(self, ip):
866 proto = 'https' if self.certfile else 'http'
870 proto = 'https' if self.certfile else 'http'
867 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
871 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
868
872
869 def init_terminals(self):
873 def init_terminals(self):
870 try:
874 try:
871 from .terminal import initialize
875 from .terminal import initialize
872 initialize(self.web_app)
876 initialize(self.web_app)
873 self.web_app.settings['terminals_available'] = True
877 self.web_app.settings['terminals_available'] = True
874 except ImportError as e:
878 except ImportError as e:
875 self.log.info("Terminals not available (error was %s)", e)
879 self.log.info("Terminals not available (error was %s)", e)
876
880
877 def init_signal(self):
881 def init_signal(self):
878 if not sys.platform.startswith('win'):
882 if not sys.platform.startswith('win'):
879 signal.signal(signal.SIGINT, self._handle_sigint)
883 signal.signal(signal.SIGINT, self._handle_sigint)
880 signal.signal(signal.SIGTERM, self._signal_stop)
884 signal.signal(signal.SIGTERM, self._signal_stop)
881 if hasattr(signal, 'SIGUSR1'):
885 if hasattr(signal, 'SIGUSR1'):
882 # Windows doesn't support SIGUSR1
886 # Windows doesn't support SIGUSR1
883 signal.signal(signal.SIGUSR1, self._signal_info)
887 signal.signal(signal.SIGUSR1, self._signal_info)
884 if hasattr(signal, 'SIGINFO'):
888 if hasattr(signal, 'SIGINFO'):
885 # only on BSD-based systems
889 # only on BSD-based systems
886 signal.signal(signal.SIGINFO, self._signal_info)
890 signal.signal(signal.SIGINFO, self._signal_info)
887
891
888 def _handle_sigint(self, sig, frame):
892 def _handle_sigint(self, sig, frame):
889 """SIGINT handler spawns confirmation dialog"""
893 """SIGINT handler spawns confirmation dialog"""
890 # register more forceful signal handler for ^C^C case
894 # register more forceful signal handler for ^C^C case
891 signal.signal(signal.SIGINT, self._signal_stop)
895 signal.signal(signal.SIGINT, self._signal_stop)
892 # request confirmation dialog in bg thread, to avoid
896 # request confirmation dialog in bg thread, to avoid
893 # blocking the App
897 # blocking the App
894 thread = threading.Thread(target=self._confirm_exit)
898 thread = threading.Thread(target=self._confirm_exit)
895 thread.daemon = True
899 thread.daemon = True
896 thread.start()
900 thread.start()
897
901
898 def _restore_sigint_handler(self):
902 def _restore_sigint_handler(self):
899 """callback for restoring original SIGINT handler"""
903 """callback for restoring original SIGINT handler"""
900 signal.signal(signal.SIGINT, self._handle_sigint)
904 signal.signal(signal.SIGINT, self._handle_sigint)
901
905
902 def _confirm_exit(self):
906 def _confirm_exit(self):
903 """confirm shutdown on ^C
907 """confirm shutdown on ^C
904
908
905 A second ^C, or answering 'y' within 5s will cause shutdown,
909 A second ^C, or answering 'y' within 5s will cause shutdown,
906 otherwise original SIGINT handler will be restored.
910 otherwise original SIGINT handler will be restored.
907
911
908 This doesn't work on Windows.
912 This doesn't work on Windows.
909 """
913 """
910 info = self.log.info
914 info = self.log.info
911 info('interrupted')
915 info('interrupted')
912 print(self.notebook_info())
916 print(self.notebook_info())
913 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
917 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
914 sys.stdout.flush()
918 sys.stdout.flush()
915 r,w,x = select.select([sys.stdin], [], [], 5)
919 r,w,x = select.select([sys.stdin], [], [], 5)
916 if r:
920 if r:
917 line = sys.stdin.readline()
921 line = sys.stdin.readline()
918 if line.lower().startswith('y') and 'n' not in line.lower():
922 if line.lower().startswith('y') and 'n' not in line.lower():
919 self.log.critical("Shutdown confirmed")
923 self.log.critical("Shutdown confirmed")
920 ioloop.IOLoop.current().stop()
924 ioloop.IOLoop.current().stop()
921 return
925 return
922 else:
926 else:
923 print("No answer for 5s:", end=' ')
927 print("No answer for 5s:", end=' ')
924 print("resuming operation...")
928 print("resuming operation...")
925 # no answer, or answer is no:
929 # no answer, or answer is no:
926 # set it back to original SIGINT handler
930 # set it back to original SIGINT handler
927 # use IOLoop.add_callback because signal.signal must be called
931 # use IOLoop.add_callback because signal.signal must be called
928 # from main thread
932 # from main thread
929 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
933 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
930
934
931 def _signal_stop(self, sig, frame):
935 def _signal_stop(self, sig, frame):
932 self.log.critical("received signal %s, stopping", sig)
936 self.log.critical("received signal %s, stopping", sig)
933 ioloop.IOLoop.current().stop()
937 ioloop.IOLoop.current().stop()
934
938
935 def _signal_info(self, sig, frame):
939 def _signal_info(self, sig, frame):
936 print(self.notebook_info())
940 print(self.notebook_info())
937
941
938 def init_components(self):
942 def init_components(self):
939 """Check the components submodule, and warn if it's unclean"""
943 """Check the components submodule, and warn if it's unclean"""
940 status = submodule.check_submodule_status()
944 status = submodule.check_submodule_status()
941 if status == 'missing':
945 if status == 'missing':
942 self.log.warn("components submodule missing, running `git submodule update`")
946 self.log.warn("components submodule missing, running `git submodule update`")
943 submodule.update_submodules(submodule.ipython_parent())
947 submodule.update_submodules(submodule.ipython_parent())
944 elif status == 'unclean':
948 elif status == 'unclean':
945 self.log.warn("components submodule unclean, you may see 404s on static/components")
949 self.log.warn("components submodule unclean, you may see 404s on static/components")
946 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
950 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
947
951
948 def init_server_extensions(self):
952 def init_server_extensions(self):
949 """Load any extensions specified by config.
953 """Load any extensions specified by config.
950
954
951 Import the module, then call the load_jupyter_server_extension function,
955 Import the module, then call the load_jupyter_server_extension function,
952 if one exists.
956 if one exists.
953
957
954 The extension API is experimental, and may change in future releases.
958 The extension API is experimental, and may change in future releases.
955 """
959 """
956 for modulename in self.server_extensions:
960 for modulename in self.server_extensions:
957 try:
961 try:
958 mod = importlib.import_module(modulename)
962 mod = importlib.import_module(modulename)
959 func = getattr(mod, 'load_jupyter_server_extension', None)
963 func = getattr(mod, 'load_jupyter_server_extension', None)
960 if func is not None:
964 if func is not None:
961 func(self)
965 func(self)
962 except Exception:
966 except Exception:
963 self.log.warn("Error loading server extension %s", modulename,
967 self.log.warn("Error loading server extension %s", modulename,
964 exc_info=True)
968 exc_info=True)
965
969
966 @catch_config_error
970 @catch_config_error
967 def initialize(self, argv=None):
971 def initialize(self, argv=None):
968 super(NotebookApp, self).initialize(argv)
972 super(NotebookApp, self).initialize(argv)
969 self.init_logging()
973 self.init_logging()
970 self.init_kernel_argv()
974 self.init_kernel_argv()
971 self.init_configurables()
975 self.init_configurables()
972 self.init_components()
976 self.init_components()
973 self.init_webapp()
977 self.init_webapp()
974 self.init_terminals()
978 self.init_terminals()
975 self.init_signal()
979 self.init_signal()
976 self.init_server_extensions()
980 self.init_server_extensions()
977
981
978 def cleanup_kernels(self):
982 def cleanup_kernels(self):
979 """Shutdown all kernels.
983 """Shutdown all kernels.
980
984
981 The kernels will shutdown themselves when this process no longer exists,
985 The kernels will shutdown themselves when this process no longer exists,
982 but explicit shutdown allows the KernelManagers to cleanup the connection files.
986 but explicit shutdown allows the KernelManagers to cleanup the connection files.
983 """
987 """
984 self.log.info('Shutting down kernels')
988 self.log.info('Shutting down kernels')
985 self.kernel_manager.shutdown_all()
989 self.kernel_manager.shutdown_all()
986
990
987 def notebook_info(self):
991 def notebook_info(self):
988 "Return the current working directory and the server url information"
992 "Return the current working directory and the server url information"
989 info = self.contents_manager.info_string() + "\n"
993 info = self.contents_manager.info_string() + "\n"
990 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
994 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
991 return info + "The IPython Notebook is running at: %s" % self.display_url
995 return info + "The IPython Notebook is running at: %s" % self.display_url
992
996
993 def server_info(self):
997 def server_info(self):
994 """Return a JSONable dict of information about this server."""
998 """Return a JSONable dict of information about this server."""
995 return {'url': self.connection_url,
999 return {'url': self.connection_url,
996 'hostname': self.ip if self.ip else 'localhost',
1000 'hostname': self.ip if self.ip else 'localhost',
997 'port': self.port,
1001 'port': self.port,
998 'secure': bool(self.certfile),
1002 'secure': bool(self.certfile),
999 'base_url': self.base_url,
1003 'base_url': self.base_url,
1000 'notebook_dir': os.path.abspath(self.notebook_dir),
1004 'notebook_dir': os.path.abspath(self.notebook_dir),
1001 'pid': os.getpid()
1005 'pid': os.getpid()
1002 }
1006 }
1003
1007
1004 def write_server_info_file(self):
1008 def write_server_info_file(self):
1005 """Write the result of server_info() to the JSON file info_file."""
1009 """Write the result of server_info() to the JSON file info_file."""
1006 with open(self.info_file, 'w') as f:
1010 with open(self.info_file, 'w') as f:
1007 json.dump(self.server_info(), f, indent=2)
1011 json.dump(self.server_info(), f, indent=2)
1008
1012
1009 def remove_server_info_file(self):
1013 def remove_server_info_file(self):
1010 """Remove the nbserver-<pid>.json file created for this server.
1014 """Remove the nbserver-<pid>.json file created for this server.
1011
1015
1012 Ignores the error raised when the file has already been removed.
1016 Ignores the error raised when the file has already been removed.
1013 """
1017 """
1014 try:
1018 try:
1015 os.unlink(self.info_file)
1019 os.unlink(self.info_file)
1016 except OSError as e:
1020 except OSError as e:
1017 if e.errno != errno.ENOENT:
1021 if e.errno != errno.ENOENT:
1018 raise
1022 raise
1019
1023
1020 def start(self):
1024 def start(self):
1021 """ Start the IPython Notebook server app, after initialization
1025 """ Start the IPython Notebook server app, after initialization
1022
1026
1023 This method takes no arguments so all configuration and initialization
1027 This method takes no arguments so all configuration and initialization
1024 must be done prior to calling this method."""
1028 must be done prior to calling this method."""
1025 if self.subapp is not None:
1029 if self.subapp is not None:
1026 return self.subapp.start()
1030 return self.subapp.start()
1027
1031
1028 info = self.log.info
1032 info = self.log.info
1029 for line in self.notebook_info().split("\n"):
1033 for line in self.notebook_info().split("\n"):
1030 info(line)
1034 info(line)
1031 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1035 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1032
1036
1033 self.write_server_info_file()
1037 self.write_server_info_file()
1034
1038
1035 if self.open_browser or self.file_to_run:
1039 if self.open_browser or self.file_to_run:
1036 try:
1040 try:
1037 browser = webbrowser.get(self.browser or None)
1041 browser = webbrowser.get(self.browser or None)
1038 except webbrowser.Error as e:
1042 except webbrowser.Error as e:
1039 self.log.warn('No web browser found: %s.' % e)
1043 self.log.warn('No web browser found: %s.' % e)
1040 browser = None
1044 browser = None
1041
1045
1042 if self.file_to_run:
1046 if self.file_to_run:
1043 if not os.path.exists(self.file_to_run):
1047 if not os.path.exists(self.file_to_run):
1044 self.log.critical("%s does not exist" % self.file_to_run)
1048 self.log.critical("%s does not exist" % self.file_to_run)
1045 self.exit(1)
1049 self.exit(1)
1046
1050
1047 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1051 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1048 uri = url_path_join('notebooks', *relpath.split(os.sep))
1052 uri = url_path_join('notebooks', *relpath.split(os.sep))
1049 else:
1053 else:
1050 uri = 'tree'
1054 uri = 'tree'
1051 if browser:
1055 if browser:
1052 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1056 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1053 new=2)
1057 new=2)
1054 threading.Thread(target=b).start()
1058 threading.Thread(target=b).start()
1055
1059
1056 self.io_loop = ioloop.IOLoop.current()
1060 self.io_loop = ioloop.IOLoop.current()
1057 try:
1061 try:
1058 self.io_loop.start()
1062 self.io_loop.start()
1059 except KeyboardInterrupt:
1063 except KeyboardInterrupt:
1060 info("Interrupted...")
1064 info("Interrupted...")
1061 finally:
1065 finally:
1062 self.cleanup_kernels()
1066 self.cleanup_kernels()
1063 self.remove_server_info_file()
1067 self.remove_server_info_file()
1064
1068
1065 def stop(self):
1069 def stop(self):
1066 def _stop():
1070 def _stop():
1067 self.http_server.stop()
1071 self.http_server.stop()
1068 self.io_loop.stop()
1072 self.io_loop.stop()
1069 self.io_loop.add_callback(_stop)
1073 self.io_loop.add_callback(_stop)
1070
1074
1071
1075
1072 def list_running_servers(profile='default'):
1076 def list_running_servers(profile='default'):
1073 """Iterate over the server info files of running notebook servers.
1077 """Iterate over the server info files of running notebook servers.
1074
1078
1075 Given a profile name, find nbserver-* files in the security directory of
1079 Given a profile name, find nbserver-* files in the security directory of
1076 that profile, and yield dicts of their information, each one pertaining to
1080 that profile, and yield dicts of their information, each one pertaining to
1077 a currently running notebook server instance.
1081 a currently running notebook server instance.
1078 """
1082 """
1079 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1083 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1080 for file in os.listdir(pd.security_dir):
1084 for file in os.listdir(pd.security_dir):
1081 if file.startswith('nbserver-'):
1085 if file.startswith('nbserver-'):
1082 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1086 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1083 info = json.load(f)
1087 info = json.load(f)
1084
1088
1085 # Simple check whether that process is really still running
1089 # Simple check whether that process is really still running
1086 # Also remove leftover files from IPython 2.x without a pid field
1090 # Also remove leftover files from IPython 2.x without a pid field
1087 if ('pid' in info) and check_pid(info['pid']):
1091 if ('pid' in info) and check_pid(info['pid']):
1088 yield info
1092 yield info
1089 else:
1093 else:
1090 # If the process has died, try to delete its info file
1094 # If the process has died, try to delete its info file
1091 try:
1095 try:
1092 os.unlink(file)
1096 os.unlink(file)
1093 except OSError:
1097 except OSError:
1094 pass # TODO: This should warn or log or something
1098 pass # TODO: This should warn or log or something
1095 #-----------------------------------------------------------------------------
1099 #-----------------------------------------------------------------------------
1096 # Main entry point
1100 # Main entry point
1097 #-----------------------------------------------------------------------------
1101 #-----------------------------------------------------------------------------
1098
1102
1099 launch_new_instance = NotebookApp.launch_instance
1103 launch_new_instance = NotebookApp.launch_instance
1100
1104
@@ -1,272 +1,282 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test installation of notebook extensions"""
2 """Test installation of notebook extensions"""
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2014 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
3
10 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
11 # Imports
5 # Distributed under the terms of the Modified BSD License.
12 #-----------------------------------------------------------------------------
13
6
14 import glob
7 import glob
15 import os
8 import os
16 import re
9 import re
17 import tarfile
10 import tarfile
18 import zipfile
11 import zipfile
19 from io import BytesIO
12 from io import BytesIO
20 from os.path import basename, join as pjoin
13 from os.path import basename, join as pjoin
21 from unittest import TestCase
14 from unittest import TestCase
22
15
23 import IPython.testing.tools as tt
16 import IPython.testing.tools as tt
24 import IPython.testing.decorators as dec
17 import IPython.testing.decorators as dec
25 from IPython.utils import py3compat
18 from IPython.utils import py3compat
26 from IPython.utils.tempdir import TemporaryDirectory
19 from IPython.utils.tempdir import TemporaryDirectory
27 from IPython.html import nbextensions
20 from IPython.html import nbextensions
28 from IPython.html.nbextensions import install_nbextension, check_nbextension
21 from IPython.html.nbextensions import install_nbextension, check_nbextension
29
22
30 #-----------------------------------------------------------------------------
31 # Test functions
32 #-----------------------------------------------------------------------------
33
23
34 def touch(file, mtime=None):
24 def touch(file, mtime=None):
35 """ensure a file exists, and set its modification time
25 """ensure a file exists, and set its modification time
36
26
37 returns the modification time of the file
27 returns the modification time of the file
38 """
28 """
39 open(file, 'a').close()
29 open(file, 'a').close()
40 # set explicit mtime
30 # set explicit mtime
41 if mtime:
31 if mtime:
42 atime = os.stat(file).st_atime
32 atime = os.stat(file).st_atime
43 os.utime(file, (atime, mtime))
33 os.utime(file, (atime, mtime))
44 return os.stat(file).st_mtime
34 return os.stat(file).st_mtime
45
35
46
47 class TestInstallNBExtension(TestCase):
36 class TestInstallNBExtension(TestCase):
48
37
49 def tempdir(self):
38 def tempdir(self):
50 td = TemporaryDirectory()
39 td = TemporaryDirectory()
51 self.tempdirs.append(td)
40 self.tempdirs.append(td)
52 return py3compat.cast_unicode(td.name)
41 return py3compat.cast_unicode(td.name)
53
42
54 def setUp(self):
43 def setUp(self):
55 self.tempdirs = []
44 self.tempdirs = []
56 src = self.src = self.tempdir()
45 src = self.src = self.tempdir()
57 self.files = files = [
46 self.files = files = [
58 pjoin(u'ƒile'),
47 pjoin(u'ƒile'),
59 pjoin(u'∂ir', u'ƒile1'),
48 pjoin(u'∂ir', u'ƒile1'),
60 pjoin(u'∂ir', u'∂ir2', u'ƒile2'),
49 pjoin(u'∂ir', u'∂ir2', u'ƒile2'),
61 ]
50 ]
62 for file in files:
51 for file in files:
63 fullpath = os.path.join(self.src, file)
52 fullpath = os.path.join(self.src, file)
64 parent = os.path.dirname(fullpath)
53 parent = os.path.dirname(fullpath)
65 if not os.path.exists(parent):
54 if not os.path.exists(parent):
66 os.makedirs(parent)
55 os.makedirs(parent)
67 touch(fullpath)
56 touch(fullpath)
68
57
69 self.ipdir = self.tempdir()
58 self.ipdir = self.tempdir()
70 self.save_get_ipython_dir = nbextensions.get_ipython_dir
59 self.save_get_ipython_dir = nbextensions.get_ipython_dir
71 nbextensions.get_ipython_dir = lambda : self.ipdir
60 nbextensions.get_ipython_dir = lambda : self.ipdir
61 self.save_system_dir = nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR
62 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = self.tempdir()
72
63
73 def tearDown(self):
64 def tearDown(self):
65 nbextensions.get_ipython_dir = self.save_get_ipython_dir
66 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.save_system_dir
74 for td in self.tempdirs:
67 for td in self.tempdirs:
75 td.cleanup()
68 td.cleanup()
76 nbextensions.get_ipython_dir = self.save_get_ipython_dir
69
77
78 def assert_dir_exists(self, path):
70 def assert_dir_exists(self, path):
79 if not os.path.exists(path):
71 if not os.path.exists(path):
80 do_exist = os.listdir(os.path.dirname(path))
72 do_exist = os.listdir(os.path.dirname(path))
81 self.fail(u"%s should exist (found %s)" % (path, do_exist))
73 self.fail(u"%s should exist (found %s)" % (path, do_exist))
82
74
83 def assert_not_dir_exists(self, path):
75 def assert_not_dir_exists(self, path):
84 if os.path.exists(path):
76 if os.path.exists(path):
85 self.fail(u"%s should not exist" % path)
77 self.fail(u"%s should not exist" % path)
86
78
87 def assert_installed(self, relative_path, ipdir=None):
79 def assert_installed(self, relative_path, user=False):
80 if user:
81 nbext = pjoin(self.ipdir, u'nbextensions')
82 else:
83 nbext = self.system_nbext
88 self.assert_dir_exists(
84 self.assert_dir_exists(
89 pjoin(ipdir or self.ipdir, u'nbextensions', relative_path)
85 pjoin(nbext, relative_path)
90 )
86 )
91
87
92 def assert_not_installed(self, relative_path, ipdir=None):
88 def assert_not_installed(self, relative_path, user=False):
89 if user:
90 nbext = pjoin(self.ipdir, u'nbextensions')
91 else:
92 nbext = self.system_nbext
93 self.assert_not_dir_exists(
93 self.assert_not_dir_exists(
94 pjoin(ipdir or self.ipdir, u'nbextensions', relative_path)
94 pjoin(nbext, relative_path)
95 )
95 )
96
96
97 def test_create_ipython_dir(self):
97 def test_create_ipython_dir(self):
98 """install_nbextension when ipython_dir doesn't exist"""
98 """install_nbextension when ipython_dir doesn't exist"""
99 with TemporaryDirectory() as td:
99 with TemporaryDirectory() as td:
100 ipdir = pjoin(td, u'ipython')
100 self.ipdir = ipdir = pjoin(td, u'ipython')
101 install_nbextension(self.src, ipython_dir=ipdir)
101 install_nbextension(self.src, user=True)
102 self.assert_dir_exists(ipdir)
102 self.assert_dir_exists(ipdir)
103 for file in self.files:
103 for file in self.files:
104 self.assert_installed(
104 self.assert_installed(
105 pjoin(basename(self.src), file),
105 pjoin(basename(self.src), file),
106 ipdir
106 ipdir
107 )
107 )
108
108
109 def test_create_nbextensions(self):
109 def test_create_nbextensions_user(self):
110 with TemporaryDirectory() as ipdir:
110 with TemporaryDirectory() as td:
111 install_nbextension(self.src, ipython_dir=ipdir)
111 self.ipdir = ipdir = pjoin(td, u'ipython')
112 install_nbextension(self.src, user=True)
113 self.assert_installed(
114 pjoin(basename(self.src), u'ƒile'),
115 user=True
116 )
117
118 def test_create_nbextensions_system(self):
119 with TemporaryDirectory() as td:
120 nbextensions.SYSTEM_NBEXTENSIONS_INSTALL_DIR = self.system_nbext = pjoin(td, u'nbextensions')
121 install_nbextension(self.src, user=False)
112 self.assert_installed(
122 self.assert_installed(
113 pjoin(basename(self.src), u'ƒile'),
123 pjoin(basename(self.src), u'ƒile'),
114 ipdir
124 user=False
115 )
125 )
116
126
117 def test_single_file(self):
127 def test_single_file(self):
118 file = self.files[0]
128 file = self.files[0]
119 install_nbextension(pjoin(self.src, file))
129 install_nbextension(pjoin(self.src, file))
120 self.assert_installed(file)
130 self.assert_installed(file)
121
131
122 def test_single_dir(self):
132 def test_single_dir(self):
123 d = u'∂ir'
133 d = u'∂ir'
124 install_nbextension(pjoin(self.src, d))
134 install_nbextension(pjoin(self.src, d))
125 self.assert_installed(self.files[-1])
135 self.assert_installed(self.files[-1])
126
136
127 def test_install_nbextension(self):
137 def test_install_nbextension(self):
128 install_nbextension(glob.glob(pjoin(self.src, '*')))
138 install_nbextension(glob.glob(pjoin(self.src, '*')))
129 for file in self.files:
139 for file in self.files:
130 self.assert_installed(file)
140 self.assert_installed(file)
131
141
132 def test_overwrite_file(self):
142 def test_overwrite_file(self):
133 with TemporaryDirectory() as d:
143 with TemporaryDirectory() as d:
134 fname = u'ƒ.js'
144 fname = u'ƒ.js'
135 src = pjoin(d, fname)
145 src = pjoin(d, fname)
136 with open(src, 'w') as f:
146 with open(src, 'w') as f:
137 f.write('first')
147 f.write('first')
138 mtime = touch(src)
148 mtime = touch(src)
139 dest = pjoin(self.ipdir, u'nbextensions', fname)
149 dest = pjoin(self.system_nbext, fname)
140 install_nbextension(src)
150 install_nbextension(src)
141 with open(src, 'w') as f:
151 with open(src, 'w') as f:
142 f.write('overwrite')
152 f.write('overwrite')
143 mtime = touch(src, mtime - 100)
153 mtime = touch(src, mtime - 100)
144 install_nbextension(src, overwrite=True)
154 install_nbextension(src, overwrite=True)
145 with open(dest) as f:
155 with open(dest) as f:
146 self.assertEqual(f.read(), 'overwrite')
156 self.assertEqual(f.read(), 'overwrite')
147
157
148 def test_overwrite_dir(self):
158 def test_overwrite_dir(self):
149 with TemporaryDirectory() as src:
159 with TemporaryDirectory() as src:
150 # src = py3compat.cast_unicode_py2(src)
151 base = basename(src)
160 base = basename(src)
152 fname = u'ƒ.js'
161 fname = u'ƒ.js'
153 touch(pjoin(src, fname))
162 touch(pjoin(src, fname))
154 install_nbextension(src)
163 install_nbextension(src)
155 self.assert_installed(pjoin(base, fname))
164 self.assert_installed(pjoin(base, fname))
156 os.remove(pjoin(src, fname))
165 os.remove(pjoin(src, fname))
157 fname2 = u'∂.js'
166 fname2 = u'∂.js'
158 touch(pjoin(src, fname2))
167 touch(pjoin(src, fname2))
159 install_nbextension(src, overwrite=True)
168 install_nbextension(src, overwrite=True)
160 self.assert_installed(pjoin(base, fname2))
169 self.assert_installed(pjoin(base, fname2))
161 self.assert_not_installed(pjoin(base, fname))
170 self.assert_not_installed(pjoin(base, fname))
162
171
163 def test_update_file(self):
172 def test_update_file(self):
164 with TemporaryDirectory() as d:
173 with TemporaryDirectory() as d:
165 fname = u'ƒ.js'
174 fname = u'ƒ.js'
166 src = pjoin(d, fname)
175 src = pjoin(d, fname)
167 with open(src, 'w') as f:
176 with open(src, 'w') as f:
168 f.write('first')
177 f.write('first')
169 mtime = touch(src)
178 mtime = touch(src)
170 install_nbextension(src)
179 install_nbextension(src)
171 self.assert_installed(fname)
180 self.assert_installed(fname)
172 dest = pjoin(self.ipdir, u'nbextensions', fname)
181 dest = pjoin(self.system_nbext, fname)
173 old_mtime = os.stat(dest).st_mtime
182 old_mtime = os.stat(dest).st_mtime
174 with open(src, 'w') as f:
183 with open(src, 'w') as f:
175 f.write('overwrite')
184 f.write('overwrite')
176 touch(src, mtime + 10)
185 touch(src, mtime + 10)
177 install_nbextension(src)
186 install_nbextension(src)
178 with open(dest) as f:
187 with open(dest) as f:
179 self.assertEqual(f.read(), 'overwrite')
188 self.assertEqual(f.read(), 'overwrite')
180
189
181 def test_skip_old_file(self):
190 def test_skip_old_file(self):
182 with TemporaryDirectory() as d:
191 with TemporaryDirectory() as d:
183 fname = u'ƒ.js'
192 fname = u'ƒ.js'
184 src = pjoin(d, fname)
193 src = pjoin(d, fname)
185 mtime = touch(src)
194 mtime = touch(src)
186 install_nbextension(src)
195 install_nbextension(src)
187 self.assert_installed(fname)
196 self.assert_installed(fname)
188 dest = pjoin(self.ipdir, u'nbextensions', fname)
197 dest = pjoin(self.system_nbext, fname)
189 old_mtime = os.stat(dest).st_mtime
198 old_mtime = os.stat(dest).st_mtime
190
199
191 mtime = touch(src, mtime - 100)
200 mtime = touch(src, mtime - 100)
192 install_nbextension(src)
201 install_nbextension(src)
193 new_mtime = os.stat(dest).st_mtime
202 new_mtime = os.stat(dest).st_mtime
194 self.assertEqual(new_mtime, old_mtime)
203 self.assertEqual(new_mtime, old_mtime)
195
204
196 def test_quiet(self):
205 def test_quiet(self):
197 with tt.AssertNotPrints(re.compile(r'.+')):
206 with tt.AssertNotPrints(re.compile(r'.+')):
198 install_nbextension(self.src, verbose=0)
207 install_nbextension(self.src, verbose=0)
199
208
200 def test_install_zip(self):
209 def test_install_zip(self):
201 path = pjoin(self.src, "myjsext.zip")
210 path = pjoin(self.src, "myjsext.zip")
202 with zipfile.ZipFile(path, 'w') as f:
211 with zipfile.ZipFile(path, 'w') as f:
203 f.writestr("a.js", b"b();")
212 f.writestr("a.js", b"b();")
204 f.writestr("foo/a.js", b"foo();")
213 f.writestr("foo/a.js", b"foo();")
205 install_nbextension(path)
214 install_nbextension(path)
206 self.assert_installed("a.js")
215 self.assert_installed("a.js")
207 self.assert_installed(pjoin("foo", "a.js"))
216 self.assert_installed(pjoin("foo", "a.js"))
208
217
209 def test_install_tar(self):
218 def test_install_tar(self):
210 def _add_file(f, fname, buf):
219 def _add_file(f, fname, buf):
211 info = tarfile.TarInfo(fname)
220 info = tarfile.TarInfo(fname)
212 info.size = len(buf)
221 info.size = len(buf)
213 f.addfile(info, BytesIO(buf))
222 f.addfile(info, BytesIO(buf))
214
223
215 for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")):
224 for i,ext in enumerate((".tar.gz", ".tgz", ".tar.bz2")):
216 path = pjoin(self.src, "myjsext" + ext)
225 path = pjoin(self.src, "myjsext" + ext)
217 with tarfile.open(path, 'w') as f:
226 with tarfile.open(path, 'w') as f:
218 _add_file(f, "b%i.js" % i, b"b();")
227 _add_file(f, "b%i.js" % i, b"b();")
219 _add_file(f, "foo/b%i.js" % i, b"foo();")
228 _add_file(f, "foo/b%i.js" % i, b"foo();")
220 install_nbextension(path)
229 install_nbextension(path)
221 self.assert_installed("b%i.js" % i)
230 self.assert_installed("b%i.js" % i)
222 self.assert_installed(pjoin("foo", "b%i.js" % i))
231 self.assert_installed(pjoin("foo", "b%i.js" % i))
223
232
224 def test_install_url(self):
233 def test_install_url(self):
225 def fake_urlretrieve(url, dest):
234 def fake_urlretrieve(url, dest):
226 touch(dest)
235 touch(dest)
227 save_urlretrieve = nbextensions.urlretrieve
236 save_urlretrieve = nbextensions.urlretrieve
228 nbextensions.urlretrieve = fake_urlretrieve
237 nbextensions.urlretrieve = fake_urlretrieve
229 try:
238 try:
230 install_nbextension("http://example.com/path/to/foo.js")
239 install_nbextension("http://example.com/path/to/foo.js")
231 self.assert_installed("foo.js")
240 self.assert_installed("foo.js")
232 install_nbextension("https://example.com/path/to/another/bar.js")
241 install_nbextension("https://example.com/path/to/another/bar.js")
233 self.assert_installed("bar.js")
242 self.assert_installed("bar.js")
234 finally:
243 finally:
235 nbextensions.urlretrieve = save_urlretrieve
244 nbextensions.urlretrieve = save_urlretrieve
236
245
237 def test_check_nbextension(self):
246 def test_check_nbextension(self):
238 with TemporaryDirectory() as d:
247 with TemporaryDirectory() as d:
239 f = u'ƒ.js'
248 f = u'ƒ.js'
240 src = pjoin(d, f)
249 src = pjoin(d, f)
241 touch(src)
250 touch(src)
242 install_nbextension(src)
251 install_nbextension(src, user=True)
243
252
244 assert check_nbextension(f, self.ipdir)
253 nbext = pjoin(self.ipdir, u'nbextensions')
245 assert check_nbextension([f], self.ipdir)
254 assert check_nbextension(f, nbext)
246 assert not check_nbextension([f, pjoin('dne', f)], self.ipdir)
255 assert check_nbextension([f], nbext)
256 assert not check_nbextension([f, pjoin('dne', f)], nbext)
247
257
248 @dec.skip_win32
258 @dec.skip_win32
249 def test_install_symlink(self):
259 def test_install_symlink(self):
250 with TemporaryDirectory() as d:
260 with TemporaryDirectory() as d:
251 f = u'ƒ.js'
261 f = u'ƒ.js'
252 src = pjoin(d, f)
262 src = pjoin(d, f)
253 touch(src)
263 touch(src)
254 install_nbextension(src, symlink=True)
264 install_nbextension(src, symlink=True)
255 dest = pjoin(self.ipdir, u'nbextensions', f)
265 dest = pjoin(self.system_nbext, f)
256 assert os.path.islink(dest)
266 assert os.path.islink(dest)
257 link = os.readlink(dest)
267 link = os.readlink(dest)
258 self.assertEqual(link, src)
268 self.assertEqual(link, src)
259
269
260 def test_install_symlink_bad(self):
270 def test_install_symlink_bad(self):
261 with self.assertRaises(ValueError):
271 with self.assertRaises(ValueError):
262 install_nbextension("http://example.com/foo.js", symlink=True)
272 install_nbextension("http://example.com/foo.js", symlink=True)
263
273
264 with TemporaryDirectory() as d:
274 with TemporaryDirectory() as d:
265 zf = u'ƒ.zip'
275 zf = u'ƒ.zip'
266 zsrc = pjoin(d, zf)
276 zsrc = pjoin(d, zf)
267 with zipfile.ZipFile(zsrc, 'w') as z:
277 with zipfile.ZipFile(zsrc, 'w') as z:
268 z.writestr("a.js", b"b();")
278 z.writestr("a.js", b"b();")
269
279
270 with self.assertRaises(ValueError):
280 with self.assertRaises(ValueError):
271 install_nbextension(zsrc, symlink=True)
281 install_nbextension(zsrc, symlink=True)
272
282
General Comments 0
You need to be logged in to leave comments. Login now