Merge branch 'mgsloan-allow-closed-stdin'

This commit is contained in:
Jakub Roztocil 2019-08-29 13:40:00 +02:00
commit d998013655
6 changed files with 24 additions and 9 deletions

View File

@ -7,7 +7,6 @@ env:
global: global:
- NEWEST_PYTHON=3.7 - NEWEST_PYTHON=3.7
python: python:
- 3.5
- 3.6 - 3.6
# - 3.7 # is done in the matrix below as described in travis-ci/travis-ci#9069 # - 3.7 # is done in the matrix below as described in travis-ci/travis-ci#9069
# pypy3 currently fails because of a Flask issue # pypy3 currently fails because of a Flask issue

View File

@ -14,6 +14,7 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
* Added ``--max-headers`` to allow setting the max header limit. * Added ``--max-headers`` to allow setting the max header limit.
* Added ``--compress``. * Added ``--compress``.
* Added ``https`` alias command with ``https://`` as the default scheme. * Added ``https`` alias command with ``https://`` as the default scheme.
* Fixed an exception when ``stdin`` was a closed fd.
`1.0.3`_ (2019-08-26) `1.0.3`_ (2019-08-26)

View File

@ -126,7 +126,7 @@ and always provides the latest version) is to use `pip`_:
Python version Python version
-------------- --------------
Starting with version 2.0.0 (currently under development) Python 3.5+ is required. Starting with version 2.0.0 (currently under development) Python 3.6+ is required.
Unstable version Unstable version

View File

@ -1,4 +1,7 @@
import sys import sys
from typing import Union, IO, Optional
try: try:
import curses import curses
except ImportError: except ImportError:
@ -22,8 +25,8 @@ class Environment(object):
""" """
is_windows = is_windows is_windows = is_windows
config_dir = DEFAULT_CONFIG_DIR config_dir = DEFAULT_CONFIG_DIR
stdin = sys.stdin stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
stdin_isatty = stdin.isatty() stdin_isatty = stdin.isatty() if stdin else False
stdin_encoding = None stdin_encoding = None
stdout = sys.stdout stdout = sys.stdout
stdout_isatty = stdout.isatty() stdout_isatty = stdout.isatty()
@ -61,7 +64,7 @@ class Environment(object):
self.__dict__.update(**kwargs) self.__dict__.update(**kwargs)
# Keyword arguments > stream.encoding > default utf8 # Keyword arguments > stream.encoding > default utf8
if self.stdin_encoding is None: if self.stdin and self.stdin_encoding is None:
self.stdin_encoding = getattr( self.stdin_encoding = getattr(
self.stdin, 'encoding', None) or 'utf8' self.stdin, 'encoding', None) or 'utf8'
if self.stdout_encoding is None: if self.stdout_encoding is None:

View File

@ -134,6 +134,7 @@ class HTTPieArgumentParser(ArgumentParser):
super(HTTPieArgumentParser, self).__init__(*args, **kwargs) super(HTTPieArgumentParser, self).__init__(*args, **kwargs)
self.env = None self.env = None
self.args = None self.args = None
self.has_stdin_data = False
# noinspection PyMethodOverriding # noinspection PyMethodOverriding
def parse_args(self, env, program_name='http', args=None, namespace=None): def parse_args(self, env, program_name='http', args=None, namespace=None):
@ -144,6 +145,12 @@ class HTTPieArgumentParser(ArgumentParser):
if self.args.debug: if self.args.debug:
self.args.traceback = True self.args.traceback = True
self.has_stdin_data = (
self.env.stdin
and not self.args.ignore_stdin
and not self.env.stdin_isatty
)
# Arguments processing and environment setup. # Arguments processing and environment setup.
self._apply_no_options(no_options) self._apply_no_options(no_options)
self._validate_download_options() self._validate_download_options()
@ -152,7 +159,8 @@ class HTTPieArgumentParser(ArgumentParser):
self._process_pretty_options() self._process_pretty_options()
self._guess_method() self._guess_method()
self._parse_items() self._parse_items()
if not self.args.ignore_stdin and not env.stdin_isatty:
if self.has_stdin_data:
self._body_from_file(self.env.stdin) self._body_from_file(self.env.stdin)
if not URL_SCHEME_RE.match(self.args.url): if not URL_SCHEME_RE.match(self.args.url):
if os.path.basename(program_name) == 'https': if os.path.basename(program_name) == 'https':
@ -320,7 +328,7 @@ class HTTPieArgumentParser(ArgumentParser):
if self.args.method is None: if self.args.method is None:
# Invoked as `http URL'. # Invoked as `http URL'.
assert not self.args.items assert not self.args.items
if not self.args.ignore_stdin and not self.env.stdin_isatty: if self.has_stdin_data:
self.args.method = HTTP_POST self.args.method = HTTP_POST
else: else:
self.args.method = HTTP_GET self.args.method = HTTP_GET
@ -344,7 +352,7 @@ class HTTPieArgumentParser(ArgumentParser):
self.args.url = self.args.method self.args.url = self.args.method
# Infer the method # Infer the method
has_data = ( has_data = (
(not self.args.ignore_stdin and not self.env.stdin_isatty) self.has_stdin_data
or any( or any(
item.sep in SEP_GROUP_DATA_ITEMS item.sep in SEP_GROUP_DATA_ITEMS
for item in self.args.items for item in self.args.items

View File

@ -310,7 +310,7 @@ class TestNoOptions:
assert 'GET /get HTTP/1.1' not in r assert 'GET /get HTTP/1.1' not in r
class TestIgnoreStdin: class TestStdin:
def test_ignore_stdin(self, httpbin): def test_ignore_stdin(self, httpbin):
with open(FILE_PATH) as f: with open(FILE_PATH) as f:
@ -327,6 +327,10 @@ class TestIgnoreStdin:
assert r.exit_status == ExitStatus.ERROR assert r.exit_status == ExitStatus.ERROR
assert 'because --ignore-stdin' in r.stderr assert 'because --ignore-stdin' in r.stderr
def test_stdin_closed(self, httpbin):
r = http(httpbin + '/get', env=MockEnvironment(stdin=None))
assert HTTP_OK in r
class TestSchemes: class TestSchemes: