mirror of
https://github.com/httpie/cli.git
synced 2025-06-10 23:57:28 +02:00
Proper JSON handling for :=/:=@ (#1213)
* Proper JSON handling for :=/:=@ * document the behavior * fixup docs
This commit is contained in:
parent
0fc6331ee0
commit
6bdcdf1eba
@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Added support for sending multiple HTTP headers with the same name. ([#130](https://github.com/httpie/httpie/issues/130))
|
- Added support for sending multiple HTTP headers with the same name. ([#130](https://github.com/httpie/httpie/issues/130))
|
||||||
- Added support for receving multiple HTTP headers with the same name, individually. ([#1207](https://github.com/httpie/httpie/issues/1207))
|
- Added support for receving multiple HTTP headers with the same name, individually. ([#1207](https://github.com/httpie/httpie/issues/1207))
|
||||||
- Added support for keeping `://` in the URL argument to allow quick conversions of pasted URLs into HTTPie calls just by adding a space after the protocol name (`$ https ://pie.dev` → `https://pie.dev`). ([#1195](https://github.com/httpie/httpie/issues/1195))
|
- Added support for keeping `://` in the URL argument to allow quick conversions of pasted URLs into HTTPie calls just by adding a space after the protocol name (`$ https ://pie.dev` → `https://pie.dev`). ([#1195](https://github.com/httpie/httpie/issues/1195))
|
||||||
|
- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212))
|
||||||
|
|
||||||
## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14)
|
## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14)
|
||||||
|
|
||||||
|
@ -710,6 +710,10 @@ Host: pie.dev
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `:=`/`:=@` syntax is JSON-specific. You can switch your request to `--form` or `--multipart`,
|
||||||
|
and string, float, and number values will continue to be serialized (as string form values).
|
||||||
|
Other JSON types, however, are not allowed with `--form` or `--multipart`.
|
||||||
|
|
||||||
### Raw and complex JSON
|
### Raw and complex JSON
|
||||||
|
|
||||||
Please note that with the [request items](#request-items) data field syntax, commands can quickly become unwieldy when sending complex structures.
|
Please note that with the [request items](#request-items) data field syntax, commands can quickly become unwieldy when sending complex structures.
|
||||||
|
@ -364,7 +364,7 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
|
|||||||
try:
|
try:
|
||||||
request_items = RequestItems.from_args(
|
request_items = RequestItems.from_args(
|
||||||
request_item_args=self.args.request_items,
|
request_item_args=self.args.request_items,
|
||||||
as_form=self.args.form,
|
request_type=self.args.request_type,
|
||||||
)
|
)
|
||||||
except ParseError as e:
|
except ParseError as e:
|
||||||
if self.args.traceback:
|
if self.args.traceback:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import functools
|
||||||
from typing import Callable, Dict, IO, List, Optional, Tuple, Union
|
from typing import Callable, Dict, IO, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from .argtypes import KeyValueArg
|
from .argtypes import KeyValueArg
|
||||||
@ -7,7 +8,7 @@ from .constants import (
|
|||||||
SEPARATOR_DATA_EMBED_RAW_JSON_FILE,
|
SEPARATOR_DATA_EMBED_RAW_JSON_FILE,
|
||||||
SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD,
|
SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD,
|
||||||
SEPARATOR_FILE_UPLOAD_TYPE, SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY,
|
SEPARATOR_FILE_UPLOAD_TYPE, SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY,
|
||||||
SEPARATOR_QUERY_PARAM,
|
SEPARATOR_QUERY_PARAM, RequestType
|
||||||
)
|
)
|
||||||
from .dicts import (
|
from .dicts import (
|
||||||
BaseMultiDict, MultipartRequestDataDict, RequestDataDict,
|
BaseMultiDict, MultipartRequestDataDict, RequestDataDict,
|
||||||
@ -20,9 +21,11 @@ from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys
|
|||||||
|
|
||||||
class RequestItems:
|
class RequestItems:
|
||||||
|
|
||||||
def __init__(self, as_form=False):
|
def __init__(self, request_type: Optional[RequestType] = None):
|
||||||
self.headers = HTTPHeadersDict()
|
self.headers = HTTPHeadersDict()
|
||||||
self.data = RequestDataDict() if as_form else RequestJSONDataDict()
|
self.request_type = request_type
|
||||||
|
self.is_json = request_type is None or request_type is RequestType.JSON
|
||||||
|
self.data = RequestJSONDataDict() if self.is_json else RequestDataDict()
|
||||||
self.files = RequestFilesDict()
|
self.files = RequestFilesDict()
|
||||||
self.params = RequestQueryParamsDict()
|
self.params = RequestQueryParamsDict()
|
||||||
# To preserve the order of fields in file upload multipart requests.
|
# To preserve the order of fields in file upload multipart requests.
|
||||||
@ -32,9 +35,9 @@ class RequestItems:
|
|||||||
def from_args(
|
def from_args(
|
||||||
cls,
|
cls,
|
||||||
request_item_args: List[KeyValueArg],
|
request_item_args: List[KeyValueArg],
|
||||||
as_form=False,
|
request_type: Optional[RequestType] = None,
|
||||||
) -> 'RequestItems':
|
) -> 'RequestItems':
|
||||||
instance = cls(as_form=as_form)
|
instance = cls(request_type=request_type)
|
||||||
rules: Dict[str, Tuple[Callable, dict]] = {
|
rules: Dict[str, Tuple[Callable, dict]] = {
|
||||||
SEPARATOR_HEADER: (
|
SEPARATOR_HEADER: (
|
||||||
process_header_arg,
|
process_header_arg,
|
||||||
@ -61,11 +64,11 @@ class RequestItems:
|
|||||||
instance.data,
|
instance.data,
|
||||||
),
|
),
|
||||||
SEPARATOR_DATA_RAW_JSON: (
|
SEPARATOR_DATA_RAW_JSON: (
|
||||||
process_data_raw_json_embed_arg,
|
json_only(instance, process_data_raw_json_embed_arg),
|
||||||
instance.data,
|
instance.data,
|
||||||
),
|
),
|
||||||
SEPARATOR_DATA_EMBED_RAW_JSON_FILE: (
|
SEPARATOR_DATA_EMBED_RAW_JSON_FILE: (
|
||||||
process_data_embed_raw_json_file_arg,
|
json_only(instance, process_data_embed_raw_json_file_arg),
|
||||||
instance.data,
|
instance.data,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -127,6 +130,29 @@ def process_data_embed_file_contents_arg(arg: KeyValueArg) -> str:
|
|||||||
return load_text_file(arg)
|
return load_text_file(arg)
|
||||||
|
|
||||||
|
|
||||||
|
def json_only(items: RequestItems, func: Callable[[KeyValueArg], JSONType]) -> str:
|
||||||
|
if items.is_json:
|
||||||
|
return func
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs) -> str:
|
||||||
|
try:
|
||||||
|
ret = func(*args, **kwargs)
|
||||||
|
except ParseError:
|
||||||
|
ret = None
|
||||||
|
|
||||||
|
# If it is a basic type, then allow it
|
||||||
|
if isinstance(ret, (str, int, float)):
|
||||||
|
return str(ret)
|
||||||
|
else:
|
||||||
|
raise ParseError(
|
||||||
|
'Can\'t use complex JSON value types with '
|
||||||
|
'--form/--multipart.'
|
||||||
|
)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType:
|
def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType:
|
||||||
contents = load_text_file(arg)
|
contents = load_text_file(arg)
|
||||||
value = load_json(arg, contents)
|
value = load_json(arg, contents)
|
||||||
|
@ -132,7 +132,7 @@ class TestItemParsing:
|
|||||||
self.key_value_arg('text_field=a'),
|
self.key_value_arg('text_field=a'),
|
||||||
self.key_value_arg('text_field=b')
|
self.key_value_arg('text_field=b')
|
||||||
],
|
],
|
||||||
as_form=True,
|
request_type=constants.RequestType.FORM,
|
||||||
)
|
)
|
||||||
assert items.data['text_field'] == ['a', 'b']
|
assert items.data['text_field'] == ['a', 'b']
|
||||||
assert list(items.data.items()) == [
|
assert list(items.data.items()) == [
|
||||||
|
@ -4,6 +4,7 @@ import pytest
|
|||||||
import responses
|
import responses
|
||||||
|
|
||||||
from httpie.cli.constants import PRETTY_MAP
|
from httpie.cli.constants import PRETTY_MAP
|
||||||
|
from httpie.cli.exceptions import ParseError
|
||||||
from httpie.compat import is_windows
|
from httpie.compat import is_windows
|
||||||
from httpie.output.formatters.colors import ColorFormatter
|
from httpie.output.formatters.colors import ColorFormatter
|
||||||
from httpie.utils import JsonDictPreservingDuplicateKeys
|
from httpie.utils import JsonDictPreservingDuplicateKeys
|
||||||
@ -116,3 +117,38 @@ def test_duplicate_keys_support_from_input_file():
|
|||||||
# Check --unsorted
|
# Check --unsorted
|
||||||
r = http(*args, '--unsorted')
|
r = http(*args, '--unsorted')
|
||||||
assert JSON_WITH_DUPES_FORMATTED_UNSORTED in r
|
assert JSON_WITH_DUPES_FORMATTED_UNSORTED in r
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", [
|
||||||
|
1,
|
||||||
|
1.1,
|
||||||
|
True,
|
||||||
|
'some_value'
|
||||||
|
])
|
||||||
|
def test_simple_json_arguments_with_non_json(httpbin, value):
|
||||||
|
r = http(
|
||||||
|
'--form',
|
||||||
|
httpbin + '/post',
|
||||||
|
f'option:={json.dumps(value)}',
|
||||||
|
)
|
||||||
|
assert r.json['form'] == {'option': str(value)}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("request_type", [
|
||||||
|
"--form",
|
||||||
|
"--multipart",
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize("value", [
|
||||||
|
[1, 2, 3],
|
||||||
|
{'a': 'b'},
|
||||||
|
None
|
||||||
|
])
|
||||||
|
def test_complex_json_arguments_with_non_json(httpbin, request_type, value):
|
||||||
|
with pytest.raises(ParseError) as cm:
|
||||||
|
http(
|
||||||
|
request_type,
|
||||||
|
httpbin + '/post',
|
||||||
|
f'option:={json.dumps(value)}',
|
||||||
|
)
|
||||||
|
|
||||||
|
cm.match('Can\'t use complex JSON value types')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user