Merge pull request #7609 from jasongrout/install-single-nbextension

Change install_nbextension to take install only a single nbextension, with an optional destination
This commit is contained in:
Thomas Kluyver 2015-01-28 14:31:33 -08:00
commit c571fa545d
2 changed files with 97 additions and 82 deletions

View File

@ -134,7 +134,7 @@ def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None):
return all(os.path.exists(pjoin(nbext, f)) for f in files) return all(os.path.exists(pjoin(nbext, f)) for f in files)
def install_nbextension(files, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, verbose=1): def install_nbextension(path, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, destination=None, verbose=1):
"""Install a Javascript extension for the notebook """Install a Javascript extension for the notebook
Stages files and/or directories into the nbextensions directory. Stages files and/or directories into the nbextensions directory.
@ -144,11 +144,9 @@ def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi
Parameters Parameters
---------- ----------
files : list(paths or URLs) or dict(install_name: path or URL) path : path to file, directory, zip or tarball archive, or URL to install
One or more paths or URLs to existing files directories to install. By default, the file will be installed with its base name, so '/path/to/foo'
If given as a list, these will be installed with their base name, so '/path/to/foo' will install to 'nbextensions/foo'. See the destination argument below to change this.
will install to 'nbextensions/foo'. If given as a dict, such as {'bar': '/path/to/foo'},
then '/path/to/foo' will install to 'nbextensions/bar'.
Archives (zip or tarballs) will be extracted into the nbextensions directory. Archives (zip or tarballs) will be extracted into the nbextensions directory.
overwrite : bool [default: False] overwrite : bool [default: False]
If True, always install the files, regardless of what may already be installed. If True, always install the files, regardless of what may already be installed.
@ -165,6 +163,10 @@ def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi
Will install to prefix/share/jupyter/nbextensions Will install to prefix/share/jupyter/nbextensions
nbextensions_dir : str [optional] nbextensions_dir : str [optional]
Specify absolute path of nbextensions directory explicitly. Specify absolute path of nbextensions directory explicitly.
destination : str [optional]
name the nbextension is installed to. For example, if destination is 'foo', then
the source file will be installed to 'nbextensions/foo', regardless of the source name.
This cannot be specified if an archive is given as the source.
verbose : int [default: 1] verbose : int [default: 1]
Set verbosity level. The default is 1, where file actions are printed. Set verbosity level. The default is 1, where file actions are printed.
set verbose=2 for more output, or verbose=0 for silence. set verbose=2 for more output, or verbose=0 for silence.
@ -173,75 +175,60 @@ def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi
# make sure nbextensions dir exists # make sure nbextensions dir exists
ensure_dir_exists(nbext) ensure_dir_exists(nbext)
if isinstance(files, string_types): if isinstance(path, (list, tuple)):
# one file given, turn it into a list raise TypeError("path must be a string pointing to a single extension to install; call this function multiple times to install multiple extensions")
files = [files]
if isinstance(files, (list,tuple)):
# list given, turn into dict
_files = {}
for path in map(cast_unicode_py2, files):
if path.startswith(('https://', 'http://')):
destination = urlparse(path).path.split('/')[-1]
elif path.endswith('.zip') or _safe_is_tarfile(path):
destination = str(uuid.uuid4()) # ignored for archives
else:
destination = basename(path)
_files[destination] = path
files = _files
for dest_basename,path in (map(cast_unicode_py2, item) for item in files.items()): path = cast_unicode_py2(path)
if path.startswith(('https://', 'http://')): if path.startswith(('https://', 'http://')):
if symlink: if symlink:
raise ValueError("Cannot symlink from URLs") raise ValueError("Cannot symlink from URLs")
# Given a URL, download it # Given a URL, download it
with TemporaryDirectory() as td: with TemporaryDirectory() as td:
filename = urlparse(path).path.split('/')[-1] filename = urlparse(path).path.split('/')[-1]
local_path = os.path.join(td, filename) local_path = os.path.join(td, filename)
if verbose >= 1: if verbose >= 1:
print("downloading %s to %s" % (path, local_path)) print("downloading %s to %s" % (path, local_path))
urlretrieve(path, local_path) urlretrieve(path, local_path)
# now install from the local copy # now install from the local copy
install_nbextension({dest_basename: local_path}, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, verbose=verbose) install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, destination=destination, verbose=verbose)
continue elif path.endswith('.zip') or _safe_is_tarfile(path):
if symlink:
# handle archives raise ValueError("Cannot symlink from archives")
archive = None if destination:
raise ValueError("Cannot give destination for archives")
if verbose >= 1:
print("extracting %s to %s" % (path, nbext))
if path.endswith('.zip'): if path.endswith('.zip'):
archive = zipfile.ZipFile(path) archive = zipfile.ZipFile(path)
elif _safe_is_tarfile(path): elif _safe_is_tarfile(path):
archive = tarfile.open(path) archive = tarfile.open(path)
archive.extractall(nbext)
if archive: archive.close()
if symlink: else:
raise ValueError("Cannot symlink from archives") if not destination:
destination = basename(path)
destination = cast_unicode_py2(destination)
full_dest = pjoin(nbext, destination)
if overwrite and os.path.exists(full_dest):
if verbose >= 1: if verbose >= 1:
print("extracting %s to %s" % (path, nbext)) print("removing %s" % full_dest)
archive.extractall(nbext) if os.path.isdir(full_dest) and not os.path.islink(full_dest):
archive.close() shutil.rmtree(full_dest)
continue
dest = pjoin(nbext, dest_basename)
if overwrite and os.path.exists(dest):
if verbose >= 1:
print("removing %s" % dest)
if os.path.isdir(dest) and not os.path.islink(dest):
shutil.rmtree(dest)
else: else:
os.remove(dest) os.remove(full_dest)
if symlink: if symlink:
path = os.path.abspath(path) path = os.path.abspath(path)
if not os.path.exists(dest): if not os.path.exists(full_dest):
if verbose >= 1: if verbose >= 1:
print("symlink %s -> %s" % (dest, path)) print("symlink %s -> %s" % (full_dest, path))
os.symlink(path, dest) os.symlink(path, full_dest)
continue elif os.path.isdir(path):
if os.path.isdir(path):
path = pjoin(os.path.abspath(path), '') # end in path separator path = pjoin(os.path.abspath(path), '') # end in path separator
for parent, dirs, files in os.walk(path): for parent, dirs, files in os.walk(path):
dest_dir = pjoin(dest, parent[len(path):]) dest_dir = pjoin(full_dest, parent[len(path):])
if not os.path.exists(dest_dir): if not os.path.exists(dest_dir):
if verbose >= 2: if verbose >= 2:
print("making directory %s" % dest_dir) print("making directory %s" % dest_dir)
@ -253,7 +240,7 @@ def install_nbextension(files, overwrite=False, symlink=False, user=False, prefi
_maybe_copy(src, dest_file, verbose) _maybe_copy(src, dest_file, verbose)
else: else:
src = path src = path
_maybe_copy(src, dest, verbose) _maybe_copy(src, full_dest, verbose)
#---------------------------------------------------------------------- #----------------------------------------------------------------------
# install nbextension app # install nbextension app
@ -281,7 +268,7 @@ flags = {
"symlink" : ({ "symlink" : ({
"NBExtensionApp" : { "NBExtensionApp" : {
"symlink" : True, "symlink" : True,
}}, "Create symlinks instead of copying files" }}, "Create symlink instead of copying files"
), ),
"user" : ({ "user" : ({
"NBExtensionApp" : { "NBExtensionApp" : {
@ -295,6 +282,7 @@ aliases = {
"ipython-dir" : "NBExtensionApp.ipython_dir", "ipython-dir" : "NBExtensionApp.ipython_dir",
"prefix" : "NBExtensionApp.prefix", "prefix" : "NBExtensionApp.prefix",
"nbextensions" : "NBExtensionApp.nbextensions_dir", "nbextensions" : "NBExtensionApp.nbextensions_dir",
"destination" : "NBExtensionApp.destination",
} }
class NBExtensionApp(BaseIPythonApplication): class NBExtensionApp(BaseIPythonApplication):
@ -304,9 +292,9 @@ class NBExtensionApp(BaseIPythonApplication):
Usage Usage
ipython install-nbextension file [more files, folders, archives or urls] ipython install-nbextension path/url
This copies files and/or folders into the IPython nbextensions directory. This copies a file or a folder into the IPython nbextensions directory.
If a URL is given, it will be downloaded. If a URL is given, it will be downloaded.
If an archive is given, it will be extracted into nbextensions. If an archive is given, it will be extracted into nbextensions.
If the requested files are already up to date, no action is taken If the requested files are already up to date, no action is taken
@ -314,7 +302,7 @@ class NBExtensionApp(BaseIPythonApplication):
""" """
examples = """ examples = """
ipython install-nbextension /path/to/d3.js /path/to/myextension ipython install-nbextension /path/to/myextension
""" """
aliases = aliases aliases = aliases
flags = flags flags = flags
@ -324,17 +312,21 @@ class NBExtensionApp(BaseIPythonApplication):
user = Bool(False, config=True, help="Whether to do a user install") user = Bool(False, config=True, help="Whether to do a user install")
prefix = Unicode('', config=True, help="Installation prefix") prefix = Unicode('', config=True, help="Installation prefix")
nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)") nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)")
destination = Unicode('', config=True, help="Destination for the copy or symlink")
verbose = Enum((0,1,2), default_value=1, config=True, verbose = Enum((0,1,2), default_value=1, config=True,
help="Verbosity level" help="Verbosity level"
) )
def install_extensions(self): def install_extensions(self):
install_nbextension(self.extra_args, if len(self.extra_args)>1:
raise ValueError("only one nbextension allowed at a time. Call multiple times to install multiple extensions.")
install_nbextension(self.extra_args[0],
overwrite=self.overwrite, overwrite=self.overwrite,
symlink=self.symlink, symlink=self.symlink,
verbose=self.verbose, verbose=self.verbose,
user=self.user, user=self.user,
prefix=self.prefix, prefix=self.prefix,
destination=self.destination,
nbextensions_dir=self.nbextensions_dir, nbextensions_dir=self.nbextensions_dir,
) )

View File

@ -133,13 +133,21 @@ class TestInstallNBExtension(TestCase):
d = u'∂ir' d = u'∂ir'
install_nbextension(pjoin(self.src, d)) install_nbextension(pjoin(self.src, d))
self.assert_installed(self.files[-1]) self.assert_installed(self.files[-1])
install_nbextension({'test': pjoin(self.src, d)})
self.assert_installed(pjoin('test', u'∂ir2', u'ƒile2'))
def test_destination_file(self):
file = self.files[0]
install_nbextension(pjoin(self.src, file), destination = u'ƒiledest')
self.assert_installed(u'ƒiledest')
def test_destination_dir(self):
d = u'∂ir'
install_nbextension(pjoin(self.src, d), destination = u'ƒiledest2')
self.assert_installed(pjoin(u'ƒiledest2', u'∂ir2', u'ƒile2'))
def test_install_nbextension(self): def test_install_nbextension(self):
install_nbextension(glob.glob(pjoin(self.src, '*'))) with self.assertRaises(TypeError):
for file in self.files: install_nbextension(glob.glob(pjoin(self.src, '*')))
self.assert_installed(file)
def test_overwrite_file(self): def test_overwrite_file(self):
with TemporaryDirectory() as d: with TemporaryDirectory() as d:
@ -242,7 +250,8 @@ class TestInstallNBExtension(TestCase):
self.assert_installed("foo.js") self.assert_installed("foo.js")
install_nbextension("https://example.com/path/to/another/bar.js") install_nbextension("https://example.com/path/to/another/bar.js")
self.assert_installed("bar.js") self.assert_installed("bar.js")
install_nbextension({'foobar.js': "https://example.com/path/to/another/bar.js"}) install_nbextension("https://example.com/path/to/another/bar.js",
destination = 'foobar.js')
self.assert_installed("foobar.js") self.assert_installed("foobar.js")
finally: finally:
nbextensions.urlretrieve = save_urlretrieve nbextensions.urlretrieve = save_urlretrieve
@ -270,6 +279,19 @@ class TestInstallNBExtension(TestCase):
link = os.readlink(dest) link = os.readlink(dest)
self.assertEqual(link, src) self.assertEqual(link, src)
@dec.skip_win32
def test_install_symlink_destination(self):
with TemporaryDirectory() as d:
f = u'ƒ.js'
flink = u'ƒlink.js'
src = pjoin(d, f)
touch(src)
install_nbextension(src, symlink=True, destination=flink)
dest = pjoin(self.system_nbext, flink)
assert os.path.islink(dest)
link = os.readlink(dest)
self.assertEqual(link, src)
def test_install_symlink_bad(self): def test_install_symlink_bad(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
install_nbextension("http://example.com/foo.js", symlink=True) install_nbextension("http://example.com/foo.js", symlink=True)
@ -283,11 +305,12 @@ class TestInstallNBExtension(TestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
install_nbextension(zsrc, symlink=True) install_nbextension(zsrc, symlink=True)
def test_install_different_name(self): def test_install_destination_bad(self):
with TemporaryDirectory() as d: with TemporaryDirectory() as d:
f = u'ƒ.js' zf = u'ƒ.zip'
src = pjoin(d, f) zsrc = pjoin(d, zf)
dest_f = u'ƒile.js' with zipfile.ZipFile(zsrc, 'w') as z:
touch(src) z.writestr("a.js", b"b();")
install_nbextension({dest_f: src})
self.assert_installed(dest_f) with self.assertRaises(ValueError):
install_nbextension(zsrc, destination='foo')