Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install multiple versions of same extension #6857

Merged
merged 14 commits into from Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 33 additions & 10 deletions jupyterlab/commands.py
Expand Up @@ -303,7 +303,7 @@ def watch(app_dir=None, logger=None):
return handler.watch()


def install_extension(extension, app_dir=None, logger=None):
def install_extension(extension, app_dir=None, logger=None, pin=None):
"""Install an extension package into JupyterLab.

The extension is first validated.
Expand All @@ -313,7 +313,7 @@ def install_extension(extension, app_dir=None, logger=None):
logger = _ensure_logger(logger)
_node_check(logger)
handler = _AppHandler(app_dir, logger)
return handler.install_extension(extension)
return handler.install_extension(extension, pin=pin)


def uninstall_extension(name=None, app_dir=None, logger=None, all_=False):
Expand Down Expand Up @@ -487,7 +487,7 @@ def __init__(self, app_dir, logger=None, kill_event=None):
# TODO: Make this configurable
self.registry = 'https://registry.npmjs.org'

def install_extension(self, extension, existing=None):
def install_extension(self, extension, existing=None, pin=None):
"""Install an extension package into JupyterLab.

The extension is first validated.
Expand All @@ -514,7 +514,7 @@ def install_extension(self, extension, existing=None):

# Install the package using a temporary directory.
with TemporaryDirectory() as tempdir:
info = self._install_extension(extension, tempdir)
info = self._install_extension(extension, tempdir, pin=pin)

name = info['name']

Expand Down Expand Up @@ -784,14 +784,18 @@ def _update_extension(self, name):

Returns `True` if a rebuild is recommended, `False` otherwise.
"""
data = self.info['extensions'][name]
if data["alias_package_source"]:
self.logger.warn("Skipping updating pinned extension '%s'." % name)
return False
saulshanabrook marked this conversation as resolved.
Show resolved Hide resolved
try:
latest = self._latest_compatible_package_version(name)
except URLError:
return False
if latest is None:
self.logger.warn('No compatible version found for %s!' % (name,))
return False
if latest == self.info['extensions'][name]['version']:
if latest == data['version']:
self.logger.info('Extension %r already up to date' % name)
return False
self.logger.info('Updating %s to version %s' % (name, latest))
Expand Down Expand Up @@ -1208,17 +1212,25 @@ def _get_extensions_in_dir(self, dname, core_data):
name = data['name']
jlab = data.get('jupyterlab', dict())
path = osp.abspath(target)

filename = osp.basename(target)
if filename.startswith("pin:"):
alias = filename[len("pin:"):-len(".tgz")]
saulshanabrook marked this conversation as resolved.
Show resolved Hide resolved
else:
alias = None
# homepage, repository are optional
if 'homepage' in data:
url = data['homepage']
elif 'repository' in data and isinstance(data['repository'], dict):
url = data['repository'].get('url', '')
else:
url = ''
extensions[name] = dict(path=path,
extensions[alias or name] = dict(path=path,
filename=osp.basename(path),
url=url,
version=data['version'],
# Only save the package name if the extension name is an alias
alias_package_source=name if alias else None,
jupyterlab=jlab,
dependencies=deps,
tar_dir=osp.dirname(path),
Expand Down Expand Up @@ -1312,7 +1324,12 @@ def _list_extensions(self, info, ext_type):
extra += ' %s' % GREEN_OK
if data['is_local']:
extra += '*'
logger.info(' %s v%s%s' % (name, version, extra))
# If we have the package name in the data, this means this extension's name is the alias name
alias_package_source = data['alias_package_source']
if alias_package_source:
logger.info(' %s %s v%s%s' % (name, alias_package_source, version, extra))
else:
logger.info(' %s v%s%s' % (name, version, extra))
if errors:
error_accumulator[name] = (version, errors)

Expand Down Expand Up @@ -1377,10 +1394,10 @@ def _get_local_data(self, source):

return data

def _install_extension(self, extension, tempdir):
def _install_extension(self, extension, tempdir, pin=None):
"""Install an extension with validation and return the name and path.
"""
info = self._extract_package(extension, tempdir)
info = self._extract_package(extension, tempdir, pin=pin)
data = info['data']

# Verify that the package is an extension.
Expand Down Expand Up @@ -1435,7 +1452,7 @@ def _install_extension(self, extension, tempdir):
info['path'] = target
return info

def _extract_package(self, source, tempdir):
def _extract_package(self, source, tempdir, pin=None):
"""Call `npm pack` for an extension.

The pack command will download the package tar if `source` is
Expand All @@ -1462,6 +1479,12 @@ def _extract_package(self, source, tempdir):
info['path'] = target
else:
info['path'] = path
# If we are pinning the package, rename it `pin:<name>``
if pin:
old_path = info['path']
new_path = pjoin(osp.dirname(old_path), 'pin:{}.tgz'.format(pin))
shutil.move(old_path, new_path)
info['path'] = new_path

info['filename'] = osp.basename(info['path'])
info['name'] = info['data']['name']
Expand Down
34 changes: 30 additions & 4 deletions jupyterlab/labextensions.py
Expand Up @@ -11,7 +11,7 @@

from jupyter_core.application import JupyterApp, base_flags, base_aliases

from traitlets import Bool, Unicode
from traitlets import Bool, Unicode, Unicode

from .commands import (
install_extension, uninstall_extension, list_extensions,
Expand Down Expand Up @@ -56,6 +56,8 @@
aliases['minimize'] = 'BaseExtensionApp.minimize'
aliases['debug-log-path'] = 'DebugLogFileMixin.debug_log_path'

install_aliases = copy(aliases)
install_aliases['pin-version-as'] = 'InstallLabExtensionApp.pin'

VERSION = get_app_version()

Expand Down Expand Up @@ -106,13 +108,37 @@ def _log_format_default(self):


class InstallLabExtensionApp(BaseExtensionApp):
description = "Install labextension(s)"
description = """Install labextension(s)

Usage

jupyter labextension install [--pin-version-as <alias,...>] <package...>

This installs JupyterLab extensions similar to yarn add or npm install.

Pass a list of comma seperate names to the --pin-version-as flag
to use as alises for the packages providers. This is useful to
install multiple versions of the same extension.
These can be uninstalled with the alias you provided
to the flag, similar to the "alias" feature of yarn add.
"""
aliases = install_aliases

pin = Unicode('', config=True,
help="Pin this version with a certain alias")

def run_task(self):
pinned_versions = self.pin.split(',')
self.extra_args = self.extra_args or [os.getcwd()]
return any([
install_extension(arg, self.app_dir, logger=self.log)
for arg in self.extra_args
install_extension(
arg,
self.app_dir,
logger=self.log,
# Pass in pinned alias if we have it
pin=pinned_versions[i] if i < len(pinned_versions) else None
)
for i, arg in enumerate(self.extra_args)
])


Expand Down
6 changes: 6 additions & 0 deletions scripts/travis_script.sh
Expand Up @@ -142,6 +142,12 @@ if [[ $GROUP == usage ]]; then
jupyter lab workspaces export newspace > newspace.json
rm workspace.json newspace.json

# Test pinned alias packages
jupyter labextension install --no-build \
--pin-version-as test-1,test-2 \
jupyterlab-test-extension@1.0 \
jupyterlab-test-extension@2.0
jupyter labextension uninstall test-1 test-2
saulshanabrook marked this conversation as resolved.
Show resolved Hide resolved

# Make sure we can call help on all the cli apps.
jupyter lab -h
Expand Down