From ee0de17a37dfba59a4ac1e22539819269cd78975 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Mon, 28 Oct 2024 22:50:45 +0200 Subject: [PATCH 1/2] test: better explanation for --formatter=none unit test repo fixture --- src/darker/tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index 484c67d9c..d8d1850f5 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -666,7 +666,7 @@ def test_long_command_length(git_repo): @pytest.fixture(scope="module") def formatter_none_repo(git_repo_m): - """Create a Git repository with a single file and a formatter that does nothing.""" + """Create a Git repo with a single file to test a formatter that does nothing.""" files = git_repo_m.add({"file1.py": "# old content\n"}, commit="Initial") files["file1.py"].write_text( dedent( From 3a715abf2e52e20f76113e894078f4deaa8785a0 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Wed, 1 Jan 2025 22:52:43 +0200 Subject: [PATCH 2/2] fix: improve Black import error [optional-black-improvement] --- src/darker/formatters/black_formatter.py | 32 ++++++------------- src/darker/formatters/black_wrapper.py | 39 +++++++++++++++++++++++ src/darker/tests/helpers.py | 1 + src/darker/tests/test_command_line.py | 6 ++-- src/darker/tests/test_formatters_black.py | 8 ++--- 5 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 src/darker/formatters/black_wrapper.py diff --git a/src/darker/formatters/black_formatter.py b/src/darker/formatters/black_formatter.py index 604b717f1..94fc444b0 100644 --- a/src/darker/formatters/black_formatter.py +++ b/src/darker/formatters/black_formatter.py @@ -39,7 +39,6 @@ import logging from typing import TYPE_CHECKING, TypedDict -from darker.exceptions import DependencyError from darker.files import find_pyproject_toml from darker.formatters.base_formatter import BaseFormatter from darkgraylib.config import ConfigurationError @@ -93,24 +92,11 @@ def read_config(self, src: tuple[str, ...], args: Namespace) -> None: def _read_config_file(self, config_path: str) -> None: # noqa: C901 # Local import so Darker can be run without Black installed. # Do error handling here. This is the first Black importing method being hit. - try: - from black import ( # pylint: disable=import-outside-toplevel - parse_pyproject_toml, - re_compile_maybe_verbose, - ) - except ImportError as exc: - logger.warning( - "To re-format code using Black, install it using e.g." - " `pip install 'darker[black]'` or" - " `pip install black`" - ) - logger.warning( - "To use a different formatter or no formatter, select it on the" - " command line (e.g. `--formatter=none`) or configuration" - " (e.g. `formatter=none`)" - ) - message = "Can't find the Black package" - raise DependencyError(message) from exc + # pylint: disable=import-outside-toplevel + from darker.formatters.black_wrapper import ( + parse_pyproject_toml, + re_compile_maybe_verbose, + ) raw_config = parse_pyproject_toml(config_path) if "line_length" in raw_config: @@ -171,7 +157,8 @@ def run(self, content: TextDocument) -> TextDocument: """ # Local import so Darker can be run without Black installed. # No need for error handling, already done in `BlackFormatter.read_config`. - from black import format_str # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel + from darker.formatters.black_wrapper import format_str contents_for_black = content.string_with_newline("\n") if contents_for_black.strip(): @@ -196,8 +183,9 @@ def _make_black_options(self) -> Mode: # Local import so Darker can be run without Black installed. # No need for error handling, already done in `BlackFormatter.read_config`. - from black import FileMode as Mode # pylint: disable=import-outside-toplevel - from black import TargetVersion # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel + from darker.formatters.black_wrapper import FileMode as Mode + from darker.formatters.black_wrapper import TargetVersion mode = BlackModeAttributes() if "line_length" in self.config: diff --git a/src/darker/formatters/black_wrapper.py b/src/darker/formatters/black_wrapper.py new file mode 100644 index 000000000..1a724c31d --- /dev/null +++ b/src/darker/formatters/black_wrapper.py @@ -0,0 +1,39 @@ +"""Attempt to import Black internals needed by the Black formatter plugin.""" + +import logging + +from darker.exceptions import DependencyError + +logger = logging.getLogger(__name__) + +try: + import black # noqa: F401 # pylint: disable=unused-import +except ImportError as exc: + logger.warning( + "To re-format code using Black, install it using e.g." + " `pip install 'darker[black]'` or" + " `pip install black`" + ) + logger.warning( + "To use a different formatter or no formatter, select it on the" + " command line (e.g. `--formatter=none`) or configuration" + " (e.g. `formatter=none`)" + ) + MESSAGE = "Can't find the Black package" + raise DependencyError(MESSAGE) from exc + +from black import ( # noqa: E402 # pylint: disable=unused-import,wrong-import-position + FileMode, + TargetVersion, + format_str, + parse_pyproject_toml, + re_compile_maybe_verbose, +) + +__all__ = [ + "FileMode", + "TargetVersion", + "format_str", + "parse_pyproject_toml", + "re_compile_maybe_verbose", +] diff --git a/src/darker/tests/helpers.py b/src/darker/tests/helpers.py index fffcf8cb1..bd587ccbc 100644 --- a/src/darker/tests/helpers.py +++ b/src/darker/tests/helpers.py @@ -28,6 +28,7 @@ def _package_present( def black_present(*, present: bool) -> Generator[None, None, None]: """Context manager to remove or add the ``black`` package temporarily for a test.""" with _package_present("black", present): + del sys.modules["darker.formatters.black_wrapper"] yield diff --git a/src/darker/tests/test_command_line.py b/src/darker/tests/test_command_line.py index 90abff117..9609516ac 100644 --- a/src/darker/tests/test_command_line.py +++ b/src/darker/tests/test_command_line.py @@ -589,7 +589,9 @@ def test_black_options(black_options_files, options, expect): # shared by all test cases. The "main.py" file modified by the test run needs to be # reset to its original content before the next test case. black_options_files["main.py"].write_bytes(b'print ("Hello World!")\n') - with patch("black.FileMode", wraps=FileMode) as file_mode_class: + with patch( + "darker.formatters.black_wrapper.FileMode", wraps=FileMode + ) as file_mode_class: # end of test setup, now call the function under test main(options + [str(path) for path in black_options_files.values()]) @@ -718,7 +720,7 @@ def test_black_config_file_and_options( mode_class_mock = Mock(wraps=FileMode) # Speed up tests by mocking `format_str` to skip running Black format_str = Mock(return_value="a = [1, 2,]") - with patch("black.FileMode", mode_class_mock), patch( + with patch("darker.formatters.black_wrapper.FileMode", mode_class_mock), patch( "black.format_str", format_str ): # end of test setup, now call the function under test diff --git a/src/darker/tests/test_formatters_black.py b/src/darker/tests/test_formatters_black.py index 4f7534c75..94332a03f 100644 --- a/src/darker/tests/test_formatters_black.py +++ b/src/darker/tests/test_formatters_black.py @@ -278,7 +278,7 @@ def test_run(encoding, newline): def test_run_always_uses_unix_newlines(newline): """Content is always passed to Black with Unix newlines""" src = TextDocument.from_str(f"print ( 'touché' ){newline}") - with patch("black.format_str") as format_str: + with patch("darker.formatters.black_wrapper.format_str") as format_str: format_str.return_value = 'print("touché")\n' _ = BlackFormatter().run(src) @@ -390,9 +390,9 @@ def test_run_configuration( ): """`BlackFormatter.run` passes correct configuration to Black.""" src = TextDocument.from_str("import os\n") - with patch("black.format_str") as format_str, raises_or_matches( - expect, [] - ) as check: + with patch( + "darker.formatters.black_wrapper.format_str" + ) as format_str, raises_or_matches(expect, []) as check: format_str.return_value = "import os\n" formatter = BlackFormatter() formatter.config = black_config