From 4481bfb3322ff0f5831b2b0579c278b5435d0d78 Mon Sep 17 00:00:00 2001 From: Elias Floreteng <18127101+eliasfloreteng@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:22:23 +0100 Subject: [PATCH] Parse and pass request body (#15) * Handle and skip empty request segments Co-authored-by: Jakub Rybak * Fix splitting of requests Co-authored-by: Jakub Rybak * Parse and pass request body Co-authored-by: Jakub Rybak * Format files Co-authored-by: Jakub Rybak * Format definition.py to follow code style Co-authored-by: Jakub Rybak --------- Co-authored-by: Jakub Rybak --- httpie/cli/definition.py | 543 ++++++++++++++++++++------------------- httpie/core.py | 154 +++++------ httpie/http_parser.py | 83 +++--- 3 files changed, 414 insertions(+), 366 deletions(-) diff --git a/httpie/cli/definition.py b/httpie/cli/definition.py index 444b4a75..78c72467 100644 --- a/httpie/cli/definition.py +++ b/httpie/cli/definition.py @@ -5,20 +5,39 @@ import textwrap from argparse import FileType from httpie import __doc__, __version__ -from httpie.cli.argtypes import (KeyValueArgType, SessionNameValidator, - SSLCredentials, readable_file_arg, - response_charset_type, response_mime_type) -from httpie.cli.constants import (BASE_OUTPUT_OPTIONS, DEFAULT_FORMAT_OPTIONS, - OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY, - OUT_RESP_HEAD, OUT_RESP_META, OUTPUT_OPTIONS, - OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP, - PRETTY_STDOUT_TTY_ONLY, - SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY, - SORTED_FORMAT_OPTIONS_STRING, - UNSORTED_FORMAT_OPTIONS_STRING, RequestType) +from httpie.cli.argtypes import ( + KeyValueArgType, + SessionNameValidator, + SSLCredentials, + readable_file_arg, + response_charset_type, + response_mime_type, +) +from httpie.cli.constants import ( + BASE_OUTPUT_OPTIONS, + DEFAULT_FORMAT_OPTIONS, + OUT_REQ_BODY, + OUT_REQ_HEAD, + OUT_RESP_BODY, + OUT_RESP_HEAD, + OUT_RESP_META, + OUTPUT_OPTIONS, + OUTPUT_OPTIONS_DEFAULT, + PRETTY_MAP, + PRETTY_STDOUT_TTY_ONLY, + SEPARATOR_GROUP_ALL_ITEMS, + SEPARATOR_PROXY, + SORTED_FORMAT_OPTIONS_STRING, + UNSORTED_FORMAT_OPTIONS_STRING, + RequestType, +) from httpie.cli.options import ParserSpec, Qualifiers, to_argparse -from httpie.output.formatters.colors import (AUTO_STYLE, DEFAULT_STYLE, BUNDLED_STYLES, - get_available_styles) +from httpie.output.formatters.colors import ( + AUTO_STYLE, + DEFAULT_STYLE, + BUNDLED_STYLES, + get_available_styles, +) from httpie.plugins.builtin import BuiltinAuthPlugin from httpie.plugins.registry import plugin_manager from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING @@ -26,12 +45,12 @@ from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_S # Man pages are static (built when making a release). # We use this check to not include generated, system-specific information there (e.g., default --ciphers). -IS_MAN_PAGE = bool(os.environ.get('HTTPIE_BUILDING_MAN_PAGES')) +IS_MAN_PAGE = bool(os.environ.get("HTTPIE_BUILDING_MAN_PAGES")) options = ParserSpec( - 'http', - description=f'{__doc__.strip()} ', + "http", + description=f"{__doc__.strip()} ", epilog=""" For every --OPTION there is also a --no-OPTION that reverts OPTION to its default value. @@ -39,7 +58,7 @@ options = ParserSpec( Suggestions and bug reports are greatly appreciated: https://github.com/httpie/cli/issues """, - source_file=__file__ + source_file=__file__, ) ####################################################################### @@ -47,7 +66,7 @@ options = ParserSpec( ####################################################################### positional_arguments = options.add_group( - 'Positional arguments', + "Positional arguments", description=""" These arguments come after any flags and in the order they are listed here. Only URL is required. @@ -55,11 +74,11 @@ positional_arguments = options.add_group( ) positional_arguments.add_argument( - dest='method', - metavar='METHOD', + dest="method", + metavar="METHOD", nargs=Qualifiers.OPTIONAL, default=None, - short_help='The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).', + short_help="The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).", help=""" The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...). @@ -72,9 +91,9 @@ positional_arguments.add_argument( """, ) positional_arguments.add_argument( - dest='url', - metavar='URL', - short_help='The request URL.', + dest="url", + metavar="URL", + short_help="The request URL.", help=""" The request URL. Scheme defaults to 'http://' if the URL does not include one. (You can override this with: --default-scheme=http/https) @@ -87,21 +106,29 @@ positional_arguments.add_argument( """, ) positional_arguments.add_argument( - dest='request_items', - metavar='REQUEST_ITEM', + dest="request_items", + metavar="REQUEST_ITEM", nargs=Qualifiers.ZERO_OR_MORE, default=None, type=KeyValueArgType(*SEPARATOR_GROUP_ALL_ITEMS), short_help=( - 'HTTPie’s request items syntax for specifying HTTP headers, JSON/Form' - 'data, files, and URL parameters.' + "HTTPie’s request items syntax for specifying HTTP headers, JSON/Form" + "data, files, and URL parameters." ), nested_options=[ - ('HTTP Headers', 'Name:Value', 'Arbitrary HTTP header, e.g X-API-Token:123'), - ('URL Parameters', 'name==value', 'Querystring parameter to the URL, e.g limit==50'), - ('Data Fields', 'field=value', 'Data fields to be serialized as JSON (default) or Form Data (with --form)'), - ('Raw JSON Fields', 'field:=json', 'Data field for real JSON types.'), - ('File upload Fields', 'field@/dir/file', 'Path field for uploading a file.'), + ("HTTP Headers", "Name:Value", "Arbitrary HTTP header, e.g X-API-Token:123"), + ( + "URL Parameters", + "name==value", + "Querystring parameter to the URL, e.g limit==50", + ), + ( + "Data Fields", + "field=value", + "Data fields to be serialized as JSON (default) or Form Data (with --form)", + ), + ("Raw JSON Fields", "field:=json", "Data field for real JSON types."), + ("File upload Fields", "field@/dir/file", "Path field for uploading a file."), ], help=r""" Optional key-value pairs to be included in the request. The separator used @@ -148,15 +175,15 @@ positional_arguments.add_argument( # Content type. ####################################################################### -content_types = options.add_group('Predefined content types') +content_types = options.add_group("Predefined content types") content_types.add_argument( - '--json', - '-j', - action='store_const', + "--json", + "-j", + action="store_const", const=RequestType.JSON, - dest='request_type', - short_help='(default) Serialize data items from the command line as a JSON object.', + dest="request_type", + short_help="(default) Serialize data items from the command line as a JSON object.", help=""" (default) Data items from the command line are serialized as a JSON object. The Content-Type and Accept headers are set to application/json @@ -165,12 +192,12 @@ content_types.add_argument( """, ) content_types.add_argument( - '--form', - '-f', - action='store_const', + "--form", + "-f", + action="store_const", const=RequestType.FORM, - dest='request_type', - short_help='Serialize data items from the command line as form field data.', + dest="request_type", + short_help="Serialize data items from the command line as form field data.", help=""" Data items from the command line are serialized as form fields. @@ -181,25 +208,25 @@ content_types.add_argument( """, ) content_types.add_argument( - '--multipart', - action='store_const', + "--multipart", + action="store_const", const=RequestType.MULTIPART, - dest='request_type', + dest="request_type", short_help=( - 'Similar to --form, but always sends a multipart/form-data ' - 'request (i.e., even without files).' - ) + "Similar to --form, but always sends a multipart/form-data " + "request (i.e., even without files)." + ), ) content_types.add_argument( - '--boundary', + "--boundary", short_help=( - 'Specify a custom boundary string for multipart/form-data requests. ' - 'Only has effect only together with --form.' - ) + "Specify a custom boundary string for multipart/form-data requests. " + "Only has effect only together with --form." + ), ) content_types.add_argument( - '--raw', - short_help='Pass raw request data without extra processing.', + "--raw", + short_help="Pass raw request data without extra processing.", help=""" This option allows you to pass raw request data without extra processing (as opposed to the structured request items syntax): @@ -234,14 +261,14 @@ content_types.add_argument( # Content processing. ####################################################################### -processing_options = options.add_group('Content processing options') +processing_options = options.add_group("Content processing options") processing_options.add_argument( - '--compress', - '-x', - action='count', + "--compress", + "-x", + action="count", default=0, - short_help='Compress the content with Deflate algorithm.', + short_help="Compress the content with Deflate algorithm.", help=""" Content compressed (encoded) with Deflate algorithm. The Content-Encoding header is set to deflate. @@ -265,9 +292,9 @@ def format_style_help(available_styles, *, isolation_mode: bool = False): {available_styles} """ if isolation_mode: - text += '\n\n' - text += 'For finding out all available styles in your system, try:\n\n' - text += ' $ http --style\n' + text += "\n\n" + text += "For finding out all available styles in your system, try:\n\n" + text += " $ http --style\n" text += textwrap.dedent(""" The "{auto_style}" style follows your terminal's ANSI color styles. For non-{auto_style} styles to work properly, please make sure that the @@ -278,9 +305,8 @@ def format_style_help(available_styles, *, isolation_mode: bool = False): if isolation_mode: available_styles = sorted(BUNDLED_STYLES) - available_styles_text = '\n'.join( - f' {line.strip()}' - for line in textwrap.wrap(', '.join(available_styles), 60) + available_styles_text = "\n".join( + f" {line.strip()}" for line in textwrap.wrap(", ".join(available_styles), 60) ).strip() return text.format( default=DEFAULT_STYLE, @@ -290,24 +316,24 @@ def format_style_help(available_styles, *, isolation_mode: bool = False): _sorted_kwargs = { - 'action': 'append_const', - 'const': SORTED_FORMAT_OPTIONS_STRING, - 'dest': 'format_options', + "action": "append_const", + "const": SORTED_FORMAT_OPTIONS_STRING, + "dest": "format_options", } _unsorted_kwargs = { - 'action': 'append_const', - 'const': UNSORTED_FORMAT_OPTIONS_STRING, - 'dest': 'format_options', + "action": "append_const", + "const": UNSORTED_FORMAT_OPTIONS_STRING, + "dest": "format_options", } -output_processing = options.add_group('Output processing') +output_processing = options.add_group("Output processing") output_processing.add_argument( - '--pretty', - dest='prettify', + "--pretty", + dest="prettify", default=PRETTY_STDOUT_TTY_ONLY, choices=sorted(PRETTY_MAP.keys()), - short_help='Control the processing of console outputs.', + short_help="Control the processing of console outputs.", help=""" Controls output processing. The value can be "none" to not prettify the output (default for redirected output), "all" to apply both colors @@ -316,12 +342,12 @@ output_processing.add_argument( """, ) output_processing.add_argument( - '--style', - '-s', - dest='style', - metavar='STYLE', + "--style", + "-s", + dest="style", + metavar="STYLE", default=DEFAULT_STYLE, - action='lazy_choices', + action="lazy_choices", getter=get_available_styles, short_help=f'Output coloring style (default is "{DEFAULT_STYLE}").', help_formatter=format_style_help, @@ -330,16 +356,16 @@ output_processing.add_argument( # The closest approx. of the documented resetting to default via --no-