Skip to content

Commit 6078d13

Browse files
committed
Fix AssertionError when METHOD appears before options
Fixes #1614 When the HTTP method (POST, GET, etc.) was specified before optional arguments like --auth-type, argparse's nargs=OPTIONAL behavior caused incorrect argument parsing, resulting in an AssertionError. Example failing command: http POST --auth-type bearer --auth TOKEN https://example.org Argparse incorrectly parsed this as: - method=None - url='POST' - URL and request items ended up in unparsed arguments This commit adds a new _fix_argument_order() method that: 1. Detects when arguments were misparsed 2. Correctly reassigns method, url, and request_items 3. Validates all request items atomically before adding them 4. Properly initializes request_items if None The fix runs before _apply_no_options() to prevent errors from unrecognized arguments and maintains backward compatibility with existing usage patterns. Tests added: - test_fix_argument_order_method_before_options - test_fix_argument_order_method_before_options_with_items - test_bearer_auth_method_before_options
1 parent 5b604c3 commit 6078d13

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

httpie/cli/argparser.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ def parse_args(
166166
)
167167
self.has_input_data = self.has_stdin_data or self.args.raw is not None
168168
# Arguments processing and environment setup.
169+
# Fix argument misparsing before processing no_options
170+
no_options = self._fix_argument_order(no_options)
169171
self._apply_no_options(no_options)
170172
self._process_request_type()
171173
self._process_download_options()
@@ -193,6 +195,54 @@ def parse_args(
193195

194196
return self.args
195197

198+
def _fix_argument_order(self, no_options):
199+
"""Fix argument parsing issues caused by argparse's nargs=OPTIONAL
200+
not working well with intermixed arguments.
201+
202+
When METHOD is specified before options (e.g., POST --auth-type bearer URL),
203+
argparse may incorrectly parse:
204+
- method=None, url=POST, and URL+items end up in no_options
205+
206+
This method detects and fixes such cases.
207+
"""
208+
if (
209+
self.args.method is None
210+
and self.args.url
211+
and re.match('^[a-zA-Z]+$', self.args.url)
212+
and no_options
213+
and len(no_options) >= 1
214+
and not no_options[0].startswith('-')
215+
):
216+
# Likely case: method was not parsed, URL is in method position,
217+
# and actual URL (and possibly request items) are in no_options
218+
self.args.method = self.args.url.upper()
219+
self.args.url = no_options[0]
220+
221+
# Any remaining items in no_options should be request items
222+
# We need to parse them as KeyValue args and add to request_items
223+
remaining = no_options[1:]
224+
if remaining:
225+
# Bug fix: Validate all items before adding any (atomicity)
226+
parsed_items = []
227+
for item_str in remaining:
228+
try:
229+
parsed_item = KeyValueArgType(
230+
*SEPARATOR_GROUP_ALL_ITEMS).__call__(item_str)
231+
parsed_items.append(parsed_item)
232+
except argparse.ArgumentTypeError:
233+
# If any item fails to parse, return it as an unrecognized arg
234+
# so _apply_no_options will handle the error
235+
return [item_str]
236+
237+
# Bug fix: Initialize request_items if it's None
238+
if self.args.request_items is None:
239+
self.args.request_items = []
240+
241+
# All items parsed successfully, add them to request_items
242+
self.args.request_items.extend(parsed_items)
243+
return []
244+
return no_options
245+
196246
def _process_request_type(self):
197247
request_type = self.args.request_type
198248
self.args.json = request_type is RequestType.JSON

tests/test_auth.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ def test_bearer_auth(httpbin_both, token):
3838
assert r.json == {'authenticated': True, 'token': token}
3939

4040

41+
def test_bearer_auth_method_before_options(httpbin_both):
42+
"""Test fix for bug #1614: METHOD before --auth-type works correctly."""
43+
# This used to raise AssertionError due to incorrect argument parsing
44+
# when the METHOD appeared before the auth options
45+
r = http('GET', '--auth-type', 'bearer', '--auth', 'test-token',
46+
httpbin_both + '/bearer')
47+
48+
assert HTTP_OK in r
49+
assert r.json == {'authenticated': True, 'token': 'test-token'}
50+
51+
4152
@mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',
4253
new=lambda self, prompt: 'password')
4354
def test_password_prompt(httpbin):

tests/test_cli.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,45 @@ def test_guess_when_method_set_but_invalid_and_item_exists(self):
316316
key='old_item', value='b', sep='=', orig='old_item=b'),
317317
]
318318

319+
def test_fix_argument_order_method_before_options(self):
320+
"""Test fix for bug #1614: METHOD before options causes misparsing."""
321+
# Simulate: http POST --auth-type bearer https://example.org
322+
# Argparse incorrectly parses as: method=None, url='POST', no_options=['https://example.org']
323+
self.parser.args = argparse.Namespace()
324+
self.parser.args.method = None
325+
self.parser.args.url = 'POST'
326+
self.parser.args.request_items = []
327+
no_options = ['https://example.org']
328+
329+
# Call _fix_argument_order to correct the misparsing
330+
result = self.parser._fix_argument_order(no_options)
331+
332+
# Verify the arguments were fixed
333+
assert self.parser.args.method == 'POST'
334+
assert self.parser.args.url == 'https://example.org'
335+
assert result == []
336+
337+
def test_fix_argument_order_method_before_options_with_items(self):
338+
"""Test fix for bug #1614 with request items."""
339+
# Simulate: http POST --auth-type bearer https://example.org foo=bar
340+
# Argparse incorrectly parses as: method=None, url='POST', no_options=['https://example.org', 'foo=bar']
341+
self.parser.args = argparse.Namespace()
342+
self.parser.args.method = None
343+
self.parser.args.url = 'POST'
344+
self.parser.args.request_items = []
345+
no_options = ['https://example.org', 'foo=bar']
346+
347+
# Call _fix_argument_order to correct the misparsing
348+
result = self.parser._fix_argument_order(no_options)
349+
350+
# Verify the arguments were fixed
351+
assert self.parser.args.method == 'POST'
352+
assert self.parser.args.url == 'https://example.org'
353+
assert len(self.parser.args.request_items) == 1
354+
assert self.parser.args.request_items[0].key == 'foo'
355+
assert self.parser.args.request_items[0].value == 'bar'
356+
assert result == []
357+
319358

320359
class TestNoOptions:
321360

0 commit comments

Comments
 (0)