archunitpython 1.5.0__tar.gz → 1.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/workflows/docs.yaml +2 -2
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/workflows/integrate.yaml +6 -6
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/workflows/stale.yaml +1 -1
- {archunitpython-1.5.0 → archunitpython-1.6.1}/BACKLOG.md +5 -5
- {archunitpython-1.5.0 → archunitpython-1.6.1}/CHANGELOG.md +20 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/PKG-INFO +81 -12
- {archunitpython-1.5.0 → archunitpython-1.6.1}/README.md +79 -10
- {archunitpython-1.5.0 → archunitpython-1.6.1}/pyproject.toml +1 -1
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/__init__.py +1 -1
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/extraction/extract_graph.py +152 -20
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/extraction/graph.py +1 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/assertion/custom_file_logic.py +3 -3
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/assertion/depend_on_files.py +3 -3
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/assertion/matching_files.py +3 -3
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/assertion/admissible_edges.py +1 -1
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_extract_graph.py +376 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.editorconfig +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.gitattributes +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/CODEOWNERS +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/FUNDING.yml +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/ISSUE_TEMPLATE/question.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/PAGES.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/dependabot.yml +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.github/pull_request_template.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.gitignore +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/.releaserc.json +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/CONTRIBUTING.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/LICENSE +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/assets/logo-rounded.png +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/research/product-direction/architecture-testing-landscape.md +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/scripts/bump_release_version.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/scripts/check_release_metadata.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/assertion/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/assertion/violation.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/error/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/error/errors.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/extraction/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/fluentapi/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/fluentapi/checkable.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/logging/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/logging/types.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/pattern_matching.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/cycles/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/cycles/cycle_utils.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/cycles/cycles.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/cycles/johnsons_apsp.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/cycles/model.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/cycles/tarjan_scc.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/edge_projections.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/project_cycles.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/project_edges.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/project_nodes.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/projection/types.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/regex_factory.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/types.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/util/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/util/declaration_detector.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/common/util/logger.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/config/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/config/loader.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/assertion/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/assertion/cycle_free.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/assertion/depend_on_external_modules.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/fluentapi/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/files/fluentapi/files.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/graph/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/graph/graph_reporter.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/layers/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/layers/assertion/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/layers/assertion/layer_dependencies.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/layers/fluentapi/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/layers/fluentapi/layers.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/assertion/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/assertion/metric_thresholds.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/calculation/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/calculation/count.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/calculation/distance.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/calculation/lcom.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/common/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/common/types.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/extraction/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/extraction/extract_class_info.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/fluentapi/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/fluentapi/export_utils.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/fluentapi/metrics.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/metrics/projection/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/py.typed +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/assertion/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/fluentapi/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/fluentapi/slices.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/projection/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/projection/slicing_projections.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/uml/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/uml/export_diagram.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/slices/uml/generate_rules.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/testing/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/testing/assertion.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/testing/common/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/testing/common/color_utils.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/testing/common/violation_factory.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/src/archunitpython/testing/pytest_plugin/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_core_types.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_cycles.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_declaration_detector.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_logger.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_pattern_matching.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/common/test_projection.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/config/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/config/test_config_loader.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/files/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/files/test_file_assertions.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/files/test_files_fluentapi.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/metrics_project/service.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/architecture.puml +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/controllers/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/controllers/controller.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/models/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/models/model.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/services/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/services/service.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/services/service_a.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/services/service_b.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/utils/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/fixtures/sample_project/utils/helpers.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/graph/test_graph_reporter.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/integration/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/integration/test_e2e.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/layers/test_layers.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/metrics/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/metrics/test_export.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/metrics/test_metrics.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/metrics/test_metrics_fluentapi.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/slices/__init__.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/slices/test_slices.py +0 -0
- {archunitpython-1.5.0 → archunitpython-1.6.1}/tests/test_setup.py +0 -0
|
@@ -20,12 +20,12 @@ jobs:
|
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
steps:
|
|
22
22
|
- name: Checkout
|
|
23
|
-
uses: actions/checkout@
|
|
23
|
+
uses: actions/checkout@v7
|
|
24
24
|
with:
|
|
25
25
|
fetch-depth: 0
|
|
26
26
|
|
|
27
27
|
- name: Setup Python
|
|
28
|
-
uses: actions/setup-python@
|
|
28
|
+
uses: actions/setup-python@v7
|
|
29
29
|
with:
|
|
30
30
|
python-version: '3.12'
|
|
31
31
|
|
|
@@ -12,13 +12,13 @@ jobs:
|
|
|
12
12
|
matrix:
|
|
13
13
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
15
|
+
- uses: actions/checkout@v7
|
|
16
16
|
- name: Set up Python ${{ matrix.python-version }}
|
|
17
|
-
uses: actions/setup-python@
|
|
17
|
+
uses: actions/setup-python@v7
|
|
18
18
|
with:
|
|
19
19
|
python-version: ${{ matrix.python-version }}
|
|
20
20
|
- name: Cache pip
|
|
21
|
-
uses: actions/cache@
|
|
21
|
+
uses: actions/cache@v6
|
|
22
22
|
with:
|
|
23
23
|
path: ~/.cache/pip
|
|
24
24
|
key: pip-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
|
|
@@ -47,16 +47,16 @@ jobs:
|
|
|
47
47
|
pull-requests: write
|
|
48
48
|
id-token: write
|
|
49
49
|
steps:
|
|
50
|
-
- uses: actions/checkout@
|
|
50
|
+
- uses: actions/checkout@v7
|
|
51
51
|
with:
|
|
52
52
|
fetch-depth: 0
|
|
53
53
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
54
54
|
- name: Setup Node.js
|
|
55
|
-
uses: actions/setup-node@
|
|
55
|
+
uses: actions/setup-node@v7
|
|
56
56
|
with:
|
|
57
57
|
node-version: 22
|
|
58
58
|
- name: Set up Python
|
|
59
|
-
uses: actions/setup-python@
|
|
59
|
+
uses: actions/setup-python@v7
|
|
60
60
|
with:
|
|
61
61
|
python-version: "3.12"
|
|
62
62
|
- name: Install semantic-release plugins
|
|
@@ -19,8 +19,8 @@ This backlog collects product and maintenance ideas from project research.
|
|
|
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.1](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.6.0...v1.6.1) (2026-09-13)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** bump the actions-updates group across 1 directory with 5 updates ([7a0785f](https://github.com/LukasNiessen/ArchUnitPython/commit/7a0785fddfed6c14eb1a245e34e6be2ea949f78d))
|
|
7
|
+
|
|
8
|
+
# [1.6.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.5.0...v1.6.0) (2026-09-12)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* preserve package targets for relative imports ([09c9d19](https://github.com/LukasNiessen/ArchUnitPython/commit/09c9d199dec095aaa60be227bfd7b5c2b7ffb107))
|
|
14
|
+
* preserve relative conditional import targets ([0ae22bb](https://github.com/LukasNiessen/ArchUnitPython/commit/0ae22bbed008c39f39b80ac6428fa1207115a394))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* classify conditional imports ([5f0f600](https://github.com/LukasNiessen/ArchUnitPython/commit/5f0f600e02d82249e0735738b43cc75317eff790))
|
|
20
|
+
|
|
1
21
|
# [1.5.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.4.0...v1.5.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.1
|
|
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
|
|
|
@@ -378,6 +377,17 @@ ArchUnitPython detects string-based dynamic imports such as `importlib.import_mo
|
|
|
378
377
|
from my_app.adapters.sql import Repository # archunit: ignore
|
|
379
378
|
```
|
|
380
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
|
+
|
|
381
391
|
### Naming Conventions
|
|
382
392
|
|
|
383
393
|
```python
|
|
@@ -393,6 +403,10 @@ def test_naming_patterns():
|
|
|
393
403
|
|
|
394
404
|
### Code Metrics
|
|
395
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
|
+
|
|
396
410
|
```python
|
|
397
411
|
def test_no_large_files():
|
|
398
412
|
rule = metrics("src/").count().lines_of_code().should_be_below(1000)
|
|
@@ -403,7 +417,7 @@ def test_high_class_cohesion():
|
|
|
403
417
|
assert_passes(rule)
|
|
404
418
|
|
|
405
419
|
def test_method_count():
|
|
406
|
-
rule = metrics("src/").count().method_count().
|
|
420
|
+
rule = metrics("src/").count().method_count().should_be_below_or_equal(20)
|
|
407
421
|
assert_passes(rule)
|
|
408
422
|
|
|
409
423
|
def test_field_count_for_data_classes():
|
|
@@ -417,6 +431,45 @@ def test_field_count_for_data_classes():
|
|
|
417
431
|
assert_passes(rule)
|
|
418
432
|
```
|
|
419
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
|
+
|
|
420
473
|
### Distance Metrics
|
|
421
474
|
|
|
422
475
|
```python
|
|
@@ -512,13 +565,13 @@ from archunitpython import project_graph
|
|
|
512
565
|
def test_export_dependency_graph_reports():
|
|
513
566
|
graph = project_graph("src/requests").titled("Application Architecture")
|
|
514
567
|
|
|
515
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.
|
|
568
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.mmd")
|
|
516
569
|
|
|
517
570
|
if __name__ == "__main__":
|
|
518
571
|
test_export_dependency_graph_reports()
|
|
519
572
|
```
|
|
520
|
-
**Exported
|
|
521
|
-
```
|
|
573
|
+
**Exported Mermaid diagram**
|
|
574
|
+
```mermaid
|
|
522
575
|
flowchart LR
|
|
523
576
|
n0["__init__.py"]
|
|
524
577
|
n1["__version__.py"]
|
|
@@ -636,20 +689,36 @@ When you create reports through `project_graph("src/")`, internal file paths are
|
|
|
636
689
|
|
|
637
690
|
### Reports
|
|
638
691
|
|
|
639
|
-
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._
|
|
640
695
|
|
|
641
696
|
```python
|
|
642
697
|
from archunitpython.metrics.fluentapi.export_utils import MetricsExporter, ExportOptions
|
|
643
698
|
|
|
644
|
-
|
|
645
|
-
|
|
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,
|
|
646
708
|
ExportOptions(
|
|
647
709
|
output_path="reports/metrics.html",
|
|
648
710
|
title="Architecture Metrics Dashboard",
|
|
711
|
+
include_timestamp=False,
|
|
649
712
|
),
|
|
650
713
|
)
|
|
714
|
+
|
|
715
|
+
assert "Maximum method count" in html
|
|
651
716
|
```
|
|
652
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
|
+
|
|
653
722
|
## 🔎 Pattern Matching System
|
|
654
723
|
|
|
655
724
|
We offer three targeting options for pattern matching across all modules:
|
|
@@ -984,7 +1053,7 @@ If ArchUnitPython helps your project, please consider:
|
|
|
984
1053
|
|
|
985
1054
|
### Star History
|
|
986
1055
|
|
|
987
|
-
[](https://star-history.dera.page/#LukasNiessen/ArchUnitPython&Date)
|
|
988
1057
|
|
|
989
1058
|
## 📄 License
|
|
990
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
|
|
|
@@ -345,6 +344,17 @@ ArchUnitPython detects string-based dynamic imports such as `importlib.import_mo
|
|
|
345
344
|
from my_app.adapters.sql import Repository # archunit: ignore
|
|
346
345
|
```
|
|
347
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
|
+
|
|
348
358
|
### Naming Conventions
|
|
349
359
|
|
|
350
360
|
```python
|
|
@@ -360,6 +370,10 @@ def test_naming_patterns():
|
|
|
360
370
|
|
|
361
371
|
### Code Metrics
|
|
362
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
|
+
|
|
363
377
|
```python
|
|
364
378
|
def test_no_large_files():
|
|
365
379
|
rule = metrics("src/").count().lines_of_code().should_be_below(1000)
|
|
@@ -370,7 +384,7 @@ def test_high_class_cohesion():
|
|
|
370
384
|
assert_passes(rule)
|
|
371
385
|
|
|
372
386
|
def test_method_count():
|
|
373
|
-
rule = metrics("src/").count().method_count().
|
|
387
|
+
rule = metrics("src/").count().method_count().should_be_below_or_equal(20)
|
|
374
388
|
assert_passes(rule)
|
|
375
389
|
|
|
376
390
|
def test_field_count_for_data_classes():
|
|
@@ -384,6 +398,45 @@ def test_field_count_for_data_classes():
|
|
|
384
398
|
assert_passes(rule)
|
|
385
399
|
```
|
|
386
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
|
+
|
|
387
440
|
### Distance Metrics
|
|
388
441
|
|
|
389
442
|
```python
|
|
@@ -479,13 +532,13 @@ from archunitpython import project_graph
|
|
|
479
532
|
def test_export_dependency_graph_reports():
|
|
480
533
|
graph = project_graph("src/requests").titled("Application Architecture")
|
|
481
534
|
|
|
482
|
-
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.
|
|
535
|
+
graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.mmd")
|
|
483
536
|
|
|
484
537
|
if __name__ == "__main__":
|
|
485
538
|
test_export_dependency_graph_reports()
|
|
486
539
|
```
|
|
487
|
-
**Exported
|
|
488
|
-
```
|
|
540
|
+
**Exported Mermaid diagram**
|
|
541
|
+
```mermaid
|
|
489
542
|
flowchart LR
|
|
490
543
|
n0["__init__.py"]
|
|
491
544
|
n1["__version__.py"]
|
|
@@ -603,20 +656,36 @@ When you create reports through `project_graph("src/")`, internal file paths are
|
|
|
603
656
|
|
|
604
657
|
### Reports
|
|
605
658
|
|
|
606
|
-
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._
|
|
607
662
|
|
|
608
663
|
```python
|
|
609
664
|
from archunitpython.metrics.fluentapi.export_utils import MetricsExporter, ExportOptions
|
|
610
665
|
|
|
611
|
-
|
|
612
|
-
|
|
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,
|
|
613
675
|
ExportOptions(
|
|
614
676
|
output_path="reports/metrics.html",
|
|
615
677
|
title="Architecture Metrics Dashboard",
|
|
678
|
+
include_timestamp=False,
|
|
616
679
|
),
|
|
617
680
|
)
|
|
681
|
+
|
|
682
|
+
assert "Maximum method count" in html
|
|
618
683
|
```
|
|
619
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
|
+
|
|
620
689
|
## 🔎 Pattern Matching System
|
|
621
690
|
|
|
622
691
|
We offer three targeting options for pattern matching across all modules:
|
|
@@ -951,7 +1020,7 @@ If ArchUnitPython helps your project, please consider:
|
|
|
951
1020
|
|
|
952
1021
|
### Star History
|
|
953
1022
|
|
|
954
|
-
[](https://star-history.dera.page/#LukasNiessen/ArchUnitPython&Date)
|
|
955
1024
|
|
|
956
1025
|
## 📄 License
|
|
957
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.1"
|
|
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"
|