From 0ce5bfc0594cb9eb741d7d89484042aa7a9638df Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Fri, 30 Oct 2020 15:10:36 +0100 Subject: [PATCH 1/3] Implement custom output handling in sort_file This will allow the following to be used: isort_diff = StringIO() isort_output = StringIO() isort.file("x.py", show_diff=isort_diff, output=isort_output) Fixes #1583 --- isort/api.py | 110 +++++++++++++++++---------- tests/unit/test_ticketed_features.py | 25 ++++++ 2 files changed, 94 insertions(+), 41 deletions(-) diff --git a/isort/api.py b/isort/api.py index 611187437..502994b66 100644 --- a/isort/api.py +++ b/isort/api.py @@ -284,6 +284,7 @@ def sort_file( ask_to_apply: bool = False, show_diff: Union[bool, TextIO] = False, write_to_stdout: bool = False, + output: Optional[TextIO] = None, **config_kwargs, ) -> bool: """Sorts and formats any groups of imports imports within the provided file or Path. @@ -298,6 +299,8 @@ def sort_file( - **show_diff**: If `True` the changes that need to be done will be printed to stdout, if a TextIO stream is provided results will be written to it, otherwise no diff will be computed. - **write_to_stdout**: If `True`, write to stdout instead of the input file. + - **output**: If a TextIO is provided, results will be written there rather than replacing + the original file content. - ****config_kwargs**: Any config modifications. """ with io.File.read(filename) as source_file: @@ -315,49 +318,74 @@ def sort_file( extension=extension, ) else: - tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted") - try: - with tmp_file.open( - "w", encoding=source_file.encoding, newline="" - ) as output_stream: - shutil.copymode(filename, tmp_file) - changed = sort_stream( - input_stream=source_file.stream, - output_stream=output_stream, - config=config, - file_path=actual_file_path, - disregard_skip=disregard_skip, - extension=extension, - ) + if output is None: + tmp_file = source_file.path.with_suffix(source_file.path.suffix + ".isorted") + try: + with tmp_file.open( + "w", encoding=source_file.encoding, newline="" + ) as output_stream: + shutil.copymode(filename, tmp_file) + changed = sort_stream( + input_stream=source_file.stream, + output_stream=output_stream, + config=config, + file_path=actual_file_path, + disregard_skip=disregard_skip, + extension=extension, + ) + if changed: + if show_diff or ask_to_apply: + source_file.stream.seek(0) + with tmp_file.open( + encoding=source_file.encoding, newline="" + ) as tmp_out: + show_unified_diff( + file_input=source_file.stream.read(), + file_output=tmp_out.read(), + file_path=actual_file_path, + output=None + if show_diff is True + else cast(TextIO, show_diff), + color_output=config.color_output, + ) + if show_diff or ( + ask_to_apply + and not ask_whether_to_apply_changes_to_file( + str(source_file.path) + ) + ): + return False + source_file.stream.close() + tmp_file.replace(source_file.path) + if not config.quiet: + print(f"Fixing {source_file.path}") + finally: + try: # Python 3.8+: use `missing_ok=True` instead of try except. + tmp_file.unlink() + except FileNotFoundError: + pass # pragma: no cover + else: + changed = sort_stream( + input_stream=source_file.stream, + output_stream=output, + config=config, + file_path=actual_file_path, + disregard_skip=disregard_skip, + extension=extension, + ) if changed: - if show_diff or ask_to_apply: + if show_diff: source_file.stream.seek(0) - with tmp_file.open( - encoding=source_file.encoding, newline="" - ) as tmp_out: - show_unified_diff( - file_input=source_file.stream.read(), - file_output=tmp_out.read(), - file_path=actual_file_path, - output=None if show_diff is True else cast(TextIO, show_diff), - color_output=config.color_output, - ) - if show_diff or ( - ask_to_apply - and not ask_whether_to_apply_changes_to_file( - str(source_file.path) - ) - ): - return False - source_file.stream.close() - tmp_file.replace(source_file.path) - if not config.quiet: - print(f"Fixing {source_file.path}") - finally: - try: # Python 3.8+: use `missing_ok=True` instead of try except. - tmp_file.unlink() - except FileNotFoundError: - pass # pragma: no cover + output.seek(0) + show_unified_diff( + file_input=source_file.stream.read(), + file_output=output.read(), + file_path=actual_file_path, + output=None if show_diff is True else cast(TextIO, show_diff), + color_output=config.color_output, + ) + source_file.stream.close() + except ExistingSyntaxErrors: warn(f"{actual_file_path} unable to sort due to existing syntax errors") except IntroducedSyntaxErrors: # pragma: no cover diff --git a/tests/unit/test_ticketed_features.py b/tests/unit/test_ticketed_features.py index 51b130095..050d1c4c7 100644 --- a/tests/unit/test_ticketed_features.py +++ b/tests/unit/test_ticketed_features.py @@ -848,3 +848,28 @@ def my_function(): pass """ ) + + +def test_api_to_allow_custom_diff_and_output_stream_1583(capsys, tmpdir): + """isort should provide a way from the Python API to process an existing + file and output to a stream the new version of that file, as well as a diff + to a different stream. + See: https://github.com/PyCQA/isort/issues/1583 + """ + + tmp_file = tmpdir.join("file.py") + tmp_file.write("import b\nimport a\n") + + isort_diff = StringIO() + isort_output = StringIO() + + isort.file(tmp_file, show_diff=isort_diff, output=isort_output) + + _, error = capsys.readouterr() + assert not error + + isort_diff.seek(0) + assert "+import a\n import b\n-import a\n" in isort_diff.read() + + isort_output.seek(0) + assert isort_output.read() == "import a\nimport b\n" From 4cc84eec18cbc31d82741a5f8bc7d57f59ee8653 Mon Sep 17 00:00:00 2001 From: Timothy Edmund Crosley Date: Sun, 1 Nov 2020 00:03:34 -0700 Subject: [PATCH 2/3] Update test_ticketed_features.py Update test to be OS newline agnostic for diff --- tests/unit/test_ticketed_features.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_ticketed_features.py b/tests/unit/test_ticketed_features.py index 050d1c4c7..58a3efcae 100644 --- a/tests/unit/test_ticketed_features.py +++ b/tests/unit/test_ticketed_features.py @@ -869,7 +869,10 @@ def test_api_to_allow_custom_diff_and_output_stream_1583(capsys, tmpdir): assert not error isort_diff.seek(0) - assert "+import a\n import b\n-import a\n" in isort_diff.read() - + isort_diff_content = isort_diff.read() + assert "+import a" in isort_diff_content + assert " import b" in isort_diff_content + assert "-import a" in isort_diff_content + isort_output.seek(0) assert isort_output.read() == "import a\nimport b\n" From d40ca26754c9eed472131a07b96f5d3ba73dd5bb Mon Sep 17 00:00:00 2001 From: Timothy Edmund Crosley Date: Sun, 1 Nov 2020 00:11:37 -0700 Subject: [PATCH 3/3] OS agnostic test changes --- tests/unit/test_ticketed_features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_ticketed_features.py b/tests/unit/test_ticketed_features.py index 58a3efcae..76f08894c 100644 --- a/tests/unit/test_ticketed_features.py +++ b/tests/unit/test_ticketed_features.py @@ -873,6 +873,6 @@ def test_api_to_allow_custom_diff_and_output_stream_1583(capsys, tmpdir): assert "+import a" in isort_diff_content assert " import b" in isort_diff_content assert "-import a" in isort_diff_content - + isort_output.seek(0) - assert isort_output.read() == "import a\nimport b\n" + assert isort_output.read().splitlines() == ["import a", "import b"]