Skip to content

Commit d0119c9

Browse files
authored
Merge pull request #751 from akaihola/isort-skip-glob
Respect isort's `skip_glob` setting
2 parents f485af9 + 411a84c commit d0119c9

File tree

8 files changed

+82
-29
lines changed

8 files changed

+82
-29
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ __pycache__/
77
/.vscode/
88
/build
99
/venv
10+
/.venv
1011
uv.lock

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Added
2020
Black support.
2121
- pyupgrade_ is now supported as a formatter plugin. Note that changes from pyupgrade
2222
are applied on a per-file basis, not only for modified lines as with Black_ and Ruff_.
23+
- Respect the ``skip_glob`` setting of isort_.
2324

2425
Removed
2526
-------
@@ -28,6 +29,7 @@ Removed
2829
- In the Darker configuration file under ``[tool.darker]``, the Black configuration
2930
options ``skip_string_normalization`` and ``skip_magic_trailing_comma`` are no longer
3031
valid. Use ``[tool.black]`` instead.
32+
- Drop support for isort_ 5.1.x.
3133

3234
Fixed
3335
-----

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ flynt = [
3939
"flynt>=0.76",
4040
]
4141
isort = [
42-
"isort>=5.1.0",
42+
"isort>=5.2.0",
4343
]
4444
ruff = [
4545
"ruff>=0.0.292",
@@ -55,7 +55,7 @@ dev = [
5555
"defusedxml>=0.7.1",
5656
"flake8>=3.8.0",
5757
"flynt>=0.76",
58-
"isort>=5.1.0",
58+
"isort>=5.2.0",
5959
"mypy>=1.15",
6060
"packaging>=14",
6161
"pydocstyle>=2.0.0",

setup.cfg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
[flake8]
22
# Line length according to Black rules
33
max-line-length = 88
4+
# Skip some directories
5+
exclude =
6+
.*
7+
build
48
# Ignore rules which conflict with Black
59
ignore =
610
# C408 Unnecessary dict call - rewrite as a literal.

src/darker/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ def _modify_and_reformat_single_file( # noqa: PLR0913
145145
rev2_isorted = apply_isort(
146146
rev2_content,
147147
relative_path_in_rev2,
148+
root,
148149
exclude.isort,
149150
edited_linenums_differ,
150151
formatter.get_config_path(),

src/darker/import_sorting.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Helpers for invoking ``isort`` and acting on its output"""
22

33
import logging
4+
from contextlib import suppress
45
from pathlib import Path
56
from typing import Any, Collection, List, Optional, TypedDict
67

@@ -51,9 +52,10 @@ class IsortArgs(TypedDict, total=False):
5152
settings_path: str
5253

5354

54-
def apply_isort( # pylint: disable=too-many-arguments
55+
def apply_isort( # pylint: disable=too-many-arguments # noqa: PLR0913
5556
content: TextDocument,
56-
src: Path,
57+
relative_path: Path,
58+
root: Path,
5759
exclude: Collection[str],
5860
edited_linenums_differ: EditedLinenumsDiffer,
5961
config: Optional[str] = None,
@@ -62,27 +64,28 @@ def apply_isort( # pylint: disable=too-many-arguments
6264
"""Run isort on the given Python source file content
6365
6466
:param content: The contents of the Python source code file to sort imports in
65-
:param src: The relative path to the file. This must be the actual path in the
66-
repository, which may differ from the path given on the command line in
67-
case of VSCode temporary files.
67+
:param relative_path: The path to the file relative to ``root``. Note that this may
68+
differ from the path given on the command line in case of
69+
VSCode temporary files.
70+
:param root: The root directory based on which ``src`` is resolved.
6871
:param exclude: The file path patterns to exclude from import sorting
6972
:param edited_linenums_differ: Helper for finding out which lines were edited
7073
:param config: Path to configuration file
7174
:param line_length: Maximum line length to use
7275
:return: Original Python source file contents with imports sorted
7376
7477
"""
75-
if glob_any(src, exclude):
78+
if glob_any(relative_path, exclude):
7679
return content
7780
edited_linenums = edited_linenums_differ.revision_vs_lines(
78-
src,
81+
relative_path,
7982
content,
8083
context_lines=0,
8184
) # pylint: disable=duplicate-code
8285
if not edited_linenums:
8386
return content
84-
isort_args = _build_isort_args(src, config, line_length)
85-
rev2_isorted = _call_isort_code(content, isort_args)
87+
isort_args = _build_isort_args(root / relative_path, config, line_length)
88+
rev2_isorted = _call_isort_code(content, root / relative_path, isort_args)
8689
# Get the chunks in the diff between the edited and import-sorted file
8790
isort_chunks = diff_chunks(content, rev2_isorted)
8891
if not isort_chunks:
@@ -98,15 +101,16 @@ def apply_isort( # pylint: disable=too-many-arguments
98101

99102

100103
def _build_isort_args(
101-
src: Path,
104+
path_from_cwd: Path,
102105
config: Optional[str] = None,
103106
line_length: Optional[int] = None,
104107
) -> IsortArgs:
105108
"""Build ``isort.code()`` keyword arguments
106109
107-
:param src: The relative path to the file. This must be the actual path in the
108-
repository, which may differ from the path given on the command line in
109-
case of VSCode temporary files.
110+
:param path_from_cwd: The path to the file. This must be either an absolute path or
111+
a relative path from the current working directory. Note that
112+
this may differ from the path given on the command line in
113+
case of VSCode temporary files.
110114
:param config: Path to configuration file
111115
:param line_length: Maximum line length to use
112116
@@ -115,16 +119,20 @@ def _build_isort_args(
115119
if config:
116120
isort_args["settings_file"] = config
117121
else:
118-
isort_args["settings_path"] = str(find_project_root((str(src),)))
122+
isort_args["settings_path"] = str(find_project_root((str(path_from_cwd),)))
119123
if line_length:
120124
isort_args["line_length"] = line_length
121125
return isort_args
122126

123127

124-
def _call_isort_code(content: TextDocument, isort_args: IsortArgs) -> TextDocument:
128+
def _call_isort_code(
129+
content: TextDocument, path_from_cwd: Path, isort_args: IsortArgs
130+
) -> TextDocument:
125131
"""Call ``isort.code()`` and return the result as a `TextDocument` object
126132
127133
:param content: The contents of the Python source code file to sort imports in
134+
:param path_from_cwd: The path to the file with the given content, either relative
135+
to the current working directory or an absolute path.
128136
:param isort_args: Keyword arguments for ``isort.code()``
129137
130138
"""
@@ -133,10 +141,8 @@ def _call_isort_code(content: TextDocument, isort_args: IsortArgs) -> TextDocume
133141
"isort.code(code=..., %s)",
134142
", ".join(f"{k}={v!r}" for k, v in isort_args.items()),
135143
)
136-
try:
137-
code = isort_code(code=code, **isort_args)
138-
except isort.exceptions.FileSkipComment:
139-
pass
144+
with suppress(isort.exceptions.FileSkipped):
145+
code = isort_code(code=code, file_path=path_from_cwd, **isort_args)
140146
return TextDocument.from_str(
141147
code,
142148
encoding=content.encoding,

src/darker/tests/test_import_sorting.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,19 @@ def apply_isort_repo_root(request, tmp_path_factory):
6464
)
6565
def test_apply_isort(apply_isort_repo_root, encoding, newline, content, expect):
6666
"""Imports are sorted if edits overlap them, with encoding and newline intact"""
67+
repo_root = apply_isort_repo_root[newline]
6768
edited_linenums_differ = EditedLinenumsDiffer(
68-
apply_isort_repo_root[newline], RevisionRange("HEAD", ":WORKTREE:")
69+
repo_root, RevisionRange("HEAD", ":WORKTREE:")
6970
)
7071
src = Path("test1.py")
7172
content_ = TextDocument.from_lines(content, encoding=encoding, newline=newline)
7273

7374
result = darker.import_sorting.apply_isort(
74-
content_, src, exclude=[], edited_linenums_differ=edited_linenums_differ
75+
content_,
76+
src,
77+
repo_root,
78+
exclude=[],
79+
edited_linenums_differ=edited_linenums_differ,
7580
)
7681

7782
assert result.lines == expect
@@ -99,14 +104,19 @@ def test_apply_isort_exclude(
99104
apply_isort_repo_root, encoding, newline, content, exclude, expect
100105
):
101106
"""Import sorting is skipped if file path matches exclusion patterns"""
107+
repo_root = apply_isort_repo_root[newline]
102108
edited_linenums_differ = EditedLinenumsDiffer(
103-
apply_isort_repo_root[newline], RevisionRange("HEAD", ":WORKTREE:")
109+
repo_root, RevisionRange("HEAD", ":WORKTREE:")
104110
)
105111
src = Path("test1.py")
106112
content_ = TextDocument.from_lines(content, encoding=encoding, newline=newline)
107113

108114
result = darker.import_sorting.apply_isort(
109-
content_, src, exclude=exclude, edited_linenums_differ=edited_linenums_differ
115+
content_,
116+
src,
117+
repo_root,
118+
exclude=exclude,
119+
edited_linenums_differ=edited_linenums_differ,
110120
)
111121

112122
assert result.lines == expect
@@ -148,10 +158,10 @@ def test_apply_isort_exclude(
148158
),
149159
),
150160
)
151-
def test_isort_config(monkeypatch, tmpdir, line_length, settings_file, expect):
161+
def test_isort_config(monkeypatch, tmp_path, line_length, settings_file, expect):
152162
"""``apply_isort()`` parses ``pyproject.toml``correctly"""
153-
monkeypatch.chdir(tmpdir)
154-
(tmpdir / "pyproject.toml").write(
163+
monkeypatch.chdir(tmp_path)
164+
(tmp_path / "pyproject.toml").write_text(
155165
dedent(
156166
f"""\
157167
[tool.isort]
@@ -161,11 +171,14 @@ def test_isort_config(monkeypatch, tmpdir, line_length, settings_file, expect):
161171
)
162172

163173
content = "from module import ab, cd, ef, gh, ij, kl, mn, op, qr, st, uv, wx, yz"
164-
config = str(tmpdir / settings_file) if settings_file else None
174+
# isort doesn't read the file but skips formatting if the file doesn't exist:
175+
(tmp_path / "test1.py").write_text(content)
176+
config = str(tmp_path / settings_file) if settings_file else None
165177

166178
actual = darker.import_sorting.apply_isort(
167179
TextDocument.from_str(content),
168180
Path("test1.py"),
181+
tmp_path,
169182
set(),
170183
EditedLinenumsDiffer(Path("."), RevisionRange("master", "HEAD")),
171184
config,
@@ -206,6 +219,7 @@ def test_isort_file_skip_comment():
206219
actual = darker.import_sorting.apply_isort(
207220
TextDocument.from_str(content),
208221
Path("test1.py"),
222+
Path(),
209223
set(),
210224
EditedLinenumsDiffer(Path("."), RevisionRange("master", "HEAD")),
211225
)

src/darker/tests/test_main_isort.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# pylint: disable=no-member,redefined-outer-name,unused-argument,use-dict-literal
44

5+
from textwrap import dedent
56
from types import SimpleNamespace
67
from unittest.mock import patch
78

@@ -90,6 +91,30 @@ def test_isort_option_with_isort_calls_sortimports(run_isort, isort_args):
9091
"""Relevant config options are passed from command line to ``isort``."""
9192
run_isort.isort_code.assert_called_once_with(
9293
code="changed",
94+
file_path=run_isort.root / "test1.py",
9395
settings_path=str(run_isort.root),
9496
**isort_args,
9597
)
98+
99+
100+
def test_isort_respects_skip_glob(tmp_path):
101+
"""Test that Darker respects isort's skip_glob setting."""
102+
# Create a pyproject.toml file with isort skip_glob configuration
103+
configuration = dedent(
104+
"""
105+
[tool.isort]
106+
skip_glob = ['*/conf/settings/*']
107+
filter_files = true
108+
"""
109+
)
110+
(tmp_path / "pyproject.toml").write_text(configuration)
111+
# Create a file that should be skipped
112+
settings_dir = tmp_path / "conf" / "settings"
113+
settings_dir.mkdir(parents=True)
114+
backoffice_py = settings_dir / "backoffice.py"
115+
backoffice_py.write_text("import sys\nimport os\n")
116+
117+
# Run darker with --isort
118+
darker.__main__.main(["--isort", str(settings_dir / "backoffice.py")])
119+
120+
assert backoffice_py.read_text() == "import sys\nimport os\n"

0 commit comments

Comments
 (0)