This commit is contained in:
Jakub Roztocil 2020-09-25 13:44:28 +02:00
parent c431ed7728
commit d12af4a569
8 changed files with 140 additions and 47 deletions

View File

@ -15,12 +15,10 @@ from httpie.cli.argtypes import (
parse_format_options, parse_format_options,
) )
from httpie.cli.constants import ( from httpie.cli.constants import (
DEFAULT_FORMAT_OPTIONS, HTTP_GET, HTTP_POST, OUTPUT_OPTIONS, HTTP_GET, HTTP_POST, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT,
OUTPUT_OPTIONS_DEFAULT, OUTPUT_OPTIONS_DEFAULT_OFFLINE, OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED,
OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED, OUT_RESP_BODY, PRETTY_MAP, OUT_RESP_BODY, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, SEPARATOR_CREDENTIALS,
PRETTY_STDOUT_TTY_ONLY, SEPARATOR_CREDENTIALS, SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_GROUP_DATA_ITEMS, URL_SCHEME_RE,
SEPARATOR_GROUP_DATA_ITEMS, URL_SCHEME_RE,
OUTPUT_OPTIONS_DEFAULT_OFFLINE,
) )
from httpie.cli.exceptions import ParseError from httpie.cli.exceptions import ParseError
from httpie.cli.requestitems import RequestItems from httpie.cli.requestitems import RequestItems
@ -165,7 +163,8 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
if self.args.quiet: if self.args.quiet:
self.env.stderr = self.env.devnull self.env.stderr = self.env.devnull
if not (self.args.output_file_specified and not self.args.download): if not (
self.args.output_file_specified and not self.args.download):
self.env.stdout = self.env.devnull self.env.stdout = self.env.devnull
def _process_auth(self): def _process_auth(self):
@ -193,8 +192,8 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
plugin = plugin_manager.get_auth_plugin(self.args.auth_type)() plugin = plugin_manager.get_auth_plugin(self.args.auth_type)()
if (not self.args.ignore_netrc if (not self.args.ignore_netrc
and self.args.auth is None and self.args.auth is None
and plugin.netrc_parse): and plugin.netrc_parse):
# Only host needed, so its OK URL not finalized. # Only host needed, so its OK URL not finalized.
netrc_credentials = get_netrc_auth(self.args.url) netrc_credentials = get_netrc_auth(self.args.url)
if netrc_credentials: if netrc_credentials:
@ -222,7 +221,7 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
credentials = parse_auth(self.args.auth) credentials = parse_auth(self.args.auth)
if (not credentials.has_password() if (not credentials.has_password()
and plugin.prompt_password): and plugin.prompt_password):
if self.args.ignore_stdin: if self.args.ignore_stdin:
# Non-tty stdin read by now # Non-tty stdin read by now
self.error( self.error(
@ -276,7 +275,13 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
'data (key=value) cannot be mixed. Pass ' 'data (key=value) cannot be mixed. Pass '
'--ignore-stdin to let key/value take priority. ' '--ignore-stdin to let key/value take priority. '
'See https://httpie.org/doc#scripting for details.') 'See https://httpie.org/doc#scripting for details.')
self.args.data = getattr(fd, 'buffer', fd).read() buffer = getattr(fd, 'buffer', fd)
# if fd is self.env.stdin and not self.args.chunked:
# self.args.data = buffer.read()
# else:
# self.args.data = buffer
# print(type(fd))
self.args.data = buffer
def _guess_method(self): def _guess_method(self):
"""Set `args.method` if not specified to either POST or GET """Set `args.method` if not specified to either POST or GET
@ -312,8 +317,8 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
has_data = ( has_data = (
self.has_stdin_data self.has_stdin_data
or any( or any(
item.sep in SEPARATOR_GROUP_DATA_ITEMS item.sep in SEPARATOR_GROUP_DATA_ITEMS
for item in self.args.request_items) for item in self.args.request_items)
) )
self.args.method = HTTP_POST if has_data else HTTP_GET self.args.method = HTTP_POST if has_data else HTTP_GET
@ -416,11 +421,12 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
if self.args.download_resume: if self.args.download_resume:
self.error('--continue only works with --download') self.error('--continue only works with --download')
if self.args.download_resume and not ( if self.args.download_resume and not (
self.args.download and self.args.output_file): self.args.download and self.args.output_file):
self.error('--continue requires --output to be specified') self.error('--continue requires --output to be specified')
def _process_format_options(self): def _process_format_options(self):
parsed_options = PARSED_DEFAULT_FORMAT_OPTIONS parsed_options = PARSED_DEFAULT_FORMAT_OPTIONS
for options_group in self.args.format_options or []: for options_group in self.args.format_options or []:
parsed_options = parse_format_options(options_group, defaults=parsed_options) parsed_options = parse_format_options(options_group,
defaults=parsed_options)
self.args.format_options = parsed_options self.args.format_options = parsed_options

