From 6eadac438bc75792581d4de57b478f46cd45446d Mon Sep 17 00:00:00 2001 From: Thijs Kramer Date: Tue, 19 Oct 2021 19:33:52 +0200 Subject: [PATCH] only write to stdout buffer when buffer object is available --- src/flake8/formatting/base.py | 10 +++++++++- tests/unit/test_base_formatter.py | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/flake8/formatting/base.py b/src/flake8/formatting/base.py index 7919f92e9..c8f69ddac 100644 --- a/src/flake8/formatting/base.py +++ b/src/flake8/formatting/base.py @@ -184,7 +184,15 @@ def _write(self, output: str) -> None: if self.output_fd is not None: self.output_fd.write(output + self.newline) if self.output_fd is None or self.options.tee: - sys.stdout.buffer.write(output.encode() + self.newline.encode()) + # sys.stdout might be replaced, and thus could also be + # a file like object like io.StringIO, which do not + # support the 'buffer' attribute. + try: + sys.stdout.buffer.write( + output.encode() + self.newline.encode() + ) + except AttributeError: + sys.stdout.write(output + self.newline) def write(self, line: Optional[str], source: Optional[str]) -> None: """Write the line either to the output file or stdout. diff --git a/tests/unit/test_base_formatter.py b/tests/unit/test_base_formatter.py index 895890317..463dff7ab 100644 --- a/tests/unit/test_base_formatter.py +++ b/tests/unit/test_base_formatter.py @@ -136,6 +136,32 @@ def test_write_produces_stdout(capsys): assert capsys.readouterr().out == f"{line}\n{source}\n" +def test_write_without_stdout_buffer(): + """Verify that stdout writes when it has no buffer.""" + line = "Something to write" + source = "source" + + buffer_side_effect = AttributeError("'_io.StringIO' object has no \ + attribute 'buffer'") + buffer_write_mock = mock.Mock(side_effect=buffer_side_effect) + + write_mock = mock.Mock() + mock.patch("flake8.formatting.base.sys.stdout.write", write_mock) + + with mock.patch( + "flake8.formatting.base.sys.stdout.buffer.write", + buffer_write_mock + ): + formatter = base.BaseFormatter(options()) + formatter.write(line, source) + assert write_mock.called is True + assert write_mock.call_count == 2 + assert write_mock.mock_calls == [ + mock.call(line + formatter.newline), + mock.call(source + formatter.newline), + ] + + class AfterInitFormatter(base.BaseFormatter): """Subclass for testing after_init."""