archunitpython 1.2.0__tar.gz → 1.3.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.0 → archunitpython-1.3.0}/BACKLOG.md +1 -1
- {archunitpython-1.2.0 → archunitpython-1.3.0}/CHANGELOG.md +25 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/PKG-INFO +127 -18
- {archunitpython-1.2.0 → archunitpython-1.3.0}/README.md +126 -17
- {archunitpython-1.2.0 → archunitpython-1.3.0}/pyproject.toml +1 -1
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/__init__.py +1 -1
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/__init__.py +6 -1
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/extraction/extract_graph.py +8 -22
- archunitpython-1.3.0/src/archunitpython/common/fluentapi/__init__.py +7 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/fluentapi/checkable.py +23 -1
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/pattern_matching.py +1 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/cycles/johnsons_apsp.py +4 -9
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/cycles/tarjan_scc.py +2 -6
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/project_cycles.py +1 -2
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/util/logger.py +1 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/assertion/custom_file_logic.py +3 -9
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/assertion/depend_on_external_modules.py +3 -8
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/assertion/depend_on_files.py +4 -12
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/fluentapi/files.py +15 -33
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/assertion/metric_thresholds.py +1 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/calculation/distance.py +1 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/extraction/extract_class_info.py +3 -9
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/fluentapi/export_utils.py +2 -6
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/fluentapi/metrics.py +19 -45
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/fluentapi/slices.py +7 -15
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/uml/generate_rules.py +2 -6
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/testing/assertion.py +11 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/testing/common/violation_factory.py +1 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_extract_graph.py +5 -19
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/files/test_files_fluentapi.py +36 -70
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/metrics_project/service.py +1 -2
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/integration/test_e2e.py +30 -6
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/metrics/test_export.py +2 -6
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/metrics/test_metrics.py +5 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/metrics/test_metrics_fluentapi.py +53 -31
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/slices/test_slices.py +36 -9
- archunitpython-1.2.0/src/archunitpython/common/fluentapi/__init__.py +0 -3
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.editorconfig +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.gitattributes +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/CODEOWNERS +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/FUNDING.yml +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/PAGES.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/dependabot.yml +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/pull_request_template.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/workflows/docs.yaml +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/workflows/integrate.yaml +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.github/workflows/stale.yaml +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.gitignore +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/.releaserc.json +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/CONTRIBUTING.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/LICENSE +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/assets/logo-rounded.png +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/research/product-direction/architecture-testing-landscape.md +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/scripts/bump_release_version.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/scripts/check_release_metadata.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/assertion/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/assertion/violation.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/error/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/error/errors.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/extraction/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/extraction/graph.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/logging/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/logging/types.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/cycles/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/cycles/cycle_utils.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/cycles/cycles.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/cycles/model.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/edge_projections.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/project_edges.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/project_nodes.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/projection/types.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/regex_factory.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/types.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/util/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/common/util/declaration_detector.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/assertion/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/assertion/cycle_free.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/assertion/matching_files.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/files/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/graph/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/graph/graph_reporter.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/layers/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/layers/assertion/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/layers/assertion/layer_dependencies.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/layers/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/layers/fluentapi/layers.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/assertion/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/calculation/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/calculation/count.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/calculation/lcom.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/common/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/common/types.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/extraction/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/metrics/projection/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/py.typed +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/assertion/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/assertion/admissible_edges.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/fluentapi/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/projection/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/projection/slicing_projections.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/uml/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/slices/uml/export_diagram.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/testing/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/testing/common/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/testing/common/color_utils.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/src/archunitpython/testing/pytest_plugin/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_core_types.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_cycles.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_declaration_detector.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_logger.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_pattern_matching.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/common/test_projection.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/files/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/files/test_file_assertions.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/architecture.puml +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/controllers/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/controllers/controller.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/models/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/models/model.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/services/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/services/service.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/services/service_a.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/services/service_b.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/utils/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/fixtures/sample_project/utils/helpers.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/graph/test_graph_reporter.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/integration/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/layers/test_layers.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/metrics/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/slices/__init__.py +0 -0
- {archunitpython-1.2.0 → archunitpython-1.3.0}/tests/test_setup.py +0 -0
|
@@ -12,7 +12,7 @@ This backlog collects product and maintenance ideas from project research.
|
|
|
12
12
|
## P1 - Adoption Workflow
|
|
13
13
|
|
|
14
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.
|
|
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,28 @@
|
|
|
1
|
+
# [1.3.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.2.1...v1.3.0) (2026-07-05)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add because rule rationales ([491c666](https://github.com/LukasNiessen/ArchUnitPython/commit/491c666f1f8a6ef2f5ccd782a4e4c79ed39321f6))
|
|
7
|
+
|
|
8
|
+
## [1.2.1](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.2.0...v1.2.1) (2026-06-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **extract_graph:** validate AST line number before range lookup ([84c9ef4](https://github.com/LukasNiessen/ArchUnitPython/commit/84c9ef4a97333ed1fa302778725ab6d5ba891574))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Performance Improvements
|
|
17
|
+
|
|
18
|
+
* **extract_graph:** optimize file path normalization ([14b4a95](https://github.com/LukasNiessen/ArchUnitPython/commit/14b4a955576d4ee2464c8c2cceb71f28b9135198))
|
|
19
|
+
* **extract_graph:** replace linear search in `_in_type_checking()` with binary search ([1285b5e](https://github.com/LukasNiessen/ArchUnitPython/commit/1285b5e5ac513d7897d42b5e6c1fcb0bd83c1835))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Reverts
|
|
23
|
+
|
|
24
|
+
* Reverted the binary search implementation in `_in_type_checking()` function. ([d06a455](https://github.com/LukasNiessen/ArchUnitPython/commit/d06a45569fdfe5eab5e6825f6046c773b66d61c7))
|
|
25
|
+
|
|
1
26
|
# [1.2.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.1.2...v1.2.0) (2026-06-27)
|
|
2
27
|
|
|
3
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: archunitpython
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -39,11 +39,9 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
<!-- spacer -->
|
|
40
40
|
<p></p>
|
|
41
41
|
|
|
42
|
-
[](https://opensource.org/licenses/MIT)
|
|
43
|
-
[](https://pepy.tech/project/archunitpython)
|
|
46
|
-
[](https://github.com/LukasNiessen/ArchUnitPython)
|
|
42
|
+
[](https://opensource.org/licenses/MIT) [](https://github.com/LukasNiessen/ArchUnitPython/actions/workflows/integrate.yaml) [](https://github.com/LukasNiessen/ArchUnitPython)<br>
|
|
43
|
+
[](https://pepy.tech/project/archunitpython) [](https://pepy.tech/project/archunitpython)
|
|
44
|
+
<!-- [](https://pypi.org/project/archunitpython/) -->
|
|
47
45
|
|
|
48
46
|
</div>
|
|
49
47
|
|
|
@@ -53,7 +51,7 @@ The #1 ArchUnit-style architecture testing library for Python, measured by GitHu
|
|
|
53
51
|
|
|
54
52
|
_Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
|
|
55
53
|
|
|
56
|
-
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Contributing](CONTRIBUTING.md)
|
|
54
|
+
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Sponsor](https://github.com/sponsors/LukasNiessen) • [Contributing](CONTRIBUTING.md)
|
|
57
55
|
|
|
58
56
|
## ⚡ 5 min Quickstart
|
|
59
57
|
|
|
@@ -190,6 +188,25 @@ options = CheckOptions(
|
|
|
190
188
|
violations = rule.check(options)
|
|
191
189
|
```
|
|
192
190
|
|
|
191
|
+
### Explaining Rules With `.because(...)`
|
|
192
|
+
|
|
193
|
+
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
rule = (
|
|
197
|
+
project_files("src/")
|
|
198
|
+
.in_folder("**/controllers/**")
|
|
199
|
+
.should_not()
|
|
200
|
+
.depend_on_files()
|
|
201
|
+
.in_folder("**/database/**")
|
|
202
|
+
.because("controllers should stay thin and delegate persistence")
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
assert_passes(rule)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
When the rule fails, the rationale is included in the assertion message.
|
|
209
|
+
|
|
193
210
|
## 🐹 Use Cases
|
|
194
211
|
|
|
195
212
|
Here is an overview of common use cases.
|
|
@@ -433,21 +450,113 @@ def test_no_forbidden_dependency():
|
|
|
433
450
|
|
|
434
451
|
Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
|
|
435
452
|
|
|
453
|
+
**Using `requests` library repo for example**
|
|
454
|
+
|
|
436
455
|
```python
|
|
437
456
|
from archunitpython import project_graph
|
|
438
457
|
|
|
439
458
|
def test_export_dependency_graph_reports():
|
|
440
|
-
graph = project_graph("src/").titled("Application Architecture")
|
|
441
|
-
|
|
442
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
459
|
+
graph = project_graph("src/requests").titled("Application Architecture")
|
|
460
|
+
|
|
461
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.md")
|
|
462
|
+
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
test_export_dependency_graph_reports()
|
|
465
|
+
```
|
|
466
|
+
**Rendered mermain diagram**
|
|
467
|
+
``` mermaid
|
|
468
|
+
flowchart LR
|
|
469
|
+
n0["__init__.py"]
|
|
470
|
+
n1["__version__.py"]
|
|
471
|
+
n2["_internal_utils.py"]
|
|
472
|
+
n3["_types.py"]
|
|
473
|
+
n4["adapters.py"]
|
|
474
|
+
n5["api.py"]
|
|
475
|
+
n6["auth.py"]
|
|
476
|
+
n7["certs.py"]
|
|
477
|
+
n8["compat.py"]
|
|
478
|
+
n9["cookies.py"]
|
|
479
|
+
n10["exceptions.py"]
|
|
480
|
+
n11["help.py"]
|
|
481
|
+
n12["hooks.py"]
|
|
482
|
+
n13["models.py"]
|
|
483
|
+
n14["packages.py"]
|
|
484
|
+
n15["sessions.py"]
|
|
485
|
+
n16["status_codes.py"]
|
|
486
|
+
n17["structures.py"]
|
|
487
|
+
n18["utils.py"]
|
|
488
|
+
n0 --> n1
|
|
489
|
+
n0 --> n5
|
|
490
|
+
n0 --> n10
|
|
491
|
+
n0 --> n13
|
|
492
|
+
n0 --> n15
|
|
493
|
+
n0 --> n16
|
|
494
|
+
n2 --> n8
|
|
495
|
+
n3 --> n6
|
|
496
|
+
n3 --> n9
|
|
497
|
+
n3 --> n13
|
|
498
|
+
n3 --> n17
|
|
499
|
+
n4 --> n0
|
|
500
|
+
n4 --> n3
|
|
501
|
+
n4 --> n6
|
|
502
|
+
n4 --> n8
|
|
503
|
+
n4 --> n9
|
|
504
|
+
n4 --> n10
|
|
505
|
+
n4 --> n13
|
|
506
|
+
n4 --> n17
|
|
507
|
+
n4 --> n18
|
|
508
|
+
n5 --> n0
|
|
509
|
+
n5 --> n13
|
|
510
|
+
n6 --> n2
|
|
511
|
+
n6 --> n8
|
|
512
|
+
n6 --> n9
|
|
513
|
+
n6 --> n13
|
|
514
|
+
n6 --> n18
|
|
515
|
+
n9 --> n2
|
|
516
|
+
n9 --> n3
|
|
517
|
+
n9 --> n8
|
|
518
|
+
n9 --> n13
|
|
519
|
+
n10 --> n8
|
|
520
|
+
n10 --> n13
|
|
521
|
+
n11 --> n0
|
|
522
|
+
n12 --> n0
|
|
523
|
+
n12 --> n13
|
|
524
|
+
n13 --> n0
|
|
525
|
+
n13 --> n2
|
|
526
|
+
n13 --> n4
|
|
527
|
+
n13 --> n6
|
|
528
|
+
n13 --> n8
|
|
529
|
+
n13 --> n9
|
|
530
|
+
n13 --> n10
|
|
531
|
+
n13 --> n12
|
|
532
|
+
n13 --> n16
|
|
533
|
+
n13 --> n17
|
|
534
|
+
n13 --> n18
|
|
535
|
+
n14 --> n8
|
|
536
|
+
n15 --> n0
|
|
537
|
+
n15 --> n2
|
|
538
|
+
n15 --> n3
|
|
539
|
+
n15 --> n4
|
|
540
|
+
n15 --> n6
|
|
541
|
+
n15 --> n8
|
|
542
|
+
n15 --> n9
|
|
543
|
+
n15 --> n10
|
|
544
|
+
n15 --> n12
|
|
545
|
+
n15 --> n13
|
|
546
|
+
n15 --> n16
|
|
547
|
+
n15 --> n17
|
|
548
|
+
n15 --> n18
|
|
549
|
+
n16 --> n17
|
|
550
|
+
n17 --> n8
|
|
551
|
+
n18 --> n0
|
|
552
|
+
n18 --> n1
|
|
553
|
+
n18 --> n2
|
|
554
|
+
n18 --> n3
|
|
555
|
+
n18 --> n8
|
|
556
|
+
n18 --> n9
|
|
557
|
+
n18 --> n10
|
|
558
|
+
n18 --> n13
|
|
559
|
+
n18 --> n17
|
|
451
560
|
```
|
|
452
561
|
|
|
453
562
|
Supported formats:
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
<!-- spacer -->
|
|
7
7
|
<p></p>
|
|
8
8
|
|
|
9
|
-
[](https://opensource.org/licenses/MIT)
|
|
10
|
-
[](https://pepy.tech/project/archunitpython)
|
|
13
|
-
[](https://github.com/LukasNiessen/ArchUnitPython)
|
|
9
|
+
[](https://opensource.org/licenses/MIT) [](https://github.com/LukasNiessen/ArchUnitPython/actions/workflows/integrate.yaml) [](https://github.com/LukasNiessen/ArchUnitPython)<br>
|
|
10
|
+
[](https://pepy.tech/project/archunitpython) [](https://pepy.tech/project/archunitpython)
|
|
11
|
+
<!-- [](https://pypi.org/project/archunitpython/) -->
|
|
14
12
|
|
|
15
13
|
</div>
|
|
16
14
|
|
|
@@ -20,7 +18,7 @@ The #1 ArchUnit-style architecture testing library for Python, measured by GitHu
|
|
|
20
18
|
|
|
21
19
|
_Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
|
|
22
20
|
|
|
23
|
-
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Contributing](CONTRIBUTING.md)
|
|
21
|
+
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Sponsor](https://github.com/sponsors/LukasNiessen) • [Contributing](CONTRIBUTING.md)
|
|
24
22
|
|
|
25
23
|
## ⚡ 5 min Quickstart
|
|
26
24
|
|
|
@@ -157,6 +155,25 @@ options = CheckOptions(
|
|
|
157
155
|
violations = rule.check(options)
|
|
158
156
|
```
|
|
159
157
|
|
|
158
|
+
### Explaining Rules With `.because(...)`
|
|
159
|
+
|
|
160
|
+
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
rule = (
|
|
164
|
+
project_files("src/")
|
|
165
|
+
.in_folder("**/controllers/**")
|
|
166
|
+
.should_not()
|
|
167
|
+
.depend_on_files()
|
|
168
|
+
.in_folder("**/database/**")
|
|
169
|
+
.because("controllers should stay thin and delegate persistence")
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
assert_passes(rule)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
When the rule fails, the rationale is included in the assertion message.
|
|
176
|
+
|
|
160
177
|
## 🐹 Use Cases
|
|
161
178
|
|
|
162
179
|
Here is an overview of common use cases.
|
|
@@ -400,21 +417,113 @@ def test_no_forbidden_dependency():
|
|
|
400
417
|
|
|
401
418
|
Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
|
|
402
419
|
|
|
420
|
+
**Using `requests` library repo for example**
|
|
421
|
+
|
|
403
422
|
```python
|
|
404
423
|
from archunitpython import project_graph
|
|
405
424
|
|
|
406
425
|
def test_export_dependency_graph_reports():
|
|
407
|
-
graph = project_graph("src/").titled("Application Architecture")
|
|
408
|
-
|
|
409
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
426
|
+
graph = project_graph("src/requests").titled("Application Architecture")
|
|
427
|
+
|
|
428
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.md")
|
|
429
|
+
|
|
430
|
+
if __name__ == "__main__":
|
|
431
|
+
test_export_dependency_graph_reports()
|
|
432
|
+
```
|
|
433
|
+
**Rendered mermain diagram**
|
|
434
|
+
``` mermaid
|
|
435
|
+
flowchart LR
|
|
436
|
+
n0["__init__.py"]
|
|
437
|
+
n1["__version__.py"]
|
|
438
|
+
n2["_internal_utils.py"]
|
|
439
|
+
n3["_types.py"]
|
|
440
|
+
n4["adapters.py"]
|
|
441
|
+
n5["api.py"]
|
|
442
|
+
n6["auth.py"]
|
|
443
|
+
n7["certs.py"]
|
|
444
|
+
n8["compat.py"]
|
|
445
|
+
n9["cookies.py"]
|
|
446
|
+
n10["exceptions.py"]
|
|
447
|
+
n11["help.py"]
|
|
448
|
+
n12["hooks.py"]
|
|
449
|
+
n13["models.py"]
|
|
450
|
+
n14["packages.py"]
|
|
451
|
+
n15["sessions.py"]
|
|
452
|
+
n16["status_codes.py"]
|
|
453
|
+
n17["structures.py"]
|
|
454
|
+
n18["utils.py"]
|
|
455
|
+
n0 --> n1
|
|
456
|
+
n0 --> n5
|
|
457
|
+
n0 --> n10
|
|
458
|
+
n0 --> n13
|
|
459
|
+
n0 --> n15
|
|
460
|
+
n0 --> n16
|
|
461
|
+
n2 --> n8
|
|
462
|
+
n3 --> n6
|
|
463
|
+
n3 --> n9
|
|
464
|
+
n3 --> n13
|
|
465
|
+
n3 --> n17
|
|
466
|
+
n4 --> n0
|
|
467
|
+
n4 --> n3
|
|
468
|
+
n4 --> n6
|
|
469
|
+
n4 --> n8
|
|
470
|
+
n4 --> n9
|
|
471
|
+
n4 --> n10
|
|
472
|
+
n4 --> n13
|
|
473
|
+
n4 --> n17
|
|
474
|
+
n4 --> n18
|
|
475
|
+
n5 --> n0
|
|
476
|
+
n5 --> n13
|
|
477
|
+
n6 --> n2
|
|
478
|
+
n6 --> n8
|
|
479
|
+
n6 --> n9
|
|
480
|
+
n6 --> n13
|
|
481
|
+
n6 --> n18
|
|
482
|
+
n9 --> n2
|
|
483
|
+
n9 --> n3
|
|
484
|
+
n9 --> n8
|
|
485
|
+
n9 --> n13
|
|
486
|
+
n10 --> n8
|
|
487
|
+
n10 --> n13
|
|
488
|
+
n11 --> n0
|
|
489
|
+
n12 --> n0
|
|
490
|
+
n12 --> n13
|
|
491
|
+
n13 --> n0
|
|
492
|
+
n13 --> n2
|
|
493
|
+
n13 --> n4
|
|
494
|
+
n13 --> n6
|
|
495
|
+
n13 --> n8
|
|
496
|
+
n13 --> n9
|
|
497
|
+
n13 --> n10
|
|
498
|
+
n13 --> n12
|
|
499
|
+
n13 --> n16
|
|
500
|
+
n13 --> n17
|
|
501
|
+
n13 --> n18
|
|
502
|
+
n14 --> n8
|
|
503
|
+
n15 --> n0
|
|
504
|
+
n15 --> n2
|
|
505
|
+
n15 --> n3
|
|
506
|
+
n15 --> n4
|
|
507
|
+
n15 --> n6
|
|
508
|
+
n15 --> n8
|
|
509
|
+
n15 --> n9
|
|
510
|
+
n15 --> n10
|
|
511
|
+
n15 --> n12
|
|
512
|
+
n15 --> n13
|
|
513
|
+
n15 --> n16
|
|
514
|
+
n15 --> n17
|
|
515
|
+
n15 --> n18
|
|
516
|
+
n16 --> n17
|
|
517
|
+
n17 --> n8
|
|
518
|
+
n18 --> n0
|
|
519
|
+
n18 --> n1
|
|
520
|
+
n18 --> n2
|
|
521
|
+
n18 --> n3
|
|
522
|
+
n18 --> n8
|
|
523
|
+
n18 --> n9
|
|
524
|
+
n18 --> n10
|
|
525
|
+
n18 --> n13
|
|
526
|
+
n18 --> n17
|
|
418
527
|
```
|
|
419
528
|
|
|
420
529
|
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.3.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"
|
|
@@ -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.0 → archunitpython-1.3.0}/src/archunitpython/common/extraction/extract_graph.py
RENAMED
|
@@ -91,12 +91,8 @@ def extract_graph(
|
|
|
91
91
|
excludes = (
|
|
92
92
|
list(set(exclude_patterns)) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
|
|
93
93
|
)
|
|
94
|
-
ignore_type_checking_imports = bool(
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
cache_key = _build_cache_key(
|
|
98
|
-
project_path, excludes, ignore_type_checking_imports
|
|
99
|
-
)
|
|
94
|
+
ignore_type_checking_imports = bool(options and options.ignore_type_checking_imports)
|
|
95
|
+
cache_key = _build_cache_key(project_path, excludes, ignore_type_checking_imports)
|
|
100
96
|
|
|
101
97
|
if options and options.clear_cache:
|
|
102
98
|
_graph_cache.pop(cache_key, None)
|
|
@@ -137,6 +133,7 @@ def _extract_graph_uncached(
|
|
|
137
133
|
|
|
138
134
|
edges: list[Edge] = []
|
|
139
135
|
py_files_set = set(py_files)
|
|
136
|
+
normalized_py_file_set = {_normalize(f) for f in py_files_set}
|
|
140
137
|
|
|
141
138
|
for file_path in py_files:
|
|
142
139
|
# Add self-referencing edge (ensures the file appears as a node)
|
|
@@ -148,7 +145,6 @@ def _extract_graph_uncached(
|
|
|
148
145
|
)
|
|
149
146
|
)
|
|
150
147
|
|
|
151
|
-
# Extract and resolve imports
|
|
152
148
|
imports = _extract_located_imports(file_path)
|
|
153
149
|
for located_import in imports:
|
|
154
150
|
module_name = located_import.module_name
|
|
@@ -163,9 +159,7 @@ def _extract_graph_uncached(
|
|
|
163
159
|
)
|
|
164
160
|
if resolved and resolved != _normalize(file_path):
|
|
165
161
|
# Check if the resolved path is in our project
|
|
166
|
-
if not is_external and resolved not in
|
|
167
|
-
_normalize(f) for f in py_files_set
|
|
168
|
-
}:
|
|
162
|
+
if not is_external and resolved not in normalized_py_file_set:
|
|
169
163
|
is_external = True
|
|
170
164
|
|
|
171
165
|
edges.append(
|
|
@@ -190,11 +184,7 @@ def _find_python_files(root: str, exclude: list[str]) -> list[str]:
|
|
|
190
184
|
py_files: list[str] = []
|
|
191
185
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
192
186
|
# Filter out excluded directories in-place
|
|
193
|
-
dirnames[:] = [
|
|
194
|
-
d
|
|
195
|
-
for d in dirnames
|
|
196
|
-
if not _should_exclude(d, exclude)
|
|
197
|
-
]
|
|
187
|
+
dirnames[:] = [d for d in dirnames if not _should_exclude(d, exclude)]
|
|
198
188
|
|
|
199
189
|
for filename in filenames:
|
|
200
190
|
if filename.endswith(".py") and not _should_exclude(filename, exclude):
|
|
@@ -349,18 +339,14 @@ def _find_type_checking_ranges(tree: ast.Module) -> list[tuple[int, int]]:
|
|
|
349
339
|
if is_type_checking and node.body:
|
|
350
340
|
start = node.body[0].lineno
|
|
351
341
|
end = max(
|
|
352
|
-
getattr(n, "end_lineno", n.lineno)
|
|
353
|
-
for n in node.body
|
|
354
|
-
if hasattr(n, "lineno")
|
|
342
|
+
getattr(n, "end_lineno", n.lineno) for n in node.body if hasattr(n, "lineno")
|
|
355
343
|
)
|
|
356
344
|
ranges.append((start, end))
|
|
357
345
|
|
|
358
|
-
return ranges
|
|
346
|
+
return sorted(ranges, key=lambda ele: ele[0])
|
|
359
347
|
|
|
360
348
|
|
|
361
|
-
def _in_type_checking(
|
|
362
|
-
node: ast.AST, ranges: list[tuple[int, int]]
|
|
363
|
-
) -> bool:
|
|
349
|
+
def _in_type_checking(node: ast.AST, ranges: list[tuple[int, int]]) -> bool:
|
|
364
350
|
"""Check if a node is inside a TYPE_CHECKING block."""
|
|
365
351
|
if not hasattr(node, "lineno"):
|
|
366
352
|
return False
|
{archunitpython-1.2.0 → archunitpython-1.3.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
|
|
|
@@ -50,9 +50,7 @@ def matches_pattern(file_path: str, filter_: Filter) -> bool:
|
|
|
50
50
|
return bool(filter_.regexp.search(target_string))
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def matches_pattern_classname(
|
|
54
|
-
class_name: str, file_path: str, filter_: Filter
|
|
55
|
-
) -> bool:
|
|
53
|
+
def matches_pattern_classname(class_name: str, file_path: str, filter_: Filter) -> bool:
|
|
56
54
|
"""Check if a class/file matches a filter, supporting classname target."""
|
|
57
55
|
target = filter_.options.target
|
|
58
56
|
|
|
@@ -53,13 +53,9 @@ class JohnsonsAPSP:
|
|
|
53
53
|
if self._is_part_of_current_start_cycle(current_node):
|
|
54
54
|
self._unblock(current_node)
|
|
55
55
|
else:
|
|
56
|
-
for neighbour in CycleUtils.get_outgoing_neighbours(
|
|
57
|
-
current_node, self._graph
|
|
58
|
-
):
|
|
56
|
+
for neighbour in CycleUtils.get_outgoing_neighbours(current_node, self._graph):
|
|
59
57
|
if self._is_blocked(neighbour):
|
|
60
|
-
self._blocked_map.append(
|
|
61
|
-
_BlockedBy(blocked=current_node, by=neighbour)
|
|
62
|
-
)
|
|
58
|
+
self._blocked_map.append(_BlockedBy(blocked=current_node, by=neighbour))
|
|
63
59
|
|
|
64
60
|
def _unblock(self, node: NumberNode) -> None:
|
|
65
61
|
self._blocked = [n for n in self._blocked if n is not node]
|
|
@@ -74,9 +70,8 @@ class JohnsonsAPSP:
|
|
|
74
70
|
if self._start is None:
|
|
75
71
|
return False
|
|
76
72
|
for cycle in self._cycles:
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
and any(e.from_node == current_node.node for e in cycle)
|
|
73
|
+
if cycle[0].from_node == self._start.node and any(
|
|
74
|
+
e.from_node == current_node.node for e in cycle
|
|
80
75
|
):
|
|
81
76
|
return True
|
|
82
77
|
return False
|
|
@@ -18,9 +18,7 @@ class _Vertex:
|
|
|
18
18
|
class TarjanSCC:
|
|
19
19
|
"""Tarjan's algorithm for finding strongly connected components."""
|
|
20
20
|
|
|
21
|
-
def find_strongly_connected_components(
|
|
22
|
-
self, edges: list[NumberEdge]
|
|
23
|
-
) -> list[list[NumberEdge]]:
|
|
21
|
+
def find_strongly_connected_components(self, edges: list[NumberEdge]) -> list[list[NumberEdge]]:
|
|
24
22
|
"""Find all strongly connected components in the graph.
|
|
25
23
|
|
|
26
24
|
Returns a list of edge lists, where each inner list contains
|
|
@@ -78,9 +76,7 @@ class TarjanSCC:
|
|
|
78
76
|
if scc_vertices:
|
|
79
77
|
scc_ids = {v.id for v in scc_vertices}
|
|
80
78
|
scc_edges = [
|
|
81
|
-
e
|
|
82
|
-
for e in self._edges
|
|
83
|
-
if e.from_node in scc_ids and e.to_node in scc_ids
|
|
79
|
+
e for e in self._edges if e.from_node in scc_ids and e.to_node in scc_ids
|
|
84
80
|
]
|
|
85
81
|
if scc_edges:
|
|
86
82
|
self._sccs.append(scc_edges)
|
|
@@ -44,9 +44,7 @@ class CheckLogger:
|
|
|
44
44
|
|
|
45
45
|
mode = "a" if options.append_to_log_file else "w"
|
|
46
46
|
self._file_handler = logging.FileHandler(str(log_path), mode=mode)
|
|
47
|
-
self._file_handler.setFormatter(
|
|
48
|
-
logging.Formatter("[%(levelname)s] %(message)s")
|
|
49
|
-
)
|
|
47
|
+
self._file_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
|
|
50
48
|
self._logger.addHandler(self._file_handler)
|
|
51
49
|
|
|
52
50
|
def _log(self, level: str, options: LoggingOptions | None, message: str) -> None:
|
|
@@ -83,9 +83,7 @@ def gather_custom_file_violations(
|
|
|
83
83
|
|
|
84
84
|
for node in nodes:
|
|
85
85
|
# Check if node matches all pre-filters
|
|
86
|
-
if pre_filters and not all(
|
|
87
|
-
matches_pattern(node.label, f) for f in pre_filters
|
|
88
|
-
):
|
|
86
|
+
if pre_filters and not all(matches_pattern(node.label, f) for f in pre_filters):
|
|
89
87
|
continue
|
|
90
88
|
|
|
91
89
|
file_info = _build_file_info(node.label)
|
|
@@ -94,14 +92,10 @@ def gather_custom_file_violations(
|
|
|
94
92
|
if is_negated:
|
|
95
93
|
# shouldNot: violation if condition IS True
|
|
96
94
|
if result:
|
|
97
|
-
violations.append(
|
|
98
|
-
CustomFileViolation(message=message, file_info=file_info)
|
|
99
|
-
)
|
|
95
|
+
violations.append(CustomFileViolation(message=message, file_info=file_info))
|
|
100
96
|
else:
|
|
101
97
|
# should: violation if condition is NOT True
|
|
102
98
|
if not result:
|
|
103
|
-
violations.append(
|
|
104
|
-
CustomFileViolation(message=message, file_info=file_info)
|
|
105
|
-
)
|
|
99
|
+
violations.append(CustomFileViolation(message=message, file_info=file_info))
|
|
106
100
|
|
|
107
101
|
return violations
|