From c15794853188d56af7a1d53473e1ad821f3ec861 Mon Sep 17 00:00:00 2001 From: Batuhan Taskaya Date: Sun, 3 Apr 2022 16:06:42 +0300 Subject: [PATCH] Add `httpie cli plugins` in favor of the new cli namespace. (#1320) * Add `httpie cli plugins` in favor of the new cli namespace. * Separate each task to individual modules. * Move httpie.manager.plugins to httpie.manager.tasks.plugins Co-authored-by: Jakub Roztocil --- CHANGELOG.md | 2 + docs/README.md | 24 +++---- httpie/manager/cli.py | 64 ++++++++++--------- httpie/manager/core.py | 4 +- httpie/manager/tasks/__init__.py | 9 +++ httpie/manager/tasks/export_args.py | 27 ++++++++ httpie/manager/{ => tasks}/plugins.py | 21 ++++-- .../manager/{tasks.py => tasks/sessions.py} | 39 +---------- tests/test_plugins_cli.py | 17 +++-- tests/utils/plugins_cli.py | 13 ++-- 10 files changed, 120 insertions(+), 100 deletions(-) create mode 100644 httpie/manager/tasks/__init__.py create mode 100644 httpie/manager/tasks/export_args.py rename httpie/manager/{ => tasks}/plugins.py (94%) rename httpie/manager/{tasks.py => tasks/sessions.py} (78%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 509e7ee2..c8cd462e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## [3.1.1.dev0](https://github.com/httpie/httpie/compare/3.1.0...HEAD) (Unreleased) +- Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/httpie/issues/1320)) - Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/httpie/issues/1310)) + ## [3.1.0](https://github.com/httpie/httpie/compare/3.0.2...3.1.0) (2022-03-08) - **SECURITY** Fixed the [vulnerability](https://github.com/httpie/httpie/security/advisories/GHSA-9w4w-cpc8-h2fq) that caused exposure of cookies on redirects to third party hosts. ([#1312](https://github.com/httpie/httpie/pull/1312)) diff --git a/docs/README.md b/docs/README.md index 605640f1..dbab8714 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2347,7 +2347,7 @@ However, it is not recommended modifying the default behavior in a way that woul #### `plugins_dir` The directory where the plugins will be installed. HTTPie needs to have read/write access on that directory, since -`httpie plugins install` will download new plugins to there. See [plugin manager](#plugin-manager) for more information. +`httpie cli plugins install` will download new plugins to there. See [plugin manager](#plugin-manager) for more information. ### Un-setting previously specified options @@ -2425,7 +2425,7 @@ $ httpie cli export-args | jq '"Program: " + .spec.name + ", Version: " + .vers "Program: http, Version: 0.0.1a0" ``` -### `httpie plugins` +#### `httpie cli plugins` `plugins` interface is a very simple plugin manager for installing, listing and uninstalling HTTPie plugins. @@ -2436,13 +2436,13 @@ plugin installations on every installation method. By default, the plugins (and their missing dependencies) will be stored under the configuration directory, but this can be modified through `plugins_dir` variable on the config. -#### `httpie plugins install` +##### `httpie cli plugins install` -For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie plugins install` +For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie cli plugins install` can be used. ```bash -$ httpie plugins install httpie-plugin +$ httpie cli plugins install httpie-plugin Installing httpie-plugin... Successfully installed httpie-plugin-1.0.2 ``` @@ -2450,12 +2450,12 @@ Successfully installed httpie-plugin-1.0.2 > Tip: Generally HTTPie plugins start with `httpie-` prefix. Try searching for it on [PyPI](https://pypi.org/search/?q=httpie-) > to find out all plugins from the community. -#### `httpie plugins list` +##### `httpie cli plugins list` List all installed plugins. ```bash -$ httpie plugins list +$ httpie cli plugins list httpie_plugin (1.0.2) httpie_plugin (httpie.plugins.auth.v1) httpie_plugin_2 (1.0.6) @@ -2465,21 +2465,21 @@ httpie_converter (1.0.0) httpie_konsole_konverter (httpie.plugins.converter.v1) ``` -#### `httpie plugins upgrade` +##### `httpie cli plugins upgrade` For upgrading already installed plugins, use `httpie plugins upgrade`. ```bash -$ httpie plugins upgrade httpie-plugin +$ httpie cli plugins upgrade httpie-plugin ``` -#### `httpie plugins uninstall` +##### `httpie cli plugins uninstall` Uninstall plugins from the isolated plugins directory. If the plugin is not installed -through `httpie plugins install`, it won’t uninstall it. +through `httpie cli plugins install`, it won’t uninstall it. ```bash -$ httpie plugins uninstall httpie-plugin +$ httpie cli plugins uninstall httpie-plugin ``` ## Meta diff --git a/httpie/manager/cli.py b/httpie/manager/cli.py index 313f1450..2ae34fdb 100644 --- a/httpie/manager/cli.py +++ b/httpie/manager/cli.py @@ -12,37 +12,6 @@ CLI_SESSION_UPGRADE_FLAGS = [ ] COMMANDS = { - 'plugins': { - 'help': 'Manage HTTPie plugins.', - 'install': [ - 'Install the given targets from PyPI ' - 'or from a local paths.', - { - 'dest': 'targets', - 'nargs': '+', - 'help': 'targets to install' - } - ], - 'upgrade': [ - 'Upgrade the given plugins', - { - 'dest': 'targets', - 'nargs': '+', - 'help': 'targets to upgrade' - } - ], - 'uninstall': [ - 'Uninstall the given HTTPie plugins.', - { - 'dest': 'targets', - 'nargs': '+', - 'help': 'targets to install' - } - ], - 'list': [ - 'List all installed HTTPie plugins.' - ], - }, 'cli': { 'help': 'Manage HTTPie for Terminal', 'export-args': [ @@ -82,6 +51,39 @@ COMMANDS = { } +COMMANDS['plugins'] = COMMANDS['cli']['plugins'] = { + 'help': 'Manage HTTPie plugins.', + 'install': [ + 'Install the given targets from PyPI ' + 'or from a local paths.', + { + 'dest': 'targets', + 'nargs': '+', + 'help': 'targets to install' + } + ], + 'upgrade': [ + 'Upgrade the given plugins', + { + 'dest': 'targets', + 'nargs': '+', + 'help': 'targets to upgrade' + } + ], + 'uninstall': [ + 'Uninstall the given HTTPie plugins.', + { + 'dest': 'targets', + 'nargs': '+', + 'help': 'targets to install' + } + ], + 'list': [ + 'List all installed HTTPie plugins.' + ], +} + + def missing_subcommand(*args) -> str: base = COMMANDS for arg in args: diff --git a/httpie/manager/core.py b/httpie/manager/core.py index 1289fef1..0d669ecc 100644 --- a/httpie/manager/core.py +++ b/httpie/manager/core.py @@ -2,7 +2,6 @@ import argparse from typing import Optional from httpie.context import Environment -from httpie.manager.plugins import PluginInstaller from httpie.status import ExitStatus from httpie.manager.cli import missing_subcommand, parser from httpie.manager.tasks import CLI_TASKS @@ -36,8 +35,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus: parser.error(MSG_NAKED_INVOCATION) if args.action == 'plugins': - plugins = PluginInstaller(env, debug=args.debug) - return plugins.run(args.plugins_action, args) + return dispatch_cli_task(env, args.action, args) elif args.action == 'cli': return dispatch_cli_task(env, args.cli_action, args) diff --git a/httpie/manager/tasks/__init__.py b/httpie/manager/tasks/__init__.py new file mode 100644 index 00000000..9c591a24 --- /dev/null +++ b/httpie/manager/tasks/__init__.py @@ -0,0 +1,9 @@ +from httpie.manager.tasks.sessions import cli_sessions +from httpie.manager.tasks.export_args import cli_export_args +from httpie.manager.tasks.plugins import cli_plugins + +CLI_TASKS = { + 'sessions': cli_sessions, + 'export-args': cli_export_args, + 'plugins': cli_plugins, +} diff --git a/httpie/manager/tasks/export_args.py b/httpie/manager/tasks/export_args.py new file mode 100644 index 00000000..9a684a49 --- /dev/null +++ b/httpie/manager/tasks/export_args.py @@ -0,0 +1,27 @@ +import argparse +import json + +from httpie.cli.definition import options +from httpie.cli.options import to_data +from httpie.output.writer import write_raw_data +from httpie.status import ExitStatus +from httpie.context import Environment + + +FORMAT_TO_CONTENT_TYPE = { + 'json': 'application/json' +} + + +def cli_export_args(env: Environment, args: argparse.Namespace) -> ExitStatus: + if args.format == 'json': + data = json.dumps(to_data(options)) + else: + raise NotImplementedError(f'Unexpected format value: {args.format}') + + write_raw_data( + env, + data, + stream_kwargs={'mime_overwrite': FORMAT_TO_CONTENT_TYPE[args.format]}, + ) + return ExitStatus.SUCCESS diff --git a/httpie/manager/plugins.py b/httpie/manager/tasks/plugins.py similarity index 94% rename from httpie/manager/plugins.py rename to httpie/manager/tasks/plugins.py index 899e9925..e6b1ee23 100644 --- a/httpie/manager/plugins.py +++ b/httpie/manager/tasks/plugins.py @@ -1,18 +1,18 @@ import argparse import os +import re +import shutil 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 Tuple, Optional, List +from typing import List, Optional, Tuple -from httpie.manager.cli import parser, missing_subcommand -from httpie.compat import importlib_metadata, get_dist_name +from httpie.compat import get_dist_name, importlib_metadata from httpie.context import Environment +from httpie.manager.cli import missing_subcommand, parser from httpie.status import ExitStatus from httpie.utils import as_site @@ -248,3 +248,14 @@ class PluginInstaller: status = self.list() return status or ExitStatus.SUCCESS + + +def cli_plugins(env: Environment, args: argparse.Namespace) -> ExitStatus: + plugins = PluginInstaller(env, debug=args.debug) + + try: + action = args.cli_plugins_action + except AttributeError: + action = args.plugins_action + + return plugins.run(action, args) diff --git a/httpie/manager/tasks.py b/httpie/manager/tasks/sessions.py similarity index 78% rename from httpie/manager/tasks.py rename to httpie/manager/tasks/sessions.py index 5b726a9b..10866cae 100644 --- a/httpie/manager/tasks.py +++ b/httpie/manager/tasks/sessions.py @@ -1,5 +1,5 @@ import argparse -from typing import TypeVar, Callable, Tuple +from typing import Tuple from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session from httpie.status import ExitStatus @@ -7,19 +7,7 @@ from httpie.context import Environment from httpie.legacy import cookie_format as legacy_cookies from httpie.manager.cli import missing_subcommand, parser -T = TypeVar('T') -CLI_TASKS = {} - - -def task(name: str) -> Callable[[T], T]: - def wrapper(func: T) -> T: - CLI_TASKS[name] = func - return func - return wrapper - - -@task('sessions') def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus: action = args.cli_sessions_action if action is None: @@ -114,28 +102,3 @@ def cli_upgrade_all_sessions(env: Environment, args: argparse.Namespace) -> Exit session_name=session_name ) return status - - -FORMAT_TO_CONTENT_TYPE = { - 'json': 'application/json' -} - - -@task('export-args') -def cli_export(env: Environment, args: argparse.Namespace) -> ExitStatus: - import json - from httpie.cli.definition import options - from httpie.cli.options import to_data - from httpie.output.writer import write_raw_data - - if args.format == 'json': - data = json.dumps(to_data(options)) - else: - raise NotImplementedError(f'Unexpected format value: {args.format}') - - write_raw_data( - env, - data, - stream_kwargs={'mime_overwrite': FORMAT_TO_CONTENT_TYPE[args.format]}, - ) - return ExitStatus.SUCCESS diff --git a/tests/test_plugins_cli.py b/tests/test_plugins_cli.py index 70cecb1f..7be110de 100644 --- a/tests/test_plugins_cli.py +++ b/tests/test_plugins_cli.py @@ -5,8 +5,9 @@ from tests.utils.plugins_cli import parse_listing @pytest.mark.requires_installation -def test_plugins_installation(httpie_plugins_success, interface, dummy_plugin): - lines = httpie_plugins_success('install', dummy_plugin.path) +@pytest.mark.parametrize('cli_mode', [True, False]) +def test_plugins_installation(httpie_plugins_success, interface, dummy_plugin, cli_mode): + lines = httpie_plugins_success('install', dummy_plugin.path, cli_mode=cli_mode) assert lines[0].startswith( f'Installing {dummy_plugin.path}' ) @@ -28,8 +29,9 @@ def test_plugin_installation_with_custom_config(httpie_plugins_success, interfac @pytest.mark.requires_installation -def test_plugins_listing(httpie_plugins_success, interface, dummy_plugin): - httpie_plugins_success('install', dummy_plugin.path) +@pytest.mark.parametrize('cli_mode', [True, False]) +def test_plugins_listing(httpie_plugins_success, interface, dummy_plugin, cli_mode): + httpie_plugins_success('install', dummy_plugin.path, cli_mode=cli_mode) data = parse_listing(httpie_plugins_success('list')) assert data == { @@ -50,9 +52,10 @@ def test_plugins_listing_multiple(interface, httpie_plugins_success, dummy_plugi @pytest.mark.requires_installation -def test_plugins_uninstall(interface, httpie_plugins_success, dummy_plugin): - httpie_plugins_success('install', dummy_plugin.path) - httpie_plugins_success('uninstall', dummy_plugin.name) +@pytest.mark.parametrize('cli_mode', [True, False]) +def test_plugins_uninstall(interface, httpie_plugins_success, dummy_plugin, cli_mode): + httpie_plugins_success('install', dummy_plugin.path, cli_mode=cli_mode) + httpie_plugins_success('uninstall', dummy_plugin.name, cli_mode=cli_mode) assert not interface.is_installed(dummy_plugin.name) diff --git a/tests/utils/plugins_cli.py b/tests/utils/plugins_cli.py index a750e664..39b515d4 100644 --- a/tests/utils/plugins_cli.py +++ b/tests/utils/plugins_cli.py @@ -208,12 +208,17 @@ def httpie_plugins(interface): from tests.utils import httpie from httpie.plugins.registry import plugin_manager - def runner(*args): + def runner(*args, cli_mode: bool = True): + args = list(args) + if cli_mode: + args.insert(0, 'cli') + args.insert(cli_mode, 'plugins') + # Prevent installed plugins from showing up. original_plugins = plugin_manager.copy() clean_sys_path = set(sys.path).difference(site.getsitepackages()) with patch('sys.path', list(clean_sys_path)): - response = httpie('plugins', *args, env=interface.environment) + response = httpie(*args, env=interface.environment) plugin_manager.clear() plugin_manager.extend(original_plugins) return response @@ -223,8 +228,8 @@ def httpie_plugins(interface): @pytest.fixture def httpie_plugins_success(httpie_plugins): - def runner(*args): - response = httpie_plugins(*args) + def runner(*args, cli_mode: bool = True): + response = httpie_plugins(*args, cli_mode=True) assert response.exit_status == ExitStatus.SUCCESS return response.splitlines() return runner