mirror of
https://github.com/httpie/cli.git
synced 2024-11-24 16:53:35 +01:00
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:
parent
2b78d04410
commit
a5d8b51e47
@ -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
|
||||
|
@ -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.',
|
||||
{
|
||||
|
@ -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()
|
||||
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:
|
||||
self.pip(
|
||||
'install',
|
||||
f'--prefix={self.dir}',
|
||||
'--no-warn-script-location',
|
||||
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':
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user