1
0
mirror of https://github.com/httpie/cli.git synced 2025-07-15 01:34:27 +02:00

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 <jakub@roztocil.co>
This commit is contained in:
Batuhan Taskaya
2022-04-03 16:06:42 +03:00
committed by GitHub
parent 33ea977b64
commit c157948531
10 changed files with 120 additions and 100 deletions

View File

@ -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) ## [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)) - 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) ## [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)) - **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))

View File

@ -2347,7 +2347,7 @@ However, it is not recommended modifying the default behavior in a way that woul
*) echo 'Other Error!' ;; *) echo 'Other Error!' ;;
esac esac
fi fi
``` ```
### Best practices ### Best practices
@ -2425,7 +2425,7 @@ $ httpie cli export-args | jq '"Program: " + .spec.name + ", Version: " + .vers
##### `httpie cli plugins list` ##### `httpie cli plugins list`
List all installed plugins. List all installed plugins.
```bash ```bash
$ httpie cli plugins list $ httpie cli plugins list
@ -2436,13 +2436,13 @@ plugin installations on every installation method.
httpie_converter (1.0.0) httpie_converter (1.0.0)
httpie_iterm_converter (httpie.plugins.converter.v1) httpie_iterm_converter (httpie.plugins.converter.v1)
httpie_konsole_konverter (httpie.plugins.converter.v1) httpie_konsole_konverter (httpie.plugins.converter.v1)
``` ```
#### `httpie plugins upgrade` ##### `httpie cli plugins upgrade`
For upgrading already installed plugins, use `httpie plugins upgrade`. For upgrading already installed plugins, use `httpie plugins upgrade`.
```bash ```bash
$ httpie cli plugins upgrade httpie-plugin $ httpie cli plugins upgrade httpie-plugin
``` ```
@ -2450,12 +2450,12 @@ Successfully installed httpie-plugin-1.0.2
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
through `httpie cli plugins install`, it won’t uninstall it. through `httpie cli plugins install`, it won’t uninstall it.
```bash ```bash
$ httpie cli plugins uninstall httpie-plugin $ httpie cli plugins uninstall httpie-plugin
``` ```
## Meta ## Meta
### Interface design ### Interface design
@ -2465,21 +2465,21 @@ httpie_converter (1.0.0)
For example, compare this HTTP request: For example, compare this HTTP request:
```http ```http
POST /post HTTP/1.1 POST /post HTTP/1.1
Host: pie.dev Host: pie.dev
X-API-Key: 123 X-API-Key: 123
User-Agent: Bacon/1.0 User-Agent: Bacon/1.0
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
name=value&name2=value2 name=value&name2=value2
``` ```
with the HTTPie command that sends it: with the HTTPie command that sends it:
```bash ```bash
$ http -f POST pie.dev/post \ $ http -f POST pie.dev/post \
X-API-Key:123 \ X-API-Key:123 \
User-Agent:Bacon/1.0 \ User-Agent:Bacon/1.0 \
name=value \ name=value \
name2=value2 name2=value2
``` ```

View File

@ -12,37 +12,6 @@ CLI_SESSION_UPGRADE_FLAGS = [
] ]
COMMANDS = { 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': { 'cli': {
'help': 'Manage HTTPie for Terminal', 'help': 'Manage HTTPie for Terminal',
'export-args': [ '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: def missing_subcommand(*args) -> str:
base = COMMANDS base = COMMANDS
for arg in args: for arg in args:

View File

@ -2,7 +2,6 @@ import argparse
from typing import Optional from typing import Optional
from httpie.context import Environment from httpie.context import Environment
from httpie.manager.plugins import PluginInstaller
from httpie.status import ExitStatus from httpie.status import ExitStatus
from httpie.manager.cli import missing_subcommand, parser from httpie.manager.cli import missing_subcommand, parser
from httpie.manager.tasks import CLI_TASKS from httpie.manager.tasks import CLI_TASKS
@ -36,8 +35,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
parser.error(MSG_NAKED_INVOCATION) parser.error(MSG_NAKED_INVOCATION)
if args.action == 'plugins': if args.action == 'plugins':
plugins = PluginInstaller(env, debug=args.debug) return dispatch_cli_task(env, args.action, args)
return plugins.run(args.plugins_action, args)
elif args.action == 'cli': elif args.action == 'cli':
return dispatch_cli_task(env, args.cli_action, args) return dispatch_cli_task(env, args.cli_action, args)

View File

@ -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,
}

View File

@ -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

View File

@ -1,18 +1,18 @@
import argparse import argparse
import os import os
import re
import shutil
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 Tuple, Optional, List from typing import List, Optional, Tuple
from httpie.manager.cli import parser, missing_subcommand from httpie.compat import get_dist_name, importlib_metadata
from httpie.compat import importlib_metadata, get_dist_name
from httpie.context import Environment from httpie.context import Environment
from httpie.manager.cli import missing_subcommand, parser
from httpie.status import ExitStatus from httpie.status import ExitStatus
from httpie.utils import as_site from httpie.utils import as_site
@ -248,3 +248,14 @@ class PluginInstaller:
status = self.list() status = self.list()
return status or ExitStatus.SUCCESS 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)

