Default to POST also when stdin redirected.

+clean up
This commit is contained in:
Jakub Roztocil 2012-06-24 01:25:30 +02:00
parent 5a47f00bac
commit 4613d947a8
5 changed files with 84 additions and 59 deletions

View File

@ -18,12 +18,6 @@ TYPE_JSON = 'application/json; charset=utf-8'
def _get_response(parser, args, stdin, stdin_isatty):
if not stdin_isatty:
if args.data:
parser.error('Request body (stdin) and request '
'data (key=value) cannot be mixed.')
args.data = stdin.read()
if args.json or (not args.form and args.data):
# JSON
if not args.files and (
@ -114,7 +108,11 @@ def main(args=None,
stdin=sys.stdin, stdin_isatty=sys.stdin.isatty(),
stdout=sys.stdout, stdout_isatty=sys.stdout.isatty()):
parser = cli.parser
args = parser.parse_args(args if args is not None else sys.argv[1:])
args = parser.parse_args(
args=args if args is not None else sys.argv[1:],
stdin=stdin,
stdin_isatty=stdin_isatty
)
response = _get_response(parser, args, stdin, stdin_isatty)
output = _get_output(args, stdout_isatty, response)
output_bytes = output.encode('utf8')

View File

@ -176,9 +176,8 @@ parser.add_argument(
help=_('''
The HTTP method to be used for the request
(GET, POST, PUT, DELETE, PATCH, ...).
If this argument is omitted then httpie will guess HTTP method.
If there is either form simple field or JSON data field
or file field presents then method is POST otherwise it is GET.
If this argument is omitted then httpie will guess the HTTP method.
If there is any data to be sent then method is POST otherwise it is GET.
''')
)
parser.add_argument(

View File

@ -3,6 +3,7 @@ CLI argument parsing logic.
"""
import os
import sys
import re
import json
import argparse
@ -24,6 +25,11 @@ SEP_HEADERS = SEP_COMMON
SEP_DATA = '='
SEP_DATA_RAW_JSON = ':='
SEP_FILES = '@'
DATA_ITEM_SEPARATORS = {
SEP_DATA,
SEP_DATA_RAW_JSON,
SEP_FILES
}
OUT_REQ_HEADERS = 'H'
@ -43,15 +49,25 @@ DEFAULT_UA = 'HTTPie/%s' % __version__
class HTTPieArgumentParser(argparse.ArgumentParser):
def parse_args(self, args=None, namespace=None):
def parse_args(self, args=None, namespace=None,
stdin=sys.stdin,
stdin_isatty=sys.stdin.isatty()):
args = super(HTTPieArgumentParser, self).parse_args(args, namespace)
self._validate_output_options(args)
self._validate_auth_options(args)
self.suggest_method(args)
self._guess_method(args, stdin_isatty)
self._parse_items(args)
if not stdin_isatty:
self._process_stdin(args, stdin)
return args
def suggest_method(self, args):
def _process_stdin(self, args, stdin):
if args.data:
self.error('Request body (stdin) and request '
'data (key=value) cannot be mixed.')
args.data = stdin.read()
def _guess_method(self, args, stdin_isatty=sys.stdin.isatty()):
"""Suggests HTTP method by positional argument values.
In following description by data item it means one of:
@ -74,28 +90,47 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
The first argument should be treated as method
if it matches ^[a-zA-Z]+$ regexp. Otherwise it is url.
"""
if args.method is None:
# Invoked as `http URL'.
assert not args.items
args.method = 'GET'
if not stdin_isatty:
args.method = 'POST'
else:
args.method = 'GET'
# FIXME: False positive, e.g., "localhost" matches but is a valid URL.
elif not re.match('^[a-zA-Z]+$', args.method):
# If first position argument is not http method going guessing mode.
# The second positional argument (if any) definitely must be an item.
# Invoked as `http URL item+':
# - The URL is now in `args.method`.
# - The first item is now in `args.url`.
#
# So we need to:
# - Guess the HTTP method.
# - Set `args.url` correctly.
# - Parse the first item and move it to `args.items[0]`.
item = KeyValueType(
SEP_COMMON,
SEP_DATA,
SEP_DATA_RAW_JSON,
SEP_FILES
)(args.url)
SEP_FILES).__call__(args.url)
args.url = args.method
args.items.insert(0, item)
# Check if any data item presents
if any(item[2] in (SEP_DATA, SEP_DATA_RAW_JSON, SEP_FILES) for item in args.items):
has_data = not stdin_isatty or any(
item.sep in DATA_ITEM_SEPARATORS for item in args.items)
if has_data:
args.method = 'POST'
else:
args.method = 'GET'
def _parse_items(self, args):
"""
Parse `args.items` into `args.headers`, `args.data` and `args.files`.
"""
args.headers = CaseInsensitiveDict()
args.headers['User-Agent'] = DEFAULT_UA
args.data = OrderedDict()

View File

@ -6,68 +6,68 @@ from httpie.cliparse import HTTPieArgumentParser, KeyValue
__author__ = 'vladimir'
class HTTPieArgumentParserTestCase(unittest.TestCase):
def setUp(self):
self.HTTPieArgumentParserStub = type(HTTPieArgumentParser.__name__, (HTTPieArgumentParser,), {})
self.HTTPieArgumentParserStub.__init__ = lambda self: None
self.httpie_argument_parser = self.HTTPieArgumentParserStub()
self.parser = HTTPieArgumentParser()
def test_suggest_when_method_set_and_valid(self):
def test_guess_when_method_set_and_valid(self):
args = Namespace()
args.method = 'GET'
args.url = 'http://example.com/'
args.items = []
self.httpie_argument_parser.suggest_method(args)
self.parser._guess_method(args)
self.assertEquals(args.method, 'GET')
self.assertEquals(args.url, 'http://example.com/')
self.assertEquals(args.items, [])
def test_suggest_when_method_not_set(self):
def test_guess_when_method_not_set(self):
args = Namespace()
args.method = None
args.url = 'http://example.com/'
args.items = []
self.httpie_argument_parser.suggest_method(args)
self.parser._guess_method(args)
self.assertEquals(args.method, 'GET')
self.assertEquals(args.url, 'http://example.com/')
self.assertEquals(args.items, [])
def test_suggest_when_method_set_but_invalid_and_data_field(self):
def test_guess_when_method_set_but_invalid_and_data_field(self):
args = Namespace()
args.method = 'http://example.com/'
args.url = 'data=field'
args.items = []
self.httpie_argument_parser.suggest_method(args)
self.parser._guess_method(args)
self.assertEquals(args.method, 'POST')
self.assertEquals(args.url, 'http://example.com/')
self.assertEquals(args.items, [KeyValue(key='data', value='field', sep='=', orig='data=field')])
def test_suggest_when_method_set_but_invalid_and_header_field(self):
def test_guess_when_method_set_but_invalid_and_header_field(self):
args = Namespace()
args.method = 'http://example.com/'
args.url = 'test:header'
args.items = []
self.httpie_argument_parser.suggest_method(args)
self.parser._guess_method(args)
self.assertEquals(args.method, 'GET')
self.assertEquals(args.url, 'http://example.com/')
self.assertEquals(args.items, [KeyValue(key='test', value='header', sep=':', orig='test:header')])
def test_suggest_when_method_set_but_invalid_and_item_exists(self):
def test_guess_when_method_set_but_invalid_and_item_exists(self):
args = Namespace()
args.method = 'http://example.com/'
args.url = 'new_item=a'
args.items = [KeyValue(key='old_item', value='b', sep='=', orig='old_item=b')]
self.httpie_argument_parser.suggest_method(args)
self.parser._guess_method(args)
self.assertEquals(args.items, [
KeyValue(key='new_item', value='a', sep='=', orig='new_item=a'),

View File

@ -9,6 +9,7 @@ import tempfile
TESTS_ROOT = os.path.dirname(__file__)
sys.path.insert(0, os.path.realpath(os.path.join(TESTS_ROOT, '..')))
from httpie import __main__
from httpie import cliparse
@ -145,39 +146,31 @@ class TestHTTPie(BaseTest):
self.assertIn('"Foo": "bar"', response)
class TestImplicitHTTPMethod(BaseTest):
class TestHTTPieSuggestion(BaseTest):
def test_get(self):
http('http://httpbin.org/get')
def test_implicit_GET(self):
r = http('http://httpbin.org/get')
self.assertIn('HTTP/1.1 200', r)
def test_post(self):
def test_implicit_GET_with_headers(self):
r = http('http://httpbin.org/headers', 'Foo:bar')
self.assertIn('"Foo": "bar"', r)
self.assertIn('HTTP/1.1 200', r)
def test_implicit_POST_json(self):
r = http('http://httpbin.org/post', 'hello=world')
self.assertIn('"hello": "world"', r)
self.assertIn('HTTP/1.1 200', r)
def test_verbose(self):
r = http('--verbose', 'http://httpbin.org/get', 'test-header:__test__')
self.assertEqual(r.count('__test__'), 2)
def test_implicit_POST_form(self):
r = http('--form', 'http://httpbin.org/post', 'foo=bar')
self.assertIn('"foo": "bar"', r)
self.assertIn('HTTP/1.1 200', r)
def test_verbose_form(self):
r = http('--verbose', '--form', 'http://httpbin.org/post', 'foo=bar', 'baz=bar')
self.assertIn('foo=bar&baz=bar', r)
def test_json(self):
response = http('http://httpbin.org/post', 'foo=bar')
self.assertIn('"foo": "bar"', response)
response2 = http('-j', 'GET', 'http://httpbin.org/headers')
self.assertIn('"Accept": "application/json"', response2)
response3 = http('-j', 'GET', 'http://httpbin.org/headers', 'Accept:application/xml')
self.assertIn('"Accept": "application/xml"', response3)
def test_form(self):
response = http('--form', 'http://httpbin.org/post', 'foo=bar')
self.assertIn('"foo": "bar"', response)
def test_headers(self):
response = http('http://httpbin.org/headers', 'Foo:bar')
self.assertIn('"User-Agent": "HTTPie', response)
self.assertIn('"Foo": "bar"', response)
def test_implicit_POST_stdin(self):
r = http('--form', 'http://httpbin.org/post',
stdin=open(TEST_FILE), stdin_isatty=False)
self.assertIn('HTTP/1.1 200', r)
class TestPrettyFlag(BaseTest):