Implement httpie upgrade for upgrading plugins (#1241)

* Implement `httpie upgrade` for upgrading plugins

* Support upgrades for every installation type

* Fix decoding problems
This commit is contained in:
Batuhan Taskaya 2021-12-17 10:59:39 +03:00 committed by GitHub
parent 2b78d04410
commit a5d8b51e47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 9 deletions

View File

@ -2224,6 +2224,14 @@ httpie_converter (1.0.0)
httpie_konsole_konverter (httpie.plugins.converter.v1)
```
#### `httpie plugins upgrade`
For upgrading already installed plugins, use `httpie plugins upgrade`.
```bash
$ httpie plugins upgrade httpie-plugin
```
#### `httpie plugins uninstall`
Uninstall plugins from the isolated plugins directory. If the plugin is not installed

View File

@ -14,6 +14,14 @@ COMMANDS = {
'help': 'targets to install'
}
],
'upgrade': [
'Upgrade the given plugins',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to upgrade'
}
],
'uninstall': [
'Uninstall the given HTTPie plugins.',
{

View File

@ -3,15 +3,20 @@ import os
import subprocess
import sys
import textwrap
import re
import shutil
from collections import defaultdict
from contextlib import suppress
from pathlib import Path
from typing import Optional, List
from typing import Tuple, Optional, List
from httpie.manager.cli import parser, missing_subcommand
from httpie.compat import importlib_metadata, get_dist_name
from httpie.context import Environment
from httpie.status import ExitStatus
from httpie.utils import as_site
PEP_503 = re.compile(r"[-_.]+")
class PluginInstaller:
@ -68,16 +73,22 @@ class PluginInstaller:
**options
)
def install(self, targets: List[str]) -> Optional[ExitStatus]:
self.env.stdout.write(f"Installing {', '.join(targets)}...\n")
self.env.stdout.flush()
try:
self.pip(
def _install(self, targets: List[str], mode='install', **process_options) -> Tuple[
Optional[bytes], ExitStatus
]:
pip_args = [
'install',
f'--prefix={self.dir}',
'--no-warn-script-location',
]
if mode == 'upgrade':
pip_args.append('--upgrade')
try:
process = self.pip(
*pip_args,
*targets,
**process_options,
)
except subprocess.CalledProcessError as error:
reason = None
@ -94,7 +105,58 @@ class PluginInstaller:
if severity == 'ERROR':
reason = message
return self.fail('install', ', '.join(targets), reason)
stdout = error.stdout
exit_status = self.fail(mode, ', '.join(targets), reason)
else:
stdout = process.stdout
exit_status = ExitStatus.SUCCESS
return stdout, exit_status
def install(self, targets: List[str]) -> ExitStatus:
self.env.stdout.write(f"Installing {', '.join(targets)}...\n")
self.env.stdout.flush()
_, exit_status = self._install(targets)
return exit_status
def _clear_metadata(self, targets: List[str]) -> None:
# Due to an outstanding pip problem[0], we have to get rid of
# existing metadata for old versions manually.
# [0]: https://github.com/pypa/pip/issues/10727
result_deps = defaultdict(list)
for child in as_site(self.dir).iterdir():
if child.suffix in {'.dist-info', '.egg-info'}:
name, _, version = child.stem.rpartition('-')
result_deps[name].append((version, child))
for target in targets:
name, _, version = target.rpartition('-')
name = PEP_503.sub("-", name).lower().replace('-', '_')
if name not in result_deps:
continue
for result_version, meta_path in result_deps[name]:
if version != result_version:
shutil.rmtree(meta_path)
def upgrade(self, targets: List[str]) -> ExitStatus:
self.env.stdout.write(f"Upgrading {', '.join(targets)}...\n")
self.env.stdout.flush()
raw_stdout, exit_status = self._install(
targets,
mode='upgrade',
stdout=subprocess.PIPE
)
if not raw_stdout:
return exit_status
stdout = raw_stdout.decode()
self.env.stdout.write(stdout)
installation_line = stdout.splitlines()[-1]
if installation_line.startswith('Successfully installed'):
self._clear_metadata(installation_line.split()[2:])
def _uninstall(self, target: str) -> Optional[ExitStatus]:
try:
@ -178,6 +240,8 @@ class PluginInstaller:
with enable_plugins(self.dir):
if action == 'install':
status = self.install(args.targets)
elif action == 'upgrade':
status = self.upgrade(args.targets)
elif action == 'uninstall':
status = self.uninstall(args.targets)
elif action == 'list':

View File

@ -93,6 +93,18 @@ def test_plugins_double_uninstall(httpie_plugins, httpie_plugins_success, dummy_
)
def test_plugins_upgrade(httpie_plugins, httpie_plugins_success, dummy_plugin):
httpie_plugins_success("install", dummy_plugin.path)
# Make a new version of the plugin
dummy_plugin.version = '2.0.0'
dummy_plugin.build()
httpie_plugins_success("upgrade", dummy_plugin.path)
data = parse_listing(httpie_plugins_success('list'))
assert data[dummy_plugin.name]['version'] == '2.0.0'
def test_broken_plugins(httpie_plugins, httpie_plugins_success, dummy_plugin, broken_plugin):
httpie_plugins_success("install", dummy_plugin.path, broken_plugin.path)