archunitpython 1.2.1__tar.gz → 1.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.
- {archunitpython-1.2.1 → archunitpython-1.4.0}/BACKLOG.md +2 -2
- {archunitpython-1.2.1 → archunitpython-1.4.0}/CHANGELOG.md +20 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/PKG-INFO +141 -12
- {archunitpython-1.2.1 → archunitpython-1.4.0}/README.md +140 -11
- {archunitpython-1.2.1 → archunitpython-1.4.0}/pyproject.toml +1 -1
- {archunitpython-1.2.1 → archunitpython-1.4.0}/scripts/check_release_metadata.py +1 -1
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/__init__.py +1 -1
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/__init__.py +6 -1
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/extraction/extract_graph.py +79 -11
- archunitpython-1.4.0/src/archunitpython/common/fluentapi/__init__.py +7 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/fluentapi/checkable.py +23 -1
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/fluentapi/files.py +6 -6
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/extraction/extract_class_info.py +6 -3
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/fluentapi/metrics.py +9 -9
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/fluentapi/slices.py +3 -3
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/testing/assertion.py +11 -3
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_declaration_detector.py +2 -2
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_extract_graph.py +68 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_logger.py +0 -4
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/files/test_file_assertions.py +0 -2
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/files/test_files_fluentapi.py +19 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/utils/helpers.py +5 -1
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/integration/test_e2e.py +25 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/metrics/test_metrics.py +8 -9
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/metrics/test_metrics_fluentapi.py +26 -0
- archunitpython-1.2.1/src/archunitpython/common/fluentapi/__init__.py +0 -3
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.editorconfig +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.gitattributes +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/CODEOWNERS +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/FUNDING.yml +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/PAGES.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/dependabot.yml +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/pull_request_template.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/workflows/docs.yaml +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/workflows/integrate.yaml +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.github/workflows/stale.yaml +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.gitignore +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/.releaserc.json +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/CONTRIBUTING.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/LICENSE +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/assets/logo-rounded.png +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/research/product-direction/architecture-testing-landscape.md +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/scripts/bump_release_version.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/assertion/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/assertion/violation.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/error/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/error/errors.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/extraction/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/extraction/graph.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/logging/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/logging/types.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/pattern_matching.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/cycles/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/cycles/cycle_utils.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/cycles/cycles.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/cycles/johnsons_apsp.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/cycles/model.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/cycles/tarjan_scc.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/edge_projections.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/project_cycles.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/project_edges.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/project_nodes.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/projection/types.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/regex_factory.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/types.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/util/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/util/declaration_detector.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/util/logger.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/assertion/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/assertion/custom_file_logic.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/assertion/cycle_free.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/assertion/depend_on_external_modules.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/assertion/depend_on_files.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/assertion/matching_files.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/files/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/graph/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/graph/graph_reporter.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/layers/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/layers/assertion/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/layers/assertion/layer_dependencies.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/layers/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/layers/fluentapi/layers.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/assertion/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/assertion/metric_thresholds.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/calculation/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/calculation/count.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/calculation/distance.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/calculation/lcom.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/common/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/common/types.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/extraction/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/fluentapi/export_utils.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/metrics/projection/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/py.typed +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/assertion/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/assertion/admissible_edges.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/projection/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/projection/slicing_projections.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/uml/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/uml/export_diagram.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/slices/uml/generate_rules.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/testing/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/testing/common/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/testing/common/color_utils.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/testing/common/violation_factory.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/testing/pytest_plugin/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_core_types.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_cycles.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_pattern_matching.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/common/test_projection.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/files/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/metrics_project/service.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/architecture.puml +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/controllers/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/controllers/controller.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/models/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/models/model.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/services/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/services/service.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/services/service_a.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/services/service_b.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/fixtures/sample_project/utils/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/graph/test_graph_reporter.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/integration/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/layers/test_layers.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/metrics/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/metrics/test_export.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/slices/__init__.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/slices/test_slices.py +0 -0
- {archunitpython-1.2.1 → archunitpython-1.4.0}/tests/test_setup.py +0 -0
|
@@ -11,8 +11,8 @@ This backlog collects product and maintenance ideas from project research.
|
|
|
11
11
|
|
|
12
12
|
## P1 - Adoption Workflow
|
|
13
13
|
|
|
14
|
-
- Add an `.archignore` or similar file, modeled after `.gitignore`, for files that should never be analyzed.
|
|
15
|
-
- Add a `.because(...)` API so rules can carry user-facing rationale into failure messages and generated architecture documentation.
|
|
14
|
+
- [x] Add an `.archignore` or similar file, modeled after `.gitignore`, for files that should never be analyzed.
|
|
15
|
+
- [x] Add a `.because(...)` API so rules can carry user-facing rationale into failure messages and generated architecture documentation.
|
|
16
16
|
- Add configuration-file support for common rules, while keeping the fluent Python API as the primary interface.
|
|
17
17
|
- Add support for monorepo and multi-package Python projects.
|
|
18
18
|
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.3.0...v1.4.0) (2026-07-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* clarify metadata and exclude messages ([999bb68](https://github.com/LukasNiessen/ArchUnitPython/commit/999bb689594ff613b5f4445191509bcdaa9a514f))
|
|
7
|
+
* harden archignore loading ([ae88c93](https://github.com/LukasNiessen/ArchUnitPython/commit/ae88c932ae2838cff0eabab687ac948f4920c3eb))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* support archignore exclusions ([c51c401](https://github.com/LukasNiessen/ArchUnitPython/commit/c51c40148425b08ce0257d75dd95c996d0505b00))
|
|
13
|
+
|
|
14
|
+
# [1.3.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.2.1...v1.3.0) (2026-07-05)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add because rule rationales ([491c666](https://github.com/LukasNiessen/ArchUnitPython/commit/491c666f1f8a6ef2f5ccd782a4e4c79ed39321f6))
|
|
20
|
+
|
|
1
21
|
## [1.2.1](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.2.0...v1.2.1) (2026-06-28)
|
|
2
22
|
|
|
3
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: archunitpython
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Architecture testing library for Python projects. Enforce dependency rules, detect cycles, validate metrics.
|
|
5
5
|
Project-URL: Homepage, https://github.com/LukasNiessen/ArchUnitPython
|
|
6
6
|
Project-URL: Repository, https://github.com/LukasNiessen/ArchUnitPython.git
|
|
@@ -188,6 +188,43 @@ options = CheckOptions(
|
|
|
188
188
|
violations = rule.check(options)
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
+
### Excluding Files With `.archignore`
|
|
192
|
+
|
|
193
|
+
Add a `.archignore` file to your project root to permanently exclude generated or
|
|
194
|
+
irrelevant files from architecture checks and file-based metrics:
|
|
195
|
+
|
|
196
|
+
```gitignore
|
|
197
|
+
# Generated code
|
|
198
|
+
generated/
|
|
199
|
+
|
|
200
|
+
# Migration scripts
|
|
201
|
+
migrations/*.py
|
|
202
|
+
|
|
203
|
+
# A single root-level file
|
|
204
|
+
/legacy_adapter.py
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Patterns support comments, blank lines, glob syntax, root-relative paths, path
|
|
208
|
+
patterns, and directory patterns with a trailing `/`.
|
|
209
|
+
### Explaining Rules With `.because(...)`
|
|
210
|
+
|
|
211
|
+
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
rule = (
|
|
215
|
+
project_files("src/")
|
|
216
|
+
.in_folder("**/controllers/**")
|
|
217
|
+
.should_not()
|
|
218
|
+
.depend_on_files()
|
|
219
|
+
.in_folder("**/database/**")
|
|
220
|
+
.because("controllers should stay thin and delegate persistence")
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
assert_passes(rule)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
When the rule fails, the rationale is included in the assertion message.
|
|
227
|
+
|
|
191
228
|
## 🐹 Use Cases
|
|
192
229
|
|
|
193
230
|
Here is an overview of common use cases.
|
|
@@ -431,21 +468,113 @@ def test_no_forbidden_dependency():
|
|
|
431
468
|
|
|
432
469
|
Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
|
|
433
470
|
|
|
471
|
+
**Using [`requests`](https://github.com/psf/requests) library repo for example**
|
|
472
|
+
|
|
434
473
|
```python
|
|
435
474
|
from archunitpython import project_graph
|
|
436
475
|
|
|
437
476
|
def test_export_dependency_graph_reports():
|
|
438
|
-
graph = project_graph("src/").titled("Application Architecture")
|
|
439
|
-
|
|
440
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
477
|
+
graph = project_graph("src/requests").titled("Application Architecture")
|
|
478
|
+
|
|
479
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.md")
|
|
480
|
+
|
|
481
|
+
if __name__ == "__main__":
|
|
482
|
+
test_export_dependency_graph_reports()
|
|
483
|
+
```
|
|
484
|
+
**Exported mermaid diagram**
|
|
485
|
+
``` mermaid
|
|
486
|
+
flowchart LR
|
|
487
|
+
n0["__init__.py"]
|
|
488
|
+
n1["__version__.py"]
|
|
489
|
+
n2["_internal_utils.py"]
|
|
490
|
+
n3["_types.py"]
|
|
491
|
+
n4["adapters.py"]
|
|
492
|
+
n5["api.py"]
|
|
493
|
+
n6["auth.py"]
|
|
494
|
+
n7["certs.py"]
|
|
495
|
+
n8["compat.py"]
|
|
496
|
+
n9["cookies.py"]
|
|
497
|
+
n10["exceptions.py"]
|
|
498
|
+
n11["help.py"]
|
|
499
|
+
n12["hooks.py"]
|
|
500
|
+
n13["models.py"]
|
|
501
|
+
n14["packages.py"]
|
|
502
|
+
n15["sessions.py"]
|
|
503
|
+
n16["status_codes.py"]
|
|
504
|
+
n17["structures.py"]
|
|
505
|
+
n18["utils.py"]
|
|
506
|
+
n0 --> n1
|
|
507
|
+
n0 --> n5
|
|
508
|
+
n0 --> n10
|
|
509
|
+
n0 --> n13
|
|
510
|
+
n0 --> n15
|
|
511
|
+
n0 --> n16
|
|
512
|
+
n2 --> n8
|
|
513
|
+
n3 --> n6
|
|
514
|
+
n3 --> n9
|
|
515
|
+
n3 --> n13
|
|
516
|
+
n3 --> n17
|
|
517
|
+
n4 --> n0
|
|
518
|
+
n4 --> n3
|
|
519
|
+
n4 --> n6
|
|
520
|
+
n4 --> n8
|
|
521
|
+
n4 --> n9
|
|
522
|
+
n4 --> n10
|
|
523
|
+
n4 --> n13
|
|
524
|
+
n4 --> n17
|
|
525
|
+
n4 --> n18
|
|
526
|
+
n5 --> n0
|
|
527
|
+
n5 --> n13
|
|
528
|
+
n6 --> n2
|
|
529
|
+
n6 --> n8
|
|
530
|
+
n6 --> n9
|
|
531
|
+
n6 --> n13
|
|
532
|
+
n6 --> n18
|
|
533
|
+
n9 --> n2
|
|
534
|
+
n9 --> n3
|
|
535
|
+
n9 --> n8
|
|
536
|
+
n9 --> n13
|
|
537
|
+
n10 --> n8
|
|
538
|
+
n10 --> n13
|
|
539
|
+
n11 --> n0
|
|
540
|
+
n12 --> n0
|
|
541
|
+
n12 --> n13
|
|
542
|
+
n13 --> n0
|
|
543
|
+
n13 --> n2
|
|
544
|
+
n13 --> n4
|
|
545
|
+
n13 --> n6
|
|
546
|
+
n13 --> n8
|
|
547
|
+
n13 --> n9
|
|
548
|
+
n13 --> n10
|
|
549
|
+
n13 --> n12
|
|
550
|
+
n13 --> n16
|
|
551
|
+
n13 --> n17
|
|
552
|
+
n13 --> n18
|
|
553
|
+
n14 --> n8
|
|
554
|
+
n15 --> n0
|
|
555
|
+
n15 --> n2
|
|
556
|
+
n15 --> n3
|
|
557
|
+
n15 --> n4
|
|
558
|
+
n15 --> n6
|
|
559
|
+
n15 --> n8
|
|
560
|
+
n15 --> n9
|
|
561
|
+
n15 --> n10
|
|
562
|
+
n15 --> n12
|
|
563
|
+
n15 --> n13
|
|
564
|
+
n15 --> n16
|
|
565
|
+
n15 --> n17
|
|
566
|
+
n15 --> n18
|
|
567
|
+
n16 --> n17
|
|
568
|
+
n17 --> n8
|
|
569
|
+
n18 --> n0
|
|
570
|
+
n18 --> n1
|
|
571
|
+
n18 --> n2
|
|
572
|
+
n18 --> n3
|
|
573
|
+
n18 --> n8
|
|
574
|
+
n18 --> n9
|
|
575
|
+
n18 --> n10
|
|
576
|
+
n18 --> n13
|
|
577
|
+
n18 --> n17
|
|
449
578
|
```
|
|
450
579
|
|
|
451
580
|
Supported formats:
|
|
@@ -155,6 +155,43 @@ options = CheckOptions(
|
|
|
155
155
|
violations = rule.check(options)
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
### Excluding Files With `.archignore`
|
|
159
|
+
|
|
160
|
+
Add a `.archignore` file to your project root to permanently exclude generated or
|
|
161
|
+
irrelevant files from architecture checks and file-based metrics:
|
|
162
|
+
|
|
163
|
+
```gitignore
|
|
164
|
+
# Generated code
|
|
165
|
+
generated/
|
|
166
|
+
|
|
167
|
+
# Migration scripts
|
|
168
|
+
migrations/*.py
|
|
169
|
+
|
|
170
|
+
# A single root-level file
|
|
171
|
+
/legacy_adapter.py
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Patterns support comments, blank lines, glob syntax, root-relative paths, path
|
|
175
|
+
patterns, and directory patterns with a trailing `/`.
|
|
176
|
+
### Explaining Rules With `.because(...)`
|
|
177
|
+
|
|
178
|
+
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
rule = (
|
|
182
|
+
project_files("src/")
|
|
183
|
+
.in_folder("**/controllers/**")
|
|
184
|
+
.should_not()
|
|
185
|
+
.depend_on_files()
|
|
186
|
+
.in_folder("**/database/**")
|
|
187
|
+
.because("controllers should stay thin and delegate persistence")
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
assert_passes(rule)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
When the rule fails, the rationale is included in the assertion message.
|
|
194
|
+
|
|
158
195
|
## 🐹 Use Cases
|
|
159
196
|
|
|
160
197
|
Here is an overview of common use cases.
|
|
@@ -398,21 +435,113 @@ def test_no_forbidden_dependency():
|
|
|
398
435
|
|
|
399
436
|
Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
|
|
400
437
|
|
|
438
|
+
**Using [`requests`](https://github.com/psf/requests) library repo for example**
|
|
439
|
+
|
|
401
440
|
```python
|
|
402
441
|
from archunitpython import project_graph
|
|
403
442
|
|
|
404
443
|
def test_export_dependency_graph_reports():
|
|
405
|
-
graph = project_graph("src/").titled("Application Architecture")
|
|
406
|
-
|
|
407
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
444
|
+
graph = project_graph("src/requests").titled("Application Architecture")
|
|
445
|
+
|
|
446
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.md")
|
|
447
|
+
|
|
448
|
+
if __name__ == "__main__":
|
|
449
|
+
test_export_dependency_graph_reports()
|
|
450
|
+
```
|
|
451
|
+
**Exported mermaid diagram**
|
|
452
|
+
``` mermaid
|
|
453
|
+
flowchart LR
|
|
454
|
+
n0["__init__.py"]
|
|
455
|
+
n1["__version__.py"]
|
|
456
|
+
n2["_internal_utils.py"]
|
|
457
|
+
n3["_types.py"]
|
|
458
|
+
n4["adapters.py"]
|
|
459
|
+
n5["api.py"]
|
|
460
|
+
n6["auth.py"]
|
|
461
|
+
n7["certs.py"]
|
|
462
|
+
n8["compat.py"]
|
|
463
|
+
n9["cookies.py"]
|
|
464
|
+
n10["exceptions.py"]
|
|
465
|
+
n11["help.py"]
|
|
466
|
+
n12["hooks.py"]
|
|
467
|
+
n13["models.py"]
|
|
468
|
+
n14["packages.py"]
|
|
469
|
+
n15["sessions.py"]
|
|
470
|
+
n16["status_codes.py"]
|
|
471
|
+
n17["structures.py"]
|
|
472
|
+
n18["utils.py"]
|
|
473
|
+
n0 --> n1
|
|
474
|
+
n0 --> n5
|
|
475
|
+
n0 --> n10
|
|
476
|
+
n0 --> n13
|
|
477
|
+
n0 --> n15
|
|
478
|
+
n0 --> n16
|
|
479
|
+
n2 --> n8
|
|
480
|
+
n3 --> n6
|
|
481
|
+
n3 --> n9
|
|
482
|
+
n3 --> n13
|
|
483
|
+
n3 --> n17
|
|
484
|
+
n4 --> n0
|
|
485
|
+
n4 --> n3
|
|
486
|
+
n4 --> n6
|
|
487
|
+
n4 --> n8
|
|
488
|
+
n4 --> n9
|
|
489
|
+
n4 --> n10
|
|
490
|
+
n4 --> n13
|
|
491
|
+
n4 --> n17
|
|
492
|
+
n4 --> n18
|
|
493
|
+
n5 --> n0
|
|
494
|
+
n5 --> n13
|
|
495
|
+
n6 --> n2
|
|
496
|
+
n6 --> n8
|
|
497
|
+
n6 --> n9
|
|
498
|
+
n6 --> n13
|
|
499
|
+
n6 --> n18
|
|
500
|
+
n9 --> n2
|
|
501
|
+
n9 --> n3
|
|
502
|
+
n9 --> n8
|
|
503
|
+
n9 --> n13
|
|
504
|
+
n10 --> n8
|
|
505
|
+
n10 --> n13
|
|
506
|
+
n11 --> n0
|
|
507
|
+
n12 --> n0
|
|
508
|
+
n12 --> n13
|
|
509
|
+
n13 --> n0
|
|
510
|
+
n13 --> n2
|
|
511
|
+
n13 --> n4
|
|
512
|
+
n13 --> n6
|
|
513
|
+
n13 --> n8
|
|
514
|
+
n13 --> n9
|
|
515
|
+
n13 --> n10
|
|
516
|
+
n13 --> n12
|
|
517
|
+
n13 --> n16
|
|
518
|
+
n13 --> n17
|
|
519
|
+
n13 --> n18
|
|
520
|
+
n14 --> n8
|
|
521
|
+
n15 --> n0
|
|
522
|
+
n15 --> n2
|
|
523
|
+
n15 --> n3
|
|
524
|
+
n15 --> n4
|
|
525
|
+
n15 --> n6
|
|
526
|
+
n15 --> n8
|
|
527
|
+
n15 --> n9
|
|
528
|
+
n15 --> n10
|
|
529
|
+
n15 --> n12
|
|
530
|
+
n15 --> n13
|
|
531
|
+
n15 --> n16
|
|
532
|
+
n15 --> n17
|
|
533
|
+
n15 --> n18
|
|
534
|
+
n16 --> n17
|
|
535
|
+
n17 --> n8
|
|
536
|
+
n18 --> n0
|
|
537
|
+
n18 --> n1
|
|
538
|
+
n18 --> n2
|
|
539
|
+
n18 --> n3
|
|
540
|
+
n18 --> n8
|
|
541
|
+
n18 --> n9
|
|
542
|
+
n18 --> n10
|
|
543
|
+
n18 --> n13
|
|
544
|
+
n18 --> n17
|
|
416
545
|
```
|
|
417
546
|
|
|
418
547
|
Supported formats:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "archunitpython"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "Architecture testing library for Python projects. Enforce dependency rules, detect cycles, validate metrics."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -17,7 +17,7 @@ def read_project_version() -> str:
|
|
|
17
17
|
content = PYPROJECT.read_text(encoding="utf-8")
|
|
18
18
|
match = re.search(r'^version = "([^"]+)"$', content, re.MULTILINE)
|
|
19
19
|
if match is None:
|
|
20
|
-
raise RuntimeError("Could not find project.version in pyproject.toml")
|
|
20
|
+
raise RuntimeError("Could not find [project].version in pyproject.toml")
|
|
21
21
|
return match.group(1)
|
|
22
22
|
|
|
23
23
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
from archunitpython.common.assertion.violation import EmptyTestViolation, Violation
|
|
2
2
|
from archunitpython.common.error.errors import TechnicalError, UserError
|
|
3
|
-
from archunitpython.common.fluentapi.checkable import
|
|
3
|
+
from archunitpython.common.fluentapi.checkable import (
|
|
4
|
+
Checkable,
|
|
5
|
+
CheckOptions,
|
|
6
|
+
RuleRationaleMixin,
|
|
7
|
+
)
|
|
4
8
|
from archunitpython.common.logging.types import LoggingOptions
|
|
5
9
|
from archunitpython.common.types import Filter, Pattern, PatternMatchingOptions
|
|
6
10
|
|
|
@@ -11,6 +15,7 @@ __all__ = [
|
|
|
11
15
|
"UserError",
|
|
12
16
|
"Checkable",
|
|
13
17
|
"CheckOptions",
|
|
18
|
+
"RuleRationaleMixin",
|
|
14
19
|
"LoggingOptions",
|
|
15
20
|
"Pattern",
|
|
16
21
|
"Filter",
|
{archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/extraction/extract_graph.py
RENAMED
|
@@ -29,6 +29,8 @@ _DEFAULT_EXCLUDE = [
|
|
|
29
29
|
"*.egg-info",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
+
_ARCHIGNORE_FILE = ".archignore"
|
|
33
|
+
|
|
32
34
|
_IGNORE_DIRECTIVE_REGEX = re.compile(
|
|
33
35
|
r"#\s*archunit(?::|-)\s*ignore"
|
|
34
36
|
r"(?:\([^)]*\))?"
|
|
@@ -88,9 +90,7 @@ def extract_graph(
|
|
|
88
90
|
project_path = os.getcwd()
|
|
89
91
|
|
|
90
92
|
project_path = os.path.abspath(project_path)
|
|
91
|
-
excludes = (
|
|
92
|
-
list(set(exclude_patterns)) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
|
|
93
|
-
)
|
|
93
|
+
excludes = _resolve_exclude_patterns(project_path, exclude_patterns)
|
|
94
94
|
ignore_type_checking_imports = bool(options and options.ignore_type_checking_imports)
|
|
95
95
|
cache_key = _build_cache_key(project_path, excludes, ignore_type_checking_imports)
|
|
96
96
|
|
|
@@ -122,6 +122,34 @@ def _build_cache_key(
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def _resolve_exclude_patterns(
|
|
126
|
+
project_path: str,
|
|
127
|
+
exclude_patterns: list[str] | None,
|
|
128
|
+
) -> list[str]:
|
|
129
|
+
"""Resolve exclude patterns (explicit or defaults) plus any .archignore patterns."""
|
|
130
|
+
excludes = list(exclude_patterns) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
|
|
131
|
+
excludes.extend(_load_archignore_patterns(project_path))
|
|
132
|
+
return excludes
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _load_archignore_patterns(project_path: str) -> list[str]:
|
|
136
|
+
"""Load .archignore patterns from a project root, if present."""
|
|
137
|
+
archignore_path = os.path.join(project_path, _ARCHIGNORE_FILE)
|
|
138
|
+
try:
|
|
139
|
+
with open(archignore_path, "r", encoding="utf-8", errors="replace") as f:
|
|
140
|
+
lines = f.readlines()
|
|
141
|
+
except OSError:
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
patterns: list[str] = []
|
|
145
|
+
for line in lines:
|
|
146
|
+
pattern = line.strip()
|
|
147
|
+
if not pattern or pattern.startswith("#"):
|
|
148
|
+
continue
|
|
149
|
+
patterns.append(pattern)
|
|
150
|
+
return patterns
|
|
151
|
+
|
|
152
|
+
|
|
125
153
|
def _extract_graph_uncached(
|
|
126
154
|
project_path: str,
|
|
127
155
|
exclude_patterns: list[str],
|
|
@@ -160,7 +188,7 @@ def _extract_graph_uncached(
|
|
|
160
188
|
if resolved and resolved != _normalize(file_path):
|
|
161
189
|
# Check if the resolved path is in our project
|
|
162
190
|
if not is_external and resolved not in normalized_py_file_set:
|
|
163
|
-
|
|
191
|
+
continue
|
|
164
192
|
|
|
165
193
|
edges.append(
|
|
166
194
|
Edge(
|
|
@@ -182,25 +210,65 @@ def _normalize(path: str) -> str:
|
|
|
182
210
|
def _find_python_files(root: str, exclude: list[str]) -> list[str]:
|
|
183
211
|
"""Recursively find all .py files, excluding specified patterns."""
|
|
184
212
|
py_files: list[str] = []
|
|
213
|
+
root = os.path.abspath(root)
|
|
185
214
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
186
215
|
# Filter out excluded directories in-place
|
|
187
|
-
dirnames[:] = [
|
|
216
|
+
dirnames[:] = [
|
|
217
|
+
d
|
|
218
|
+
for d in dirnames
|
|
219
|
+
if not _should_exclude_path(os.path.join(dirpath, d), root, exclude, is_dir=True)
|
|
220
|
+
]
|
|
188
221
|
|
|
189
222
|
for filename in filenames:
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
full_path = os.path.join(dirpath, filename)
|
|
224
|
+
if filename.endswith(".py") and not _should_exclude_path(
|
|
225
|
+
full_path, root, exclude, is_dir=False
|
|
226
|
+
):
|
|
192
227
|
py_files.append(os.path.abspath(full_path))
|
|
193
228
|
|
|
194
229
|
return py_files
|
|
195
230
|
|
|
196
231
|
|
|
197
|
-
def
|
|
198
|
-
|
|
232
|
+
def _should_exclude_path(
|
|
233
|
+
path: str,
|
|
234
|
+
root: str,
|
|
235
|
+
patterns: list[str],
|
|
236
|
+
*,
|
|
237
|
+
is_dir: bool,
|
|
238
|
+
) -> bool:
|
|
239
|
+
"""Check if a path matches any exclude pattern."""
|
|
199
240
|
import fnmatch
|
|
200
241
|
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
rel_path = _normalize(os.path.relpath(path, root))
|
|
243
|
+
name = os.path.basename(path)
|
|
244
|
+
|
|
245
|
+
for raw_pattern in patterns:
|
|
246
|
+
pattern = raw_pattern.strip().replace("\\", "/")
|
|
247
|
+
if not pattern or pattern.startswith("#"):
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
pattern = pattern.removeprefix("./")
|
|
251
|
+
anchored = pattern.startswith("/")
|
|
252
|
+
if anchored:
|
|
253
|
+
pattern = pattern[1:]
|
|
254
|
+
|
|
255
|
+
dir_only = pattern.endswith("/")
|
|
256
|
+
if dir_only:
|
|
257
|
+
pattern = pattern.rstrip("/")
|
|
258
|
+
if not is_dir:
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
if not pattern:
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
if "/" in pattern or anchored:
|
|
265
|
+
if fnmatch.fnmatch(rel_path, pattern):
|
|
266
|
+
return True
|
|
267
|
+
if is_dir and rel_path == pattern:
|
|
268
|
+
return True
|
|
269
|
+
elif fnmatch.fnmatch(name, pattern):
|
|
203
270
|
return True
|
|
271
|
+
|
|
204
272
|
return False
|
|
205
273
|
|
|
206
274
|
|
{archunitpython-1.2.1 → archunitpython-1.4.0}/src/archunitpython/common/fluentapi/checkable.py
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Protocol
|
|
6
|
+
from typing import Protocol, TypeVar
|
|
7
7
|
|
|
8
8
|
from archunitpython.common.assertion.violation import Violation
|
|
9
9
|
from archunitpython.common.logging.types import LoggingOptions
|
|
@@ -19,6 +19,28 @@ class CheckOptions:
|
|
|
19
19
|
ignore_type_checking_imports: bool = False
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
T = TypeVar("T", bound="RuleRationaleMixin")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RuleRationaleMixin:
|
|
26
|
+
"""Mixin for checkable rules that can carry a human-readable rationale."""
|
|
27
|
+
|
|
28
|
+
_because_reason: str | None = None
|
|
29
|
+
|
|
30
|
+
def because(self: T, reason: str) -> T:
|
|
31
|
+
"""Attach a rationale explaining why the rule exists."""
|
|
32
|
+
reason = reason.strip()
|
|
33
|
+
if not reason:
|
|
34
|
+
raise ValueError("Rule rationale must not be empty.")
|
|
35
|
+
self._because_reason = reason
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def because_reason(self) -> str | None:
|
|
40
|
+
"""Return the rationale attached with because(), if any."""
|
|
41
|
+
return self._because_reason
|
|
42
|
+
|
|
43
|
+
|
|
22
44
|
class Checkable(Protocol):
|
|
23
45
|
"""Protocol for any architecture rule that can be checked.
|
|
24
46
|
|
|
@@ -14,7 +14,7 @@ from collections.abc import Sequence
|
|
|
14
14
|
|
|
15
15
|
from archunitpython.common.assertion.violation import EmptyTestViolation, Violation
|
|
16
16
|
from archunitpython.common.extraction.extract_graph import extract_graph
|
|
17
|
-
from archunitpython.common.fluentapi.checkable import CheckOptions
|
|
17
|
+
from archunitpython.common.fluentapi.checkable import CheckOptions, RuleRationaleMixin
|
|
18
18
|
from archunitpython.common.pattern_matching import matches_all_patterns
|
|
19
19
|
from archunitpython.common.projection.edge_projections import (
|
|
20
20
|
per_external_edge,
|
|
@@ -322,7 +322,7 @@ def _check_empty_test(
|
|
|
322
322
|
return None
|
|
323
323
|
|
|
324
324
|
|
|
325
|
-
class CycleFreeFileCondition:
|
|
325
|
+
class CycleFreeFileCondition(RuleRationaleMixin):
|
|
326
326
|
"""Checkable that verifies no cycles exist among filtered files."""
|
|
327
327
|
|
|
328
328
|
def __init__(self, project_path: str | None, filters: list[Filter]) -> None:
|
|
@@ -350,7 +350,7 @@ class CycleFreeFileCondition:
|
|
|
350
350
|
return gather_cycle_violations(cycles)
|
|
351
351
|
|
|
352
352
|
|
|
353
|
-
class DependOnFileCondition:
|
|
353
|
+
class DependOnFileCondition(RuleRationaleMixin):
|
|
354
354
|
"""Checkable that verifies file dependency rules."""
|
|
355
355
|
|
|
356
356
|
def __init__(
|
|
@@ -377,7 +377,7 @@ class DependOnFileCondition:
|
|
|
377
377
|
)
|
|
378
378
|
|
|
379
379
|
|
|
380
|
-
class DependOnExternalModuleCondition:
|
|
380
|
+
class DependOnExternalModuleCondition(RuleRationaleMixin):
|
|
381
381
|
"""Checkable that verifies external module dependency rules."""
|
|
382
382
|
|
|
383
383
|
def __init__(
|
|
@@ -409,7 +409,7 @@ class DependOnExternalModuleCondition:
|
|
|
409
409
|
)
|
|
410
410
|
|
|
411
411
|
|
|
412
|
-
class MatchPatternFileCondition:
|
|
412
|
+
class MatchPatternFileCondition(RuleRationaleMixin):
|
|
413
413
|
"""Checkable that verifies files match/don't match patterns."""
|
|
414
414
|
|
|
415
415
|
def __init__(
|
|
@@ -434,7 +434,7 @@ class MatchPatternFileCondition:
|
|
|
434
434
|
return gather_regex_matching_violations(nodes, self._check_filters, self._is_negated)
|
|
435
435
|
|
|
436
436
|
|
|
437
|
-
class CustomFileCheckableCondition:
|
|
437
|
+
class CustomFileCheckableCondition(RuleRationaleMixin):
|
|
438
438
|
"""Checkable that evaluates a custom condition on files."""
|
|
439
439
|
|
|
440
440
|
def __init__(
|
|
@@ -5,7 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
import ast
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
-
from archunitpython.common.extraction.extract_graph import
|
|
8
|
+
from archunitpython.common.extraction.extract_graph import (
|
|
9
|
+
_find_python_files,
|
|
10
|
+
_resolve_exclude_patterns,
|
|
11
|
+
)
|
|
9
12
|
from archunitpython.metrics.common.types import (
|
|
10
13
|
ClassInfo,
|
|
11
14
|
EnhancedClassInfo,
|
|
@@ -33,7 +36,7 @@ def extract_class_info(
|
|
|
33
36
|
project_path = os.getcwd()
|
|
34
37
|
|
|
35
38
|
project_path = os.path.abspath(project_path)
|
|
36
|
-
excludes =
|
|
39
|
+
excludes = _resolve_exclude_patterns(project_path, exclude_patterns)
|
|
37
40
|
py_files = _find_python_files(project_path, excludes)
|
|
38
41
|
|
|
39
42
|
classes: list[ClassInfo] = []
|
|
@@ -53,7 +56,7 @@ def extract_enhanced_class_info(
|
|
|
53
56
|
project_path = os.getcwd()
|
|
54
57
|
|
|
55
58
|
project_path = os.path.abspath(project_path)
|
|
56
|
-
excludes =
|
|
59
|
+
excludes = _resolve_exclude_patterns(project_path, exclude_patterns)
|
|
57
60
|
py_files = _find_python_files(project_path, excludes)
|
|
58
61
|
|
|
59
62
|
results: list[FileAnalysisResult] = []
|