nbextensions.py
350 lines
| 12.9 KiB
| text/x-python
|
PythonLexer
MinRK
|
r15220 | # coding: utf-8 | ||
"""Utilities for installing Javascript extensions for the notebook""" | ||||
MinRK
|
r16486 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
MinRK
|
r15220 | |||
from __future__ import print_function | ||||
import os | ||||
import shutil | ||||
Min RK
|
r19854 | import sys | ||
MinRK
|
r15223 | import tarfile | ||
import zipfile | ||||
Jason Grout
|
r20071 | import uuid | ||
MinRK
|
r15220 | from os.path import basename, join as pjoin | ||
MinRK
|
r15223 | # Deferred imports | ||
try: | ||||
from urllib.parse import urlparse # Py3 | ||||
from urllib.request import urlretrieve | ||||
except ImportError: | ||||
from urlparse import urlparse | ||||
from urllib import urlretrieve | ||||
MinRK
|
r16486 | from IPython.utils.path import get_ipython_dir, ensure_dir_exists | ||
MinRK
|
r15220 | from IPython.utils.py3compat import string_types, cast_unicode_py2 | ||
MinRK
|
r15223 | from IPython.utils.tempdir import TemporaryDirectory | ||
MinRK
|
r15220 | |||
Min RK
|
r19856 | class ArgumentConflict(ValueError): | ||
pass | ||||
MinRK
|
r15220 | |||
Min RK
|
r19854 | # Packagers: modify the next block if you store system-installed nbextensions elsewhere (unlikely) | ||
SYSTEM_NBEXTENSIONS_DIRS = [] | ||||
if os.name == 'nt': | ||||
programdata = os.environ.get('PROGRAMDATA', None) | ||||
if programdata: # PROGRAMDATA is not defined by default on XP. | ||||
SYSTEM_NBEXTENSIONS_DIRS = [pjoin(programdata, 'jupyter', 'nbextensions')] | ||||
prefixes = [] | ||||
else: | ||||
Min RK
|
r19857 | prefixes = [os.path.sep + pjoin('usr', 'local'), os.path.sep + 'usr'] | ||
Min RK
|
r19854 | |||
# add sys.prefix at the front | ||||
if sys.prefix not in prefixes: | ||||
prefixes.insert(0, sys.prefix) | ||||
for prefix in prefixes: | ||||
Min RK
|
r19857 | nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions') | ||
Min RK
|
r19854 | if nbext not in SYSTEM_NBEXTENSIONS_DIRS: | ||
SYSTEM_NBEXTENSIONS_DIRS.append(nbext) | ||||
if os.name == 'nt': | ||||
# PROGRAMDATA | ||||
SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-1] | ||||
else: | ||||
# /usr/local | ||||
SYSTEM_NBEXTENSIONS_INSTALL_DIR = SYSTEM_NBEXTENSIONS_DIRS[-2] | ||||
MinRK
|
r15220 | def _should_copy(src, dest, verbose=1): | ||
"""should a file be copied?""" | ||||
if not os.path.exists(dest): | ||||
return True | ||||
Jason Grout
|
r20080 | if os.stat(src).st_mtime - os.stat(dest).st_mtime > 1e-6: | ||
# we add a fudge factor to work around a bug in python 2.x | ||||
# that was fixed in python 3.x: http://bugs.python.org/issue12904 | ||||
MinRK
|
r15220 | if verbose >= 2: | ||
print("%s is out of date" % dest) | ||||
return True | ||||
if verbose >= 2: | ||||
print("%s is up to date" % dest) | ||||
return False | ||||
def _maybe_copy(src, dest, verbose=1): | ||||
"""copy a file if it needs updating""" | ||||
if _should_copy(src, dest, verbose): | ||||
if verbose >= 1: | ||||
print("copying %s -> %s" % (src, dest)) | ||||
Jason Grout
|
r20171 | shutil.copy2(src, dest) | ||
MinRK
|
r15220 | |||
MinRK
|
r15223 | def _safe_is_tarfile(path): | ||
"""safe version of is_tarfile, return False on IOError""" | ||||
try: | ||||
return tarfile.is_tarfile(path) | ||||
except IOError: | ||||
return False | ||||
Jason Grout
|
r20083 | def _get_nbext_dir(nbextensions_dir=None, user=False, prefix=None): | ||
"""Return the nbextension directory specified""" | ||||
if sum(map(bool, [user, prefix, nbextensions_dir])) > 1: | ||||
raise ArgumentConflict("Cannot specify more than one of user, prefix, or nbextensions_dir.") | ||||
if user: | ||||
nbext = pjoin(get_ipython_dir(), u'nbextensions') | ||||
else: | ||||
if prefix: | ||||
nbext = pjoin(prefix, 'share', 'jupyter', 'nbextensions') | ||||
elif nbextensions_dir: | ||||
nbext = nbextensions_dir | ||||
else: | ||||
nbext = SYSTEM_NBEXTENSIONS_INSTALL_DIR | ||||
return nbext | ||||
Jason Grout
|
r20085 | def check_nbextension(files, user=False, prefix=None, nbextensions_dir=None): | ||
MinRK
|
r15227 | """Check whether nbextension files have been installed | ||
Returns True if all files are found, False if any are missing. | ||||
Jason Grout
|
r20085 | |||
Parameters | ||||
---------- | ||||
files : list(paths) | ||||
a list of relative paths within nbextensions. | ||||
user : bool [default: False] | ||||
Whether to check the user's .ipython/nbextensions directory. | ||||
Otherwise check a system-wide install (e.g. /usr/local/share/jupyter/nbextensions). | ||||
prefix : str [optional] | ||||
Specify install prefix, if it should differ from default (e.g. /usr/local). | ||||
Will check prefix/share/jupyter/nbextensions | ||||
nbextensions_dir : str [optional] | ||||
Specify absolute path of nbextensions directory explicitly. | ||||
MinRK
|
r15227 | """ | ||
Jason Grout
|
r20083 | nbext = _get_nbext_dir(nbextensions_dir, user, prefix) | ||
MinRK
|
r15227 | # make sure nbextensions dir exists | ||
if not os.path.exists(nbext): | ||||
return False | ||||
if isinstance(files, string_types): | ||||
# one file given, turn it into a list | ||||
files = [files] | ||||
MinRK
|
r15340 | return all(os.path.exists(pjoin(nbext, f)) for f in files) | ||
MinRK
|
r15227 | |||
Jason Grout
|
r20217 | def install_nbextension(path, overwrite=False, symlink=False, user=False, prefix=None, nbextensions_dir=None, destination=None, verbose=1): | ||
MinRK
|
r15220 | """Install a Javascript extension for the notebook | ||
Min RK
|
r19855 | Stages files and/or directories into the nbextensions directory. | ||
MinRK
|
r15223 | By default, this compares modification time, and only stages files that need updating. | ||
MinRK
|
r15220 | If `overwrite` is specified, matching files are purged before proceeding. | ||
Parameters | ||||
---------- | ||||
Jason Grout
|
r20217 | path : path to file, directory, zip or tarball archive, or URL to install | ||
By default, the file will be installed with its base name, so '/path/to/foo' | ||||
will install to 'nbextensions/foo'. See the destination argument below to change this. | ||||
MinRK
|
r15223 | Archives (zip or tarballs) will be extracted into the nbextensions directory. | ||
MinRK
|
r15220 | overwrite : bool [default: False] | ||
If True, always install the files, regardless of what may already be installed. | ||||
MinRK
|
r15226 | symlink : bool [default: False] | ||
If True, create a symlink in nbextensions, rather than copying files. | ||||
Thomas Kluyver
|
r18725 | Not allowed with URLs or archives. Windows support for symlinks requires | ||
Vista or above, Python 3, and a permission bit which only admin users | ||||
have by default, so don't rely on it. | ||||
Min RK
|
r19854 | user : bool [default: False] | ||
Whether to install to the user's .ipython/nbextensions directory. | ||||
Otherwise do a system-wide install (e.g. /usr/local/share/jupyter/nbextensions). | ||||
prefix : str [optional] | ||||
Specify install prefix, if it should differ from default (e.g. /usr/local). | ||||
Matthias Bussonnier
|
r20403 | Will install to ``<prefix>/share/jupyter/nbextensions`` | ||
Min RK
|
r19855 | nbextensions_dir : str [optional] | ||
Min RK
|
r19854 | Specify absolute path of nbextensions directory explicitly. | ||
Jason Grout
|
r20217 | 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. | ||||
MinRK
|
r15220 | verbose : int [default: 1] | ||
Set verbosity level. The default is 1, where file actions are printed. | ||||
set verbose=2 for more output, or verbose=0 for silence. | ||||
""" | ||||
Jason Grout
|
r20083 | nbext = _get_nbext_dir(nbextensions_dir, user, prefix) | ||
MinRK
|
r15220 | # make sure nbextensions dir exists | ||
MinRK
|
r16486 | ensure_dir_exists(nbext) | ||
MinRK
|
r15220 | |||
Jason Grout
|
r20217 | if isinstance(path, (list, tuple)): | ||
raise TypeError("path must be a string pointing to a single extension to install; call this function multiple times to install multiple extensions") | ||||
MinRK
|
r15220 | |||
Jason Grout
|
r20217 | path = cast_unicode_py2(path) | ||
if path.startswith(('https://', 'http://')): | ||||
if symlink: | ||||
raise ValueError("Cannot symlink from URLs") | ||||
# Given a URL, download it | ||||
with TemporaryDirectory() as td: | ||||
filename = urlparse(path).path.split('/')[-1] | ||||
local_path = os.path.join(td, filename) | ||||
if verbose >= 1: | ||||
print("downloading %s to %s" % (path, local_path)) | ||||
urlretrieve(path, local_path) | ||||
# now install from the local copy | ||||
install_nbextension(local_path, overwrite=overwrite, symlink=symlink, nbextensions_dir=nbext, destination=destination, verbose=verbose) | ||||
elif path.endswith('.zip') or _safe_is_tarfile(path): | ||||
if symlink: | ||||
raise ValueError("Cannot symlink from archives") | ||||
if destination: | ||||
raise ValueError("Cannot give destination for archives") | ||||
if verbose >= 1: | ||||
print("extracting %s to %s" % (path, nbext)) | ||||
MinRK
|
r15223 | if path.endswith('.zip'): | ||
archive = zipfile.ZipFile(path) | ||||
elif _safe_is_tarfile(path): | ||||
archive = tarfile.open(path) | ||||
Jason Grout
|
r20217 | archive.extractall(nbext) | ||
archive.close() | ||||
else: | ||||
if not destination: | ||||
destination = basename(path) | ||||
Jason Grout
|
r20228 | destination = cast_unicode_py2(destination) | ||
Jason Grout
|
r20217 | full_dest = pjoin(nbext, destination) | ||
Jason Grout
|
r20245 | if overwrite and os.path.lexists(full_dest): | ||
MinRK
|
r15220 | if verbose >= 1: | ||
Jason Grout
|
r20217 | print("removing %s" % full_dest) | ||
if os.path.isdir(full_dest) and not os.path.islink(full_dest): | ||||
shutil.rmtree(full_dest) | ||||
MinRK
|
r15220 | else: | ||
Jason Grout
|
r20217 | os.remove(full_dest) | ||
MinRK
|
r15226 | if symlink: | ||
path = os.path.abspath(path) | ||||
Jason Grout
|
r20217 | if not os.path.exists(full_dest): | ||
MinRK
|
r15226 | if verbose >= 1: | ||
Jason Grout
|
r20217 | print("symlink %s -> %s" % (full_dest, path)) | ||
os.symlink(path, full_dest) | ||||
elif os.path.isdir(path): | ||||
Jason Grout
|
r20078 | path = pjoin(os.path.abspath(path), '') # end in path separator | ||
MinRK
|
r15220 | for parent, dirs, files in os.walk(path): | ||
Jason Grout
|
r20217 | dest_dir = pjoin(full_dest, parent[len(path):]) | ||
MinRK
|
r15220 | if not os.path.exists(dest_dir): | ||
if verbose >= 2: | ||||
print("making directory %s" % dest_dir) | ||||
os.makedirs(dest_dir) | ||||
for file in files: | ||||
src = pjoin(parent, file) | ||||
# print("%r, %r" % (dest_dir, file)) | ||||
Jason Grout
|
r20077 | dest_file = pjoin(dest_dir, file) | ||
_maybe_copy(src, dest_file, verbose) | ||||
MinRK
|
r15220 | else: | ||
src = path | ||||
Jason Grout
|
r20218 | _maybe_copy(src, full_dest, verbose) | ||
MinRK
|
r15221 | |||
#---------------------------------------------------------------------- | ||||
# install nbextension app | ||||
#---------------------------------------------------------------------- | ||||
Min RK
|
r19854 | from IPython.utils.traitlets import Bool, Enum, Unicode, TraitError | ||
MinRK
|
r15221 | from IPython.core.application import BaseIPythonApplication | ||
flags = { | ||||
"overwrite" : ({ | ||||
"NBExtensionApp" : { | ||||
"overwrite" : True, | ||||
}}, "Force overwrite of existing files" | ||||
), | ||||
"debug" : ({ | ||||
"NBExtensionApp" : { | ||||
"verbose" : 2, | ||||
}}, "Extra output" | ||||
), | ||||
"quiet" : ({ | ||||
"NBExtensionApp" : { | ||||
"verbose" : 0, | ||||
}}, "Minimal output" | ||||
), | ||||
MinRK
|
r15226 | "symlink" : ({ | ||
"NBExtensionApp" : { | ||||
"symlink" : True, | ||||
Jason Grout
|
r20217 | }}, "Create symlink instead of copying files" | ||
MinRK
|
r15226 | ), | ||
Min RK
|
r19854 | "user" : ({ | ||
"NBExtensionApp" : { | ||||
"user" : True, | ||||
}}, "Install to the user's IPython directory" | ||||
), | ||||
MinRK
|
r15221 | } | ||
MinRK
|
r15226 | flags['s'] = flags['symlink'] | ||
MinRK
|
r15221 | aliases = { | ||
Min RK
|
r19854 | "ipython-dir" : "NBExtensionApp.ipython_dir", | ||
"prefix" : "NBExtensionApp.prefix", | ||||
Min RK
|
r19855 | "nbextensions" : "NBExtensionApp.nbextensions_dir", | ||
Jason Grout
|
r20217 | "destination" : "NBExtensionApp.destination", | ||
MinRK
|
r15221 | } | ||
class NBExtensionApp(BaseIPythonApplication): | ||||
"""Entry point for installing notebook extensions""" | ||||
description = """Install IPython notebook extensions | ||||
Usage | ||||
Jason Grout
|
r20220 | ipython install-nbextension path/url | ||
MinRK
|
r15221 | |||
Jason Grout
|
r20220 | This copies a file or a folder into the IPython nbextensions directory. | ||
MinRK
|
r15223 | If a URL is given, it will be downloaded. | ||
If an archive is given, it will be extracted into nbextensions. | ||||
MinRK
|
r15221 | If the requested files are already up to date, no action is taken | ||
unless --overwrite is specified. | ||||
""" | ||||
examples = """ | ||||
Jason Grout
|
r20217 | ipython install-nbextension /path/to/myextension | ||
MinRK
|
r15221 | """ | ||
aliases = aliases | ||||
flags = flags | ||||
overwrite = Bool(False, config=True, help="Force overwrite of existing files") | ||||
MinRK
|
r15226 | symlink = Bool(False, config=True, help="Create symlinks instead of copying files") | ||
Min RK
|
r19854 | user = Bool(False, config=True, help="Whether to do a user install") | ||
prefix = Unicode('', config=True, help="Installation prefix") | ||||
Min RK
|
r19855 | nbextensions_dir = Unicode('', config=True, help="Full path to nbextensions dir (probably use prefix or user)") | ||
Jason Grout
|
r20217 | destination = Unicode('', config=True, help="Destination for the copy or symlink") | ||
MinRK
|
r15221 | verbose = Enum((0,1,2), default_value=1, config=True, | ||
help="Verbosity level" | ||||
) | ||||
def install_extensions(self): | ||||
Jason Grout
|
r20219 | 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], | ||||
MinRK
|
r15221 | overwrite=self.overwrite, | ||
MinRK
|
r15226 | symlink=self.symlink, | ||
MinRK
|
r15221 | verbose=self.verbose, | ||
Min RK
|
r19854 | user=self.user, | ||
prefix=self.prefix, | ||||
Jason Grout
|
r20217 | destination=self.destination, | ||
Min RK
|
r19855 | nbextensions_dir=self.nbextensions_dir, | ||
MinRK
|
r15221 | ) | ||
def start(self): | ||||
if not self.extra_args: | ||||
Min RK
|
r19854 | for nbext in [pjoin(self.ipython_dir, u'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS: | ||
if os.path.exists(nbext): | ||||
print("Notebook extensions in %s:" % nbext) | ||||
for ext in os.listdir(nbext): | ||||
print(u" %s" % ext) | ||||
MinRK
|
r15221 | else: | ||
Min RK
|
r19856 | try: | ||
self.install_extensions() | ||||
except ArgumentConflict as e: | ||||
print(str(e), file=sys.stderr) | ||||
self.exit(1) | ||||
MinRK
|
r15221 | |||
if __name__ == '__main__': | ||||
NBExtensionApp.launch_instance() | ||||
Jason Grout
|
r20085 | |||