##// END OF EJS Templates
Merge pull request #7387 from minrk/system-wide-nbextensions...
Matthias Bussonnier -
r19862:6a4192ef merge
parent child Browse files
Show More
@@ -8,6 +8,7 b' 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
@@ -24,6 +25,36 b' 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
28 class ArgumentConflict(ValueError):
29 pass
30
31 # Packagers: modify the next block if you store system-installed nbextensions elsewhere (unlikely)
32 SYSTEM_NBEXTENSIONS_DIRS = []
33
34 if os.name == 'nt':
35 programdata = os.environ.get('PROGRAMDATA', None)
36 if programdata: # PROGRAMDATA is not defined by default on XP.
37 SYSTEM_NBEXTENSIONS_DIRS = [pjoin(programdata, 'jupyter', 'nbextensions')]
38 prefixes = []
39 else:
40 prefixes = [os.path.sep + pjoin('usr', 'local'), os.path.sep + 'usr']
41
42 # add sys.prefix at the front
43 if sys.prefix not in prefixes:
44 prefixes.insert(0, sys.prefix)
45
46 for prefix in prefixes:
47 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
48 if nbext not in SYSTEM_NBEXTENSIONS_DIRS:
49 SYSTEM_NBEXTENSIONS_DIRS.append(nbext)
50
51 if os.name == 'nt':
52 # PROGRAMDATA
53 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-1]
54 else:
55 # /usr/local
56 SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-2]
57
27
58
28 def _should_copy(src, dest, verbose=1):
59 def _should_copy(src, dest, verbose=1):
29 """should a file be copied?"""
60 """should a file be copied?"""
@@ -54,15 +85,17 b' def _safe_is_tarfile(path):'
54 return False
85 return False
55
86
56
87
57 def check_nbextension(files, ipython_dir=None):
88 def check_nbextension(files, nbextensions_dir=None):
58 """Check whether nbextension files have been installed
89 """Check whether nbextension files have been installed
59
90
60 files should be a list of relative paths within nbextensions.
91 files should be a list of relative paths within nbextensions.
61
92
62 Returns True if all files are found, False if any are missing.
93 Returns True if all files are found, False if any are missing.
63 """
94 """
64 ipython_dir = ipython_dir or get_ipython_dir()
95 if nbextensions_dir:
65 nbext = pjoin(ipython_dir, u'nbextensions')
96 nbext = nbextensions_dir
97 else:
98 nbext = pjoin(get_ipython_dir(), u'nbextensions')
66 # make sure nbextensions dir exists
99 # make sure nbextensions dir exists
67 if not os.path.exists(nbext):
100 if not os.path.exists(nbext):
68 return False
101 return False
@@ -74,10 +107,10 b' def check_nbextension(files, ipython_dir=None):'
74 return all(os.path.exists(pjoin(nbext, f)) for f in files)
107 return all(os.path.exists(pjoin(nbext, f)) for f in files)
75
108
76
109
77 def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, verbose=1):
110 def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1):
78 """Install a Javascript extension for the notebook
111 """Install a Javascript extension for the notebook
79
112
80 Stages files and/or directories into IPYTHONDIR/nbextensions.
113 Stages files and/or directories into the nbextensions directory.
81 By default, this compares modification time, and only stages files that need updating.
114 By default, this compares modification time, and only stages files that need updating.
82 If `overwrite` is specified, matching files are purged before proceeding.
115 If `overwrite` is specified, matching files are purged before proceeding.
83
116
@@ -96,16 +129,29 b' def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None,'
96 Not allowed with URLs or archives. Windows support for symlinks requires
129 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
130 Vista or above, Python 3, and a permission bit which only admin users
98 have by default, so don't rely on it.
131 have by default, so don't rely on it.
99 ipython_dir : str [optional]
132 user : bool [default: False]
100 The path to an IPython directory, if the default value is not desired.
133 Whether to install to the user's .ipython/nbextensions directory.
101 get_ipython_dir() is used by default.
134 Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions).
135 prefix : str [optional]
136 Specify install prefix, if it should differ from default (e.g. /usr/local).
137 Will install to prefix/share/jupyter/nbextensions
138 nbextensions_dir : str [optional]
139 Specify absolute path of nbextensions directory explicitly.
102 verbose : int [default: 1]
140 verbose : int [default: 1]
103 Set verbosity level. The default is 1, where file actions are printed.
141 Set verbosity level. The default is 1, where file actions are printed.
104 set verbose=2 for more output, or verbose=0 for silence.
142 set verbose=2 for more output, or verbose=0 for silence.
105 """
143 """
106
144 if sum(map(bool, [user, prefix, nbextensions_dir])) > 1:
107 ipython_dir = ipython_dir or get_ipython_dir()
145 raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.")
108 nbext = pjoin(ipython_dir, u'nbextensions')
146 if user:
147 nbext = pjoin(get_ipython_dir(), u'nbextensions')
148 else:
149 if prefix:
150 nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions')
151 elif nbextensions_dir:
152 nbext = nbextensions_dir
153 else:
154 nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR
109 # make sure nbextensions dir exists
155 # make sure nbextensions dir exists
110 ensure_dir_exists(nbext)
156 ensure_dir_exists(nbext)
111
157
@@ -126,7 +172,7 b' def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None,'
126 print("downloading %s to %s" % (path, local_path))
172 print("downloading %s to %s" % (path, local_path))
127 urlretrieve(path, local_path)
173 urlretrieve(path, local_path)
128 # now install from the local copy
174 # now install from the local copy
129 install_nbextension(local_path, overwrite, symlink, ipython_dir, verbose)
175 install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose)
130 continue
176 continue
131
177
132 # handle archives
178 # handle archives
@@ -183,7 +229,7 b' def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None,'
183 # install nbextension app
229 # install nbextension app
184 #----------------------------------------------------------------------
230 #----------------------------------------------------------------------
185
231
186 from IPython.utils.traitlets import Bool, Enum
232 from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError
187 from IPython.core.application import BaseIPythonApplication
233 from IPython.core.application import BaseIPythonApplication
188
234
189 flags = {
235 flags = {
@@ -207,11 +253,18 b' flags = {'
207 "symlink" : True,
253 "symlink" : True,
208 }}, "Create symlinks instead of copying files"
254 }}, "Create symlinks instead of copying files"
209 ),
255 ),
256 "user" : ({
257 "NBExtensionApp" : {
258 "user" : True,
259 }}, "Install to the user's IPython directory"
260 ),
210 }
261 }
211 flags['s'] = flags['symlink']
262 flags['s'] = flags['symlink']
212
263
213 aliases = {
264 aliases = {
214 "ipython-dir" : "NBExtensionApp.ipython_dir"
265 "ipython-dir" : "NBExtensionApp.ipython_dir",
266 "prefix" : "NBExtensionApp.prefix",
267 "nbextensions" : "NBExtensionApp.nbextensions_dir",
215 }
268 }
216
269
217 class NBExtensionApp(BaseIPythonApplication):
270 class NBExtensionApp(BaseIPythonApplication):
@@ -238,6 +291,9 b' class NBExtensionApp(BaseIPythonApplication):'
238
291
239 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
292 overwrite = Bool(False, config=True, help="Force overwrite of existing files")
240 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
293 symlink = Bool(False, config=True, help="Create symlinks instead of copying files")
294 user = Bool(False, config=True, help="Whether to do a user install")
295 prefix = Unicode('', config=True, help="Installation prefix")
296 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
241 verbose = Enum((0,1,2), default_value=1, config=True,
297 verbose = Enum((0,1,2), default_value=1, config=True,
242 help="Verbosity level"
298 help="Verbosity level"
243 )
299 )
@@ -247,17 +303,24 b' class NBExtensionApp(BaseIPythonApplication):'
247 overwrite=self.overwrite,
303 overwrite=self.overwrite,
248 symlink=self.symlink,
304 symlink=self.symlink,
249 verbose=self.verbose,
305 verbose=self.verbose,
250 ipython_dir=self.ipython_dir,
306 user=self.user,
307 prefix=self.prefix,
308 nbextensions_dir=self.nbextensions_dir,
251 )
309 )
252
310
253 def start(self):
311 def start(self):
254 if not self.extra_args:
312 if not self.extra_args:
255 nbext = pjoin(self.ipython_dir, u'nbextensions')
313 for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS:
256 print("Notebook extensions in %s:" % nbext)
314 if os.path.exists(nbext):
257 for ext in os.listdir(nbext):
315 print("Notebook extensions in %s:" % nbext)
258 print(u" %s" % ext)
316 for ext in os.listdir(nbext):
317 print(u" %s" % ext)
259 else:
318 else:
260 self.install_extensions()
319 try:
320 self.install_extensions()
321 except ArgumentConflict as e:
322 print(str(e), file=sys.stderr)
323 self.exit(1)
261
324
262
325
263 if __name__ == '__main__':
326 if __name__ == '__main__':
@@ -90,6 +90,7 b' 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 #-----------------------------------------------------------------------------
@@ -566,11 +567,14 b' class NotebookApp(BaseIPythonApplication):'
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,
@@ -1,15 +1,8 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
@@ -27,9 +20,6 b' 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
@@ -43,7 +33,6 b' def touch(file, mtime=None):'
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):
@@ -69,12 +58,15 b' class TestInstallNBExtension(TestCase):'
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))
@@ -84,21 +76,29 b' class TestInstallNBExtension(TestCase):'
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(
@@ -106,12 +106,22 b' class TestInstallNBExtension(TestCase):'
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):
@@ -136,7 +146,7 b' class TestInstallNBExtension(TestCase):'
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')
@@ -147,7 +157,6 b' class TestInstallNBExtension(TestCase):'
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))
@@ -169,7 +178,7 b' class TestInstallNBExtension(TestCase):'
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')
@@ -185,7 +194,7 b' class TestInstallNBExtension(TestCase):'
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)
@@ -239,11 +248,12 b' class TestInstallNBExtension(TestCase):'
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):
@@ -252,7 +262,7 b' class TestInstallNBExtension(TestCase):'
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)
General Comments 0
You need to be logged in to leave comments. Login now