Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --exclude and --exclude-glob flags #2153

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/configuration/options.md
Expand Up @@ -126,6 +126,7 @@ Files that isort should skip over. To even skip matching files that have been sp

- --sg
- --skip-glob
- --exclude

**Examples:**

Expand Down Expand Up @@ -156,6 +157,7 @@ Additional files that isort should skip over (extending --skip-glob). To even sk
**CLI Flags:**

- --extend-skip-glob
- --extend-exclude

**Examples:**

Expand All @@ -175,6 +177,36 @@ extend_skip_glob = ["my_*_module.py", "test/*"]

```

## Exclude Glob

Files that isort should skip over. Paths are being excluded as globs. To even skip matching files that have been specified on the command line, use [`--filter-files`](#filter-files).

**Type:** List of Strings
**Default:** `frozenset()`
**Config default:** `[]`
**Python & Config File Name:** exclude-glob
**CLI Flags:**

- --exclude-glob

**Examples:**

### Example `.isort.cfg`

```
[settings]
exclude_glob=docs/**

```

### Example `pyproject.toml`

```
[tool.isort]
exclude_glob = ["docs/**"]

```

## Skip Gitignore

Treat project as a git repository and ignore files listed in .gitignore. To even skip matching files that have been specified on the command line, use [`--filter-files`](#filter-files).
Expand Down
4 changes: 2 additions & 2 deletions isort/exceptions.py
Expand Up @@ -70,8 +70,8 @@ class FileSkipSetting(FileSkipped):

def __init__(self, file_path: str, **kwargs: str):
super().__init__(
f"{file_path} was skipped as it's listed in 'skip' setting"
" or matches a glob in 'skip_glob' setting",
f"{file_path} was skipped as it's listed in 'skip' setting, "
"matches in 'exclude' setting or matches a glob in 'exclude-glob'",
file_path=file_path,
)

Expand Down
12 changes: 10 additions & 2 deletions isort/main.py
Expand Up @@ -363,16 +363,24 @@ def _build_arg_parser() -> argparse.ArgumentParser:
target_group.add_argument(
"--sg",
"--skip-glob",
"--exclude",
help="Files that isort should skip over.",
dest="skip_glob",
action="append",
)
target_group.add_argument(
"--extend-skip-glob",
"--extend-exclude",
help="Additional files that isort should skip over (extending --skip-glob).",
dest="extend_skip_glob",
action="append",
)
target_group.add_argument(
"--exclude-glob",
help="Files that isort should skip over, In a glob notation.",
dest="exclude_glob",
action="append",
)
target_group.add_argument(
"--gitignore",
"--skip-gitignore",
Expand Down Expand Up @@ -1249,8 +1257,8 @@ def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] =
for was_skipped in skipped:
print(
f"{was_skipped} was skipped as it's listed in 'skip' setting, "
"matches a glob in 'skip_glob' setting, or is in a .gitignore file with "
"--skip-gitignore enabled."
"matches in 'exclude' setting or matches a glob in 'exclude-glob', "
"or is in a .gitignore file with --skip-gitignore enabled."
)
print(f"Skipped {num_skipped} files")

Expand Down
15 changes: 15 additions & 0 deletions isort/settings.py
Expand Up @@ -147,6 +147,7 @@ class _Config:
extend_skip: FrozenSet[str] = frozenset()
skip_glob: FrozenSet[str] = frozenset()
extend_skip_glob: FrozenSet[str] = frozenset()
exclude_glob: FrozenSet[str] = frozenset()
skip_gitignore: bool = False
line_length: int = 79
wrap_length: int = 0
Expand Down Expand Up @@ -306,6 +307,7 @@ def __init__(
self._section_comments_end: Optional[Tuple[str, ...]] = None
self._skips: Optional[FrozenSet[str]] = None
self._skip_globs: Optional[FrozenSet[str]] = None
self._exclude_globs: Optional[FrozenSet[str]] = None
self._sorting_function: Optional[Callable[..., List[str]]] = None

if config:
Expand All @@ -317,6 +319,7 @@ def __init__(
config_vars.pop("_section_comments_end")
config_vars.pop("_skips")
config_vars.pop("_skip_globs")
config_vars.pop("_exclude_globs")
config_vars.pop("_sorting_function")
super().__init__(**config_vars)
return
Expand Down Expand Up @@ -619,6 +622,10 @@ def is_skipped(self, file_path: Path) -> bool:
if fnmatch.fnmatch(file_name, sglob) or fnmatch.fnmatch("/" + file_name, sglob):
return True

for exclude_glob in self.exclude_globs:
if file_path in Path(self.directory).glob(exclude_glob):
return True

if not (os.path.isfile(os_path) or os.path.isdir(os_path) or os.path.islink(os_path)):
return True

Expand Down Expand Up @@ -703,6 +710,14 @@ def skip_globs(self) -> FrozenSet[str]:
self._skip_globs = self.skip_glob.union(self.extend_skip_glob)
return self._skip_globs

@property
def exclude_globs(self) -> FrozenSet[str]:
if self._exclude_globs is not None:
return self._exclude_globs

self._exclude_globs = self.exclude_glob
return self._exclude_globs

@property
def sorting_function(self) -> Callable[..., List[str]]:
if self._sorting_function is not None:
Expand Down
8 changes: 8 additions & 0 deletions scripts/build_config_option_docs.py
Expand Up @@ -165,6 +165,14 @@ def __str__(self):
""",
pyproject_toml="""
extend_skip_glob = ["my_*_module.py", "test/*"]
""",
),
"exclude_glob": Example(
cfg="""
exclude_glob=docs/**
""",
pyproject_toml="""
exclude_glob = ["docs/**"]
""",
),
"known_third_party": Example(
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/test_setting_combinations.py
Expand Up @@ -181,6 +181,7 @@ def _raise(*a):
}
),
skip_glob=frozenset(),
exclude_glob=frozenset(),
skip_gitignore=True,
line_length=79,
wrap_length=0,
Expand Down Expand Up @@ -615,6 +616,7 @@ def _raise(*a):
}
),
"skip_glob": frozenset(),
"exclude_glob": frozenset(),
"skip_gitignore": False,
"line_length": 79,
"wrap_length": 0,
Expand Down Expand Up @@ -1046,6 +1048,7 @@ def test_isort_is_idempotent(config: isort.Config, disregard_skip: bool) -> None
}
),
skip_glob=frozenset(),
exclude_glob=frozenset(),
skip_gitignore=True,
line_length=79,
wrap_length=0,
Expand Down Expand Up @@ -1480,6 +1483,7 @@ def test_isort_is_idempotent(config: isort.Config, disregard_skip: bool) -> None
}
),
"skip_glob": frozenset(),
"exclude_glob": frozenset(),
"skip_gitignore": False,
"line_length": 79,
"wrap_length": 0,
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/test_isort.py
Expand Up @@ -3883,6 +3883,53 @@ def test_skip_glob(tmpdir, skip_glob_assert: Tuple[List[str], int, Set[str]]) ->
assert file_names == file_names_expected


@pytest.mark.parametrize(
"exclude_glob,skipped_count,file_names_expected",
(
(
[],
0,
{
os.sep.join(("code", "file.py")),
os.sep.join(("code", "file1.py")),
os.sep.join(("code", "code_sub", "file2.py")),
os.sep.join(("code", "code_sub", "file3.py")),
},
),
(["code/**"], 1, set()),
(["code/**/*.py"], 4, set()),
(
["code/code_sub/*.py"],
2,
{
os.sep.join(("code", "file.py")),
os.sep.join(("code", "file1.py")),
},
),
),
)
def test_exclude_glob(
tmpdir, exclude_glob: List[str], skipped_count: int, file_names_expected: Set[str]
) -> None:
base_dir = tmpdir.mkdir("build")
code_dir = base_dir.mkdir("code")
code_dir.join("file.py").write("import os")
code_dir.join("file1.py").write("import os")
code_subdir = code_dir.mkdir("code_sub")
code_subdir.join("file2.py").write("import os")
code_subdir.join("file3.py").write("import os")

config = Config(exclude_glob=exclude_glob, directory=str(base_dir))
skipped: List[str] = []
broken: List[str] = []
file_names = {
os.path.relpath(f, str(base_dir))
for f in files.find([str(base_dir)], config, skipped, broken)
}
assert len(skipped) == skipped_count
assert file_names == file_names_expected


def test_broken(tmpdir) -> None:
base_dir = tmpdir.mkdir("broken")

Expand Down