archunitpython 1.4.0__tar.gz → 1.6.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.4.0 → archunitpython-1.6.0}/BACKLOG.md +6 -6
- {archunitpython-1.4.0 → archunitpython-1.6.0}/CHANGELOG.md +20 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/PKG-INFO +117 -12
- {archunitpython-1.4.0 → archunitpython-1.6.0}/README.md +115 -10
- {archunitpython-1.4.0 → archunitpython-1.6.0}/pyproject.toml +1 -1
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/__init__.py +5 -1
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/extraction/extract_graph.py +152 -20
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/extraction/graph.py +1 -0
- archunitpython-1.6.0/src/archunitpython/config/__init__.py +5 -0
- archunitpython-1.6.0/src/archunitpython/config/loader.py +119 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/assertion/custom_file_logic.py +3 -3
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/assertion/depend_on_files.py +3 -3
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/assertion/matching_files.py +3 -3
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/assertion/admissible_edges.py +1 -1
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_extract_graph.py +376 -0
- archunitpython-1.6.0/tests/config/__init__.py +1 -0
- archunitpython-1.6.0/tests/config/test_config_loader.py +115 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/integration/test_e2e.py +5 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.editorconfig +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.gitattributes +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/CODEOWNERS +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/FUNDING.yml +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/PAGES.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/dependabot.yml +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/pull_request_template.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/workflows/docs.yaml +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/workflows/integrate.yaml +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.github/workflows/stale.yaml +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.gitignore +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/.releaserc.json +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/CONTRIBUTING.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/LICENSE +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/assets/logo-rounded.png +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/research/product-direction/architecture-testing-landscape.md +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/scripts/bump_release_version.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/scripts/check_release_metadata.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/assertion/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/assertion/violation.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/error/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/error/errors.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/extraction/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/fluentapi/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/fluentapi/checkable.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/logging/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/logging/types.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/pattern_matching.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/cycles/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/cycles/cycle_utils.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/cycles/cycles.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/cycles/johnsons_apsp.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/cycles/model.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/cycles/tarjan_scc.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/edge_projections.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/project_cycles.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/project_edges.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/project_nodes.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/projection/types.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/regex_factory.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/types.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/util/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/util/declaration_detector.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/common/util/logger.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/assertion/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/assertion/cycle_free.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/assertion/depend_on_external_modules.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/fluentapi/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/files/fluentapi/files.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/graph/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/graph/graph_reporter.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/layers/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/layers/assertion/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/layers/assertion/layer_dependencies.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/layers/fluentapi/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/layers/fluentapi/layers.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/assertion/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/assertion/metric_thresholds.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/calculation/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/calculation/count.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/calculation/distance.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/calculation/lcom.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/common/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/common/types.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/extraction/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/extraction/extract_class_info.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/fluentapi/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/fluentapi/export_utils.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/fluentapi/metrics.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/metrics/projection/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/py.typed +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/assertion/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/fluentapi/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/fluentapi/slices.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/projection/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/projection/slicing_projections.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/uml/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/uml/export_diagram.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/slices/uml/generate_rules.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/testing/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/testing/assertion.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/testing/common/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/testing/common/color_utils.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/testing/common/violation_factory.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/src/archunitpython/testing/pytest_plugin/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_core_types.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_cycles.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_declaration_detector.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_logger.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_pattern_matching.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/common/test_projection.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/files/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/files/test_file_assertions.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/files/test_files_fluentapi.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/metrics_project/service.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/architecture.puml +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/controllers/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/controllers/controller.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/models/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/models/model.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/services/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/services/service.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/services/service_a.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/services/service_b.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/utils/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/fixtures/sample_project/utils/helpers.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/graph/test_graph_reporter.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/integration/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/layers/test_layers.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/metrics/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/metrics/test_export.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/metrics/test_metrics.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/metrics/test_metrics_fluentapi.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/slices/__init__.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/slices/test_slices.py +0 -0
- {archunitpython-1.4.0 → archunitpython-1.6.0}/tests/test_setup.py +0 -0
|
@@ -13,14 +13,14 @@ This backlog collects product and maintenance ideas from project research.
|
|
|
13
13
|
|
|
14
14
|
- [x] Add an `.archignore` or similar file, modeled after `.gitignore`, for files that should never be analyzed.
|
|
15
15
|
- [x] Add a `.because(...)` API so rules can carry user-facing rationale into failure messages and generated architecture documentation.
|
|
16
|
-
- Add configuration-file support for common rules, while keeping the fluent Python API as the primary interface.
|
|
16
|
+
- [x] 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
|
|
|
19
19
|
## P1 - Python Import Semantics
|
|
20
20
|
|
|
21
21
|
- Add support for namespace packages that do not contain `__init__.py`.
|
|
22
|
-
- Detect dynamic imports such as `importlib.import_module()` and `__import__()`.
|
|
23
|
-
- Detect conditional imports such as `try/except ImportError`.
|
|
22
|
+
- [x] Detect dynamic imports such as `importlib.import_module()` and `__import__()`.
|
|
23
|
+
- [x] Detect conditional imports such as `try/except ImportError`.
|
|
24
24
|
- Add better `TYPE_CHECKING` import handling, including options to ignore, include, or report type-only imports separately.
|
|
25
25
|
- Improve external dependency rules so users can express allowed and forbidden third-party packages at module or slice level.
|
|
26
26
|
- Consider a public-interface rule inspired by Tach, where modules may only import through declared package APIs.
|
|
@@ -31,11 +31,11 @@ This backlog collects product and maintenance ideas from project research.
|
|
|
31
31
|
- Auto-generate architecture documentation based on tests and rule rationales.
|
|
32
32
|
- Make logged paths clickable in IDEs and common terminal integrations.
|
|
33
33
|
- Add PlantUML or Mermaid export for discovered architecture graphs.
|
|
34
|
-
- Improve metric export examples and document how metric thresholds should be selected.
|
|
34
|
+
- [x] Improve metric export examples and document how metric thresholds should be selected.
|
|
35
35
|
|
|
36
36
|
## P2 - Rule Surface
|
|
37
37
|
|
|
38
|
-
- Add first-class layered architecture helpers so common clean/hexagonal/layered rules require less boilerplate.
|
|
38
|
+
- [x] Add first-class layered architecture helpers so common clean/hexagonal/layered rules require less boilerplate.
|
|
39
39
|
- Add slice isolation helpers for bounded contexts and modular monoliths.
|
|
40
40
|
- Add richer custom rule hooks for dependency edges, files, classes, and metrics.
|
|
41
41
|
- Add transitive dependency checks, especially for "domain must not transitively reach infrastructure" style rules.
|
|
@@ -56,7 +56,7 @@ This backlog collects product and maintenance ideas from project research.
|
|
|
56
56
|
|
|
57
57
|
## P3 - Packaging And Docs
|
|
58
58
|
|
|
59
|
-
- Publish to PyPI as part of the release pipeline if this is not already automated.
|
|
59
|
+
- [x] Publish to PyPI as part of the release pipeline if this is not already automated.
|
|
60
60
|
- Add a Sphinx or MkDocs documentation site.
|
|
61
61
|
- Add a complete example repository or examples folder covering pytest, unittest, PlantUML, metrics, and CI.
|
|
62
62
|
- Add contribution guidance for new rule types and metric implementations.
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
# [1.6.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.5.0...v1.6.0) (2026-09-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* preserve package targets for relative imports ([09c9d19](https://github.com/LukasNiessen/ArchUnitPython/commit/09c9d199dec095aaa60be227bfd7b5c2b7ffb107))
|
|
7
|
+
* preserve relative conditional import targets ([0ae22bb](https://github.com/LukasNiessen/ArchUnitPython/commit/0ae22bbed008c39f39b80ac6428fa1207115a394))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* classify conditional imports ([5f0f600](https://github.com/LukasNiessen/ArchUnitPython/commit/5f0f600e02d82249e0735738b43cc75317eff790))
|
|
13
|
+
|
|
14
|
+
# [1.5.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.4.0...v1.5.0) (2026-07-18)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* load common rules from config ([c7c968a](https://github.com/LukasNiessen/ArchUnitPython/commit/c7c968af20e929f7e7a2372275eaa7e26cb38e5f))
|
|
20
|
+
|
|
1
21
|
# [1.4.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.3.0...v1.4.0) (2026-07-18)
|
|
2
22
|
|
|
3
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: archunitpython
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
|
@@ -41,7 +41,6 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
[](https://opensource.org/licenses/MIT) [](https://github.com/LukasNiessen/ArchUnitPython/actions/workflows/integrate.yaml) [](https://github.com/LukasNiessen/ArchUnitPython)<br>
|
|
43
43
|
[](https://pepy.tech/project/archunitpython) [](https://pepy.tech/project/archunitpython)
|
|
44
|
-
<!-- [](https://pypi.org/project/archunitpython/) -->
|
|
45
44
|
|
|
46
45
|
</div>
|
|
47
46
|
|
|
@@ -51,7 +50,7 @@ The #1 ArchUnit-style architecture testing library for Python, measured by GitHu
|
|
|
51
50
|
|
|
52
51
|
_Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
|
|
53
52
|
|
|
54
|
-
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Sponsor](https://github.com/sponsors/LukasNiessen) • [Contributing](CONTRIBUTING.md)
|
|
53
|
+
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Sponsor](https://github.com/sponsors/LukasNiessen) • [Contributing](CONTRIBUTING.md) • [Documentation](https://lukasniessen.github.io/ArchUnitPython/)
|
|
55
54
|
|
|
56
55
|
## ⚡ 5 min Quickstart
|
|
57
56
|
|
|
@@ -206,6 +205,42 @@ migrations/*.py
|
|
|
206
205
|
|
|
207
206
|
Patterns support comments, blank lines, glob syntax, root-relative paths, path
|
|
208
207
|
patterns, and directory patterns with a trailing `/`.
|
|
208
|
+
|
|
209
|
+
### Loading Common Rules From Config
|
|
210
|
+
|
|
211
|
+
For straightforward shared rules, you can load a JSON config file and still run
|
|
212
|
+
the resulting rules in your normal test suite:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"project_path": "src",
|
|
217
|
+
"rules": [
|
|
218
|
+
{
|
|
219
|
+
"name": "controllers must not use services directly",
|
|
220
|
+
"type": "forbidden_dependency",
|
|
221
|
+
"source": "**/controllers/**",
|
|
222
|
+
"target": "**/services/**"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "source files have no cycles",
|
|
226
|
+
"type": "no_cycles"
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from archunitpython import assert_passes, rules_from_config
|
|
234
|
+
|
|
235
|
+
def test_configured_architecture_rules():
|
|
236
|
+
for rule in rules_from_config("archunitpython.json"):
|
|
237
|
+
assert_passes(rule)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Supported rule types are `no_cycles`, `forbidden_dependency`, and
|
|
241
|
+
`forbidden_external_dependency`. The fluent Python API remains the primary and
|
|
242
|
+
most flexible interface.
|
|
243
|
+
|
|
209
244
|
### Explaining Rules With `.because(...)`
|
|
210
245
|
|
|
211
246
|
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
@@ -342,6 +377,17 @@ ArchUnitPython detects string-based dynamic imports such as `importlib.import_mo
|
|
|
342
377
|
from my_app.adapters.sql import Repository # archunit: ignore
|
|
343
378
|
```
|
|
344
379
|
|
|
380
|
+
### Conditional Imports
|
|
381
|
+
|
|
382
|
+
Imports inside `try` blocks that handle `ImportError` or
|
|
383
|
+
`ModuleNotFoundError` are marked as conditional dependencies. This helps graph
|
|
384
|
+
reports distinguish optional imports and fallback implementations from regular
|
|
385
|
+
runtime imports. Conditional dependencies remain part of architecture checks;
|
|
386
|
+
relative and dynamic imports also retain their original import kind in graph
|
|
387
|
+
reports. An edge may therefore have multiple kinds: CSV reports use
|
|
388
|
+
pipe-delimited values such as `relative|conditional`, while HTML reports list
|
|
389
|
+
both values separately.
|
|
390
|
+
|
|
345
391
|
### Naming Conventions
|
|
346
392
|
|
|
347
393
|
```python
|
|
@@ -357,6 +403,10 @@ def test_naming_patterns():
|
|
|
357
403
|
|
|
358
404
|
### Code Metrics
|
|
359
405
|
|
|
406
|
+
Metric rules evaluate every matching file or class independently. Use filters such as
|
|
407
|
+
`in_folder()`, `with_name()`, and `for_classes_matching()` when different parts of the
|
|
408
|
+
project need different limits.
|
|
409
|
+
|
|
360
410
|
```python
|
|
361
411
|
def test_no_large_files():
|
|
362
412
|
rule = metrics("src/").count().lines_of_code().should_be_below(1000)
|
|
@@ -367,7 +417,7 @@ def test_high_class_cohesion():
|
|
|
367
417
|
assert_passes(rule)
|
|
368
418
|
|
|
369
419
|
def test_method_count():
|
|
370
|
-
rule = metrics("src/").count().method_count().
|
|
420
|
+
rule = metrics("src/").count().method_count().should_be_below_or_equal(20)
|
|
371
421
|
assert_passes(rule)
|
|
372
422
|
|
|
373
423
|
def test_field_count_for_data_classes():
|
|
@@ -381,6 +431,45 @@ def test_field_count_for_data_classes():
|
|
|
381
431
|
assert_passes(rule)
|
|
382
432
|
```
|
|
383
433
|
|
|
434
|
+
#### Comparison Semantics
|
|
435
|
+
|
|
436
|
+
Metric comparisons are exact. In particular, `should_be_below(20)` means `< 20`, so
|
|
437
|
+
a value of exactly `20` is a violation. Use the inclusive form when the limit itself
|
|
438
|
+
should be accepted.
|
|
439
|
+
|
|
440
|
+
| Method | Passing values |
|
|
441
|
+
| --- | --- |
|
|
442
|
+
| `should_be_below(n)` | `< n` |
|
|
443
|
+
| `should_be_below_or_equal(n)` | `<= n` |
|
|
444
|
+
| `should_be_above(n)` | `> n` |
|
|
445
|
+
| `should_be_above_or_equal(n)` | `>= n` |
|
|
446
|
+
| `should_be(n)` | exactly `n` |
|
|
447
|
+
|
|
448
|
+
Not every metric builder exposes every comparison. The available methods are shown
|
|
449
|
+
by the fluent API after selecting a metric.
|
|
450
|
+
|
|
451
|
+
#### Choosing Thresholds
|
|
452
|
+
|
|
453
|
+
There is no universal correct limit for every codebase. Treat thresholds as
|
|
454
|
+
architecture decisions that should reflect the role and maturity of the code:
|
|
455
|
+
|
|
456
|
+
1. Measure the current project before enabling a new rule.
|
|
457
|
+
2. Start at the current maximum, or slightly above it, to prevent further regression.
|
|
458
|
+
3. Use narrower filters when generated code, data classes, or adapters need different limits.
|
|
459
|
+
4. Lower the threshold gradually as existing violations are removed.
|
|
460
|
+
5. Record the reason with `.because(...)` so future maintainers understand the limit.
|
|
461
|
+
|
|
462
|
+
| Metric | Interpretation | Useful starting point |
|
|
463
|
+
| --- | --- | --- |
|
|
464
|
+
| Lines of code | File size and review burden | Current maximum for hand-written files |
|
|
465
|
+
| Method or field count | Class responsibility and size | Current maximum, split by class role |
|
|
466
|
+
| LCOM | Lack of class cohesion; lower is generally better | Baseline one LCOM variant and keep it consistent |
|
|
467
|
+
| Instability | Dependence on outgoing versus incoming dependencies | Compare files with similar architectural roles |
|
|
468
|
+
| Distance from main sequence | Balance between abstractness and instability; closer to zero is better | Observe the current distribution before tightening |
|
|
469
|
+
|
|
470
|
+
Thresholds are guardrails, not quality scores. A metric violation is a prompt to inspect
|
|
471
|
+
the design; it does not automatically mean the code is incorrect.
|
|
472
|
+
|
|
384
473
|
### Distance Metrics
|
|
385
474
|
|
|
386
475
|
```python
|
|
@@ -476,13 +565,13 @@ from archunitpython import project_graph
|
|
|
476
565
|
def test_export_dependency_graph_reports():
|
|
477
566
|
graph = project_graph("src/requests").titled("Application Architecture")
|
|
478
567
|
|
|
479
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.
|
|
568
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.mmd")
|
|
480
569
|
|
|
481
570
|
if __name__ == "__main__":
|
|
482
571
|
test_export_dependency_graph_reports()
|
|
483
572
|
```
|
|
484
|
-
**Exported
|
|
485
|
-
```
|
|
573
|
+
**Exported Mermaid diagram**
|
|
574
|
+
```mermaid
|
|
486
575
|
flowchart LR
|
|
487
576
|
n0["__init__.py"]
|
|
488
577
|
n1["__version__.py"]
|
|
@@ -600,20 +689,36 @@ When you create reports through `project_graph("src/")`, internal file paths are
|
|
|
600
689
|
|
|
601
690
|
### Reports
|
|
602
691
|
|
|
603
|
-
Generate HTML
|
|
692
|
+
Generate an HTML report from metric values collected by your tests or build tooling.
|
|
693
|
+
`MetricsExporter` formats the supplied dictionary; it does not execute metric rules or
|
|
694
|
+
calculate the values itself. _This feature is in beta._
|
|
604
695
|
|
|
605
696
|
```python
|
|
606
697
|
from archunitpython.metrics.fluentapi.export_utils import MetricsExporter, ExportOptions
|
|
607
698
|
|
|
608
|
-
|
|
609
|
-
|
|
699
|
+
metric_summary = {
|
|
700
|
+
"Maximum method count": "18 (limit: <= 20)",
|
|
701
|
+
"Maximum field count": "9 (limit: <= 10)",
|
|
702
|
+
"Maximum lines of code": "420 (limit: < 500)",
|
|
703
|
+
"Highest LCOM96b": "0.24 (limit: < 0.30)",
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
html = MetricsExporter.export_as_html(
|
|
707
|
+
metric_summary,
|
|
610
708
|
ExportOptions(
|
|
611
709
|
output_path="reports/metrics.html",
|
|
612
710
|
title="Architecture Metrics Dashboard",
|
|
711
|
+
include_timestamp=False,
|
|
613
712
|
),
|
|
614
713
|
)
|
|
714
|
+
|
|
715
|
+
assert "Maximum method count" in html
|
|
615
716
|
```
|
|
616
717
|
|
|
718
|
+
Keep labels and units stable if these reports are stored as CI artifacts and compared
|
|
719
|
+
between builds. Continue using executable metric rules with `assert_passes()` as the
|
|
720
|
+
enforcement mechanism.
|
|
721
|
+
|
|
617
722
|
## 🔎 Pattern Matching System
|
|
618
723
|
|
|
619
724
|
We offer three targeting options for pattern matching across all modules:
|
|
@@ -948,7 +1053,7 @@ If ArchUnitPython helps your project, please consider:
|
|
|
948
1053
|
|
|
949
1054
|
### Star History
|
|
950
1055
|
|
|
951
|
-
[](https://star-history.dera.page/#LukasNiessen/ArchUnitPython&Date)
|
|
952
1057
|
|
|
953
1058
|
## 📄 License
|
|
954
1059
|
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://opensource.org/licenses/MIT) [](https://github.com/LukasNiessen/ArchUnitPython/actions/workflows/integrate.yaml) [](https://github.com/LukasNiessen/ArchUnitPython)<br>
|
|
10
10
|
[](https://pepy.tech/project/archunitpython) [](https://pepy.tech/project/archunitpython)
|
|
11
|
-
<!-- [](https://pypi.org/project/archunitpython/) -->
|
|
12
11
|
|
|
13
12
|
</div>
|
|
14
13
|
|
|
@@ -18,7 +17,7 @@ The #1 ArchUnit-style architecture testing library for Python, measured by GitHu
|
|
|
18
17
|
|
|
19
18
|
_Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
|
|
20
19
|
|
|
21
|
-
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Sponsor](https://github.com/sponsors/LukasNiessen) • [Contributing](CONTRIBUTING.md)
|
|
20
|
+
[Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Sponsor](https://github.com/sponsors/LukasNiessen) • [Contributing](CONTRIBUTING.md) • [Documentation](https://lukasniessen.github.io/ArchUnitPython/)
|
|
22
21
|
|
|
23
22
|
## ⚡ 5 min Quickstart
|
|
24
23
|
|
|
@@ -173,6 +172,42 @@ migrations/*.py
|
|
|
173
172
|
|
|
174
173
|
Patterns support comments, blank lines, glob syntax, root-relative paths, path
|
|
175
174
|
patterns, and directory patterns with a trailing `/`.
|
|
175
|
+
|
|
176
|
+
### Loading Common Rules From Config
|
|
177
|
+
|
|
178
|
+
For straightforward shared rules, you can load a JSON config file and still run
|
|
179
|
+
the resulting rules in your normal test suite:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"project_path": "src",
|
|
184
|
+
"rules": [
|
|
185
|
+
{
|
|
186
|
+
"name": "controllers must not use services directly",
|
|
187
|
+
"type": "forbidden_dependency",
|
|
188
|
+
"source": "**/controllers/**",
|
|
189
|
+
"target": "**/services/**"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "source files have no cycles",
|
|
193
|
+
"type": "no_cycles"
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from archunitpython import assert_passes, rules_from_config
|
|
201
|
+
|
|
202
|
+
def test_configured_architecture_rules():
|
|
203
|
+
for rule in rules_from_config("archunitpython.json"):
|
|
204
|
+
assert_passes(rule)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Supported rule types are `no_cycles`, `forbidden_dependency`, and
|
|
208
|
+
`forbidden_external_dependency`. The fluent Python API remains the primary and
|
|
209
|
+
most flexible interface.
|
|
210
|
+
|
|
176
211
|
### Explaining Rules With `.because(...)`
|
|
177
212
|
|
|
178
213
|
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
@@ -309,6 +344,17 @@ ArchUnitPython detects string-based dynamic imports such as `importlib.import_mo
|
|
|
309
344
|
from my_app.adapters.sql import Repository # archunit: ignore
|
|
310
345
|
```
|
|
311
346
|
|
|
347
|
+
### Conditional Imports
|
|
348
|
+
|
|
349
|
+
Imports inside `try` blocks that handle `ImportError` or
|
|
350
|
+
`ModuleNotFoundError` are marked as conditional dependencies. This helps graph
|
|
351
|
+
reports distinguish optional imports and fallback implementations from regular
|
|
352
|
+
runtime imports. Conditional dependencies remain part of architecture checks;
|
|
353
|
+
relative and dynamic imports also retain their original import kind in graph
|
|
354
|
+
reports. An edge may therefore have multiple kinds: CSV reports use
|
|
355
|
+
pipe-delimited values such as `relative|conditional`, while HTML reports list
|
|
356
|
+
both values separately.
|
|
357
|
+
|
|
312
358
|
### Naming Conventions
|
|
313
359
|
|
|
314
360
|
```python
|
|
@@ -324,6 +370,10 @@ def test_naming_patterns():
|
|
|
324
370
|
|
|
325
371
|
### Code Metrics
|
|
326
372
|
|
|
373
|
+
Metric rules evaluate every matching file or class independently. Use filters such as
|
|
374
|
+
`in_folder()`, `with_name()`, and `for_classes_matching()` when different parts of the
|
|
375
|
+
project need different limits.
|
|
376
|
+
|
|
327
377
|
```python
|
|
328
378
|
def test_no_large_files():
|
|
329
379
|
rule = metrics("src/").count().lines_of_code().should_be_below(1000)
|
|
@@ -334,7 +384,7 @@ def test_high_class_cohesion():
|
|
|
334
384
|
assert_passes(rule)
|
|
335
385
|
|
|
336
386
|
def test_method_count():
|
|
337
|
-
rule = metrics("src/").count().method_count().
|
|
387
|
+
rule = metrics("src/").count().method_count().should_be_below_or_equal(20)
|
|
338
388
|
assert_passes(rule)
|
|
339
389
|
|
|
340
390
|
def test_field_count_for_data_classes():
|
|
@@ -348,6 +398,45 @@ def test_field_count_for_data_classes():
|
|
|
348
398
|
assert_passes(rule)
|
|
349
399
|
```
|
|
350
400
|
|
|
401
|
+
#### Comparison Semantics
|
|
402
|
+
|
|
403
|
+
Metric comparisons are exact. In particular, `should_be_below(20)` means `< 20`, so
|
|
404
|
+
a value of exactly `20` is a violation. Use the inclusive form when the limit itself
|
|
405
|
+
should be accepted.
|
|
406
|
+
|
|
407
|
+
| Method | Passing values |
|
|
408
|
+
| --- | --- |
|
|
409
|
+
| `should_be_below(n)` | `< n` |
|
|
410
|
+
| `should_be_below_or_equal(n)` | `<= n` |
|
|
411
|
+
| `should_be_above(n)` | `> n` |
|
|
412
|
+
| `should_be_above_or_equal(n)` | `>= n` |
|
|
413
|
+
| `should_be(n)` | exactly `n` |
|
|
414
|
+
|
|
415
|
+
Not every metric builder exposes every comparison. The available methods are shown
|
|
416
|
+
by the fluent API after selecting a metric.
|
|
417
|
+
|
|
418
|
+
#### Choosing Thresholds
|
|
419
|
+
|
|
420
|
+
There is no universal correct limit for every codebase. Treat thresholds as
|
|
421
|
+
architecture decisions that should reflect the role and maturity of the code:
|
|
422
|
+
|
|
423
|
+
1. Measure the current project before enabling a new rule.
|
|
424
|
+
2. Start at the current maximum, or slightly above it, to prevent further regression.
|
|
425
|
+
3. Use narrower filters when generated code, data classes, or adapters need different limits.
|
|
426
|
+
4. Lower the threshold gradually as existing violations are removed.
|
|
427
|
+
5. Record the reason with `.because(...)` so future maintainers understand the limit.
|
|
428
|
+
|
|
429
|
+
| Metric | Interpretation | Useful starting point |
|
|
430
|
+
| --- | --- | --- |
|
|
431
|
+
| Lines of code | File size and review burden | Current maximum for hand-written files |
|
|
432
|
+
| Method or field count | Class responsibility and size | Current maximum, split by class role |
|
|
433
|
+
| LCOM | Lack of class cohesion; lower is generally better | Baseline one LCOM variant and keep it consistent |
|
|
434
|
+
| Instability | Dependence on outgoing versus incoming dependencies | Compare files with similar architectural roles |
|
|
435
|
+
| Distance from main sequence | Balance between abstractness and instability; closer to zero is better | Observe the current distribution before tightening |
|
|
436
|
+
|
|
437
|
+
Thresholds are guardrails, not quality scores. A metric violation is a prompt to inspect
|
|
438
|
+
the design; it does not automatically mean the code is incorrect.
|
|
439
|
+
|
|
351
440
|
### Distance Metrics
|
|
352
441
|
|
|
353
442
|
```python
|
|
@@ -443,13 +532,13 @@ from archunitpython import project_graph
|
|
|
443
532
|
def test_export_dependency_graph_reports():
|
|
444
533
|
graph = project_graph("src/requests").titled("Application Architecture")
|
|
445
534
|
|
|
446
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.
|
|
535
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.mmd")
|
|
447
536
|
|
|
448
537
|
if __name__ == "__main__":
|
|
449
538
|
test_export_dependency_graph_reports()
|
|
450
539
|
```
|
|
451
|
-
**Exported
|
|
452
|
-
```
|
|
540
|
+
**Exported Mermaid diagram**
|
|
541
|
+
```mermaid
|
|
453
542
|
flowchart LR
|
|
454
543
|
n0["__init__.py"]
|
|
455
544
|
n1["__version__.py"]
|
|
@@ -567,20 +656,36 @@ When you create reports through `project_graph("src/")`, internal file paths are
|
|
|
567
656
|
|
|
568
657
|
### Reports
|
|
569
658
|
|
|
570
|
-
Generate HTML
|
|
659
|
+
Generate an HTML report from metric values collected by your tests or build tooling.
|
|
660
|
+
`MetricsExporter` formats the supplied dictionary; it does not execute metric rules or
|
|
661
|
+
calculate the values itself. _This feature is in beta._
|
|
571
662
|
|
|
572
663
|
```python
|
|
573
664
|
from archunitpython.metrics.fluentapi.export_utils import MetricsExporter, ExportOptions
|
|
574
665
|
|
|
575
|
-
|
|
576
|
-
|
|
666
|
+
metric_summary = {
|
|
667
|
+
"Maximum method count": "18 (limit: <= 20)",
|
|
668
|
+
"Maximum field count": "9 (limit: <= 10)",
|
|
669
|
+
"Maximum lines of code": "420 (limit: < 500)",
|
|
670
|
+
"Highest LCOM96b": "0.24 (limit: < 0.30)",
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
html = MetricsExporter.export_as_html(
|
|
674
|
+
metric_summary,
|
|
577
675
|
ExportOptions(
|
|
578
676
|
output_path="reports/metrics.html",
|
|
579
677
|
title="Architecture Metrics Dashboard",
|
|
678
|
+
include_timestamp=False,
|
|
580
679
|
),
|
|
581
680
|
)
|
|
681
|
+
|
|
682
|
+
assert "Maximum method count" in html
|
|
582
683
|
```
|
|
583
684
|
|
|
685
|
+
Keep labels and units stable if these reports are stored as CI artifacts and compared
|
|
686
|
+
between builds. Continue using executable metric rules with `assert_passes()` as the
|
|
687
|
+
enforcement mechanism.
|
|
688
|
+
|
|
584
689
|
## 🔎 Pattern Matching System
|
|
585
690
|
|
|
586
691
|
We offer three targeting options for pattern matching across all modules:
|
|
@@ -915,7 +1020,7 @@ If ArchUnitPython helps your project, please consider:
|
|
|
915
1020
|
|
|
916
1021
|
### Star History
|
|
917
1022
|
|
|
918
|
-
[](https://star-history.dera.page/#LukasNiessen/ArchUnitPython&Date)
|
|
919
1024
|
|
|
920
1025
|
## 📄 License
|
|
921
1026
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "archunitpython"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.6.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,6 @@
|
|
|
1
1
|
"""ArchUnitPython - Architecture testing library for Python projects."""
|
|
2
2
|
|
|
3
|
-
__version__ = "1.
|
|
3
|
+
__version__ = "1.6.0"
|
|
4
4
|
|
|
5
5
|
# Files API
|
|
6
6
|
# Common
|
|
@@ -12,6 +12,7 @@ from archunitpython.common import (
|
|
|
12
12
|
Violation,
|
|
13
13
|
)
|
|
14
14
|
from archunitpython.common.extraction import clear_graph_cache, extract_graph
|
|
15
|
+
from archunitpython.config import ConfiguredRule, rules_from_config
|
|
15
16
|
from archunitpython.files import files, project_files
|
|
16
17
|
from archunitpython.graph import dependency_graph, project_graph
|
|
17
18
|
from archunitpython.layers import layers, project_layers
|
|
@@ -35,6 +36,9 @@ __all__ = [
|
|
|
35
36
|
# Layers
|
|
36
37
|
"project_layers",
|
|
37
38
|
"layers",
|
|
39
|
+
# Config
|
|
40
|
+
"rules_from_config",
|
|
41
|
+
"ConfiguredRule",
|
|
38
42
|
# Slices
|
|
39
43
|
"project_slices",
|
|
40
44
|
# Metrics
|