Skip to content
Open
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
15 changes: 14 additions & 1 deletion httpie/cli/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,20 @@ 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:
# An optional argument sitting between METHOD/URL and the
# first REQUEST_ITEM splits the positional run in two, and
# argparse fills each run's slots independently: METHOD
# and URL end up absorbing the first run between them,
# leaving REQUEST_ITEM to catch whatever comes after the
# split, which is why method lands as None here even
# though a request item was given.
self.error(
'METHOD, URL and REQUEST_ITEM all have to sit next to '
'each other with nothing else in between; move any '
'other arguments before METHOD or after the last '
'REQUEST_ITEM.'
)
if self.has_input_data:
self.args.method = HTTP_POST
else:
Expand Down
30 changes: 30 additions & 0 deletions tests/test_httpie.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""High-level tests."""
import io
import sys
from unittest import mock

import pytest
Expand Down Expand Up @@ -143,6 +144,35 @@ def test_form_POST_file_redirected_stdin(httpbin):
assert 'cannot be mixed' in r.stderr


@pytest.mark.skipif(
sys.version_info < (3, 13),
reason=(
"argparse only splits the positional run this way on 3.13+; "
"on earlier versions the same command is rejected upstream "
"with its own 'unrecognized arguments' error before _guess_method "
"ever runs, so there's nothing here for the fix to catch"
),
)
def test_option_between_method_and_url_reports_a_clear_error():
"""
https://github.com/httpie/cli/issues/1614

An option sitting between METHOD and URL splits the positional run
argparse sees into two, and on Python 3.13+ argparse fills each half
on its own: METHOD and URL absorb the first half, URL's real value
gets read back as a REQUEST_ITEM, and METHOD is left unset entirely.
That used to reach an internal assertion and crash with a bare
AssertionError instead of telling the caller what went wrong.
"""
r = http(
'POST', '--auth-type', 'bearer', '--auth', 'token',
'http://example.org',
tolerate_error_exit_status=True,
)
assert r.exit_status == ExitStatus.ERROR
assert 'METHOD, URL and REQUEST_ITEM' in r.stderr


def test_raw_POST_key_values_supplied(httpbin):
r = http(
'--raw',
Expand Down
Loading