1
0
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:
Batuhan Taskaya 2021-11-26 14:45:46 +03:00 committed by GitHub
parent 0fc6331ee0
commit 6bdcdf1eba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 9 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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:

View File

@ -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)

View File

@ -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()) == [

View File

@ -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')