From 6ef0ec236fc70333e79a1699e245474dab16366c Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Fri, 19 Jul 2019 16:53:24 -0400 Subject: [PATCH 01/12] Install multiple versions of same extension This adds support for installing multiple versions of the same JupyterLab extension, fixing #6491. It reuses the syntax for [yarn add's alias command](https://yarnpkg.com/lang/en/docs/cli/add/#toc-yarn-add-alias) so that you can do: ```bash jupyter labextension install test-1@npm:jupyterlab-test-extension@1.0 jupyter labextension install test-2@npm:jupyterlab-test-extension@2.0 ``` This is a real extension I published two versions of, with different extension ID's in each, to test with https://github.com/saulshanabrook/jupyterlab-test-extension. Once you install both of them, you should get two different console logs on startup. If we run `jupyter labextension list` after installing these, we see both versions: ```bash JupyterLab v1.0.2 Known labextensions: app dir: /usr/local/miniconda3/envs/jupyterlab/share/jupyter/lab test-1 jupyterlab-test-extension v1.0.0 enabled OK test-2 jupyterlab-test-extension v2.0.0 enabled OK ``` And we can uninstall each separately: ```bash jupyter labextension uninstall test-1 ``` As well as update each of them: ```bash jupyter labextension update test-1 ``` ## How? Normally, zipped extensions are stored with the name like `jupyterlab-test-extension-1.0.0.tgz`. However, we need to differentiate extensions installed as aliases, without mucking with their actual contents.So we save them instead like `test-1@npm:jupyterlab-test-extension-1.0.0.tgz`. Then, we we go to read our packages, we know they have an alias. --- jupyterlab/commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jupyterlab/commands.py b/jupyterlab/commands.py index d2e2c89c638e..a1605a41fcf9 100644 --- a/jupyterlab/commands.py +++ b/jupyterlab/commands.py @@ -1432,12 +1432,14 @@ def _extract_package(self, source, tempdir): a package name, or run `npm pack` locally if `source` is a directory. """ + info = {"source": source} + if '@npm:' in source: + source = source.split('@npm:')[1] is_dir = osp.exists(source) and osp.isdir(source) + info['is_dir'] = is_dir if is_dir and not osp.exists(pjoin(source, 'node_modules')): self._run(['node', YARN_PATH, 'install'], cwd=source) - info = dict(source=source, is_dir=is_dir) - ret = self._run([which('npm'), 'pack', source], cwd=tempdir) if ret != 0: msg = '"%s" is not a valid npm package' From b43e7585979606f7ade9a1d03fc49178e7faa75a Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Thu, 25 Jul 2019 12:30:22 -0400 Subject: [PATCH 02/12] Revert "Install multiple versions of same extension" 6ef0ec236fc70333e79a1699e245474dab16366c --- jupyterlab/commands.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jupyterlab/commands.py b/jupyterlab/commands.py index a1605a41fcf9..d2e2c89c638e 100644 --- a/jupyterlab/commands.py +++ b/jupyterlab/commands.py @@ -1432,14 +1432,12 @@ def _extract_package(self, source, tempdir): a package name, or run `npm pack` locally if `source` is a directory. """ - info = {"source": source} - if '@npm:' in source: - source = source.split('@npm:')[1] is_dir = osp.exists(source) and osp.isdir(source) - info['is_dir'] = is_dir if is_dir and not osp.exists(pjoin(source, 'node_modules')): self._run(['node', YARN_PATH, 'install'], cwd=source) + info = dict(source=source, is_dir=is_dir) + ret = self._run([which('npm'), 'pack', source], cwd=tempdir) if ret != 0: msg = '"%s" is not a valid npm package' From cb57a445c73a451a781459725bb59fc9d7e6a085 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Thu, 25 Jul 2019 12:27:43 -0400 Subject: [PATCH 03/12] Support installing pinned extensions --- jupyterlab/commands.py | 43 ++++++++++++++++++++++++++++--------- jupyterlab/labextensions.py | 20 ++++++++++++++--- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/jupyterlab/commands.py b/jupyterlab/commands.py index d2e2c89c638e..2d83b54cb2db 100644 --- a/jupyterlab/commands.py +++ b/jupyterlab/commands.py @@ -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. @@ -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): @@ -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. @@ -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'] @@ -775,6 +775,10 @@ def _update_extension(self, name): Returns `True` if a rebuild is recommended, `False` otherwise. """ + data = self.info['extensions'][name] + if data["package_name"]: + self.logger.warn("Skipping updating pinned extension '%s'." % name) + return False try: latest = self._latest_compatible_package_version(name) except URLError: @@ -782,7 +786,7 @@ def _update_extension(self, name): 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)) @@ -1198,6 +1202,12 @@ 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")] + else: + alias = None # homepage, repository are optional if 'homepage' in data: url = data['homepage'] @@ -1205,10 +1215,12 @@ def _get_extensions_in_dir(self, dname, core_data): 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 + package_name=name if alias else None, jupyterlab=jlab, dependencies=deps, tar_dir=osp.dirname(path), @@ -1302,7 +1314,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 + package_name = data['package_name'] + if package_name: + logger.info(' %s %s v%s%s' % (name, package_name, version, extra)) + else: + logger.info(' %s v%s%s' % (name, version, extra)) if errors: error_accumulator[name] = (version, errors) @@ -1367,10 +1384,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. @@ -1425,7 +1442,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 @@ -1452,6 +1469,12 @@ def _extract_package(self, source, tempdir): info['path'] = target else: info['path'] = path + # If we are pinning the package, rename it `pin:`` + 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'] diff --git a/jupyterlab/labextensions.py b/jupyterlab/labextensions.py index 09b6802b3b0e..4233cf65e97f 100644 --- a/jupyterlab/labextensions.py +++ b/jupyterlab/labextensions.py @@ -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, @@ -55,6 +55,9 @@ aliases['dev-build'] = 'BaseExtensionApp.dev_build' aliases['debug-log-path'] = 'DebugLogFileMixin.debug_log_path' +install_aliases = copy(aliases) +install_aliases['pin-version-as'] = 'InstallLabExtensionApp.pin' + VERSION = get_app_version() @@ -95,12 +98,23 @@ def _log_format_default(self): class InstallLabExtensionApp(BaseExtensionApp): description = "Install labextension(s)" + 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) ]) From dc6716c753d23f11f7fb01e38bab107a70d486a7 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Wed, 31 Jul 2019 12:46:35 -0400 Subject: [PATCH 04/12] Add dscription to labextension install --- jupyterlab/labextensions.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/jupyterlab/labextensions.py b/jupyterlab/labextensions.py index 4233cf65e97f..b3d3fc0e6515 100644 --- a/jupyterlab/labextensions.py +++ b/jupyterlab/labextensions.py @@ -97,7 +97,20 @@ def _log_format_default(self): class InstallLabExtensionApp(BaseExtensionApp): - description = "Install labextension(s)" + description = """Install labextension(s) + + Usage + + jupyter labextension install [--pin-version-as ] + + 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, From 79d5b8f7818d3313f285a344ce57b772cc8ccb15 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Fri, 2 Aug 2019 11:52:42 -0400 Subject: [PATCH 05/12] Rename package_name to alias_package_source --- jupyterlab/commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jupyterlab/commands.py b/jupyterlab/commands.py index 6776f9bb3c4b..524fcbd57150 100644 --- a/jupyterlab/commands.py +++ b/jupyterlab/commands.py @@ -785,7 +785,7 @@ def _update_extension(self, name): Returns `True` if a rebuild is recommended, `False` otherwise. """ data = self.info['extensions'][name] - if data["package_name"]: + if data["alias_package_source"]: self.logger.warn("Skipping updating pinned extension '%s'." % name) return False try: @@ -1230,7 +1230,7 @@ def _get_extensions_in_dir(self, dname, core_data): url=url, version=data['version'], # Only save the package name if the extension name is an alias - package_name=name if alias else None, + alias_package_source=name if alias else None, jupyterlab=jlab, dependencies=deps, tar_dir=osp.dirname(path), @@ -1325,9 +1325,9 @@ def _list_extensions(self, info, ext_type): if data['is_local']: extra += '*' # If we have the package name in the data, this means this extension's name is the alias name - package_name = data['package_name'] - if package_name: - logger.info(' %s %s v%s%s' % (name, package_name, version, extra)) + 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: From 38993a3e85ed33e55ac6698d1be80ec318f77ad1 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Fri, 2 Aug 2019 11:57:26 -0400 Subject: [PATCH 06/12] Test pinning --- scripts/travis_script.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index ac8ef1bef533..f3eb164127dd 100644 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -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 # Make sure we can call help on all the cli apps. jupyter lab -h From 87d68228c7c59d3b4d2e25c03ffff669a94d2564 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Wed, 7 Aug 2019 16:32:17 -0400 Subject: [PATCH 07/12] Fix python test execution on mac Otherwise I get: ``` E AssertionError: '/var/folders/m7/t8dvwtnn32z84333p845tly40000gn/T/tmpfn25l4mq/data/lab' != '/private/var/folders/m7/t8dvwtnn32z84333p845tly40000gn/T/tmpfn25l4mq/data/lab' E - /var/folders/m7/t8dvwtnn32z84333p845tly40000gn/T/tmpfn25l4mq/data/lab E + /private/var/folders/m7/t8dvwtnn32z84333p845tly40000gn/T/tmpfn25l4mq/data/lab E ? ++++++++ ``` --- jupyterlab/tests/test_jupyterlab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterlab/tests/test_jupyterlab.py b/jupyterlab/tests/test_jupyterlab.py index 415b19c23fd7..85f6d804da35 100644 --- a/jupyterlab/tests/test_jupyterlab.py +++ b/jupyterlab/tests/test_jupyterlab.py @@ -120,7 +120,7 @@ def ignore(dname, files): self.assertEqual(paths.ENV_CONFIG_PATH, [self.config_dir]) self.assertEqual(paths.ENV_JUPYTER_PATH, [self.data_dir]) self.assertEqual( - commands.get_app_dir(), + os.path.realpath(commands.get_app_dir()), os.path.realpath(pjoin(self.data_dir, 'lab')) ) From b823d2ad2b5459ce7623fde8803ff511dfd81a33 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Wed, 7 Aug 2019 16:43:56 -0400 Subject: [PATCH 08/12] Move pinned install tests to python from bash --- jupyterlab/tests/test_jupyterlab.py | 26 ++++++++++++++++++++++++++ scripts/travis_script.sh | 7 ------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/jupyterlab/tests/test_jupyterlab.py b/jupyterlab/tests/test_jupyterlab.py index 85f6d804da35..58c618be3153 100644 --- a/jupyterlab/tests/test_jupyterlab.py +++ b/jupyterlab/tests/test_jupyterlab.py @@ -227,6 +227,32 @@ def test_uninstall_core_extension(self): assert '@jupyterlab/console-extension' in extensions assert check_extension('@jupyterlab/console-extension') + def test_install_and_uninstall_pinned(self): + """ + You should be able to install different versions of the same extension with different + pinned names and uninstall them with those names. + """ + NAMES = ['test-1', 'test-2'] + assert install_extension('jupyterlab-test-extension@1.0', pin=NAMES[0]) + assert install_extension('jupyterlab-test-extension@2.0', pin=NAMES[1]) + + extensions = get_app_info(self.app_dir)['extensions'] + assert NAMES[0] in extensions + assert NAMES[1] in extensions + assert check_extension(NAMES[0]) + assert check_extension(NAMES[1]) + + # Uninstall + assert uninstall_extension(NAMES[0]) + assert uninstall_extension(NAMES[1]) + + extensions = get_app_info(self.app_dir)['extensions'] + assert NAMES[0] not in extensions + assert NAMES[1] not in extensions + assert not check_extension(NAMES[0]) + assert not check_extension(NAMES[1]) + + def test_link_extension(self): path = self.mock_extension name = self.pkg_names['extension'] diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index f3eb164127dd..1e1085dbca52 100644 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -142,13 +142,6 @@ 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 - # Make sure we can call help on all the cli apps. jupyter lab -h jupyter lab build -h From 1ef89378121f6ea51441ebdd0e90c224872793bf Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Wed, 7 Aug 2019 18:21:42 -0400 Subject: [PATCH 09/12] Add test for pinning packages from folders --- jupyterlab/tests/test_jupyterlab.py | 37 +++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/jupyterlab/tests/test_jupyterlab.py b/jupyterlab/tests/test_jupyterlab.py index 58c618be3153..6e3a0ed082ed 100644 --- a/jupyterlab/tests/test_jupyterlab.py +++ b/jupyterlab/tests/test_jupyterlab.py @@ -8,6 +8,9 @@ import os import shutil import sys +import subprocess +import shutil +import pathlib from os.path import join as pjoin from tempfile import TemporaryDirectory from unittest import TestCase @@ -126,6 +129,9 @@ def ignore(dname, files): self.app_dir = commands.get_app_dir() + # Set pinned extension names + self.pinned_packages = ['jupyterlab-test-extension@1.0', 'jupyterlab-test-extension@2.0'] + def test_install_extension(self): assert install_extension(self.mock_extension) is True path = pjoin(self.app_dir, 'extensions', '*.tgz') @@ -227,14 +233,15 @@ def test_uninstall_core_extension(self): assert '@jupyterlab/console-extension' in extensions assert check_extension('@jupyterlab/console-extension') + @pytest.mark.webtest def test_install_and_uninstall_pinned(self): """ You should be able to install different versions of the same extension with different pinned names and uninstall them with those names. """ NAMES = ['test-1', 'test-2'] - assert install_extension('jupyterlab-test-extension@1.0', pin=NAMES[0]) - assert install_extension('jupyterlab-test-extension@2.0', pin=NAMES[1]) + assert install_extension(self.pinned_packages[0], pin=NAMES[0]) + assert install_extension(self.pinned_packages[1], pin=NAMES[1]) extensions = get_app_info(self.app_dir)['extensions'] assert NAMES[0] in extensions @@ -253,6 +260,32 @@ def test_install_and_uninstall_pinned(self): assert not check_extension(NAMES[1]) + @pytest.mark.webtest + def test_install_and_uninstall_pinned_folder(self): + """ + Same as above test, but installs from a local folder instead of from npm. + """ + # Download each version of the package from NPM: + base_dir = pathlib.Path(self.tempdir()) + + # The archive file names are printed to stdout when run `npm pack` + packages = [ + subprocess.run( + ['npm', 'pack', name], + stdout=subprocess.PIPE, + universal_newlines=True, + check=True, + cwd=base_dir + ).stdout.strip() + for name in self.pinned_packages + ] + + shutil.unpack_archive(str(base_dir / packages[0]), str(base_dir / '1')) + shutil.unpack_archive(str(base_dir / packages[1]), str(base_dir / '2')) + # Change pinned packages to be these directories now, so we install from these folders + self.pinned_packages = [str(base_dir / '1' / 'package'), str(base_dir / '2' / 'package')] + self.test_install_and_uninstall_pinned() + def test_link_extension(self): path = self.mock_extension name = self.pkg_names['extension'] From f269cc0f3b781018a5cb3f4afb8ae39b610b7316 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Wed, 7 Aug 2019 18:40:12 -0400 Subject: [PATCH 10/12] Skip running npm pack on windows --- jupyterlab/tests/test_jupyterlab.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jupyterlab/tests/test_jupyterlab.py b/jupyterlab/tests/test_jupyterlab.py index 6e3a0ed082ed..4072de5c3cc9 100644 --- a/jupyterlab/tests/test_jupyterlab.py +++ b/jupyterlab/tests/test_jupyterlab.py @@ -11,6 +11,7 @@ import subprocess import shutil import pathlib +import platform from os.path import join as pjoin from tempfile import TemporaryDirectory from unittest import TestCase @@ -233,7 +234,6 @@ def test_uninstall_core_extension(self): assert '@jupyterlab/console-extension' in extensions assert check_extension('@jupyterlab/console-extension') - @pytest.mark.webtest def test_install_and_uninstall_pinned(self): """ You should be able to install different versions of the same extension with different @@ -259,8 +259,7 @@ def test_install_and_uninstall_pinned(self): assert not check_extension(NAMES[0]) assert not check_extension(NAMES[1]) - - @pytest.mark.webtest + @pytest.mark.skipif(platform.system() == 'Windows', reason='running npm pack fails on windows CI') def test_install_and_uninstall_pinned_folder(self): """ Same as above test, but installs from a local folder instead of from npm. @@ -286,6 +285,7 @@ def test_install_and_uninstall_pinned_folder(self): self.pinned_packages = [str(base_dir / '1' / 'package'), str(base_dir / '2' / 'package')] self.test_install_and_uninstall_pinned() + def test_link_extension(self): path = self.mock_extension name = self.pkg_names['extension'] From 93b08cab1c1934503c1a59763ec08ecd6206eba7 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Thu, 15 Aug 2019 09:21:59 -0400 Subject: [PATCH 11/12] Make pinned extension filename windows compat --- jupyterlab/commands.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jupyterlab/commands.py b/jupyterlab/commands.py index 524fcbd57150..06d91e1e4bcb 100644 --- a/jupyterlab/commands.py +++ b/jupyterlab/commands.py @@ -38,6 +38,9 @@ DEV_DIR = osp.abspath(os.path.join(HERE, '..', 'dev_mode')) +# If we are pinning the package, rename it `pin@` +PIN_PREFIX = 'pin@' + class ProgressProcess(Process): def __init__(self, cmd, logger=None, cwd=None, kill_event=None, @@ -1214,8 +1217,8 @@ def _get_extensions_in_dir(self, dname, core_data): path = osp.abspath(target) filename = osp.basename(target) - if filename.startswith("pin:"): - alias = filename[len("pin:"):-len(".tgz")] + if filename.startswith(PIN_PREFIX): + alias = filename[len(PIN_PREFIX):-len(".tgz")] else: alias = None # homepage, repository are optional @@ -1479,10 +1482,9 @@ def _extract_package(self, source, tempdir, pin=None): info['path'] = target else: info['path'] = path - # If we are pinning the package, rename it `pin:`` if pin: old_path = info['path'] - new_path = pjoin(osp.dirname(old_path), 'pin:{}.tgz'.format(pin)) + new_path = pjoin(osp.dirname(old_path), '{}{}.tgz'.format(PIN_PREFIX, pin)) shutil.move(old_path, new_path) info['path'] = new_path From 5edaa3fa30348c91b400e49cf2ae9ebee93b44f6 Mon Sep 17 00:00:00 2001 From: Saul Shanabrook Date: Thu, 15 Aug 2019 11:41:09 -0400 Subject: [PATCH 12/12] Fix merge --- jupyterlab/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterlab/commands.py b/jupyterlab/commands.py index 0d6e4090fc29..277ed27fabec 100644 --- a/jupyterlab/commands.py +++ b/jupyterlab/commands.py @@ -317,8 +317,8 @@ def install_extension(extension, app_dir=None, logger=None, core_config=None, pi """ logger = _ensure_logger(logger) _node_check(logger) - handler = _AppHandler(app_dir, logger) - return handler.install_extension(extension, core_config=core_config, pin=pin) + handler = _AppHandler(app_dir, logger, core_config=core_config) + return handler.install_extension(extension, pin=pin) def uninstall_extension(name=None, app_dir=None, logger=None, all_=False, core_config=None):