Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion httpie/cli/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,14 @@ def _guess_method(self):
"""
if self.args.method is None:
# Invoked as `http URL'.
assert not self.args.request_items
if self.args.request_items:
self.error(
'no HTTP method or URL detected but request items found. '
'Please make sure that the URL comes right after '
'the optional METHOD:\n'
' http [METHOD] URL [REQUEST_ITEM ...]\n'
'See https://httpie.io/docs/cli for more information.'
)
if self.has_input_data:
self.args.method = HTTP_POST
else:
Expand Down
25 changes: 25 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,31 @@ def test_guess_when_method_set_but_invalid_and_item_exists(self):
key='old_item', value='b', sep='=', orig='old_item=b'),
]

def test_guess_when_method_not_set_but_request_items_present(self):
"""When method is None but request_items exist, _guess_method
should produce a user-friendly error instead of an AssertionError.

This state can occur when argparse misassigns positional arguments
due to intermixed optional and positional arguments (e.g.,
``http POST --auth-type bearer --auth token URL``).
See https://github.com/httpie/cli/issues/1614
"""
self.parser.args = argparse.Namespace()
self.parser.args.method = None
self.parser.args.url = 'http://example.com/'
self.parser.args.request_items = [
KeyValueArg(
key='test', value='header', sep=':', orig='test:header')
]
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
# Patch print_usage since the parser isn't fully initialized in
# unit tests (no spec attribute).
self.parser.print_usage = lambda *a, **kw: None
with pytest.raises(SystemExit) as exc_info:
self.parser._guess_method()
assert exc_info.value.code == 2


class TestNoOptions:

Expand Down
34 changes: 33 additions & 1 deletion tests/test_cli_ui.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
import argparse
import io
import sys

import pytest
import shutil
import os
from tests.utils import http


# Detect whether argparse quotes choices in error messages.
# Python 3.12.4+ (and 3.13+) removed the quotes.
def _argparse_quotes_choices():
p = argparse.ArgumentParser(prog='_probe')
p.add_argument('--c', choices=['x'])
buf = io.StringIO()
saved = sys.stderr
sys.stderr = buf
try:
p.parse_args(['--c', 'z'])
except SystemExit:
pass
finally:
sys.stderr = saved
return "'x'" in buf.getvalue()


def _fmt_argparse_choice(choice):
return f"'{choice}'" if _ARGPARSE_QUOTES else choice


_ARGPARSE_QUOTES = _argparse_quotes_choices()

NAKED_BASE_TEMPLATE = """\
usage:
http {extra_args}[METHOD] URL [REQUEST_ITEM ...]
Expand All @@ -27,7 +55,11 @@

NAKED_HELP_MESSAGE_PRETTY_WITH_INVALID_ARG = NAKED_BASE_TEMPLATE.format(
extra_args="--pretty {all, colors, format, none} ",
error_msg="argument --pretty: invalid choice: '$invalid' (choose from 'all', 'colors', 'format', 'none')"
error_msg=(
f"argument --pretty: invalid choice: '$invalid' "
f"(choose from {_fmt_argparse_choice('all')}, {_fmt_argparse_choice('colors')}, "
f"{_fmt_argparse_choice('format')}, {_fmt_argparse_choice('none')})"
)
)


Expand Down
4 changes: 3 additions & 1 deletion tests/test_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@


CHARSET_TEXT_PAIRS = [
('big5', '卷首卷首卷首卷首卷卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首卷首'),
# Use varied Traditional Chinese text so charset_normalizer can
# reliably distinguish big5 from other CJK encodings (e.g. johab).
('big5', '天地玄黃宇宙洪荒日月盈昃辰宿列張寒來暑往秋收冬藏閏餘成歲律呂調陽雲騰致雨露結為霜'),
('windows-1250', 'Všichni lidé jsou si rovni. Všichni lidé jsou si rovni.'),
(UTF8, 'Všichni lidé jsou si rovni. Všichni lidé jsou si rovni.'),
]
Expand Down
Loading