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
31 changes: 27 additions & 4 deletions httpie/cli/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,19 @@ def _apply_no_options(self, no_options):
invalid.append(option)

if invalid:
self.error(f'unrecognized arguments: {" ".join(invalid)}')
# When options are placed between METHOD and URL
# (e.g., `http POST --auth-type bearer --auth token URL`),
# argparse may fail to associate the URL with the `url`
# positional and leave it as an unrecognized argument.
# Detect this case and rearrange the arguments.
if (len(invalid) == 1
and self.args.method is None
and self.args.url
and re.match('^[a-zA-Z]+$', self.args.url)):
self.args.method = self.args.url
self.args.url = invalid[0]
else:
self.error(f'unrecognized arguments: {" ".join(invalid)}')

def _body_from_file(self, fd):
"""Read the data from a file-like object.
Expand Down Expand Up @@ -412,9 +424,20 @@ def _guess_method(self):

"""
if self.args.method is None:
# Invoked as `http URL'.
assert not self.args.request_items
if self.has_input_data:
if self.args.request_items:
if re.match('^[a-zA-Z]+$', self.args.url):
# `url` holds a method name and the actual URL was
# parsed as a request item (Python 3.13+ argparse).
self.args.method = self.args.url
first_item = self.args.request_items.pop(0)
self.args.url = first_item.orig
else:
self.error(
'Got unexpected request items with no HTTP '
'method specified. Usage:\n\n'
' http [METHOD] URL [REQUEST_ITEM ...]\n'
)
elif self.has_input_data:
self.args.method = HTTP_POST
else:
self.args.method = HTTP_GET
Expand Down
33 changes: 33 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,39 @@ def test_guess_when_method_set_but_invalid_and_item_exists(self):
]


def test_guess_when_method_none_and_request_items_present(self):
self.parser.args = argparse.Namespace()
self.parser.args.method = None
self.parser.args.url = 'POST'
self.parser.args.request_items = [
KeyValueArg(
key='https', value='//example.org',
sep=':', orig='https://example.org')
]
self.parser.args.ignore_stdin = False
self.parser.env = MockEnvironment()
self.parser.has_input_data = False
self.parser._guess_method()
assert self.parser.args.method == 'POST'
assert self.parser.args.url == 'https://example.org'
assert self.parser.args.request_items == []


class TestOptionsBeforeURL:

def test_method_with_options_before_url(self):
r = http('--offline', 'POST', '--print=H',
'--auth-type', 'bearer', '--auth', 'token123',
'https://example.org')
assert 'POST / HTTP/1.1' in r
assert 'Authorization: Bearer token123' in r

def test_get_with_options_before_url(self):
r = http('--offline', 'GET', '--print=H',
'--verbose', 'https://example.org')
assert 'GET / HTTP/1.1' in r


class TestNoOptions:

def test_valid_no_options(self, httpbin):
Expand Down
Loading