From 1aa1366f9957601344302f4de194ad4e7bf683ae Mon Sep 17 00:00:00 2001 From: Jakub Roztocil Date: Sat, 15 Aug 2020 15:25:05 +0200 Subject: [PATCH] Finish `--quiet` --- .github/FUNDING.yml | 2 +- CHANGELOG.rst | 2 +- README.rst | 58 +++++++++++--------- httpie/context.py | 9 ++-- tests/test_downloads.py | 57 +++----------------- tests/test_output.py | 114 ++++++++++++++++++++++------------------ 6 files changed, 112 insertions(+), 130 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 6a7b993b..67c1a71e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username -custom: https://paypal.me/roztocil +custom: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6877d206..6ceb2923 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,8 +10,8 @@ This project adheres to `Semantic Versioning `_. ------------------------- * Added support for combining cookies specified on the CLI and in a session file (`#932`_). * Added out of the box SOCKS support with no extra installation (`#904`_). +* Added ``--quiet, -q`` flag to enforce silent behaviour. * Removed Tox testing entirely (`#943`_). -* Added a ``--quiet`` flag to enforce silent behaviour. `2.2.0`_ (2020-06-18) diff --git a/README.rst b/README.rst index 13cc71b9..ca68f756 100644 --- a/README.rst +++ b/README.rst @@ -1156,12 +1156,34 @@ be printed via several options: ``--verbose, -v`` Print the whole HTTP exchange (request and response). This option also enables ``--all`` (see below). ``--print, -p`` Selects parts of the HTTP exchange. -``--quiet, -q`` Doesn't print anything. Overrides other output flags. +``--quiet, -q`` Don't print anything to ``stdout`` and ``stderr``. ================= ===================================================== -If ``--quiet`` is used in conjuction with ``--output`` the flag is ignored -and ``stdout`` is still redirected. If ``--quiet`` is used with ``--download`` -file is still downloaded as usual but ``stdout`` and ``stdin`` are redirected -to ``devnull``. + + +What parts of the HTTP exchange should be printed +------------------------------------------------- + +All the other `output options`_ are under the hood just shortcuts for +the more powerful ``--print, -p``. It accepts a string of characters each +of which represents a specific part of the HTTP exchange: + +========== ================== +Character Stands for +========== ================== +``H`` request headers +``B`` request body +``h`` response headers +``b`` response body +========== ================== + +Print request and response headers: + +.. code-block:: bash + + $ http --print=Hh PUT httpbin.org/put hello=world + +Verbose output +-------------- ``--verbose`` can often be useful for debugging the request and generating documentation examples: @@ -1192,28 +1214,12 @@ documentation examples: […] } +Quiet output +------------ -What parts of the HTTP exchange should be printed -------------------------------------------------- - -All the other `output options`_ are under the hood just shortcuts for -the more powerful ``--print, -p``. It accepts a string of characters each -of which represents a specific part of the HTTP exchange: - -========== ================== -Character Stands for -========== ================== -``H`` request headers -``B`` request body -``h`` response headers -``b`` response body -========== ================== - -Print request and response headers: - -.. code-block:: bash - - $ http --print=Hh PUT httpbin.org/put hello=world +``--quiet`` redirects all output that would otherwise go to ``stdout`` +and ``stderr`` (except for error messages) to ``/dev/null``. +This doesn’t affect output to a file via ``--output`` or ``--download``. Viewing intermediary requests/responses diff --git a/httpie/context.py b/httpie/context.py index 55b66f4c..86330fb2 100644 --- a/httpie/context.py +++ b/httpie/context.py @@ -35,7 +35,6 @@ class Environment: stdout_encoding: str = None stderr: IO = sys.stderr stderr_isatty: bool = stderr.isatty() - _devnull = None colors = 256 program_name: str = 'http' if not is_windows: @@ -58,7 +57,7 @@ class Environment: ) del colorama - def __init__(self, **kwargs): + def __init__(self, devnull=None, **kwargs): """ Use keyword arguments to overwrite any of the class attributes for this instance. @@ -67,6 +66,10 @@ class Environment: assert all(hasattr(type(self), attr) for attr in kwargs.keys()) self.__dict__.update(**kwargs) + # The original STDERR unaffected by --quiet’ing. + self._orig_stderr = self.stderr + self._devnull = devnull + # Keyword arguments > stream.encoding > default utf8 if self.stdin and self.stdin_encoding is None: self.stdin_encoding = getattr( @@ -122,4 +125,4 @@ class Environment: def log_error(self, msg, level='error'): assert level in ['error', 'warning'] - self.stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n') + self._orig_stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n') diff --git a/tests/test_downloads.py b/tests/test_downloads.py index 494ea65a..381bd030 100644 --- a/tests/test_downloads.py +++ b/tests/test_downloads.py @@ -183,52 +183,11 @@ class TestDownloads: # Redirect from `/redirect/1` to `/get`. expected_filename = '1.json' orig_cwd = os.getcwd() - os.chdir(tempfile.mkdtemp(prefix='httpie_download_test_')) - try: - assert os.listdir('.') == [] - http('--download', httpbin.url + '/redirect/1') - assert os.listdir('.') == [expected_filename] - finally: - os.chdir(orig_cwd) - - def test_download_with_quiet_flag(self, httpbin): - robots_txt = '/robots.txt' - expected_body = requests.get(httpbin + robots_txt).text - expected_filename = 'robots.txt' - env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) - orig_cwd = os.getcwd() - os.chdir(tempfile.mkdtemp(prefix='httpie_download_quiet_test_')) - try: - assert os.listdir('.') == [] - r = http('--quiet', '--download', httpbin + robots_txt, env=env) - assert os.listdir('.') == [expected_filename] - assert r.stderr == '' - assert env.devnull == env.stderr - assert env.devnull == env.stdout - with open(expected_filename, 'r') as f: - assert f.read() == expected_body - finally: - os.chdir(orig_cwd) - - def test_download_with_quiet_and_output_flag(self, httpbin): - robots_txt = '/robots.txt' - expected_body = requests.get(httpbin + robots_txt).text - expected_filename = 'test.txt' - env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) - orig_cwd = os.getcwd() - os.chdir(tempfile.mkdtemp(prefix='httpie_download_quiet_test_')) - try: - assert os.listdir('.') == [] - r = http('--quiet', - '--download', - '--output=' + expected_filename, - httpbin + robots_txt, - env=env) - assert os.listdir('.') == [expected_filename] - assert r.stderr == '' - assert env.devnull == env.stderr - assert env.devnull == env.stdout - with open(expected_filename, 'r') as f: - assert f.read() == expected_body - finally: - os.chdir(orig_cwd) + with tempfile.TemporaryDirectory() as tmp_dirname: + os.chdir(tmp_dirname) + try: + assert os.listdir('.') == [] + http('--download', httpbin.url + '/redirect/1') + assert os.listdir('.') == [expected_filename] + finally: + os.chdir(orig_cwd) diff --git a/tests/test_output.py b/tests/test_output.py index 52b6b724..0f2b593d 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -1,4 +1,6 @@ import argparse +from pathlib import Path + import mock import json import os @@ -40,69 +42,81 @@ class TestQuietFlag: @pytest.mark.parametrize('argument_name', ['--quiet', '-q']) def test_quiet(self, httpbin, argument_name): - env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) + env = MockEnvironment( + stdin_isatty=True, + stdout_isatty=True, + devnull=io.BytesIO() + ) r = http(argument_name, 'GET', httpbin.url + '/get', env=env) - assert env.stdout == env.devnull - assert env.stderr == env.devnull - assert r.stderr == '' - assert str(r) == '' - - @pytest.mark.parametrize('argument_name', ['--quiet', '-q']) - def test_quiet_correct_message(self, httpbin, argument_name): - sim_devnull = io.BytesIO() - env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) - env.devnull = sim_devnull - r = http(argument_name, 'GET', httpbin.url + '/get', env=env) - assert env.stdout == env.devnull - assert env.stderr == env.devnull - assert r.stderr == '' + assert env.stdout is env.devnull + assert env.stderr is env.devnull assert HTTP_OK in r.devnull - assert str(r) == '' + assert r == '' + assert r.stderr == '' @mock.patch('httpie.cli.argtypes.AuthCredentials._getpass', new=lambda self, prompt: 'password') - def test_quiet_password_prompt(self, httpbin): - """ Tests whether httpie still prompts for password when request - requires authetication and only username is provided""" - env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) - env.devnull = io.BytesIO() - r = http('--quiet', '--auth', 'user', 'GET', httpbin.url + '/basic-auth/user/password', env=env) + def test_quiet_with_password_prompt(self, httpbin): + """ + Tests whether httpie still prompts for a password when request + requires authentication and only username is provided + + """ + env = MockEnvironment( + stdin_isatty=True, + stdout_isatty=True, + devnull=io.BytesIO() + ) + r = http( + '--quiet', '--auth', 'user', 'GET', + httpbin.url + '/basic-auth/user/password', + env=env + ) + assert env.stdout is env.devnull + assert env.stderr is env.devnull assert HTTP_OK in r.devnull - assert env.stdout == env.devnull - assert env.stderr == env.devnull - assert str(r) == '' + assert r == '' assert r.stderr == '' @pytest.mark.parametrize('argument_name', ['-h', '-b', '-v', '-p=hH']) - def test_quiet_flag_overrides_other_output_options(self, httpbin, argument_name): + def test_quiet_with_explicit_output_options(self, httpbin, argument_name): env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) - r = http('--quiet', argument_name, httpbin.url + '/GET', env=env) - assert env.stdout == env.devnull - assert env.stderr == env.devnull - assert str(r) == '' + r = http('--quiet', argument_name, httpbin.url + '/get', env=env) + assert env.stdout is env.devnull + assert env.stderr is env.devnull + assert r == '' assert r.stderr == '' - def test_quiet_flag_with_output_redirection(self, httpbin): - robots_txt = '/robots.txt' - expected_body = requests.get(httpbin + robots_txt).text - expected_filename = 'test.txt' - env = MockEnvironment(stdin_isatty=True, stdout_isatty=True) + @pytest.mark.parametrize('with_download', [True, False]) + def test_quiet_with_output_redirection(self, httpbin, with_download): + url = httpbin + '/robots.txt' + output_path = Path('output.txt') + env = MockEnvironment() orig_cwd = os.getcwd() - os.chdir(tempfile.mkdtemp(prefix='httpie_download_quiet_test_')) - try: - assert os.listdir('.') == [] - r = http('--quiet', - '--output=' + expected_filename, - httpbin + robots_txt, - env=env) - assert os.listdir('.') == [expected_filename] - assert r.stderr == '' - assert env.devnull == env.stderr - assert env.devnull != env.stdout - with open(expected_filename, 'r') as f: - assert f.read() == expected_body - finally: - os.chdir(orig_cwd) + output = requests.get(url).text + extra_args = ['--download'] if with_download else [] + with tempfile.TemporaryDirectory() as tmp_dirname: + os.chdir(tmp_dirname) + try: + assert os.listdir('.') == [] + r = http( + '--quiet', + '--output', str(output_path), + *extra_args, + url, + env=env + ) + assert os.listdir('.') == [str(output_path)] + assert r == '' + assert r.stderr == '' + assert env.stderr is env.devnull + if with_download: + assert env.stdout is env.devnull + else: + assert env.stdout is not env.devnull # --output swaps stdout. + assert output_path.read_text() == output + finally: + os.chdir(orig_cwd) class TestVerboseFlag: