archunitpython 1.1.1__tar.gz → 1.2.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.
Files changed (142) hide show
  1. archunitpython-1.2.0/.github/CODEOWNERS +1 -0
  2. archunitpython-1.2.0/.github/FUNDING.yml +1 -0
  3. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/workflows/integrate.yaml +5 -1
  4. {archunitpython-1.1.1 → archunitpython-1.2.0}/.releaserc.json +1 -1
  5. {archunitpython-1.1.1 → archunitpython-1.2.0}/BACKLOG.md +4 -4
  6. {archunitpython-1.1.1 → archunitpython-1.2.0}/CHANGELOG.md +15 -0
  7. {archunitpython-1.1.1 → archunitpython-1.2.0}/PKG-INFO +167 -4
  8. {archunitpython-1.1.1 → archunitpython-1.2.0}/README.md +165 -3
  9. {archunitpython-1.1.1 → archunitpython-1.2.0}/pyproject.toml +2 -1
  10. archunitpython-1.2.0/scripts/bump_release_version.py +50 -0
  11. archunitpython-1.2.0/scripts/check_release_metadata.py +72 -0
  12. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/__init__.py +9 -1
  13. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/extraction/extract_graph.py +117 -8
  14. archunitpython-1.2.0/src/archunitpython/graph/__init__.py +35 -0
  15. archunitpython-1.2.0/src/archunitpython/graph/graph_reporter.py +795 -0
  16. archunitpython-1.2.0/src/archunitpython/layers/__init__.py +8 -0
  17. archunitpython-1.2.0/src/archunitpython/layers/assertion/__init__.py +9 -0
  18. archunitpython-1.2.0/src/archunitpython/layers/assertion/layer_dependencies.py +83 -0
  19. archunitpython-1.2.0/src/archunitpython/layers/fluentapi/__init__.py +6 -0
  20. archunitpython-1.2.0/src/archunitpython/layers/fluentapi/layers.py +116 -0
  21. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/testing/common/violation_factory.py +10 -0
  22. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_extract_graph.py +161 -0
  23. archunitpython-1.2.0/tests/graph/test_graph_reporter.py +293 -0
  24. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/integration/test_e2e.py +8 -0
  25. archunitpython-1.2.0/tests/layers/test_layers.py +86 -0
  26. {archunitpython-1.1.1 → archunitpython-1.2.0}/.editorconfig +0 -0
  27. {archunitpython-1.1.1 → archunitpython-1.2.0}/.gitattributes +0 -0
  28. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  30. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  31. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/ISSUE_TEMPLATE/question.md +0 -0
  32. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/PAGES.md +0 -0
  33. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/dependabot.yml +0 -0
  34. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/pull_request_template.md +0 -0
  35. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/workflows/docs.yaml +0 -0
  36. {archunitpython-1.1.1 → archunitpython-1.2.0}/.github/workflows/stale.yaml +0 -0
  37. {archunitpython-1.1.1 → archunitpython-1.2.0}/.gitignore +0 -0
  38. {archunitpython-1.1.1 → archunitpython-1.2.0}/CONTRIBUTING.md +0 -0
  39. {archunitpython-1.1.1 → archunitpython-1.2.0}/LICENSE +0 -0
  40. {archunitpython-1.1.1 → archunitpython-1.2.0}/assets/logo-rounded.png +0 -0
  41. {archunitpython-1.1.1 → archunitpython-1.2.0}/research/product-direction/architecture-testing-landscape.md +0 -0
  42. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/__init__.py +0 -0
  43. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/assertion/__init__.py +0 -0
  44. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/assertion/violation.py +0 -0
  45. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/error/__init__.py +0 -0
  46. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/error/errors.py +0 -0
  47. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/extraction/__init__.py +0 -0
  48. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/extraction/graph.py +0 -0
  49. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/fluentapi/__init__.py +0 -0
  50. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/fluentapi/checkable.py +0 -0
  51. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/logging/__init__.py +0 -0
  52. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/logging/types.py +0 -0
  53. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/pattern_matching.py +0 -0
  54. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/__init__.py +0 -0
  55. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/cycles/__init__.py +0 -0
  56. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/cycles/cycle_utils.py +0 -0
  57. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/cycles/cycles.py +0 -0
  58. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/cycles/johnsons_apsp.py +0 -0
  59. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/cycles/model.py +0 -0
  60. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/cycles/tarjan_scc.py +0 -0
  61. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/edge_projections.py +0 -0
  62. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/project_cycles.py +0 -0
  63. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/project_edges.py +0 -0
  64. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/project_nodes.py +0 -0
  65. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/projection/types.py +0 -0
  66. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/regex_factory.py +0 -0
  67. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/types.py +0 -0
  68. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/util/__init__.py +0 -0
  69. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/util/declaration_detector.py +0 -0
  70. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/common/util/logger.py +0 -0
  71. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/__init__.py +0 -0
  72. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/assertion/__init__.py +0 -0
  73. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/assertion/custom_file_logic.py +0 -0
  74. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/assertion/cycle_free.py +0 -0
  75. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/assertion/depend_on_external_modules.py +0 -0
  76. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/assertion/depend_on_files.py +0 -0
  77. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/assertion/matching_files.py +0 -0
  78. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/fluentapi/__init__.py +0 -0
  79. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/files/fluentapi/files.py +0 -0
  80. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/__init__.py +0 -0
  81. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/assertion/__init__.py +0 -0
  82. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/assertion/metric_thresholds.py +0 -0
  83. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/calculation/__init__.py +0 -0
  84. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/calculation/count.py +0 -0
  85. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/calculation/distance.py +0 -0
  86. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/calculation/lcom.py +0 -0
  87. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/common/__init__.py +0 -0
  88. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/common/types.py +0 -0
  89. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/extraction/__init__.py +0 -0
  90. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/extraction/extract_class_info.py +0 -0
  91. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/fluentapi/__init__.py +0 -0
  92. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/fluentapi/export_utils.py +0 -0
  93. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/fluentapi/metrics.py +0 -0
  94. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/metrics/projection/__init__.py +0 -0
  95. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/py.typed +0 -0
  96. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/__init__.py +0 -0
  97. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/assertion/__init__.py +0 -0
  98. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/assertion/admissible_edges.py +0 -0
  99. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/fluentapi/__init__.py +0 -0
  100. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/fluentapi/slices.py +0 -0
  101. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/projection/__init__.py +0 -0
  102. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/projection/slicing_projections.py +0 -0
  103. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/uml/__init__.py +0 -0
  104. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/uml/export_diagram.py +0 -0
  105. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/slices/uml/generate_rules.py +0 -0
  106. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/testing/__init__.py +0 -0
  107. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/testing/assertion.py +0 -0
  108. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/testing/common/__init__.py +0 -0
  109. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/testing/common/color_utils.py +0 -0
  110. {archunitpython-1.1.1 → archunitpython-1.2.0}/src/archunitpython/testing/pytest_plugin/__init__.py +0 -0
  111. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/__init__.py +0 -0
  112. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/__init__.py +0 -0
  113. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_core_types.py +0 -0
  114. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_cycles.py +0 -0
  115. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_declaration_detector.py +0 -0
  116. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_logger.py +0 -0
  117. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_pattern_matching.py +0 -0
  118. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/common/test_projection.py +0 -0
  119. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/files/__init__.py +0 -0
  120. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/files/test_file_assertions.py +0 -0
  121. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/files/test_files_fluentapi.py +0 -0
  122. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/metrics_project/service.py +0 -0
  123. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/__init__.py +0 -0
  124. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/architecture.puml +0 -0
  125. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/controllers/__init__.py +0 -0
  126. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/controllers/controller.py +0 -0
  127. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/models/__init__.py +0 -0
  128. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/models/model.py +0 -0
  129. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/services/__init__.py +0 -0
  130. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/services/service.py +0 -0
  131. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/services/service_a.py +0 -0
  132. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/services/service_b.py +0 -0
  133. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/utils/__init__.py +0 -0
  134. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/fixtures/sample_project/utils/helpers.py +0 -0
  135. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/integration/__init__.py +0 -0
  136. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/metrics/__init__.py +0 -0
  137. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/metrics/test_export.py +0 -0
  138. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/metrics/test_metrics.py +0 -0
  139. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/metrics/test_metrics_fluentapi.py +0 -0
  140. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/slices/__init__.py +0 -0
  141. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/slices/test_slices.py +0 -0
  142. {archunitpython-1.1.1 → archunitpython-1.2.0}/tests/test_setup.py +0 -0
@@ -0,0 +1 @@
1
+ * @LukasNiessen
@@ -0,0 +1 @@
1
+ github: [LukasNiessen]
@@ -26,12 +26,16 @@ jobs:
26
26
  pip-${{ runner.os }}-${{ matrix.python-version }}-
27
27
  - name: Install dependencies
28
28
  run: pip install -e ".[dev]"
29
+ - name: Check release metadata
30
+ run: python scripts/check_release_metadata.py
29
31
  - name: Lint
30
- run: ruff check src/
32
+ run: ruff check src/ scripts/
31
33
  - name: Type check
32
34
  run: mypy src/archunitpython/ --ignore-missing-imports
33
35
  - name: Test
34
36
  run: pytest --tb=short -q
37
+ - name: Build package
38
+ run: python -m build
35
39
 
36
40
  publish:
37
41
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
@@ -12,7 +12,7 @@
12
12
  [
13
13
  "@semantic-release/exec",
14
14
  {
15
- "prepareCmd": "python -c \"import re, pathlib; files=[('pyproject.toml', r'(?m)^version = \\\"[^\\\"]+\\\"$', 'version = \\\"${nextRelease.version}\\\"'), ('src/archunitpython/__init__.py', r'(?m)^__version__ = \\\"[^\\\"]+\\\"$', '__version__ = \\\"${nextRelease.version}\\\"')]; [pathlib.Path(path).write_text(re.sub(pattern, replacement, pathlib.Path(path).read_text())) for path, pattern, replacement in files]\""
15
+ "prepareCmd": "python scripts/bump_release_version.py ${nextRelease.version}"
16
16
  }
17
17
  ],
18
18
  [
@@ -4,10 +4,10 @@ This backlog collects product and maintenance ideas from project research.
4
4
 
5
5
  ## P0 - Maintenance And Correctness
6
6
 
7
- - Keep package metadata synchronized across `pyproject.toml`, `CHANGELOG.md`, and `src/archunitpython/__init__.py`.
8
- - Keep tool configuration valid for the supported Python range, especially mypy and Ruff target versions.
9
- - Add a release metadata check that fails when the exported `__version__` differs from the project version.
10
- - Add CI jobs that run tests, Ruff, mypy, and a package build from a clean checkout.
7
+ - [x] Keep package metadata synchronized across `pyproject.toml`, `CHANGELOG.md`, and `src/archunitpython/__init__.py`.
8
+ - [x] Keep tool configuration valid for the supported Python range, especially mypy and Ruff target versions.
9
+ - [x] Add a release metadata check that fails when the exported `__version__` differs from the project version.
10
+ - [x] Add CI jobs that run tests, Ruff, mypy, and a package build from a clean checkout.
11
11
 
12
12
  ## P1 - Adoption Workflow
13
13
 
@@ -1,3 +1,18 @@
1
+ # [1.2.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.1.2...v1.2.0) (2026-06-27)
2
+
3
+
4
+ ### Features
5
+
6
+ * add graph reporting and sponsorship links ([6d5eabc](https://github.com/LukasNiessen/ArchUnitPython/commit/6d5eabc1c1395c5bb4cf18e222ac472d33c02eb4))
7
+
8
+ ## [1.1.2](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.1.1...v1.1.2) (2026-06-23)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * fixed the duplicate key issue ([283e8e3](https://github.com/LukasNiessen/ArchUnitPython/commit/283e8e3aa959d235f331912e33a72bdbd62f3d31))
14
+ * wrap long line in extract_graph to pass ruff E501 ([f291181](https://github.com/LukasNiessen/ArchUnitPython/commit/f291181336bacdd7a913187d2d26f1125f1b2a11))
15
+
1
16
  ## [1.1.1](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.1.0...v1.1.1) (2026-05-24)
2
17
 
3
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archunitpython
3
- Version: 1.1.1
3
+ Version: 1.2.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
@@ -24,6 +24,7 @@ Classifier: Topic :: Software Development :: Testing
24
24
  Classifier: Typing :: Typed
25
25
  Requires-Python: >=3.10
26
26
  Provides-Extra: dev
27
+ Requires-Dist: build>=1.0; extra == 'dev'
27
28
  Requires-Dist: mypy>=1.0; extra == 'dev'
28
29
  Requires-Dist: pytest-cov>=4.0; extra == 'dev'
29
30
  Requires-Dist: pytest>=7.0; extra == 'dev'
@@ -40,16 +41,19 @@ Description-Content-Type: text/markdown
40
41
 
41
42
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
43
  [![PyPI version](https://img.shields.io/pypi/v/archunitpython.svg)](https://pypi.org/project/archunitpython/)
43
- [![Python versions](https://img.shields.io/pypi/pyversions/archunitpython.svg)](https://pypi.org/project/archunitpython/)
44
+ [![Downloads](https://static.pepy.tech/badge/archunitpython)](https://pepy.tech/project/archunitpython)
45
+ [![Monthly downloads](https://static.pepy.tech/badge/archunitpython/month)](https://pepy.tech/project/archunitpython)
44
46
  [![GitHub stars](https://img.shields.io/github/stars/LukasNiessen/ArchUnitPython.svg)](https://github.com/LukasNiessen/ArchUnitPython)
45
47
 
46
48
  </div>
47
49
 
48
50
  Enforce architecture rules in Python projects. Check for dependency directions, detect circular dependencies, enforce coding standards and much more. Integrates with pytest and any other testing framework. Very simple setup and pipeline integration. Zero runtime dependencies.
49
51
 
52
+ The #1 ArchUnit-style architecture testing library for Python, measured by GitHub stars.
53
+
50
54
  _Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
51
55
 
52
- [Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Contributing](CONTRIBUTING.md)
56
+ [Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Contributing](CONTRIBUTING.md)
53
57
 
54
58
  ## ⚡ 5 min Quickstart
55
59
 
@@ -120,6 +124,22 @@ These tests run automatically in your testing setup, for example in your CI pipe
120
124
  run: pytest tests/test_architecture.py -v
121
125
  ```
122
126
 
127
+ You can also export dependency graph reports as CI artifacts:
128
+
129
+ ```python
130
+ from archunitpython import project_graph
131
+
132
+ def test_generate_dependency_graph_reports():
133
+ graph = project_graph("src/").titled("Application Architecture")
134
+
135
+ graph.collapse_to_folder_depth(2).export_as_html(
136
+ "reports/dependency-graph.html"
137
+ )
138
+ graph.export_as_mermaid("reports/dependency-graph.mmd")
139
+
140
+ assert graph.snapshot().summary.node_count >= 0
141
+ ```
142
+
123
143
  ## 🚐 Setup
124
144
 
125
145
  Installation:
@@ -164,6 +184,7 @@ from archunitpython import CheckOptions
164
184
  options = CheckOptions(
165
185
  allow_empty_tests=True, # Don't fail when no files match
166
186
  clear_cache=True, # Clear the graph cache
187
+ ignore_type_checking_imports=True, # Ignore imports inside if TYPE_CHECKING
167
188
  )
168
189
 
169
190
  violations = rule.check(options)
@@ -227,6 +248,65 @@ def test_business_not_depend_on_presentation():
227
248
  assert_passes(rule)
228
249
  ```
229
250
 
251
+ ### Named Layer Rules
252
+
253
+ ```python
254
+ from archunitpython import project_layers
255
+
256
+ def test_clean_architecture_layers():
257
+ rule = (
258
+ project_layers("src/")
259
+ .layer("presentation").defined_by_folder("**/presentation/**")
260
+ .layer("business").defined_by_folder("**/business/**")
261
+ .layer("database").defined_by_folder("**/database/**")
262
+ .where_layer("presentation")
263
+ .may_only_depend_on_layers("business")
264
+ .where_layer("business")
265
+ .may_only_depend_on_layers()
266
+ .where_layer("database")
267
+ .may_only_depend_on_layers()
268
+ )
269
+ assert_passes(rule)
270
+ ```
271
+
272
+ ### External Dependencies
273
+
274
+ ```python
275
+ def test_domain_does_not_import_requests():
276
+ rule = (
277
+ project_files("src/")
278
+ .in_folder("**/domain/**")
279
+ .should_not()
280
+ .depend_on_external_modules()
281
+ .matching("requests")
282
+ )
283
+ assert_passes(rule)
284
+ ```
285
+
286
+ ### TYPE_CHECKING-aware Analysis
287
+
288
+ ```python
289
+ from archunitpython import CheckOptions
290
+
291
+ def test_type_only_dependencies_do_not_count_as_runtime_coupling():
292
+ rule = (
293
+ project_files("src/")
294
+ .in_folder("**/api/**")
295
+ .should_not()
296
+ .depend_on_files()
297
+ .in_folder("**/infrastructure/**")
298
+ )
299
+ assert_passes(rule, CheckOptions(ignore_type_checking_imports=True))
300
+ ```
301
+
302
+ ### Dynamic Imports and Ignore Directives
303
+
304
+ ArchUnitPython detects string-based dynamic imports such as `importlib.import_module("my_app.adapters.sql")` and `__import__("my_app.adapters.sql")`. For known migration shims, you can suppress one import edge locally:
305
+
306
+ ```python
307
+ from my_app.adapters.sql import Repository # archunit: ignore
308
+ ```
309
+
230
310
  ### Naming Conventions
231
311
 
232
312
  ```python
@@ -349,6 +429,48 @@ def test_no_forbidden_dependency():
349
429
  assert_passes(rule)
350
430
  ```
351
431
 
432
+ ### Dependency Graph Reports
433
+
434
+ Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
435
+
436
+ ```python
437
+ from archunitpython import project_graph
438
+
439
+ 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
+ "reports/dependencies.mmd"
444
+ )
445
+
446
+ graph.focus_on("**/domain/**", 1).export_as_html(
447
+ "reports/domain-dependencies.html"
448
+ )
449
+
450
+ assert graph.snapshot().summary.node_count >= 0
451
+ ```
452
+
453
+ Supported formats:
454
+
455
+ - DOT (`export_as_dot`, `to_dot`)
456
+ - Mermaid (`export_as_mermaid`, `to_mermaid`)
457
+ - D2 (`export_as_d2`, `to_d2`)
458
+ - CSV (`export_as_csv`, `to_csv`)
459
+ - JSON (`export_as_json`, `to_json`)
460
+ - HTML (`export_as_html`, `to_html`)
461
+
462
+ Graph exploration options:
463
+
464
+ - `focus_on(pattern, depth)` keeps matching files and their neighbors.
465
+ - `reachable_from(pattern)` keeps matching files and their transitive dependencies.
466
+ - `dependents_of(pattern)` keeps files that transitively depend on the matching files.
467
+ - `collapse_to_folder_depth(depth)` aggregates files to folder-level graph nodes.
468
+ - `collapse_by_pattern(pattern, replacement)` maps files to custom graph nodes.
469
+ - `include_external_dependencies()` includes imports to external modules such as `requests` or `sqlalchemy`.
470
+ - `include_self_dependencies()` keeps self edges that are normally hidden in reports.
471
+
472
+ When you create reports through `project_graph("src/")`, internal file paths are displayed relative to that project root so the output stays readable.
473
+
352
474
  ### Reports
353
475
 
354
476
  Generate HTML reports for your metrics. _Note that this feature is in beta._
@@ -528,6 +650,40 @@ def test_from_file():
528
650
  assert_passes(rule)
529
651
  ```
530
652
 
653
+ ## 📊 Library Comparison
654
+
655
+ Here's how ArchUnitPython compares to other Python architecture-enforcement libraries.
656
+
657
+ ArchUnitPython is optimized for **architecture rules as tests**: rules live next to your normal unit tests, run in pytest/unittest/CI, and fail with test-style violation messages. Broader CLI-first tools such as [Tach](https://github.com/tach-org/tach) and [Import Linter](https://github.com/seddonym/import-linter) are excellent adjacent tools, but they solve the problem through separate configuration and commands rather than a test-native ArchUnit-style API.
658
+
659
+ | Feature | **ArchUnitPython** | **Tach** | **Import Linter** | **PyTestArch** |
660
+ | ------- | ------------------ | -------- | ----------------- | -------------- |
661
+ | **Primary workflow** | ✅ Architecture rules as unit tests | ⚠️ CLI + `tach.toml` | ⚠️ CLI + contracts config | ⚠️ pytest-oriented evaluable architecture |
662
+ | **ArchUnit-style fluent API** | ✅ Yes | ❌ No | ❌ No | ⚠️ Partial |
663
+ | **Testing framework integration** | ✅ pytest, unittest, any runner | ⚠️ CI/pre-commit CLI | ⚠️ CI/pre-commit CLI | ⚠️ pytest-focused |
664
+ | **Zero runtime dependencies** | ✅ Standard library only | ⚠️ No app runtime impact, Rust-backed tool | ❌ Tool dependencies | ❌ Tool dependencies |
665
+ | **Circular dependency detection** | ✅ First-class | ✅ First-class | ⚠️ Contract/graph based | ⚠️ Import-rule based |
666
+ | **File/folder dependency rules** | ✅ Glob + regex | ✅ Module config | ✅ Import contracts | ✅ Module rules |
667
+ | **Named layer rules** | ✅ `project_layers()` | ✅ Supported | ✅ Supported | ✅ Supported |
668
+ | **External dependency rules** | ✅ `depend_on_external_modules()` | ⚠️ Internal module focus | ⚠️ Import contract focus | ⚠️ Internal import focus |
669
+ | **TYPE_CHECKING-aware analysis** | ✅ Configurable | ⚠️ Not the core API | ⚠️ Not the core API | ⚠️ Not the core API |
670
+ | **Dynamic import detection** | ✅ `importlib` + `__import__` string calls | ⚠️ Not the core workflow | ⚠️ Not the core workflow | ⚠️ Import analysis focused |
671
+ | **Inline ignore directives** | ✅ `# archunit: ignore` | ✅ Supported | ⚠️ Config-based ignores | ⚠️ Rule/exclusion based |
672
+ | **Naming convention checks** | ✅ Files and paths | ❌ No | ❌ No | ⚠️ Module-name oriented |
673
+ | **Code metrics** | ✅ Counts, LCOM, distance metrics | ❌ No | ❌ No | ❌ No |
674
+ | **Custom rules and metrics** | ✅ Full support | ❌ No | ⚠️ Custom contracts | ⚠️ Limited custom rule composition |
675
+ | **PlantUML diagram validation** | ✅ Supported | ❌ No | ❌ No | ❌ No |
676
+ | **Empty test protection** | ✅ Fails by default | ⚠️ Config validation | ⚠️ Contract validation | ⚠️ Not the main focus |
677
+ | **Graph/reporting** | ✅ DOT, Mermaid, D2, CSV, JSON, HTML graph reports + metrics HTML | ✅ DOT, JSON, web graph | ✅ Browser UI | ⚠️ Optional graph visualization |
678
+ | **Best fit** | Architecture tests, CI fitness functions, metrics, diagrams | Modular monolith dependency governance | Config-driven import contracts | pytest import-boundary checks |
679
+
680
+ The most important differences:
681
+
682
+ - **Test-native by design**: ArchUnitPython rules are just Python tests, so architecture decisions are reviewed, run, and debugged in the same workflow as the rest of your test suite.
683
+ - **Broader rule surface**: dependency direction, cycles, layer policies, external modules, type-only imports, dynamic imports, naming, metrics, custom rules, and PlantUML validation live in one API.
684
+ - **False-positive protection**: empty checks fail by default, which helps catch typos in file and folder patterns before they silently make your architecture tests meaningless.
685
+ - **Quality beyond imports**: ArchUnitPython can enforce code metrics such as LCOM cohesion, field/method counts, abstractness, instability, and distance from the main sequence.
686
+
531
687
  ## 📢 Informative Error Messages
532
688
 
533
689
  When tests fail, you get helpful output with file paths and violation details:
@@ -589,6 +745,7 @@ The features of ArchUnitPython can very well be used as architectural fitness fu
589
745
  | **Files** | File and folder based rules | Stable |
590
746
  | **Metrics** | Code quality metrics | Stable |
591
747
  | **Slices** | Architecture slicing | Stable |
748
+ | **Graph** | Dependency graph reports | Experimental |
592
749
  | **Testing** | Test framework integration | Stable |
593
750
  | **Common** | Shared utilities | Stable |
594
751
  | **Reports** | Generate HTML reports | Experimental |
@@ -599,7 +756,12 @@ We use ourselves to ensure the architectural rules for this repository.
599
756
 
600
757
  ## 🦊 Contributing
601
758
 
602
- We highly appreciate contributions. We use GitHub Flow, meaning that we use feature branches. As soon as something is merged or pushed to `main` it gets deployed. Versioning is automated via [Conventional Commits](https://www.conventionalcommits.org/). See more in [Contributing](CONTRIBUTING.md).
759
+ We highly appreciate contributions. See [Contributing](CONTRIBUTING.md) for the full workflow.
760
+
761
+ - Use feature branches and open pull requests against `main`.
762
+ - Use [Conventional Commits](https://www.conventionalcommits.org/) so releases can be versioned automatically.
763
+ - Do not bump versions manually for normal feature or fix work; semantic-release updates `pyproject.toml`, `src/archunitpython/__init__.py`, and `CHANGELOG.md`.
764
+ - CI checks linting, typing, tests, package builds, and release metadata sync.
603
765
 
604
766
  ## ℹ️ FAQ
605
767
 
@@ -653,6 +815,7 @@ Found a bug? Want to discuss features?
653
815
  If ArchUnitPython helps your project, please consider:
654
816
 
655
817
  - Starring the repository 💚
818
+ - Sponsoring development via [GitHub Sponsors](https://github.com/sponsors/LukasNiessen)
656
819
  - Suggesting new features 💭
657
820
  - Contributing code or documentation ⌨️
658
821
 
@@ -8,16 +8,19 @@
8
8
 
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
  [![PyPI version](https://img.shields.io/pypi/v/archunitpython.svg)](https://pypi.org/project/archunitpython/)
11
- [![Python versions](https://img.shields.io/pypi/pyversions/archunitpython.svg)](https://pypi.org/project/archunitpython/)
11
+ [![Downloads](https://static.pepy.tech/badge/archunitpython)](https://pepy.tech/project/archunitpython)
12
+ [![Monthly downloads](https://static.pepy.tech/badge/archunitpython/month)](https://pepy.tech/project/archunitpython)
12
13
  [![GitHub stars](https://img.shields.io/github/stars/LukasNiessen/ArchUnitPython.svg)](https://github.com/LukasNiessen/ArchUnitPython)
13
14
 
14
15
  </div>
15
16
 
16
17
  Enforce architecture rules in Python projects. Check for dependency directions, detect circular dependencies, enforce coding standards and much more. Integrates with pytest and any other testing framework. Very simple setup and pipeline integration. Zero runtime dependencies.
17
18
 
19
+ The #1 ArchUnit-style architecture testing library for Python, measured by GitHub stars.
20
+
18
21
  _Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
19
22
 
20
- [Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Contributing](CONTRIBUTING.md)
23
+ [Setup](#-setup) • [Use Cases](#-use-cases) • [Features](#-features) • [Why ArchUnitPython?](#-library-comparison) • [Contributing](CONTRIBUTING.md)
21
24
 
22
25
  ## ⚡ 5 min Quickstart
23
26
 
@@ -88,6 +91,22 @@ These tests run automatically in your testing setup, for example in your CI pipe
88
91
  run: pytest tests/test_architecture.py -v
89
92
  ```
90
93
 
94
+ You can also export dependency graph reports as CI artifacts:
95
+
96
+ ```python
97
+ from archunitpython import project_graph
98
+
99
+ def test_generate_dependency_graph_reports():
100
+ graph = project_graph("src/").titled("Application Architecture")
101
+
102
+ graph.collapse_to_folder_depth(2).export_as_html(
103
+ "reports/dependency-graph.html"
104
+ )
105
+ graph.export_as_mermaid("reports/dependency-graph.mmd")
106
+
107
+ assert graph.snapshot().summary.node_count >= 0
108
+ ```
109
+
91
110
  ## 🚐 Setup
92
111
 
93
112
  Installation:
@@ -132,6 +151,7 @@ from archunitpython import CheckOptions
132
151
  options = CheckOptions(
133
152
  allow_empty_tests=True, # Don't fail when no files match
134
153
  clear_cache=True, # Clear the graph cache
154
+ ignore_type_checking_imports=True, # Ignore imports inside if TYPE_CHECKING
135
155
  )
136
156
 
137
157
  violations = rule.check(options)
@@ -195,6 +215,65 @@ def test_business_not_depend_on_presentation():
195
215
  assert_passes(rule)
196
216
  ```
197
217
 
218
+ ### Named Layer Rules
219
+
220
+ ```python
221
+ from archunitpython import project_layers
222
+
223
+ def test_clean_architecture_layers():
224
+ rule = (
225
+ project_layers("src/")
226
+ .layer("presentation").defined_by_folder("**/presentation/**")
227
+ .layer("business").defined_by_folder("**/business/**")
228
+ .layer("database").defined_by_folder("**/database/**")
229
+ .where_layer("presentation")
230
+ .may_only_depend_on_layers("business")
231
+ .where_layer("business")
232
+ .may_only_depend_on_layers()
233
+ .where_layer("database")
234
+ .may_only_depend_on_layers()
235
+ )
236
+ assert_passes(rule)
237
+ ```
238
+
239
+ ### External Dependencies
240
+
241
+ ```python
242
+ def test_domain_does_not_import_requests():
243
+ rule = (
244
+ project_files("src/")
245
+ .in_folder("**/domain/**")
246
+ .should_not()
247
+ .depend_on_external_modules()
248
+ .matching("requests")
249
+ )
250
+ assert_passes(rule)
251
+ ```
252
+
253
+ ### TYPE_CHECKING-aware Analysis
254
+
255
+ ```python
256
+ from archunitpython import CheckOptions
257
+
258
+ def test_type_only_dependencies_do_not_count_as_runtime_coupling():
259
+ rule = (
260
+ project_files("src/")
261
+ .in_folder("**/api/**")
262
+ .should_not()
263
+ .depend_on_files()
264
+ .in_folder("**/infrastructure/**")
265
+ )
266
+ assert_passes(rule, CheckOptions(ignore_type_checking_imports=True))
267
+ ```
268
+
269
+ ### Dynamic Imports and Ignore Directives
270
+
271
+ ArchUnitPython detects string-based dynamic imports such as `importlib.import_module("my_app.adapters.sql")` and `__import__("my_app.adapters.sql")`. For known migration shims, you can suppress one import edge locally:
272
+
273
+ ```python
274
+ from my_app.adapters.sql import Repository # archunit: ignore
275
+ ```
276
+
198
277
  ### Naming Conventions
199
278
 
200
279
  ```python
@@ -317,6 +396,48 @@ def test_no_forbidden_dependency():
317
396
  assert_passes(rule)
318
397
  ```
319
398
 
399
+ ### Dependency Graph Reports
400
+
401
+ Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
402
+
403
+ ```python
404
+ from archunitpython import project_graph
405
+
406
+ 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
+ "reports/dependencies.mmd"
411
+ )
412
+
413
+ graph.focus_on("**/domain/**", 1).export_as_html(
414
+ "reports/domain-dependencies.html"
415
+ )
416
+
417
+ assert graph.snapshot().summary.node_count >= 0
418
+ ```
419
+
420
+ Supported formats:
421
+
422
+ - DOT (`export_as_dot`, `to_dot`)
423
+ - Mermaid (`export_as_mermaid`, `to_mermaid`)
424
+ - D2 (`export_as_d2`, `to_d2`)
425
+ - CSV (`export_as_csv`, `to_csv`)
426
+ - JSON (`export_as_json`, `to_json`)
427
+ - HTML (`export_as_html`, `to_html`)
428
+
429
+ Graph exploration options:
430
+
431
+ - `focus_on(pattern, depth)` keeps matching files and their neighbors.
432
+ - `reachable_from(pattern)` keeps matching files and their transitive dependencies.
433
+ - `dependents_of(pattern)` keeps files that transitively depend on the matching files.
434
+ - `collapse_to_folder_depth(depth)` aggregates files to folder-level graph nodes.
435
+ - `collapse_by_pattern(pattern, replacement)` maps files to custom graph nodes.
436
+ - `include_external_dependencies()` includes imports to external modules such as `requests` or `sqlalchemy`.
437
+ - `include_self_dependencies()` keeps self edges that are normally hidden in reports.
438
+
439
+ When you create reports through `project_graph("src/")`, internal file paths are displayed relative to that project root so the output stays readable.
440
+
320
441
  ### Reports
321
442
 
322
443
  Generate HTML reports for your metrics. _Note that this feature is in beta._
@@ -496,6 +617,40 @@ def test_from_file():
496
617
  assert_passes(rule)
497
618
  ```
498
619
 
620
+ ## 📊 Library Comparison
621
+
622
+ Here's how ArchUnitPython compares to other Python architecture-enforcement libraries.
623
+
624
+ ArchUnitPython is optimized for **architecture rules as tests**: rules live next to your normal unit tests, run in pytest/unittest/CI, and fail with test-style violation messages. Broader CLI-first tools such as [Tach](https://github.com/tach-org/tach) and [Import Linter](https://github.com/seddonym/import-linter) are excellent adjacent tools, but they solve the problem through separate configuration and commands rather than a test-native ArchUnit-style API.
625
+
626
+ | Feature | **ArchUnitPython** | **Tach** | **Import Linter** | **PyTestArch** |
627
+ | ------- | ------------------ | -------- | ----------------- | -------------- |
628
+ | **Primary workflow** | ✅ Architecture rules as unit tests | ⚠️ CLI + `tach.toml` | ⚠️ CLI + contracts config | ⚠️ pytest-oriented evaluable architecture |
629
+ | **ArchUnit-style fluent API** | ✅ Yes | ❌ No | ❌ No | ⚠️ Partial |
630
+ | **Testing framework integration** | ✅ pytest, unittest, any runner | ⚠️ CI/pre-commit CLI | ⚠️ CI/pre-commit CLI | ⚠️ pytest-focused |
631
+ | **Zero runtime dependencies** | ✅ Standard library only | ⚠️ No app runtime impact, Rust-backed tool | ❌ Tool dependencies | ❌ Tool dependencies |
632
+ | **Circular dependency detection** | ✅ First-class | ✅ First-class | ⚠️ Contract/graph based | ⚠️ Import-rule based |
633
+ | **File/folder dependency rules** | ✅ Glob + regex | ✅ Module config | ✅ Import contracts | ✅ Module rules |
634
+ | **Named layer rules** | ✅ `project_layers()` | ✅ Supported | ✅ Supported | ✅ Supported |
635
+ | **External dependency rules** | ✅ `depend_on_external_modules()` | ⚠️ Internal module focus | ⚠️ Import contract focus | ⚠️ Internal import focus |
636
+ | **TYPE_CHECKING-aware analysis** | ✅ Configurable | ⚠️ Not the core API | ⚠️ Not the core API | ⚠️ Not the core API |
637
+ | **Dynamic import detection** | ✅ `importlib` + `__import__` string calls | ⚠️ Not the core workflow | ⚠️ Not the core workflow | ⚠️ Import analysis focused |
638
+ | **Inline ignore directives** | ✅ `# archunit: ignore` | ✅ Supported | ⚠️ Config-based ignores | ⚠️ Rule/exclusion based |
639
+ | **Naming convention checks** | ✅ Files and paths | ❌ No | ❌ No | ⚠️ Module-name oriented |
640
+ | **Code metrics** | ✅ Counts, LCOM, distance metrics | ❌ No | ❌ No | ❌ No |
641
+ | **Custom rules and metrics** | ✅ Full support | ❌ No | ⚠️ Custom contracts | ⚠️ Limited custom rule composition |
642
+ | **PlantUML diagram validation** | ✅ Supported | ❌ No | ❌ No | ❌ No |
643
+ | **Empty test protection** | ✅ Fails by default | ⚠️ Config validation | ⚠️ Contract validation | ⚠️ Not the main focus |
644
+ | **Graph/reporting** | ✅ DOT, Mermaid, D2, CSV, JSON, HTML graph reports + metrics HTML | ✅ DOT, JSON, web graph | ✅ Browser UI | ⚠️ Optional graph visualization |
645
+ | **Best fit** | Architecture tests, CI fitness functions, metrics, diagrams | Modular monolith dependency governance | Config-driven import contracts | pytest import-boundary checks |
646
+
647
+ The most important differences:
648
+
649
+ - **Test-native by design**: ArchUnitPython rules are just Python tests, so architecture decisions are reviewed, run, and debugged in the same workflow as the rest of your test suite.
650
+ - **Broader rule surface**: dependency direction, cycles, layer policies, external modules, type-only imports, dynamic imports, naming, metrics, custom rules, and PlantUML validation live in one API.
651
+ - **False-positive protection**: empty checks fail by default, which helps catch typos in file and folder patterns before they silently make your architecture tests meaningless.
652
+ - **Quality beyond imports**: ArchUnitPython can enforce code metrics such as LCOM cohesion, field/method counts, abstractness, instability, and distance from the main sequence.
653
+
499
654
  ## 📢 Informative Error Messages
500
655
 
501
656
  When tests fail, you get helpful output with file paths and violation details:
@@ -557,6 +712,7 @@ The features of ArchUnitPython can very well be used as architectural fitness fu
557
712
  | **Files** | File and folder based rules | Stable |
558
713
  | **Metrics** | Code quality metrics | Stable |
559
714
  | **Slices** | Architecture slicing | Stable |
715
+ | **Graph** | Dependency graph reports | Experimental |
560
716
  | **Testing** | Test framework integration | Stable |
561
717
  | **Common** | Shared utilities | Stable |
562
718
  | **Reports** | Generate HTML reports | Experimental |
@@ -567,7 +723,12 @@ We use ourselves to ensure the architectural rules for this repository.
567
723
 
568
724
  ## 🦊 Contributing
569
725
 
570
- We highly appreciate contributions. We use GitHub Flow, meaning that we use feature branches. As soon as something is merged or pushed to `main` it gets deployed. Versioning is automated via [Conventional Commits](https://www.conventionalcommits.org/). See more in [Contributing](CONTRIBUTING.md).
726
+ We highly appreciate contributions. See [Contributing](CONTRIBUTING.md) for the full workflow.
727
+
728
+ - Use feature branches and open pull requests against `main`.
729
+ - Use [Conventional Commits](https://www.conventionalcommits.org/) so releases can be versioned automatically.
730
+ - Do not bump versions manually for normal feature or fix work; semantic-release updates `pyproject.toml`, `src/archunitpython/__init__.py`, and `CHANGELOG.md`.
731
+ - CI checks linting, typing, tests, package builds, and release metadata sync.
571
732
 
572
733
  ## ℹ️ FAQ
573
734
 
@@ -621,6 +782,7 @@ Found a bug? Want to discuss features?
621
782
  If ArchUnitPython helps your project, please consider:
622
783
 
623
784
  - Starring the repository 💚
785
+ - Sponsoring development via [GitHub Sponsors](https://github.com/sponsors/LukasNiessen)
624
786
  - Suggesting new features 💭
625
787
  - Contributing code or documentation ⌨️
626
788
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "archunitpython"
7
- version = "1.1.1"
7
+ version = "1.2.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"
@@ -54,6 +54,7 @@ Changelog = "https://github.com/LukasNiessen/ArchUnitPython/blob/main/CHANGELOG.
54
54
 
55
55
  [project.optional-dependencies]
56
56
  dev = [
57
+ "build>=1.0",
57
58
  "pytest>=7.0",
58
59
  "pytest-cov>=4.0",
59
60
  "mypy>=1.0",
@@ -0,0 +1,50 @@
1
+ """Synchronize release version metadata for semantic-release."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ ROOT = Path(__file__).resolve().parents[1]
10
+ PYPROJECT = ROOT / "pyproject.toml"
11
+ PACKAGE_INIT = ROOT / "src" / "archunitpython" / "__init__.py"
12
+
13
+
14
+ def replace_once(pattern: str, replacement: str, content: str, path: Path) -> str:
15
+ updated, count = re.subn(pattern, replacement, content, count=1, flags=re.MULTILINE)
16
+ if count != 1:
17
+ raise RuntimeError(f"Could not update version in {path}")
18
+ return updated
19
+
20
+
21
+ def bump_version(version: str) -> None:
22
+ pyproject = PYPROJECT.read_text(encoding="utf-8")
23
+ PYPROJECT.write_text(
24
+ replace_once(r'^version = "[^"]+"$', f'version = "{version}"', pyproject, PYPROJECT),
25
+ encoding="utf-8",
26
+ )
27
+
28
+ package_init = PACKAGE_INIT.read_text(encoding="utf-8")
29
+ PACKAGE_INIT.write_text(
30
+ replace_once(
31
+ r'^__version__ = "[^"]+"$',
32
+ f'__version__ = "{version}"',
33
+ package_init,
34
+ PACKAGE_INIT,
35
+ ),
36
+ encoding="utf-8",
37
+ )
38
+
39
+
40
+ def main() -> int:
41
+ if len(sys.argv) != 2:
42
+ print("Usage: python scripts/bump_release_version.py <version>", file=sys.stderr)
43
+ return 2
44
+
45
+ bump_version(sys.argv[1])
46
+ return 0
47
+
48
+
49
+ if __name__ == "__main__":
50
+ raise SystemExit(main())