##// END OF EJS Templates
Change install_nbextension to take install only a single nbextension (file, folder, archive, url), with an optional destination argument
Jason Grout -
Show More
@@ -134,7 +134,7 b' def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None):'
134 return all(os.path.exists(pjoin(nbext, f)) for f in files)
134 return all(os.path.exists(pjoin(nbext, f)) for f in files)
135
135
136
136
137 def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1):
137 def install_nbextension(path, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, destination=None, verbose=1):
138 """Install a Javascript extension for the notebook
138 """Install a Javascript extension for the notebook
139
139
140 Stages files and/or directories into the nbextensions directory.
140 Stages files and/or directories into the nbextensions directory.
@@ -144,11 +144,9 b' def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi'
144 Parameters
144 Parameters
145 ----------
145 ----------
146
146
147 files : list(paths or URLs) or dict(install_name: path or URL)
147 path : path to file, directory, zip or tarball archive, or URL to install
148 One or more paths or URLs to existing files directories to install.
148 By default, the file will be installed with its base name, so '/path/to/foo'
149 If given as a list, these will be installed with their base name, so '/path/to/foo'
149 will install to 'nbextensions/foo'. See the destination argument below to change this.
150 will install to 'nbextensions/foo'. If given as a dict, such as {'bar': '/path/to/foo'},
151 then '/path/to/foo' will install to 'nbextensions/bar'.
152 Archives (zip or tarballs) will be extracted into the nbextensions directory.
150 Archives (zip or tarballs) will be extracted into the nbextensions directory.
153 overwrite : bool [default: False]
151 overwrite : bool [default: False]
154 If True, always install the files, regardless of what may already be installed.
152 If True, always install the files, regardless of what may already be installed.
@@ -165,6 +163,10 b' def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi'
165 Will install to prefix/share/jupyter/nbextensions
163 Will install to prefix/share/jupyter/nbextensions
166 nbextensions_dir : str [optional]
164 nbextensions_dir : str [optional]
167 Specify absolute path of nbextensions directory explicitly.
165 Specify absolute path of nbextensions directory explicitly.
166 destination : str [optional]
167 name the nbextension is installed to. For example, if destination is 'foo', then
168 the source file will be installed to 'nbextensions/foo', regardless of the source name.
169 This cannot be specified if an archive is given as the source.
168 verbose : int [default: 1]
170 verbose : int [default: 1]
169 Set verbosity level. The default is 1, where file actions are printed.
171 Set verbosity level. The default is 1, where file actions are printed.
170 set verbose=2 for more output, or verbose=0 for silence.
172 set verbose=2 for more output, or verbose=0 for silence.
@@ -173,23 +175,10 b' def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi'
173 # make sure nbextensions dir exists
175 # make sure nbextensions dir exists
174 ensure_dir_exists(nbext)
176 ensure_dir_exists(nbext)
175
177
176 if isinstance(files, string_types):
178 if isinstance(path, (list, tuple)):
177 # one file given, turn it into a list
179 raise TypeError("path must be a string pointing to a single extension to install; call this function multiple times to install multiple extensions")
178 files = [files]
179 if isinstance(files, (list,tuple)):
180 # list given, turn into dict
181 _files = {}
182 for path in map(cast_unicode_py2, files):
183 if path.startswith(('https://', 'http://')):
184 destination = urlparse(path).path.split('/')[-1]
185 elif path.endswith('.zip') or _safe_is_tarfile(path):
186 destination = str(uuid.uuid4()) # ignored for archives
187 else:
188 destination = basename(path)
189 _files[destination] = path
190 files = _files
191
180
192 for dest_basename,path in (map(cast_unicode_py2, item) for item in files.items()):
181 path = cast_unicode_py2(path)
193
182
194 if path.startswith(('https://', 'http://')):
183 if path.startswith(('https://', 'http://')):
195 if symlink:
184 if symlink:
@@ -202,46 +191,45 b' def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi'
202 print("downloading %s to %s" % (path, local_path))
191 print("downloading %s to %s" % (path, local_path))
203 urlretrieve(path, local_path)
192 urlretrieve(path, local_path)
204 # now install from the local copy
193 # now install from the local copy
205 install_nbextension({dest_basename: local_path}, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose)
194 install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, destination=destination, verbose=verbose)
206 continue
195 elif path.endswith('.zip') or _safe_is_tarfile(path):
196 if symlink:
197 raise ValueError("Cannot symlink from archives")
198 if destination:
199 raise ValueError("Cannot give destination for archives")
200 if verbose >= 1:
201 print("extracting %s to %s" % (path, nbext))
207
202
208 # handle archives
209 archive = None
210 if path.endswith('.zip'):
203 if path.endswith('.zip'):
211 archive = zipfile.ZipFile(path)
204 archive = zipfile.ZipFile(path)
212 elif _safe_is_tarfile(path):
205 elif _safe_is_tarfile(path):
213 archive = tarfile.open(path)
206 archive = tarfile.open(path)
214
207 else:
215 if archive:
208 raise ValueError("Could not extract archive")
216 if symlink:
217 raise ValueError("Cannot symlink from archives")
218 if verbose >= 1:
219 print("extracting %s to %s" % (path, nbext))
220 archive.extractall(nbext)
209 archive.extractall(nbext)
221 archive.close()
210 archive.close()
222 continue
211 else:
223
212 if not destination:
224 dest = pjoin(nbext, dest_basename)
213 destination = basename(path)
225 if overwrite and os.path.exists(dest):
214 full_dest = pjoin(nbext, destination)
215 if overwrite and os.path.exists(full_dest):
226 if verbose >= 1:
216 if verbose >= 1:
227 print("removing %s" % dest)
217 print("removing %s" % full_dest)
228 if os.path.isdir(dest) and not os.path.islink(dest):
218 if os.path.isdir(full_dest) and not os.path.islink(full_dest):
229 shutil.rmtree(dest)
219 shutil.rmtree(full_dest)
230 else:
220 else:
231 os.remove(dest)
221 os.remove(full_dest)
232
222
233 if symlink:
223 if symlink:
234 path = os.path.abspath(path)
224 path = os.path.abspath(path)
235 if not os.path.exists(dest):
225 if not os.path.exists(full_dest):
236 if verbose >= 1:
226 if verbose >= 1:
237 print("symlink %s -> %s" % (dest, path))
227 print("symlink %s -> %s" % (full_dest, path))
238 os.symlink(path, dest)
228 os.symlink(path, full_dest)
239 continue
229 elif os.path.isdir(path):
240
241 if os.path.isdir(path):
242 path = pjoin(os.path.abspath(path), '') # end in path separator
230 path = pjoin(os.path.abspath(path), '') # end in path separator
243 for parent, dirs, files in os.walk(path):
231 for parent, dirs, files in os.walk(path):
244 dest_dir = pjoin(dest, parent[len(path):])
232 dest_dir = pjoin(full_dest, parent[len(path):])
245 if not os.path.exists(dest_dir):
233 if not os.path.exists(dest_dir):
246 if verbose >= 2:
234 if verbose >= 2:
247 print("making directory %s" % dest_dir)
235 print("making directory %s" % dest_dir)
@@ -281,7 +269,7 b' flags = {'
281 "symlink" : ({
269 "symlink" : ({
282 "NBExtensionApp" : {
270 "NBExtensionApp" : {
283 "symlink" : True,
271 "symlink" : True,
284 }}, "Create symlinks instead of copying files"
272 }}, "Create symlink instead of copying files"
285 ),
273 ),
286 "user" : ({
274 "user" : ({
287 "NBExtensionApp" : {
275 "NBExtensionApp" : {
@@ -295,6 +283,7 b' aliases = {'
295 "ipython-dir" : "NBExtensionApp.ipython_dir",
283 "ipython-dir" : "NBExtensionApp.ipython_dir",
296 "prefix" : "NBExtensionApp.prefix",
284 "prefix" : "NBExtensionApp.prefix",
297 "nbextensions" : "NBExtensionApp.nbextensions_dir",
285 "nbextensions" : "NBExtensionApp.nbextensions_dir",
286 "destination" : "NBExtensionApp.destination",
298 }
287 }
299
288
300 class NBExtensionApp(BaseIPythonApplication):
289 class NBExtensionApp(BaseIPythonApplication):
@@ -304,9 +293,9 b' class NBExtensionApp(BaseIPythonApplication):'
304
293
305 Usage
294 Usage
306
295
307 ipython install-nbextension file [more files, folders, archives or urls]
296 ipython install-nbextension [file, folder, archive, or url]
308
297
309 This copies files and/or folders into the IPython nbextensions directory.
298 This copies a file and/or a folder into the IPython nbextensions directory.
310 If a URL is given, it will be downloaded.
299 If a URL is given, it will be downloaded.
311 If an archive is given, it will be extracted into nbextensions.
300 If an archive is given, it will be extracted into nbextensions.
312 If the requested files are already up to date, no action is taken
301 If the requested files are already up to date, no action is taken
@@ -314,7 +303,7 b' class NBExtensionApp(BaseIPythonApplication):'
314 """
303 """
315
304
316 examples = """
305 examples = """
317 ipython install-nbextension /path/to/d3.js /path/to/myextension
306 ipython install-nbextension /path/to/myextension
318 """
307 """
319 aliases = aliases
308 aliases = aliases
320 flags = flags
309 flags = flags
@@ -324,6 +313,7 b' class NBExtensionApp(BaseIPythonApplication):'
324 user = Bool(False, config=True, help="Whether to do a user install")
313 user = Bool(False, config=True, help="Whether to do a user install")
325 prefix = Unicode('', config=True, help="Installation prefix")
314 prefix = Unicode('', config=True, help="Installation prefix")
326 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
315 nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
316 destination = Unicode('', config=True, help="Destination for the copy or symlink")
327 verbose = Enum((0,1,2), default_value=1, config=True,
317 verbose = Enum((0,1,2), default_value=1, config=True,
328 help="Verbosity level"
318 help="Verbosity level"
329 )
319 )
@@ -335,6 +325,7 b' class NBExtensionApp(BaseIPythonApplication):'
335 verbose=self.verbose,
325 verbose=self.verbose,
336 user=self.user,
326 user=self.user,
337 prefix=self.prefix,
327 prefix=self.prefix,
328 destination=self.destination,
338 nbextensions_dir=self.nbextensions_dir,
329 nbextensions_dir=self.nbextensions_dir,
339 )
330 )
340
331
@@ -133,13 +133,21 b' class TestInstallNBExtension(TestCase):'
133 d = u'∂ir'
133 d = u'∂ir'
134 install_nbextension(pjoin(self.src, d))
134 install_nbextension(pjoin(self.src, d))
135 self.assert_installed(self.files[-1])
135 self.assert_installed(self.files[-1])
136 install_nbextension({'test': pjoin(self.src, d)})
136
137 self.assert_installed(pjoin('test', u'∂ir2', u'ƒile2'))
137
138 def test_destination_file(self):
139 file = self.files[0]
140 install_nbextension(pjoin(self.src, file), destination = u'ƒiledest')
141 self.assert_installed(u'ƒiledest')
142
143 def test_destination_dir(self):
144 d = u'∂ir'
145 install_nbextension(pjoin(self.src, d), destination = u'ƒiledest2')
146 self.assert_installed(pjoin(u'ƒiledest2', u'∂ir2', u'ƒile2'))
138
147
139 def test_install_nbextension(self):
148 def test_install_nbextension(self):
149 with self.assertRaises(TypeError):
140 install_nbextension(glob.glob(pjoin(self.src, '*')))
150 install_nbextension(glob.glob(pjoin(self.src, '*')))
141 for file in self.files:
142 self.assert_installed(file)
143
151
144 def test_overwrite_file(self):
152 def test_overwrite_file(self):
145 with TemporaryDirectory() as d:
153 with TemporaryDirectory() as d:
@@ -242,7 +250,8 b' class TestInstallNBExtension(TestCase):'
242 self.assert_installed("foo.js")
250 self.assert_installed("foo.js")
243 install_nbextension("https://example.com/path/to/another/bar.js")
251 install_nbextension("https://example.com/path/to/another/bar.js")
244 self.assert_installed("bar.js")
252 self.assert_installed("bar.js")
245 install_nbextension({'foobar.js': "https://example.com/path/to/another/bar.js"})
253 install_nbextension("https://example.com/path/to/another/bar.js",
254 destination = 'foobar.js')
246 self.assert_installed("foobar.js")
255 self.assert_installed("foobar.js")
247 finally:
256 finally:
248 nbextensions.urlretrieve = save_urlretrieve
257 nbextensions.urlretrieve = save_urlretrieve
@@ -270,6 +279,19 b' class TestInstallNBExtension(TestCase):'
270 link = os.readlink(dest)
279 link = os.readlink(dest)
271 self.assertEqual(link, src)
280 self.assertEqual(link, src)
272
281
282 @dec.skip_win32
283 def test_install_symlink_destination(self):
284 with TemporaryDirectory() as d:
285 f = u'ƒ.js'
286 flink = u'ƒlink.js'
287 src = pjoin(d, f)
288 touch(src)
289 install_nbextension(src, symlink=True, destination=flink)
290 dest = pjoin(self.system_nbext, flink)
291 assert os.path.islink(dest)
292 link = os.readlink(dest)
293 self.assertEqual(link, src)
294
273 def test_install_symlink_bad(self):
295 def test_install_symlink_bad(self):
274 with self.assertRaises(ValueError):
296 with self.assertRaises(ValueError):
275 install_nbextension("http://example.com/foo.js", symlink=True)
297 install_nbextension("http://example.com/foo.js", symlink=True)
@@ -283,11 +305,12 b' class TestInstallNBExtension(TestCase):'
283 with self.assertRaises(ValueError):
305 with self.assertRaises(ValueError):
284 install_nbextension(zsrc, symlink=True)
306 install_nbextension(zsrc, symlink=True)
285
307
286 def test_install_different_name(self):
308 def test_install_destination_bad(self):
287 with TemporaryDirectory() as d:
309 with TemporaryDirectory() as d:
288 f = u'ƒ.js'
310 zf = u'ƒ.zip'
289 src = pjoin(d, f)
311 zsrc = pjoin(d, zf)
290 dest_f = u'ƒile.js'
312 with zipfile.ZipFile(zsrc, 'w') as z:
291 touch(src)
313 z.writestr("a.js", b"b();")
292 install_nbextension({dest_f: src})
314
293 self.assert_installed(dest_f)
315 with self.assertRaises(ValueError):
316 install_nbextension(zsrc, destination='foo')
General Comments 0
You need to be logged in to leave comments. Login now