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_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` #### `httpie plugins uninstall`
Uninstall plugins from the isolated plugins directory. If the plugin is not installed Uninstall plugins from the isolated plugins directory. If the plugin is not installed

View File

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

View File

@ -3,15 +3,20 @@ import os
import subprocess import subprocess
import sys import sys
import textwrap import textwrap
import re
import shutil
from collections import defaultdict from collections import defaultdict
from contextlib import suppress from contextlib import suppress
from pathlib import Path 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.manager.cli import parser, missing_subcommand
from httpie.compat import importlib_metadata, get_dist_name from httpie.compat import importlib_metadata, get_dist_name
from httpie.context import Environment from httpie.context import Environment
from httpie.status import ExitStatus from httpie.status import ExitStatus
from httpie.utils import as_site
PEP_503 = re.compile(r"[-_.]+")
class PluginInstaller: class PluginInstaller:
@ -68,16 +73,22 @@ class PluginInstaller:
**options **options
) )
def install(self, targets: List[str]) -> Optional[ExitStatus]: def _install(self, targets: List[str], mode='install', **process_options) -> Tuple[
self.env.stdout.write(f"Installing {', '.join(targets)}...\n") Optional[bytes], ExitStatus
self.env.stdout.flush() ]:
pip_args = [
'install',
f'--prefix={self.dir}',
'--no-warn-script-location',
]
if mode == 'upgrade':
pip_args.append('--upgrade')
try: try:
self.pip( process = self.pip(
'install', *pip_args,
f'--prefix={self.dir}',
'--no-warn-script-location',
*targets, *targets,
**process_options,
) )
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:
reason = None reason = None
@ -94,7 +105,58 @@ class PluginInstaller:
if severity == 'ERROR': if severity == 'ERROR':
reason = message 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]: def _uninstall(self, target: str) -> Optional[ExitStatus]:
try: try:
@ -178,6 +240,8 @@ class PluginInstaller:
with enable_plugins(self.dir): with enable_plugins(self.dir):
if action == 'install': if action == 'install':
status = self.install(args.targets) status = self.install(args.targets)
elif action == 'upgrade':
status = self.upgrade(args.targets)
elif action == 'uninstall': elif action == 'uninstall':
status = self.uninstall(args.targets) status = self.uninstall(args.targets)
elif action == 'list': 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): def test_broken_plugins(httpie_plugins, httpie_plugins_success, dummy_plugin, broken_plugin):
httpie_plugins_success("install", dummy_plugin.path, broken_plugin.path) httpie_plugins_success("install", dummy_plugin.path, broken_plugin.path)