mirror of
https://github.com/httpie/cli.git
synced 2024-11-22 07:43:20 +01:00
Merge pull request #72 from jakebasile/issue-61
Added Query String Parameters (param=:value).
This commit is contained in:
commit
1d6fcfff73
16
README.rst
16
README.rst
@ -54,7 +54,7 @@ Synopsis::
|
|||||||
|
|
||||||
http [flags] [METHOD] URL [items]
|
http [flags] [METHOD] URL [items]
|
||||||
|
|
||||||
There are four types of key-value pair items available:
|
There are five types of key-value pair items available:
|
||||||
|
|
||||||
Headers (``Name:Value``)
|
Headers (``Name:Value``)
|
||||||
Arbitrary HTTP headers. The ``:`` character is used to separate a header's
|
Arbitrary HTTP headers. The ``:`` character is used to separate a header's
|
||||||
@ -77,6 +77,9 @@ File fields (``field@/path/to/file``)
|
|||||||
``screenshot@/path/to/file.png``. The presence of a file field results into
|
``screenshot@/path/to/file.png``. The presence of a file field results into
|
||||||
a ``multipart/form-data`` request.
|
a ``multipart/form-data`` request.
|
||||||
|
|
||||||
|
Query String Parameters (``name=:value``)
|
||||||
|
Appends the given name/value pair as a query string to the URL.
|
||||||
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
@ -118,6 +121,12 @@ The above will send the same request as if the following HTML form were submitte
|
|||||||
<input type="file" name="cv" />
|
<input type="file" name="cv" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
Query string parameters can be added to any request::
|
||||||
|
|
||||||
|
http GET example.com/ search=:donuts
|
||||||
|
|
||||||
|
Will GET the URL "example.com/?search=donuts".
|
||||||
|
|
||||||
A whole request body can be passed in via **``stdin``** instead, in which
|
A whole request body can be passed in via **``stdin``** instead, in which
|
||||||
case it will be used with no further processing::
|
case it will be used with no further processing::
|
||||||
|
|
||||||
@ -172,8 +181,9 @@ See ``http -h`` for more details::
|
|||||||
include one.
|
include one.
|
||||||
ITEM A key-value pair whose type is defined by the
|
ITEM A key-value pair whose type is defined by the
|
||||||
separator used. It can be an HTTP header
|
separator used. It can be an HTTP header
|
||||||
(header:value), a data field to be used in the request
|
(header:value), a query parameter (name=:value),
|
||||||
body (field_name=value), a raw JSON data field
|
a data field to be used in the request body
|
||||||
|
(field_name=value), a raw JSON data field
|
||||||
(field_name:=value), or a file field
|
(field_name:=value), or a file field
|
||||||
(field_name@/path/to/file). You can use a backslash to
|
(field_name@/path/to/file). You can use a backslash to
|
||||||
escape a colliding separator in the field name.
|
escape a colliding separator in the field name.
|
||||||
|
@ -41,6 +41,7 @@ def _get_response(args):
|
|||||||
# the `Content-Type` for us.
|
# the `Content-Type` for us.
|
||||||
args.headers['Content-Type'] = TYPE_FORM
|
args.headers['Content-Type'] = TYPE_FORM
|
||||||
|
|
||||||
|
|
||||||
# Fire the request.
|
# Fire the request.
|
||||||
try:
|
try:
|
||||||
credentials = None
|
credentials = None
|
||||||
@ -61,6 +62,7 @@ def _get_response(args):
|
|||||||
proxies=dict((p.key, p.value) for p in args.proxy),
|
proxies=dict((p.key, p.value) for p in args.proxy),
|
||||||
files=args.files,
|
files=args.files,
|
||||||
allow_redirects=args.allow_redirects,
|
allow_redirects=args.allow_redirects,
|
||||||
|
params=args.queries,
|
||||||
)
|
)
|
||||||
|
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
@ -203,6 +203,7 @@ parser.add_argument(
|
|||||||
metavar='ITEM',
|
metavar='ITEM',
|
||||||
type=cliparse.KeyValueType(
|
type=cliparse.KeyValueType(
|
||||||
cliparse.SEP_COMMON,
|
cliparse.SEP_COMMON,
|
||||||
|
cliparse.SEP_QUERY,
|
||||||
cliparse.SEP_DATA,
|
cliparse.SEP_DATA,
|
||||||
cliparse.SEP_DATA_RAW_JSON,
|
cliparse.SEP_DATA_RAW_JSON,
|
||||||
cliparse.SEP_FILES
|
cliparse.SEP_FILES
|
||||||
@ -211,7 +212,8 @@ parser.add_argument(
|
|||||||
A key-value pair whose type is defined by the
|
A key-value pair whose type is defined by the
|
||||||
separator used. It can be an HTTP header (header:value),
|
separator used. It can be an HTTP header (header:value),
|
||||||
a data field to be used in the request body (field_name=value),
|
a data field to be used in the request body (field_name=value),
|
||||||
a raw JSON data field (field_name:=value),
|
a raw JSON data field (field_name:=value),
|
||||||
|
a query parameter (name=:value),
|
||||||
or a file field (field_name@/path/to/file).
|
or a file field (field_name@/path/to/file).
|
||||||
You can use a backslash to escape a colliding
|
You can use a backslash to escape a colliding
|
||||||
separator in the field name.
|
separator in the field name.
|
||||||
|
@ -25,6 +25,7 @@ SEP_HEADERS = SEP_COMMON
|
|||||||
SEP_DATA = '='
|
SEP_DATA = '='
|
||||||
SEP_DATA_RAW_JSON = ':='
|
SEP_DATA_RAW_JSON = ':='
|
||||||
SEP_FILES = '@'
|
SEP_FILES = '@'
|
||||||
|
SEP_QUERY = '=:'
|
||||||
DATA_ITEM_SEPARATORS = [
|
DATA_ITEM_SEPARATORS = [
|
||||||
SEP_DATA,
|
SEP_DATA,
|
||||||
SEP_DATA_RAW_JSON,
|
SEP_DATA_RAW_JSON,
|
||||||
@ -102,6 +103,7 @@ class Parser(argparse.ArgumentParser):
|
|||||||
|
|
||||||
item = KeyValueType(
|
item = KeyValueType(
|
||||||
SEP_COMMON,
|
SEP_COMMON,
|
||||||
|
SEP_QUERY,
|
||||||
SEP_DATA,
|
SEP_DATA,
|
||||||
SEP_DATA_RAW_JSON,
|
SEP_DATA_RAW_JSON,
|
||||||
SEP_FILES).__call__(args.url)
|
SEP_FILES).__call__(args.url)
|
||||||
@ -118,16 +120,17 @@ class Parser(argparse.ArgumentParser):
|
|||||||
|
|
||||||
def _parse_items(self, args):
|
def _parse_items(self, args):
|
||||||
"""
|
"""
|
||||||
Parse `args.items` into `args.headers`, `args.data` and `args.files`.
|
Parse `args.items` into `args.headers`, `args.data`, `args.queries`, and `args.files`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args.headers = CaseInsensitiveDict()
|
args.headers = CaseInsensitiveDict()
|
||||||
args.headers['User-Agent'] = DEFAULT_UA
|
args.headers['User-Agent'] = DEFAULT_UA
|
||||||
args.data = OrderedDict()
|
args.data = OrderedDict()
|
||||||
args.files = OrderedDict()
|
args.files = OrderedDict()
|
||||||
|
args.queries = CaseInsensitiveDict()
|
||||||
try:
|
try:
|
||||||
parse_items(items=args.items, headers=args.headers,
|
parse_items(items=args.items, headers=args.headers,
|
||||||
data=args.data, files=args.files)
|
data=args.data, files=args.files, queries=args.queries)
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
if args.traceback:
|
if args.traceback:
|
||||||
raise
|
raise
|
||||||
@ -207,6 +210,8 @@ class KeyValueType(object):
|
|||||||
if start >= estart and end <= eend:
|
if start >= estart and end <= eend:
|
||||||
inside_escape = True
|
inside_escape = True
|
||||||
break
|
break
|
||||||
|
if start in found and len(found[start]) > len(sep):
|
||||||
|
break
|
||||||
if not inside_escape:
|
if not inside_escape:
|
||||||
found[start] = sep
|
found[start] = sep
|
||||||
|
|
||||||
@ -264,19 +269,23 @@ class AuthCredentialsType(KeyValueType):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_items(items, data=None, headers=None, files=None):
|
def parse_items(items, data=None, headers=None, files=None, queries=None):
|
||||||
"""Parse `KeyValueType` `items` into `data`, `headers` and `files`."""
|
"""Parse `KeyValueType` `items` into `data`, `headers`, `files`, and `queries`."""
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
if files is None:
|
if files is None:
|
||||||
files = {}
|
files = {}
|
||||||
|
if queries is None:
|
||||||
|
queries = {}
|
||||||
for item in items:
|
for item in items:
|
||||||
value = item.value
|
value = item.value
|
||||||
key = item.key
|
key = item.key
|
||||||
if item.sep == SEP_HEADERS:
|
if item.sep == SEP_HEADERS:
|
||||||
target = headers
|
target = headers
|
||||||
|
elif item.sep == SEP_QUERY:
|
||||||
|
target = queries
|
||||||
elif item.sep == SEP_FILES:
|
elif item.sep == SEP_FILES:
|
||||||
try:
|
try:
|
||||||
value = open(os.path.expanduser(item.value), 'r')
|
value = open(os.path.expanduser(item.value), 'r')
|
||||||
@ -301,4 +310,4 @@ def parse_items(items, data=None, headers=None, files=None):
|
|||||||
|
|
||||||
target[key] = value
|
target[key] = value
|
||||||
|
|
||||||
return headers, data, files
|
return headers, data, files, queries
|
||||||
|
@ -341,6 +341,7 @@ class ItemParsingTest(BaseTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.key_value_type = cliparse.KeyValueType(
|
self.key_value_type = cliparse.KeyValueType(
|
||||||
cliparse.SEP_HEADERS,
|
cliparse.SEP_HEADERS,
|
||||||
|
cliparse.SEP_QUERY,
|
||||||
cliparse.SEP_DATA,
|
cliparse.SEP_DATA,
|
||||||
cliparse.SEP_DATA_RAW_JSON,
|
cliparse.SEP_DATA_RAW_JSON,
|
||||||
cliparse.SEP_FILES,
|
cliparse.SEP_FILES,
|
||||||
@ -353,7 +354,7 @@ class ItemParsingTest(BaseTestCase):
|
|||||||
lambda: self.key_value_type(item))
|
lambda: self.key_value_type(item))
|
||||||
|
|
||||||
def test_escape(self):
|
def test_escape(self):
|
||||||
headers, data, files = cliparse.parse_items([
|
headers, data, files, queries = cliparse.parse_items([
|
||||||
# headers
|
# headers
|
||||||
self.key_value_type('foo\\:bar:baz'),
|
self.key_value_type('foo\\:bar:baz'),
|
||||||
self.key_value_type('jack\\@jill:hill'),
|
self.key_value_type('jack\\@jill:hill'),
|
||||||
@ -372,7 +373,7 @@ class ItemParsingTest(BaseTestCase):
|
|||||||
self.assertIn('bar@baz', files)
|
self.assertIn('bar@baz', files)
|
||||||
|
|
||||||
def test_escape_longsep(self):
|
def test_escape_longsep(self):
|
||||||
headers, data, files = cliparse.parse_items([
|
headers, data, files, queries = cliparse.parse_items([
|
||||||
self.key_value_type('bob\\:==foo'),
|
self.key_value_type('bob\\:==foo'),
|
||||||
])
|
])
|
||||||
self.assertDictEqual(data, {
|
self.assertDictEqual(data, {
|
||||||
@ -380,7 +381,7 @@ class ItemParsingTest(BaseTestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def test_valid_items(self):
|
def test_valid_items(self):
|
||||||
headers, data, files = cliparse.parse_items([
|
headers, data, files, queries = cliparse.parse_items([
|
||||||
self.key_value_type('string=value'),
|
self.key_value_type('string=value'),
|
||||||
self.key_value_type('header:value'),
|
self.key_value_type('header:value'),
|
||||||
self.key_value_type('list:=["a", 1, {}, false]'),
|
self.key_value_type('list:=["a", 1, {}, false]'),
|
||||||
@ -389,6 +390,7 @@ class ItemParsingTest(BaseTestCase):
|
|||||||
self.key_value_type('ed='),
|
self.key_value_type('ed='),
|
||||||
self.key_value_type('bool:=true'),
|
self.key_value_type('bool:=true'),
|
||||||
self.key_value_type('test-file@%s' % TEST_FILE_PATH),
|
self.key_value_type('test-file@%s' % TEST_FILE_PATH),
|
||||||
|
self.key_value_type('query=:value'),
|
||||||
])
|
])
|
||||||
self.assertDictEqual(headers, {
|
self.assertDictEqual(headers, {
|
||||||
'header': 'value',
|
'header': 'value',
|
||||||
@ -401,8 +403,19 @@ class ItemParsingTest(BaseTestCase):
|
|||||||
"list": ["a", 1, {}, False],
|
"list": ["a", 1, {}, False],
|
||||||
"obj": {"a": "b"}
|
"obj": {"a": "b"}
|
||||||
})
|
})
|
||||||
|
self.assertDictEqual(queries, {
|
||||||
|
'query': 'value',
|
||||||
|
})
|
||||||
self.assertIn('test-file', files)
|
self.assertIn('test-file', files)
|
||||||
|
|
||||||
|
def test_query_string(self):
|
||||||
|
headers, data, files, queries = cliparse.parse_items([
|
||||||
|
self.key_value_type('query=:value'),
|
||||||
|
])
|
||||||
|
self.assertDictEqual(queries, {
|
||||||
|
'query': 'value',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class ArgumentParserTestCase(unittest.TestCase):
|
class ArgumentParserTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user