python-dependency-linter 0.5.0__tar.gz → 0.5.1__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.
Files changed (50) hide show
  1. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/CHANGELOG.md +5 -0
  2. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/PKG-INFO +12 -7
  3. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/README.md +11 -6
  4. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/matcher.py +20 -1
  5. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_matcher.py +59 -0
  6. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.claude/skills/commit/SKILL.md +0 -0
  7. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.claude/skills/release/SKILL.md +0 -0
  8. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  9. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  10. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  11. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/dependabot.yml +0 -0
  12. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/pull_request_template.md +0 -0
  13. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/workflows/ci.yaml +0 -0
  14. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.github/workflows/publish.yaml +0 -0
  15. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.gitignore +0 -0
  16. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.pre-commit-config.yaml +0 -0
  17. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/.pre-commit-hooks.yaml +0 -0
  18. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/CLAUDE.md +0 -0
  19. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/CONTRIBUTING.md +0 -0
  20. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/LICENSE +0 -0
  21. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/pyproject.toml +0 -0
  22. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/__init__.py +0 -0
  23. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/checker.py +0 -0
  24. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/cli.py +0 -0
  25. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/config.py +0 -0
  26. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/parser.py +0 -0
  27. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/reporter.py +0 -0
  28. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/python_dependency_linter/resolver.py +0 -0
  29. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_config.yaml +0 -0
  30. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/__init__.py +0 -0
  31. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/auth/__init__.py +0 -0
  32. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/auth/application/__init__.py +0 -0
  33. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/auth/application/service.py +0 -0
  34. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/auth/domain/__init__.py +0 -0
  35. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/auth/domain/models.py +0 -0
  36. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/__init__.py +0 -0
  37. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/adapters/__init__.py +0 -0
  38. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/adapters/repository.py +0 -0
  39. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/application/__init__.py +0 -0
  40. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/application/service.py +0 -0
  41. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/domain/__init__.py +0 -0
  42. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_project/contexts/boards/domain/models.py +0 -0
  43. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/fixtures/sample_pyproject.toml +0 -0
  44. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_checker.py +0 -0
  45. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_cli.py +0 -0
  46. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_config.py +0 -0
  47. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_parser.py +0 -0
  48. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_reporter.py +0 -0
  49. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/tests/test_resolver.py +0 -0
  50. {python_dependency_linter-0.5.0 → python_dependency_linter-0.5.1}/uv.lock +0 -0
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.5.0] - 2026-03-30
6
+
7
+ ### Features
8
+
9
+ - Add named capture in module patterns for back-referencing in allow/deny (#17)
5
10
  ## [0.4.0] - 2026-03-30
6
11
 
7
12
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-dependency-linter
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: A dependency linter for Python projects
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -283,14 +283,19 @@ Named captures coexist with `*` and `**` wildcards. `{name}` always matches exac
283
283
 
284
284
  ### Submodule Matching
285
285
 
286
- When a pattern is used in `allow` or `deny`, it also matches submodules of the matched module. For example:
286
+ When a pattern is used in `modules`, `allow`, or `deny`, it also matches submodules of the matched module.
287
+
288
+ For example, the following rule applies to `contexts.boards.domain` as well as its submodules like `contexts.boards.domain.models` or `contexts.boards.domain.entities.metric`:
287
289
 
288
290
  ```yaml
289
- allow:
290
- local: [contexts.*.domain]
291
+ rules:
292
+ - name: domain-layer
293
+ modules: contexts.*.domain
294
+ allow:
295
+ local: [contexts.*.domain]
291
296
  ```
292
297
 
293
- This allows imports of `contexts.boards.domain` as well as its submodules like `contexts.boards.domain.models` or `contexts.boards.domain.entities.metric`.
298
+ > **Note:** `contexts.*.domain` matches the module itself (`__init__.py`) **and** all submodules beneath it, while `contexts.*.domain.**` matches submodules only.
294
299
 
295
300
  ### Rule Merging
296
301
 
@@ -368,7 +373,7 @@ Add to `.pre-commit-config.yaml`:
368
373
 
369
374
  ```yaml
370
375
  - repo: https://github.com/heumsi/python-dependency-linter
371
- rev: v0.1.0
376
+ rev: '' # Use the tag you want to point at (e.g., v0.5.0)
372
377
  hooks:
373
378
  - id: python-dependency-linter
374
379
  ```
@@ -377,7 +382,7 @@ To pass custom options (e.g., a different config file):
377
382
 
378
383
  ```yaml
379
384
  - repo: https://github.com/heumsi/python-dependency-linter
380
- rev: v0.1.0
385
+ rev: '' # Use the tag you want to point at (e.g., v0.5.0)
381
386
  hooks:
382
387
  - id: python-dependency-linter
383
388
  args: [--config, custom-config.yaml]
@@ -258,14 +258,19 @@ Named captures coexist with `*` and `**` wildcards. `{name}` always matches exac
258
258
 
259
259
  ### Submodule Matching
260
260
 
261
- When a pattern is used in `allow` or `deny`, it also matches submodules of the matched module. For example:
261
+ When a pattern is used in `modules`, `allow`, or `deny`, it also matches submodules of the matched module.
262
+
263
+ For example, the following rule applies to `contexts.boards.domain` as well as its submodules like `contexts.boards.domain.models` or `contexts.boards.domain.entities.metric`:
262
264
 
263
265
  ```yaml
264
- allow:
265
- local: [contexts.*.domain]
266
+ rules:
267
+ - name: domain-layer
268
+ modules: contexts.*.domain
269
+ allow:
270
+ local: [contexts.*.domain]
266
271
  ```
267
272
 
268
- This allows imports of `contexts.boards.domain` as well as its submodules like `contexts.boards.domain.models` or `contexts.boards.domain.entities.metric`.
273
+ > **Note:** `contexts.*.domain` matches the module itself (`__init__.py`) **and** all submodules beneath it, while `contexts.*.domain.**` matches submodules only.
269
274
 
270
275
  ### Rule Merging
271
276
 
@@ -343,7 +348,7 @@ Add to `.pre-commit-config.yaml`:
343
348
 
344
349
  ```yaml
345
350
  - repo: https://github.com/heumsi/python-dependency-linter
346
- rev: v0.1.0
351
+ rev: '' # Use the tag you want to point at (e.g., v0.5.0)
347
352
  hooks:
348
353
  - id: python-dependency-linter
349
354
  ```
@@ -352,7 +357,7 @@ To pass custom options (e.g., a different config file):
352
357
 
353
358
  ```yaml
354
359
  - repo: https://github.com/heumsi/python-dependency-linter
355
- rev: v0.1.0
360
+ rev: '' # Use the tag you want to point at (e.g., v0.5.0)
356
361
  hooks:
357
362
  - id: python-dependency-linter
358
363
  args: [--config, custom-config.yaml]
@@ -59,12 +59,31 @@ def _match_with_captures(
59
59
  return False
60
60
 
61
61
 
62
+ def match_pattern_with_captures_or_submodule(
63
+ pattern: str, module: str
64
+ ) -> dict[str, str] | None:
65
+ """Match pattern exactly or treat module as a submodule of the pattern."""
66
+ captures = match_pattern_with_captures(pattern, module)
67
+ if captures is not None:
68
+ return captures
69
+ # Check if a prefix of the module matches the pattern.
70
+ # e.g. "contexts.*.domain" should match "contexts.boards.domain.models"
71
+ module_parts = module.split(".")
72
+ pattern_parts = pattern.split(".")
73
+ if len(module_parts) > len(pattern_parts):
74
+ prefix = ".".join(module_parts[: len(pattern_parts)])
75
+ captures = match_pattern_with_captures(pattern, prefix)
76
+ if captures is not None:
77
+ return captures
78
+ return None
79
+
80
+
62
81
  def find_matching_rules(
63
82
  module: str, rules: list[Rule]
64
83
  ) -> list[tuple[Rule, dict[str, str]]]:
65
84
  result = []
66
85
  for r in rules:
67
- captures = match_pattern_with_captures(r.modules, module)
86
+ captures = match_pattern_with_captures_or_submodule(r.modules, module)
68
87
  if captures is not None:
69
88
  result.append((r, captures))
70
89
  return result
@@ -214,3 +214,62 @@ def test_capture_after_double_star():
214
214
  def test_capture_after_double_star_backtrack():
215
215
  result = match_pattern_with_captures("**.{x}.end", "a.b.c.end")
216
216
  assert result == {"x": "c"}
217
+
218
+
219
+ def test_find_matching_rules_submodule():
220
+ """modules pattern should match submodules automatically."""
221
+ rules = [
222
+ Rule(
223
+ name="domain-layer",
224
+ modules="contexts.*.domain",
225
+ allow=AllowDeny(local=["contexts.*.domain"]),
226
+ ),
227
+ ]
228
+ # Exact match still works
229
+ matched = find_matching_rules("contexts.boards.domain", rules)
230
+ assert len(matched) == 1
231
+ assert matched[0][0].name == "domain-layer"
232
+
233
+ # Submodule should also match
234
+ matched = find_matching_rules("contexts.boards.domain.models", rules)
235
+ assert len(matched) == 1
236
+ assert matched[0][0].name == "domain-layer"
237
+
238
+ # Deeper submodule should also match
239
+ matched = find_matching_rules("contexts.boards.domain.entities.metric", rules)
240
+ assert len(matched) == 1
241
+ assert matched[0][0].name == "domain-layer"
242
+
243
+ # Non-matching module should not match
244
+ matched = find_matching_rules("contexts.boards.application.service", rules)
245
+ assert len(matched) == 0
246
+
247
+
248
+ def test_find_matching_rules_submodule_with_captures():
249
+ """modules submodule matching should preserve captures."""
250
+ rules = [
251
+ Rule(
252
+ name="domain-layer",
253
+ modules="contexts.{context}.domain",
254
+ allow=AllowDeny(local=["contexts.{context}.domain"]),
255
+ ),
256
+ ]
257
+ matched = find_matching_rules("contexts.boards.domain.models", rules)
258
+ assert len(matched) == 1
259
+ rule, captures = matched[0]
260
+ assert rule.name == "domain-layer"
261
+ assert captures == {"context": "boards"}
262
+
263
+
264
+ def test_find_matching_rules_submodule_exact_pattern():
265
+ """Exact (no wildcard) modules pattern should also match submodules."""
266
+ rules = [
267
+ Rule(
268
+ name="shared",
269
+ modules="src.shared.domain",
270
+ allow=AllowDeny(local=["src.shared.domain"]),
271
+ ),
272
+ ]
273
+ matched = find_matching_rules("src.shared.domain.entity.user", rules)
274
+ assert len(matched) == 1
275
+ assert matched[0][0].name == "shared"