View File

@ -1,5 +1,5 @@
import argparse import argparse
from typing import TypeVar, Callable, Tuple from typing import Tuple
from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session
from httpie.status import ExitStatus 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.legacy import cookie_format as legacy_cookies
from httpie.manager.cli import missing_subcommand, parser 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: def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:
action = args.cli_sessions_action action = args.cli_sessions_action
if action is None: if action is None:
@ -114,28 +102,3 @@ def cli_upgrade_all_sessions(env: Environment, args: argparse.Namespace) -> Exit
session_name=session_name session_name=session_name
) )
return status 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

View File

@ -5,8 +5,9 @@ from tests.utils.plugins_cli import parse_listing
@pytest.mark.requires_installation @pytest.mark.requires_installation
def test_plugins_installation(httpie_plugins_success, interface, dummy_plugin): @pytest.mark.parametrize('cli_mode', [True, False])
lines = httpie_plugins_success('install', dummy_plugin.path) 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( assert lines[0].startswith(
f'Installing {dummy_plugin.path}' f'Installing {dummy_plugin.path}'
) )
@ -28,8 +29,9 @@ def test_plugin_installation_with_custom_config(httpie_plugins_success, interfac
@pytest.mark.requires_installation @pytest.mark.requires_installation
def test_plugins_listing(httpie_plugins_success, interface, dummy_plugin): @pytest.mark.parametrize('cli_mode', [True, False])
httpie_plugins_success('install', dummy_plugin.path) 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')) data = parse_listing(httpie_plugins_success('list'))
assert data == { assert data == {
@ -50,9 +52,10 @@ def test_plugins_listing_multiple(interface, httpie_plugins_success, dummy_plugi
@pytest.mark.requires_installation @pytest.mark.requires_installation
def test_plugins_uninstall(interface, httpie_plugins_success, dummy_plugin): @pytest.mark.parametrize('cli_mode', [True, False])
httpie_plugins_success('install', dummy_plugin.path) def test_plugins_uninstall(interface, httpie_plugins_success, dummy_plugin, cli_mode):
httpie_plugins_success('uninstall', dummy_plugin.name) 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) assert not interface.is_installed(dummy_plugin.name)

View File

@ -208,12 +208,17 @@ def httpie_plugins(interface):
from tests.utils import httpie from tests.utils import httpie
from httpie.plugins.registry import plugin_manager 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. # Prevent installed plugins from showing up.
original_plugins = plugin_manager.copy() original_plugins = plugin_manager.copy()
clean_sys_path = set(sys.path).difference(site.getsitepackages()) clean_sys_path = set(sys.path).difference(site.getsitepackages())
with patch('sys.path', list(clean_sys_path)): 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.clear()
plugin_manager.extend(original_plugins) plugin_manager.extend(original_plugins)
return response return response
@ -223,8 +228,8 @@ def httpie_plugins(interface):
@pytest.fixture @pytest.fixture
def httpie_plugins_success(httpie_plugins): def httpie_plugins_success(httpie_plugins):
def runner(*args): def runner(*args, cli_mode: bool = True):
response = httpie_plugins(*args) response = httpie_plugins(*args, cli_mode=True)
assert response.exit_status == ExitStatus.SUCCESS assert response.exit_status == ExitStatus.SUCCESS
return response.splitlines() return response.splitlines()
return runner return runner