View File

@ -657,6 +657,15 @@ network.add_argument(
''' '''
) )
network.add_argument(
'--chunked',
default=False,
action='store_true',
help="""
"""
)
####################################################################### #######################################################################
# SSL # SSL
####################################################################### #######################################################################

View File

@ -5,7 +5,7 @@ import sys
import zlib import zlib
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Iterable, Union from typing import Callable, Iterable, Union
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
import requests import requests
@ -16,7 +16,10 @@ from httpie.cli.dicts import RequestHeadersDict
from httpie.plugins.registry import plugin_manager from httpie.plugins.registry import plugin_manager
from httpie.sessions import get_httpie_session from httpie.sessions import get_httpie_session
from httpie.ssl import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieHTTPSAdapter from httpie.ssl import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieHTTPSAdapter
from httpie.uploads import get_multipart_data_and_content_type from httpie.uploads import (
wrap_request_data,
get_multipart_data_and_content_type,
)
from httpie.utils import get_expired_cookies, repr_dict from httpie.utils import get_expired_cookies, repr_dict
@ -31,6 +34,7 @@ DEFAULT_UA = f'HTTPie/{__version__}'
def collect_messages( def collect_messages(
args: argparse.Namespace, args: argparse.Namespace,
config_dir: Path, config_dir: Path,
body_chunk_sent_callback: Callable[[bytes], None]=None,
) -> Iterable[Union[requests.PreparedRequest, requests.Response]]: ) -> Iterable[Union[requests.PreparedRequest, requests.Response]]:
httpie_session = None httpie_session = None
httpie_session_headers = None httpie_session_headers = None
@ -46,6 +50,7 @@ def collect_messages(
request_kwargs = make_request_kwargs( request_kwargs = make_request_kwargs(
args=args, args=args,
base_headers=httpie_session_headers, base_headers=httpie_session_headers,
callback=body_chunk_sent_callback
) )
send_kwargs = make_send_kwargs(args) send_kwargs = make_send_kwargs(args)
send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args) send_kwargs_mergeable_from_env = make_send_kwargs_mergeable_from_env(args)
@ -251,7 +256,8 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
def make_request_kwargs( def make_request_kwargs(
args: argparse.Namespace, args: argparse.Namespace,
base_headers: RequestHeadersDict = None base_headers: RequestHeadersDict = None,
callback=lambda chunk: chunk
) -> dict: ) -> dict:
""" """
Translate our `args` into `requests.Request` keyword arguments. Translate our `args` into `requests.Request` keyword arguments.
@ -289,7 +295,7 @@ def make_request_kwargs(
'method': args.method.lower(), 'method': args.method.lower(),
'url': args.url, 'url': args.url,
'headers': headers, 'headers': headers,
'data': data, 'data': wrap_request_data(data, callback=callback),
'auth': args.auth, 'auth': args.auth,
'params': args.params, 'params': args.params,
'files': files, 'files': files,

View File

@ -9,6 +9,10 @@ from pygments import __version__ as pygments_version
from requests import __version__ as requests_version from requests import __version__ as requests_version
from httpie import __version__ as httpie_version from httpie import __version__ as httpie_version
from httpie.cli.constants import (
OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY,
OUT_RESP_HEAD,
)
from httpie.client import collect_messages from httpie.client import collect_messages
from httpie.context import Environment from httpie.context import Environment
from httpie.downloads import Downloader from httpie.downloads import Downloader
@ -111,6 +115,22 @@ def main(
return exit_status return exit_status
def get_output_options(
args: argparse.Namespace,
message: Union[requests.PreparedRequest, requests.Response]
) -> dict:
return {
requests.PreparedRequest: {
'with_headers': OUT_REQ_HEAD in args.output_options,
'with_body': OUT_REQ_BODY in args.output_options,
},
requests.Response: {
'with_headers': OUT_RESP_HEAD in args.output_options,
'with_body': OUT_RESP_BODY in args.output_options,
},
}[type(message)]
def program( def program(
args: argparse.Namespace, args: argparse.Namespace,
env: Environment, env: Environment,
@ -135,15 +155,36 @@ def program(
initial_request = None initial_request = None
final_response = None final_response = None
for message in collect_messages(args, env.config.directory): def upload_callback(chunk):
write_message( print('GOT', chunk)
requests_message=message,
env=env, messages = collect_messages(
args=args, args=args,
) config_dir=env.config.directory,
if isinstance(message, requests.PreparedRequest): body_chunk_sent_callback=upload_callback
)
for message in messages:
is_request = isinstance(message, requests.PreparedRequest)
output_options = get_output_options(args=args, message=message)
if not is_request or not output_options['with_body']:
write_message(
requests_message=message,
env=env,
args=args,
**output_options,
)
if is_request:
if not initial_request: if not initial_request:
initial_request = message initial_request = message
if 0and not args.offline:
output_options['with_body'] = False
write_message(
requests_message=message,
env=env,
args=args,
**output_options,
)
else: else:
final_response = message final_response = message
if args.check_status or downloader: if args.check_status or downloader:

View File

@ -23,6 +23,13 @@ LARGE_UPLOAD_SUPPRESSED_NOTICE = (
) )
def might_suppress(chunk):
if isinstance(chunk, MultipartEncoder):
raise LargeUploadSuppressedError()
# if isinstance(io.IOBase):
# pass
class DataSuppressedError(Exception): class DataSuppressedError(Exception):
message = None message = None

View File

@ -1,6 +1,6 @@
import argparse import argparse
import errno import errno
from typing import Union, IO, TextIO, Tuple, Type from typing import IO, TextIO, Tuple, Type, Union
import requests import requests
@ -8,12 +8,7 @@ from httpie.context import Environment
from httpie.models import HTTPRequest, HTTPResponse from httpie.models import HTTPRequest, HTTPResponse
from httpie.output.processing import Conversion, Formatting from httpie.output.processing import Conversion, Formatting
from httpie.output.streams import ( from httpie.output.streams import (
RawStream, PrettyStream, BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream,
BufferedPrettyStream, EncodedStream,
BaseStream,
)
from httpie.cli.constants import (
OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY, OUT_RESP_HEAD,
) )
@ -21,26 +16,18 @@ def write_message(
requests_message: Union[requests.PreparedRequest, requests.Response], requests_message: Union[requests.PreparedRequest, requests.Response],
env: Environment, env: Environment,
args: argparse.Namespace, args: argparse.Namespace,
with_headers=False,
with_body=False,
): ):
output_options_by_message_type = { if not (with_body or with_headers):
requests.PreparedRequest: {
'with_headers': OUT_REQ_HEAD in args.output_options,
'with_body': OUT_REQ_BODY in args.output_options,
},
requests.Response: {
'with_headers': OUT_RESP_HEAD in args.output_options,
'with_body': OUT_RESP_BODY in args.output_options,
},
}
output_options = output_options_by_message_type[type(requests_message)]
if not any(output_options.values()):
return return
write_stream_kwargs = { write_stream_kwargs = {
'stream': build_output_stream_for_message( 'stream': build_output_stream_for_message(
args=args, args=args,
env=env, env=env,
requests_message=requests_message, requests_message=requests_message,
**output_options, with_body=with_body,
with_headers=with_headers,
), ),
# NOTE: `env.stdout` will in fact be `stderr` with `--download` # NOTE: `env.stdout` will in fact be `stderr` with `--download`
'outfile': env.stdout, 'outfile': env.stdout,

View File

@ -1,5 +1,6 @@
from typing import Tuple, Union from typing import Tuple, Union
from requests.utils import super_len
from requests_toolbelt import MultipartEncoder from requests_toolbelt import MultipartEncoder
from httpie.cli.dicts import RequestDataDict, RequestFilesDict from httpie.cli.dicts import RequestDataDict, RequestFilesDict
@ -28,5 +29,40 @@ def get_multipart_data_and_content_type(
else: else:
content_type = encoder.content_type content_type = encoder.content_type
data = encoder.to_string() if encoder.len < UPLOAD_BUFFER else encoder data = encoder.to_string() if 0 and encoder.len < UPLOAD_BUFFER else encoder
return data, content_type return data, content_type
class Stdin:
def __init__(self, stdin, callback):
self.callback = callback
self.stdin = stdin
def __iter__(self):
for chunk in self.stdin:
print("__iter__() =>", chunk)
self.callback(chunk)
yield chunk
@classmethod
def is_stdin(cls, obj):
return super_len(obj) == 0
def wrap_request_data(data, callback=lambda chunk: print('chunk', chunk)):
if hasattr(data, 'read'):
if Stdin.is_stdin(data):
data = Stdin(data, callback=callback)
else:
orig_read = data.read
def new_read(*args):
val = orig_read(*args)
print('read() =>', val)
callback(callback)
return val
data.read = new_read
return data

View File

@ -1,5 +1,6 @@
"""High-level tests.""" """High-level tests."""
import io import io
import sys
from unittest import mock from unittest import mock
import pytest import pytest