python-dependency-linter 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/CHANGELOG.md +21 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/PKG-INFO +7 -10
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/README.md +6 -9
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/cli.py +24 -13
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/config.py +31 -2
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_cli.py +92 -24
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_config.py +44 -1
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.claude/skills/commit/SKILL.md +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.claude/skills/release/SKILL.md +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/dependabot.yml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/pull_request_template.md +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/workflows/ci.yaml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/workflows/publish.yaml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.gitignore +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.pre-commit-config.yaml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.pre-commit-hooks.yaml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/CLAUDE.md +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/CONTRIBUTING.md +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/LICENSE +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/pyproject.toml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/checker.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/matcher.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/parser.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/reporter.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/resolver.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_config.yaml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/auth/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/auth/application/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/auth/application/service.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/auth/domain/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/auth/domain/models.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/adapters/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/adapters/repository.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/application/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/application/service.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/domain/__init__.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_project/contexts/boards/domain/models.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_pyproject.toml +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_checker.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_matcher.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_parser.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_reporter.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/test_resolver.py +0 -0
- {python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/uv.lock +0 -0
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-03-30
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Use exit code 2 for config file not found (#11)
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
- Add CONTRIBUTING.md and CLAUDE.md
|
|
14
|
+
- Add PR title convention to template and CONTRIBUTING.md
|
|
15
|
+
- Add release process to CONTRIBUTING.md and /release skill
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- Resolve relative imports to absolute module names (#10)
|
|
20
|
+
- Add include/exclude file filtering options (#12)
|
|
21
|
+
|
|
22
|
+
### Miscellaneous
|
|
23
|
+
|
|
24
|
+
- Add /commit skill for Claude Code
|
|
25
|
+
- Add uv.lock for reproducible builds
|
|
5
26
|
## [0.2.0] - 2026-03-30
|
|
6
27
|
|
|
7
28
|
### Documentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-dependency-linter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A dependency linter for Python projects
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -309,14 +309,11 @@ third_party = ["boto3"]
|
|
|
309
309
|
## CLI
|
|
310
310
|
|
|
311
311
|
```bash
|
|
312
|
-
# Check with
|
|
312
|
+
# Check with auto-discovered config (searches upward from cwd)
|
|
313
313
|
pdl check
|
|
314
314
|
|
|
315
|
-
# Specify config file
|
|
315
|
+
# Specify config file (project root = config file's parent directory)
|
|
316
316
|
pdl check --config path/to/config.yaml
|
|
317
|
-
|
|
318
|
-
# Specify project root
|
|
319
|
-
pdl check --project-root path/to/project
|
|
320
317
|
```
|
|
321
318
|
|
|
322
319
|
Exit codes:
|
|
@@ -325,10 +322,10 @@ Exit codes:
|
|
|
325
322
|
- `1` — Violations found
|
|
326
323
|
- `2` — Config file not found
|
|
327
324
|
|
|
328
|
-
If no `--config` is given, the tool
|
|
325
|
+
If no `--config` is given, the tool searches upward from the current directory for `.python-dependency-linter.yaml` or `pyproject.toml` (with `[tool.python-dependency-linter]`). The config file's parent directory is used as the project root. If no config file is found, the tool prints an error and exits with code `2`:
|
|
329
326
|
|
|
330
327
|
```
|
|
331
|
-
Error: Config file not found
|
|
328
|
+
Error: Config file not found. Create .python-dependency-linter.yaml or configure [tool.python-dependency-linter] in pyproject.toml.
|
|
332
329
|
```
|
|
333
330
|
|
|
334
331
|
## Pre-commit
|
|
@@ -342,14 +339,14 @@ Add to `.pre-commit-config.yaml`:
|
|
|
342
339
|
- id: python-dependency-linter
|
|
343
340
|
```
|
|
344
341
|
|
|
345
|
-
To pass custom options (e.g., a different config file
|
|
342
|
+
To pass custom options (e.g., a different config file):
|
|
346
343
|
|
|
347
344
|
```yaml
|
|
348
345
|
- repo: https://github.com/heumsi/python-dependency-linter
|
|
349
346
|
rev: v0.1.0
|
|
350
347
|
hooks:
|
|
351
348
|
- id: python-dependency-linter
|
|
352
|
-
args: [--config, custom-config.yaml
|
|
349
|
+
args: [--config, custom-config.yaml]
|
|
353
350
|
```
|
|
354
351
|
|
|
355
352
|
## License
|
|
@@ -284,14 +284,11 @@ third_party = ["boto3"]
|
|
|
284
284
|
## CLI
|
|
285
285
|
|
|
286
286
|
```bash
|
|
287
|
-
# Check with
|
|
287
|
+
# Check with auto-discovered config (searches upward from cwd)
|
|
288
288
|
pdl check
|
|
289
289
|
|
|
290
|
-
# Specify config file
|
|
290
|
+
# Specify config file (project root = config file's parent directory)
|
|
291
291
|
pdl check --config path/to/config.yaml
|
|
292
|
-
|
|
293
|
-
# Specify project root
|
|
294
|
-
pdl check --project-root path/to/project
|
|
295
292
|
```
|
|
296
293
|
|
|
297
294
|
Exit codes:
|
|
@@ -300,10 +297,10 @@ Exit codes:
|
|
|
300
297
|
- `1` — Violations found
|
|
301
298
|
- `2` — Config file not found
|
|
302
299
|
|
|
303
|
-
If no `--config` is given, the tool
|
|
300
|
+
If no `--config` is given, the tool searches upward from the current directory for `.python-dependency-linter.yaml` or `pyproject.toml` (with `[tool.python-dependency-linter]`). The config file's parent directory is used as the project root. If no config file is found, the tool prints an error and exits with code `2`:
|
|
304
301
|
|
|
305
302
|
```
|
|
306
|
-
Error: Config file not found
|
|
303
|
+
Error: Config file not found. Create .python-dependency-linter.yaml or configure [tool.python-dependency-linter] in pyproject.toml.
|
|
307
304
|
```
|
|
308
305
|
|
|
309
306
|
## Pre-commit
|
|
@@ -317,14 +314,14 @@ Add to `.pre-commit-config.yaml`:
|
|
|
317
314
|
- id: python-dependency-linter
|
|
318
315
|
```
|
|
319
316
|
|
|
320
|
-
To pass custom options (e.g., a different config file
|
|
317
|
+
To pass custom options (e.g., a different config file):
|
|
321
318
|
|
|
322
319
|
```yaml
|
|
323
320
|
- repo: https://github.com/heumsi/python-dependency-linter
|
|
324
321
|
rev: v0.1.0
|
|
325
322
|
hooks:
|
|
326
323
|
- id: python-dependency-linter
|
|
327
|
-
args: [--config, custom-config.yaml
|
|
324
|
+
args: [--config, custom-config.yaml]
|
|
328
325
|
```
|
|
329
326
|
|
|
330
327
|
## License
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/cli.py
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import fnmatch
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
import click
|
|
6
7
|
|
|
7
8
|
from python_dependency_linter.checker import check_import
|
|
8
|
-
from python_dependency_linter.config import load_config
|
|
9
|
+
from python_dependency_linter.config import find_config, load_config
|
|
9
10
|
from python_dependency_linter.matcher import find_matching_rules, merge_rules
|
|
10
11
|
from python_dependency_linter.parser import parse_imports
|
|
11
12
|
from python_dependency_linter.reporter import format_violations
|
|
@@ -46,7 +47,7 @@ def _normalize_pattern(pattern: str, project_root: Path) -> str:
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def _matches_any(path: Path, patterns: list[str]) -> bool:
|
|
49
|
-
return any(
|
|
50
|
+
return any(fnmatch.fnmatch(str(path), p) for p in patterns)
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
def _find_python_files(
|
|
@@ -84,19 +85,29 @@ def main():
|
|
|
84
85
|
@click.option(
|
|
85
86
|
"--config",
|
|
86
87
|
"config_path",
|
|
87
|
-
default=
|
|
88
|
+
default=None,
|
|
88
89
|
help="Path to config file.",
|
|
89
90
|
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
def check(config_path: str | None):
|
|
92
|
+
if config_path is not None:
|
|
93
|
+
config_file = Path(config_path)
|
|
94
|
+
if not config_file.exists():
|
|
95
|
+
click.echo(f"Error: Config file not found: {config_file}", err=True)
|
|
96
|
+
raise SystemExit(2)
|
|
97
|
+
root = config_file.resolve().parent
|
|
98
|
+
else:
|
|
99
|
+
config_file = find_config()
|
|
100
|
+
if config_file is None:
|
|
101
|
+
click.echo(
|
|
102
|
+
"Error: Config file not found. "
|
|
103
|
+
"Create .python-dependency-linter.yaml or configure "
|
|
104
|
+
"[tool.python-dependency-linter] in pyproject.toml.",
|
|
105
|
+
err=True,
|
|
106
|
+
)
|
|
107
|
+
raise SystemExit(2)
|
|
108
|
+
root = config_file.resolve().parent
|
|
109
|
+
|
|
110
|
+
config = load_config(config_file)
|
|
100
111
|
|
|
101
112
|
all_violations = []
|
|
102
113
|
python_files = _find_python_files(root, config.include, config.exclude)
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/config.py
RENAMED
|
@@ -62,14 +62,18 @@ def _load_yaml(path: Path) -> Config:
|
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def
|
|
65
|
+
def _load_toml(path: Path) -> dict:
|
|
66
66
|
try:
|
|
67
67
|
import tomllib
|
|
68
68
|
except ImportError:
|
|
69
69
|
import tomli as tomllib # type: ignore[no-redef]
|
|
70
70
|
|
|
71
71
|
with open(path, "rb") as f:
|
|
72
|
-
|
|
72
|
+
return tomllib.load(f)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _load_pyproject_toml(path: Path) -> Config:
|
|
76
|
+
data = _load_toml(path)
|
|
73
77
|
tool_config = data["tool"]["python-dependency-linter"]
|
|
74
78
|
return Config(
|
|
75
79
|
rules=_parse_rules(tool_config["rules"]),
|
|
@@ -78,6 +82,31 @@ def _load_pyproject_toml(path: Path) -> Config:
|
|
|
78
82
|
)
|
|
79
83
|
|
|
80
84
|
|
|
85
|
+
def _has_pdl_section(path: Path) -> bool:
|
|
86
|
+
"""Check if a pyproject.toml contains [tool.python-dependency-linter]."""
|
|
87
|
+
data = _load_toml(path)
|
|
88
|
+
return "python-dependency-linter" in data.get("tool", {})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
_CONFIG_FILENAMES = [".python-dependency-linter.yaml", "pyproject.toml"]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def find_config() -> Path | None:
|
|
95
|
+
"""Search upward from cwd for a config file. Returns None if not found."""
|
|
96
|
+
current = Path.cwd().resolve()
|
|
97
|
+
while True:
|
|
98
|
+
for name in _CONFIG_FILENAMES:
|
|
99
|
+
candidate = current / name
|
|
100
|
+
if candidate.is_file():
|
|
101
|
+
if name == "pyproject.toml" and not _has_pdl_section(candidate):
|
|
102
|
+
continue
|
|
103
|
+
return candidate
|
|
104
|
+
parent = current.parent
|
|
105
|
+
if parent == current:
|
|
106
|
+
return None
|
|
107
|
+
current = parent
|
|
108
|
+
|
|
109
|
+
|
|
81
110
|
def load_config(path: Path) -> Config:
|
|
82
111
|
if not path.exists():
|
|
83
112
|
raise FileNotFoundError(f"Config file not found: {path}")
|
|
@@ -7,7 +7,7 @@ from python_dependency_linter.cli import main
|
|
|
7
7
|
FIXTURES = Path(__file__).parent / "fixtures"
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def test_cli_check_with_violations(tmp_path):
|
|
10
|
+
def test_cli_check_with_violations(tmp_path, monkeypatch):
|
|
11
11
|
config_content = """\
|
|
12
12
|
rules:
|
|
13
13
|
- name: domain-isolation
|
|
@@ -27,16 +27,15 @@ rules:
|
|
|
27
27
|
dst = tmp_path / "contexts"
|
|
28
28
|
shutil.copytree(src, dst)
|
|
29
29
|
|
|
30
|
+
monkeypatch.chdir(tmp_path)
|
|
30
31
|
runner = CliRunner()
|
|
31
|
-
result = runner.invoke(
|
|
32
|
-
main, ["check", "--config", str(config_file), "--project-root", str(tmp_path)]
|
|
33
|
-
)
|
|
32
|
+
result = runner.invoke(main, ["check"])
|
|
34
33
|
assert result.exit_code == 1
|
|
35
34
|
assert "[domain-isolation]" in result.output
|
|
36
35
|
assert "Found" in result.output
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
def test_cli_check_no_violations(tmp_path):
|
|
38
|
+
def test_cli_check_no_violations(tmp_path, monkeypatch):
|
|
40
39
|
config_content = """\
|
|
41
40
|
rules:
|
|
42
41
|
- name: allow-all
|
|
@@ -55,15 +54,14 @@ rules:
|
|
|
55
54
|
dst = tmp_path / "contexts"
|
|
56
55
|
shutil.copytree(src, dst)
|
|
57
56
|
|
|
57
|
+
monkeypatch.chdir(tmp_path)
|
|
58
58
|
runner = CliRunner()
|
|
59
|
-
result = runner.invoke(
|
|
60
|
-
main, ["check", "--config", str(config_file), "--project-root", str(tmp_path)]
|
|
61
|
-
)
|
|
59
|
+
result = runner.invoke(main, ["check"])
|
|
62
60
|
assert result.exit_code == 0
|
|
63
61
|
assert "No violations found." in result.output
|
|
64
62
|
|
|
65
63
|
|
|
66
|
-
def test_cli_check_with_include(tmp_path):
|
|
64
|
+
def test_cli_check_with_include(tmp_path, monkeypatch):
|
|
67
65
|
"""Files outside include paths should be skipped."""
|
|
68
66
|
config_content = """\
|
|
69
67
|
include:
|
|
@@ -74,7 +72,7 @@ rules:
|
|
|
74
72
|
deny:
|
|
75
73
|
third_party: [pydantic]
|
|
76
74
|
"""
|
|
77
|
-
config_file = tmp_path / "
|
|
75
|
+
config_file = tmp_path / ".python-dependency-linter.yaml"
|
|
78
76
|
config_file.write_text(config_content)
|
|
79
77
|
|
|
80
78
|
# Create files inside and outside include path
|
|
@@ -88,16 +86,52 @@ rules:
|
|
|
88
86
|
(other / "__init__.py").write_text("")
|
|
89
87
|
(other / "app.py").write_text("import pydantic\n")
|
|
90
88
|
|
|
89
|
+
monkeypatch.chdir(tmp_path)
|
|
91
90
|
runner = CliRunner()
|
|
92
|
-
result = runner.invoke(
|
|
93
|
-
main, ["check", "--config", str(config_file), "--project-root", str(tmp_path)]
|
|
94
|
-
)
|
|
91
|
+
result = runner.invoke(main, ["check"])
|
|
95
92
|
assert result.exit_code == 1
|
|
96
93
|
assert "src/app.py" in result.output
|
|
97
94
|
assert "other/app.py" not in result.output
|
|
98
95
|
|
|
99
96
|
|
|
100
|
-
def
|
|
97
|
+
def test_cli_check_with_include_nested(tmp_path, monkeypatch):
|
|
98
|
+
"""Include should match files in deeply nested subdirectories."""
|
|
99
|
+
config_content = """\
|
|
100
|
+
include:
|
|
101
|
+
- src
|
|
102
|
+
rules:
|
|
103
|
+
- name: domain-isolation
|
|
104
|
+
modules: "**"
|
|
105
|
+
deny:
|
|
106
|
+
third_party: [pydantic]
|
|
107
|
+
"""
|
|
108
|
+
config_file = tmp_path / ".python-dependency-linter.yaml"
|
|
109
|
+
config_file.write_text(config_content)
|
|
110
|
+
|
|
111
|
+
# Create deeply nested files inside include path
|
|
112
|
+
nested = tmp_path / "src" / "contexts" / "analytics" / "domain"
|
|
113
|
+
nested.mkdir(parents=True)
|
|
114
|
+
(tmp_path / "src" / "__init__.py").write_text("")
|
|
115
|
+
(tmp_path / "src" / "contexts" / "__init__.py").write_text("")
|
|
116
|
+
(tmp_path / "src" / "contexts" / "analytics" / "__init__.py").write_text("")
|
|
117
|
+
(nested / "__init__.py").write_text("")
|
|
118
|
+
(nested / "models.py").write_text("import pydantic\n")
|
|
119
|
+
|
|
120
|
+
# Create files outside include path
|
|
121
|
+
other = tmp_path / "other"
|
|
122
|
+
other.mkdir()
|
|
123
|
+
(other / "__init__.py").write_text("")
|
|
124
|
+
(other / "app.py").write_text("import pydantic\n")
|
|
125
|
+
|
|
126
|
+
monkeypatch.chdir(tmp_path)
|
|
127
|
+
runner = CliRunner()
|
|
128
|
+
result = runner.invoke(main, ["check"])
|
|
129
|
+
assert result.exit_code == 1
|
|
130
|
+
assert "src/contexts/analytics/domain/models.py" in result.output
|
|
131
|
+
assert "other/app.py" not in result.output
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_cli_check_with_exclude(tmp_path, monkeypatch):
|
|
101
135
|
"""Files matching exclude patterns should be skipped."""
|
|
102
136
|
config_content = """\
|
|
103
137
|
exclude:
|
|
@@ -108,7 +142,7 @@ rules:
|
|
|
108
142
|
deny:
|
|
109
143
|
third_party: [pydantic]
|
|
110
144
|
"""
|
|
111
|
-
config_file = tmp_path / "
|
|
145
|
+
config_file = tmp_path / ".python-dependency-linter.yaml"
|
|
112
146
|
config_file.write_text(config_content)
|
|
113
147
|
|
|
114
148
|
src = tmp_path / "src"
|
|
@@ -121,16 +155,15 @@ rules:
|
|
|
121
155
|
(generated / "__init__.py").write_text("")
|
|
122
156
|
(generated / "models.py").write_text("import pydantic\n")
|
|
123
157
|
|
|
158
|
+
monkeypatch.chdir(tmp_path)
|
|
124
159
|
runner = CliRunner()
|
|
125
|
-
result = runner.invoke(
|
|
126
|
-
main, ["check", "--config", str(config_file), "--project-root", str(tmp_path)]
|
|
127
|
-
)
|
|
160
|
+
result = runner.invoke(main, ["check"])
|
|
128
161
|
assert result.exit_code == 1
|
|
129
162
|
assert "src/app.py" in result.output
|
|
130
163
|
assert "generated/" not in result.output
|
|
131
164
|
|
|
132
165
|
|
|
133
|
-
def test_cli_check_with_include_and_exclude(tmp_path):
|
|
166
|
+
def test_cli_check_with_include_and_exclude(tmp_path, monkeypatch):
|
|
134
167
|
"""Exclude should filter within included paths."""
|
|
135
168
|
config_content = """\
|
|
136
169
|
include:
|
|
@@ -143,7 +176,7 @@ rules:
|
|
|
143
176
|
deny:
|
|
144
177
|
third_party: [pydantic]
|
|
145
178
|
"""
|
|
146
|
-
config_file = tmp_path / "
|
|
179
|
+
config_file = tmp_path / ".python-dependency-linter.yaml"
|
|
147
180
|
config_file.write_text(config_content)
|
|
148
181
|
|
|
149
182
|
app = tmp_path / "src"
|
|
@@ -156,16 +189,51 @@ rules:
|
|
|
156
189
|
(generated / "__init__.py").write_text("")
|
|
157
190
|
(generated / "models.py").write_text("import pydantic\n")
|
|
158
191
|
|
|
192
|
+
monkeypatch.chdir(tmp_path)
|
|
159
193
|
runner = CliRunner()
|
|
160
|
-
result = runner.invoke(
|
|
161
|
-
main, ["check", "--config", str(config_file), "--project-root", str(tmp_path)]
|
|
162
|
-
)
|
|
194
|
+
result = runner.invoke(main, ["check"])
|
|
163
195
|
assert result.exit_code == 1
|
|
164
196
|
assert "src/app.py" in result.output
|
|
165
197
|
assert "generated/" not in result.output
|
|
166
198
|
|
|
167
199
|
|
|
168
|
-
def test_cli_check_config_not_found():
|
|
200
|
+
def test_cli_check_config_not_found(tmp_path, monkeypatch):
|
|
201
|
+
monkeypatch.chdir(tmp_path)
|
|
202
|
+
runner = CliRunner()
|
|
203
|
+
result = runner.invoke(main, ["check"])
|
|
204
|
+
assert result.exit_code == 2
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_cli_check_explicit_config_not_found():
|
|
169
208
|
runner = CliRunner()
|
|
170
209
|
result = runner.invoke(main, ["check", "--config", "nonexistent.yaml"])
|
|
171
210
|
assert result.exit_code == 2
|
|
211
|
+
assert "not found" in result.output.lower()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_cli_check_with_explicit_config(tmp_path, monkeypatch):
|
|
215
|
+
"""--config should use the config file's parent as project root."""
|
|
216
|
+
project_dir = tmp_path / "project"
|
|
217
|
+
project_dir.mkdir()
|
|
218
|
+
|
|
219
|
+
config_content = """\
|
|
220
|
+
rules:
|
|
221
|
+
- name: domain-isolation
|
|
222
|
+
modules: "**"
|
|
223
|
+
deny:
|
|
224
|
+
third_party: [pydantic]
|
|
225
|
+
"""
|
|
226
|
+
config_file = project_dir / "custom-config.yaml"
|
|
227
|
+
config_file.write_text(config_content)
|
|
228
|
+
|
|
229
|
+
src = project_dir / "src"
|
|
230
|
+
src.mkdir()
|
|
231
|
+
(src / "__init__.py").write_text("")
|
|
232
|
+
(src / "app.py").write_text("import pydantic\n")
|
|
233
|
+
|
|
234
|
+
# Run from a different directory, but point --config to project_dir
|
|
235
|
+
monkeypatch.chdir(tmp_path)
|
|
236
|
+
runner = CliRunner()
|
|
237
|
+
result = runner.invoke(main, ["check", "--config", str(config_file)])
|
|
238
|
+
assert result.exit_code == 1
|
|
239
|
+
assert "src/app.py" in result.output
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from python_dependency_linter.config import load_config
|
|
3
|
+
from python_dependency_linter.config import find_config, load_config
|
|
4
4
|
|
|
5
5
|
FIXTURES = Path(__file__).parent / "fixtures"
|
|
6
6
|
|
|
@@ -81,3 +81,46 @@ def test_load_config_file_not_found():
|
|
|
81
81
|
|
|
82
82
|
with pytest.raises(FileNotFoundError):
|
|
83
83
|
load_config(Path("nonexistent.yaml"))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_find_config_yaml_in_cwd(tmp_path, monkeypatch):
|
|
87
|
+
(tmp_path / ".python-dependency-linter.yaml").write_text("rules: []\n")
|
|
88
|
+
monkeypatch.chdir(tmp_path)
|
|
89
|
+
assert find_config() == tmp_path / ".python-dependency-linter.yaml"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_find_config_yaml_in_parent(tmp_path, monkeypatch):
|
|
93
|
+
(tmp_path / ".python-dependency-linter.yaml").write_text("rules: []\n")
|
|
94
|
+
child = tmp_path / "sub"
|
|
95
|
+
child.mkdir()
|
|
96
|
+
monkeypatch.chdir(child)
|
|
97
|
+
assert find_config() == tmp_path / ".python-dependency-linter.yaml"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_find_config_pyproject_toml(tmp_path, monkeypatch):
|
|
101
|
+
toml_content = "[tool.python-dependency-linter]\nrules = []\n"
|
|
102
|
+
(tmp_path / "pyproject.toml").write_text(toml_content)
|
|
103
|
+
monkeypatch.chdir(tmp_path)
|
|
104
|
+
assert find_config() == tmp_path / "pyproject.toml"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_find_config_yaml_preferred_over_toml(tmp_path, monkeypatch):
|
|
108
|
+
"""When both exist in the same directory, YAML wins."""
|
|
109
|
+
(tmp_path / ".python-dependency-linter.yaml").write_text("rules: []\n")
|
|
110
|
+
toml_content = "[tool.python-dependency-linter]\nrules = []\n"
|
|
111
|
+
(tmp_path / "pyproject.toml").write_text(toml_content)
|
|
112
|
+
monkeypatch.chdir(tmp_path)
|
|
113
|
+
assert find_config() == tmp_path / ".python-dependency-linter.yaml"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_find_config_not_found(tmp_path, monkeypatch):
|
|
117
|
+
monkeypatch.chdir(tmp_path)
|
|
118
|
+
result = find_config()
|
|
119
|
+
assert result is None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_find_config_skips_pyproject_without_section(tmp_path, monkeypatch):
|
|
123
|
+
"""pyproject.toml without [tool.python-dependency-linter] should be skipped."""
|
|
124
|
+
(tmp_path / "pyproject.toml").write_text("[tool.other]\nfoo = 1\n")
|
|
125
|
+
monkeypatch.chdir(tmp_path)
|
|
126
|
+
assert find_config() is None
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.claude/skills/commit/SKILL.md
RENAMED
|
File without changes
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.claude/skills/release/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/ISSUE_TEMPLATE/config.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/pull_request_template.md
RENAMED
|
File without changes
|
|
File without changes
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/.github/workflows/publish.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/python_dependency_linter/parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_dependency_linter-0.3.0 → python_dependency_linter-0.4.0}/tests/fixtures/sample_config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|