From bcc791c9cd52dd0ec59bc9e12c67ef14f88edcd2 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 6 Feb 2022 12:33:49 +0200 Subject: [PATCH 1/5] Add compatibility with Black >= 22.1 --- mypy.ini | 3 +++ src/darker/black_compat.py | 24 ++++++++++++++++++++++++ src/darker/config.py | 3 ++- src/darker/import_sorting.py | 3 +-- src/darker/tests/conftest.py | 2 +- src/darker/tests/test_import_sorting.py | 2 +- src/darker/tests/test_main.py | 2 +- 7 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 src/darker/black_compat.py diff --git a/mypy.ini b/mypy.ini index 392b17b69..d654342dd 100644 --- a/mypy.ini +++ b/mypy.ini @@ -31,6 +31,9 @@ strict_equality = True [mypy-darker.argparse_helpers] disallow_any_explicit = False +[mypy-darker.black_compat] +disallow_any_explicit = False + [mypy-darker.command_line] disallow_any_explicit = False diff --git a/src/darker/black_compat.py b/src/darker/black_compat.py new file mode 100644 index 000000000..b5c8e2749 --- /dev/null +++ b/src/darker/black_compat.py @@ -0,0 +1,24 @@ +"""Functions for maintaining compatibility with multiple Black versions""" + +from pathlib import Path +from typing import Any, Sequence, Tuple, cast + +from black import find_project_root as black_find_project_root + + +def find_project_root(srcs: Sequence[str]) -> Path: + """Hide changed return value type in Black behind this wrapper + + :param srcs: Files and directories to find the common root for + :return: Project root path + + """ + root = cast(Any, black_find_project_root(tuple(srcs or ["."]))) + if isinstance(root, tuple): + # Black >= 22.1 + return cast(Tuple[Path], root)[0] + # Black < 22 + return cast(Path, root) + + +find_project_root.cache_clear = black_find_project_root.cache_clear diff --git a/src/darker/config.py b/src/darker/config.py index 9f6afc8ab..35f8154c3 100644 --- a/src/darker/config.py +++ b/src/darker/config.py @@ -7,7 +7,8 @@ from typing import Iterable, List, cast import toml -from black import find_project_root + +from darker.black_compat import find_project_root if sys.version_info >= (3, 8): from typing import TypedDict diff --git a/src/darker/import_sorting.py b/src/darker/import_sorting.py index 7c7596b61..21e45e4a3 100644 --- a/src/darker/import_sorting.py +++ b/src/darker/import_sorting.py @@ -3,8 +3,7 @@ from pathlib import Path from typing import Any, Optional -from black import find_project_root - +from darker.black_compat import find_project_root from darker.exceptions import IncompatiblePackageError, MissingPackageError from darker.utils import TextDocument diff --git a/src/darker/tests/conftest.py b/src/darker/tests/conftest.py index 4ceb81d87..10e72b157 100644 --- a/src/darker/tests/conftest.py +++ b/src/darker/tests/conftest.py @@ -6,8 +6,8 @@ from typing import Dict, Optional import pytest -from black import find_project_root +from darker.black_compat import find_project_root from darker.git import _git_check_output_lines diff --git a/src/darker/tests/test_import_sorting.py b/src/darker/tests/test_import_sorting.py index 3504f73a6..ecb676748 100644 --- a/src/darker/tests/test_import_sorting.py +++ b/src/darker/tests/test_import_sorting.py @@ -5,9 +5,9 @@ from textwrap import dedent import pytest -from black import find_project_root import darker.import_sorting +from darker.black_compat import find_project_root from darker.tests.helpers import isort_present from darker.utils import TextDocument diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index 45d447aae..1277c5222 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -11,11 +11,11 @@ from unittest.mock import call, patch import pytest -from black import find_project_root import darker.__main__ import darker.import_sorting import darker.linting +from darker.black_compat import find_project_root from darker.exceptions import MissingPackageError from darker.git import WORKTREE, RevisionRange from darker.tests.helpers import isort_present From ba870616df860c0696e4c0b4d4c40f3bef5fa6cc Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 6 Feb 2022 18:16:03 +0200 Subject: [PATCH 2/5] GitHub matrix job to test against old dependencies The job pins Python dependencies to oldest supported versions. --- .github/workflows/python-package.yml | 7 ++++++- constraints-oldest.txt | 13 +++++++++++++ setup.cfg | 4 ++++ src/darker/tests/test_black_diff.py | 25 ++++++++++++++++++++++--- 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 constraints-oldest.txt diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6b6268663..08499e06e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -18,6 +18,11 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + constraints: [''] + include: + - os: ubuntu-latest + python-version: '3.7' + constraints: '--constraint constraints-oldest.txt' steps: - uses: actions/checkout@v2 @@ -30,7 +35,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade 'pip>=20.3' # strict dependency resolution added in pip 20.3 - pip install -e '.[isort,test]' + pip install -e '.[isort,test]' ${{ matrix.constraints }} - name: Test with pytest run: | pytest diff --git a/constraints-oldest.txt b/constraints-oldest.txt new file mode 100644 index 000000000..48c162ce0 --- /dev/null +++ b/constraints-oldest.txt @@ -0,0 +1,13 @@ +# Constraints for pip to pin dependencies to oldest supported versions. +# This is used in a GitHub Workflow matrix job which ensures everything +# still works against oldest supported versions of both the Python +# interpreter and Python ependencies. Keep this up-to-date with minimum +# versions in `setup.cfg`. +black==21.8b0 +mypy==0.910 +pytest==6.1.0 +pytest-flake8==1.0.6 +pytest-isort==1.1.0 +pytest-kwparametrize==0.0.3 +regex==2021.4.4 +types-toml==0.1.3 diff --git a/setup.cfg b/setup.cfg index facb7025c..c732c48ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,10 +26,13 @@ package_dir = =src packages = find: install_requires = + # NOTE: remember to keep `constraints-oldest.txt` in sync with these black>=21.5b1 toml typing-extensions ; python_version < "3.8" dataclasses ; python_version < "3.7" +# NOTE: remember to keep `.github/workflows/python-package.yml` in sync +# with the minimum required Python version python_requires = >=3.6 [options.packages.find] @@ -46,6 +49,7 @@ pygments.lexers = isort = isort>=5.0.1 test = + # NOTE: remember to keep `constraints-oldest.txt` in sync with these black>=21.7b1 # to prevent Mypy error about `gen_python_files`, see issue #189 flake8<4 mypy>=0.910 diff --git a/src/darker/tests/test_black_diff.py b/src/darker/tests/test_black_diff.py index e2b48f996..ca46d1e0c 100644 --- a/src/darker/tests/test_black_diff.py +++ b/src/darker/tests/test_black_diff.py @@ -1,6 +1,7 @@ """Unit tests for `darker.black_diff`""" import re +from dataclasses import dataclass, field from pathlib import Path from unittest.mock import ANY, patch @@ -17,6 +18,20 @@ from darker.utils import TextDocument +@dataclass +class RegexEquality: + """Compare equality to either `re.Pattern` or `regex.Pattern`""" + + pattern: str + flags: int = field(default=re.UNICODE) + + def __eq__(self, other): + return ( + other.pattern == self.pattern + and other.flags & 0x1FF == re.compile(self.pattern).flags | self.flags + ) + + @pytest.mark.kwparametrize( dict( config_path=None, config_lines=["line-length = 79"], expect={"line_length": 79} @@ -46,15 +61,19 @@ dict(config_lines=[r"include = '\.pyi$'"], expect={}), dict( config_lines=[r"exclude = '\.pyx$'"], - expect={"exclude": re.compile("\\.pyx$")}, + expect={"exclude": RegexEquality("\\.pyx$")}, ), dict( config_lines=["extend-exclude = '''", r"^/setup\.py", r"|^/dummy\.py", "'''"], - expect={"extend_exclude": re.compile("(?x)^/setup\\.py\n|^/dummy\\.py\n")}, + expect={ + "extend_exclude": RegexEquality( + "(?x)^/setup\\.py\n|^/dummy\\.py\n", re.VERBOSE + ) + }, ), dict( config_lines=["force-exclude = '''", r"^/setup\.py", r"|\.pyc$", "'''"], - expect={"force_exclude": re.compile("(?x)^/setup\\.py\n|\\.pyc$\n")}, + expect={"force_exclude": RegexEquality("(?x)^/setup\\.py\n|\\.pyc$\n")}, ), config_path=None, ) From 54c00d356284837ccc0719e1814308fcef082cda Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 6 Feb 2022 18:38:25 +0200 Subject: [PATCH 3/5] Fix cache clearing in tests for find_project_root --- src/darker/black_compat.py | 3 --- src/darker/tests/conftest.py | 12 +++++++++--- src/darker/tests/test_import_sorting.py | 11 ++++++++--- src/darker/tests/test_main.py | 5 +---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/darker/black_compat.py b/src/darker/black_compat.py index b5c8e2749..81ad542c2 100644 --- a/src/darker/black_compat.py +++ b/src/darker/black_compat.py @@ -19,6 +19,3 @@ def find_project_root(srcs: Sequence[str]) -> Path: return cast(Tuple[Path], root)[0] # Black < 22 return cast(Path, root) - - -find_project_root.cache_clear = black_find_project_root.cache_clear diff --git a/src/darker/tests/conftest.py b/src/darker/tests/conftest.py index 10e72b157..a2b844ac1 100644 --- a/src/darker/tests/conftest.py +++ b/src/darker/tests/conftest.py @@ -6,8 +6,8 @@ from typing import Dict, Optional import pytest +from black import find_project_root as black_find_project_root -from darker.black_compat import find_project_root from darker.git import _git_check_output_lines @@ -97,5 +97,11 @@ def git_repo(tmp_path, monkeypatch): @pytest.fixture def find_project_root_cache_clear(): - """Clear LRU caching in :func:`black.find_project_root` before each test""" - find_project_root.cache_clear() + """Clear LRU caching in :func:`black.find_project_root` before each test + + NOTE: We use `darker.black_compat.find_project_root` to wrap Black's original + function since its signature has changed along the way. However, clearing the cache + needs to be done on the original of course. + + """ + black_find_project_root.cache_clear() diff --git a/src/darker/tests/test_import_sorting.py b/src/darker/tests/test_import_sorting.py index ecb676748..266574a5f 100644 --- a/src/darker/tests/test_import_sorting.py +++ b/src/darker/tests/test_import_sorting.py @@ -7,7 +7,6 @@ import pytest import darker.import_sorting -from darker.black_compat import find_project_root from darker.tests.helpers import isort_present from darker.utils import TextDocument @@ -76,9 +75,15 @@ def test_apply_isort(encoding, newline): ), ), ) -def test_isort_config(monkeypatch, tmpdir, line_length, settings_file, expect): +def test_isort_config( + monkeypatch, + tmpdir, + find_project_root_cache_clear, + line_length, + settings_file, + expect, +): """``apply_isort()`` parses ``pyproject.toml``correctly""" - find_project_root.cache_clear() monkeypatch.chdir(tmpdir) (tmpdir / "pyproject.toml").write( dedent( diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index 1277c5222..e385ac25d 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -15,7 +15,6 @@ import darker.__main__ import darker.import_sorting import darker.linting -from darker.black_compat import find_project_root from darker.exceptions import MissingPackageError from darker.git import WORKTREE, RevisionRange from darker.tests.helpers import isort_present @@ -38,9 +37,7 @@ def test_isort_option_without_isort(git_repo, caplog): @pytest.fixture -def run_isort(git_repo, monkeypatch, caplog, request): - find_project_root.cache_clear() - +def run_isort(git_repo, monkeypatch, caplog, request, find_project_root_cache_clear): monkeypatch.chdir(git_repo.root) paths = git_repo.add({"test1.py": "original"}, commit="Initial commit") paths["test1.py"].write_bytes(b"changed") From bc7d2df5b392f38037bc34748836d327607db13b Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 6 Feb 2022 18:53:44 +0200 Subject: [PATCH 4/5] Satisfy linters --- src/darker/tests/test_import_sorting.py | 2 ++ src/darker/tests/test_main.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/darker/tests/test_import_sorting.py b/src/darker/tests/test_import_sorting.py index 266574a5f..7f5b3ca9d 100644 --- a/src/darker/tests/test_import_sorting.py +++ b/src/darker/tests/test_import_sorting.py @@ -1,5 +1,7 @@ """Tests for :mod:`darker.import_sorting`""" +# pylint: disable=unused-argument + from importlib import reload from pathlib import Path from textwrap import dedent diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index e385ac25d..f77d965fe 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -38,6 +38,12 @@ def test_isort_option_without_isort(git_repo, caplog): @pytest.fixture def run_isort(git_repo, monkeypatch, caplog, request, find_project_root_cache_clear): + """Fixture for running Darker with requested arguments and a patched `isort` + + Provides an `run_isort.isort_code` mock object which allows checking whether and how + the `isort.code()` function was called. + + """ monkeypatch.chdir(git_repo.root) paths = git_repo.add({"test1.py": "original"}, commit="Initial commit") paths["test1.py"].write_bytes(b"changed") From d6490d2b13971fb6486673d03c7766c156e11d3a Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 6 Feb 2022 19:16:34 +0200 Subject: [PATCH 5/5] Update change log --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9eae15e1b..c653ad89a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Fixed - ``regex`` module now always available for unit tests - Compatibility with NixOS. Keep ``$PATH`` intact so Git can be called. - Updated tests to pass on new Pygments versions +- Compatibility with Black 22.1 1.3.2_ - 2021-10-28