archunitpython 1.0.1__tar.gz → 1.1.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.
Files changed (133) hide show
  1. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/workflows/docs.yaml +1 -1
  2. archunitpython-1.1.1/.releaserc.json +27 -0
  3. archunitpython-1.1.1/BACKLOG.md +62 -0
  4. {archunitpython-1.0.1 → archunitpython-1.1.1}/CHANGELOG.md +19 -0
  5. {archunitpython-1.0.1 → archunitpython-1.1.1}/CONTRIBUTING.md +1 -1
  6. {archunitpython-1.0.1 → archunitpython-1.1.1}/PKG-INFO +2 -2
  7. {archunitpython-1.0.1 → archunitpython-1.1.1}/README.md +1 -1
  8. {archunitpython-1.0.1 → archunitpython-1.1.1}/pyproject.toml +3 -3
  9. archunitpython-1.1.1/research/product-direction/architecture-testing-landscape.md +56 -0
  10. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/__init__.py +1 -1
  11. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/extraction/extract_graph.py +37 -6
  12. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/fluentapi/checkable.py +1 -0
  13. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/edge_projections.py +17 -0
  14. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/assertion/__init__.py +6 -0
  15. archunitpython-1.1.1/src/archunitpython/files/assertion/depend_on_external_modules.py +64 -0
  16. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/fluentapi/files.py +80 -1
  17. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/testing/common/violation_factory.py +12 -0
  18. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_core_types.py +3 -0
  19. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_extract_graph.py +98 -2
  20. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_projection.py +22 -1
  21. archunitpython-1.1.1/tests/files/test_files_fluentapi.py +355 -0
  22. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/integration/test_e2e.py +1 -3
  23. archunitpython-1.1.1/tests/test_setup.py +21 -0
  24. archunitpython-1.0.1/.releaserc.json +0 -27
  25. archunitpython-1.0.1/TODO.md +0 -18
  26. archunitpython-1.0.1/tests/files/test_files_fluentapi.py +0 -174
  27. archunitpython-1.0.1/tests/test_setup.py +0 -7
  28. {archunitpython-1.0.1 → archunitpython-1.1.1}/.editorconfig +0 -0
  29. {archunitpython-1.0.1 → archunitpython-1.1.1}/.gitattributes +0 -0
  30. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  31. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  32. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  33. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/ISSUE_TEMPLATE/question.md +0 -0
  34. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/PAGES.md +0 -0
  35. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/dependabot.yml +0 -0
  36. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/pull_request_template.md +0 -0
  37. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/workflows/integrate.yaml +0 -0
  38. {archunitpython-1.0.1 → archunitpython-1.1.1}/.github/workflows/stale.yaml +0 -0
  39. {archunitpython-1.0.1 → archunitpython-1.1.1}/.gitignore +0 -0
  40. {archunitpython-1.0.1 → archunitpython-1.1.1}/LICENSE +0 -0
  41. {archunitpython-1.0.1 → archunitpython-1.1.1}/assets/logo-rounded.png +0 -0
  42. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/__init__.py +0 -0
  43. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/assertion/__init__.py +0 -0
  44. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/assertion/violation.py +0 -0
  45. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/error/__init__.py +0 -0
  46. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/error/errors.py +0 -0
  47. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/extraction/__init__.py +0 -0
  48. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/extraction/graph.py +0 -0
  49. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/fluentapi/__init__.py +0 -0
  50. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/logging/__init__.py +0 -0
  51. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/logging/types.py +0 -0
  52. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/pattern_matching.py +0 -0
  53. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/__init__.py +0 -0
  54. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/cycles/__init__.py +0 -0
  55. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/cycles/cycle_utils.py +0 -0
  56. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/cycles/cycles.py +0 -0
  57. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/cycles/johnsons_apsp.py +0 -0
  58. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/cycles/model.py +0 -0
  59. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/cycles/tarjan_scc.py +0 -0
  60. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/project_cycles.py +0 -0
  61. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/project_edges.py +0 -0
  62. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/project_nodes.py +0 -0
  63. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/projection/types.py +0 -0
  64. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/regex_factory.py +0 -0
  65. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/types.py +0 -0
  66. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/util/__init__.py +0 -0
  67. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/util/declaration_detector.py +0 -0
  68. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/common/util/logger.py +0 -0
  69. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/__init__.py +0 -0
  70. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/assertion/custom_file_logic.py +0 -0
  71. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/assertion/cycle_free.py +0 -0
  72. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/assertion/depend_on_files.py +0 -0
  73. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/assertion/matching_files.py +0 -0
  74. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/files/fluentapi/__init__.py +0 -0
  75. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/__init__.py +0 -0
  76. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/assertion/__init__.py +0 -0
  77. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/assertion/metric_thresholds.py +0 -0
  78. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/calculation/__init__.py +0 -0
  79. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/calculation/count.py +0 -0
  80. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/calculation/distance.py +0 -0
  81. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/calculation/lcom.py +0 -0
  82. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/common/__init__.py +0 -0
  83. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/common/types.py +0 -0
  84. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/extraction/__init__.py +0 -0
  85. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/extraction/extract_class_info.py +0 -0
  86. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/fluentapi/__init__.py +0 -0
  87. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/fluentapi/export_utils.py +0 -0
  88. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/fluentapi/metrics.py +0 -0
  89. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/metrics/projection/__init__.py +0 -0
  90. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/py.typed +0 -0
  91. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/__init__.py +0 -0
  92. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/assertion/__init__.py +0 -0
  93. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/assertion/admissible_edges.py +0 -0
  94. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/fluentapi/__init__.py +0 -0
  95. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/fluentapi/slices.py +0 -0
  96. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/projection/__init__.py +0 -0
  97. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/projection/slicing_projections.py +0 -0
  98. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/uml/__init__.py +0 -0
  99. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/uml/export_diagram.py +0 -0
  100. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/slices/uml/generate_rules.py +0 -0
  101. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/testing/__init__.py +0 -0
  102. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/testing/assertion.py +0 -0
  103. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/testing/common/__init__.py +0 -0
  104. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/testing/common/color_utils.py +0 -0
  105. {archunitpython-1.0.1 → archunitpython-1.1.1}/src/archunitpython/testing/pytest_plugin/__init__.py +0 -0
  106. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/__init__.py +0 -0
  107. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/__init__.py +0 -0
  108. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_cycles.py +0 -0
  109. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_declaration_detector.py +0 -0
  110. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_logger.py +0 -0
  111. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/common/test_pattern_matching.py +0 -0
  112. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/files/__init__.py +0 -0
  113. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/files/test_file_assertions.py +0 -0
  114. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/metrics_project/service.py +0 -0
  115. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/__init__.py +0 -0
  116. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/architecture.puml +0 -0
  117. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/controllers/__init__.py +0 -0
  118. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/controllers/controller.py +0 -0
  119. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/models/__init__.py +0 -0
  120. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/models/model.py +0 -0
  121. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/services/__init__.py +0 -0
  122. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/services/service.py +0 -0
  123. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/services/service_a.py +0 -0
  124. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/services/service_b.py +0 -0
  125. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/utils/__init__.py +0 -0
  126. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/fixtures/sample_project/utils/helpers.py +0 -0
  127. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/integration/__init__.py +0 -0
  128. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/metrics/__init__.py +0 -0
  129. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/metrics/test_export.py +0 -0
  130. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/metrics/test_metrics.py +0 -0
  131. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/metrics/test_metrics_fluentapi.py +0 -0
  132. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/slices/__init__.py +0 -0
  133. {archunitpython-1.0.1 → archunitpython-1.1.1}/tests/slices/test_slices.py +0 -0
@@ -41,7 +41,7 @@ jobs:
41
41
  uses: actions/configure-pages@v6
42
42
 
43
43
  - name: Upload artifact
44
- uses: actions/upload-pages-artifact@v4
44
+ uses: actions/upload-pages-artifact@v5
45
45
  with:
46
46
  path: './docs'
47
47
 
@@ -0,0 +1,27 @@
1
+ {
2
+ "branches": ["main"],
3
+ "plugins": [
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ [
7
+ "@semantic-release/changelog",
8
+ {
9
+ "changelogFile": "CHANGELOG.md"
10
+ }
11
+ ],
12
+ [
13
+ "@semantic-release/exec",
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]\""
16
+ }
17
+ ],
18
+ [
19
+ "@semantic-release/git",
20
+ {
21
+ "assets": ["pyproject.toml", "src/archunitpython/__init__.py", "CHANGELOG.md"],
22
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
23
+ }
24
+ ],
25
+ "@semantic-release/github"
26
+ ]
27
+ }
@@ -0,0 +1,62 @@
1
+ # Backlog
2
+
3
+ This backlog collects product and maintenance ideas from project research.
4
+
5
+ ## P0 - Maintenance And Correctness
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.
11
+
12
+ ## P1 - Adoption Workflow
13
+
14
+ - Add an `.archignore` or similar file, modeled after `.gitignore`, for files that should never be analyzed.
15
+ - Add a `.because(...)` API so rules can carry user-facing rationale into failure messages and generated architecture documentation.
16
+ - Add configuration-file support for common rules, while keeping the fluent Python API as the primary interface.
17
+ - Add support for monorepo and multi-package Python projects.
18
+
19
+ ## P1 - Python Import Semantics
20
+
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`.
24
+ - Add better `TYPE_CHECKING` import handling, including options to ignore, include, or report type-only imports separately.
25
+ - Improve external dependency rules so users can express allowed and forbidden third-party packages at module or slice level.
26
+ - Consider a public-interface rule inspired by Tach, where modules may only import through declared package APIs.
27
+
28
+ ## P1 - Reporting And Documentation
29
+
30
+ - Add comprehensive HTML reports with dependency graphs, metric charts, and zone visualization.
31
+ - Auto-generate architecture documentation based on tests and rule rationales.
32
+ - Make logged paths clickable in IDEs and common terminal integrations.
33
+ - Add PlantUML or Mermaid export for discovered architecture graphs.
34
+ - Improve metric export examples and document how metric thresholds should be selected.
35
+
36
+ ## P2 - Rule Surface
37
+
38
+ - Add first-class layered architecture helpers so common clean/hexagonal/layered rules require less boilerplate.
39
+ - Add slice isolation helpers for bounded contexts and modular monoliths.
40
+ - Add richer custom rule hooks for dependency edges, files, classes, and metrics.
41
+ - Add transitive dependency checks, especially for "domain must not transitively reach infrastructure" style rules.
42
+ - Add naming and placement conventions for classes/functions, not only files.
43
+
44
+ ## P2 - Performance And Scale
45
+
46
+ - Improve performance for very large projects through parallel file parsing.
47
+ - Add persistent graph caching with invalidation based on file mtimes or content hashes.
48
+ - Add benchmarks for small, medium, large, and monorepo-style projects.
49
+
50
+ ## P2 - Metrics
51
+
52
+ - Add more LCOM edge case handling.
53
+ - Add metric documentation with examples for good, suspicious, and failing values.
54
+ - Add trend-friendly metric exports for CI artifacts.
55
+ - Consider additional architecture metrics such as coupling counts per slice, fan-in/fan-out summaries, and instability per package.
56
+
57
+ ## P3 - Packaging And Docs
58
+
59
+ - Publish to PyPI as part of the release pipeline if this is not already automated.
60
+ - Add a Sphinx or MkDocs documentation site.
61
+ - Add a complete example repository or examples folder covering pytest, unittest, PlantUML, metrics, and CI.
62
+ - Add contribution guidance for new rule types and metric implementations.
@@ -1,3 +1,22 @@
1
+ ## [1.1.1](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.1.0...v1.1.1) (2026-05-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** bump actions/upload-pages-artifact ([a4452fc](https://github.com/LukasNiessen/ArchUnitPython/commit/a4452fce2ce9428882cf686227de9f09cb6a21b1))
7
+
8
+ # [1.1.0](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.0.1...v1.1.0) (2026-04-26)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * linting errors ([bae7613](https://github.com/LukasNiessen/ArchUnitPython/commit/bae761376817a858a622f2320da3a4596823c891))
14
+
15
+
16
+ ### Features
17
+
18
+ * add external dependencies and TYPE_CHECKING ([02b8d01](https://github.com/LukasNiessen/ArchUnitPython/commit/02b8d01b37752e5ddd815ab5c10b36f26814f438))
19
+
1
20
  ## [1.0.1](https://github.com/LukasNiessen/ArchUnitPython/compare/v1.0.0...v1.0.1) (2026-04-02)
2
21
 
3
22
 
@@ -36,7 +36,7 @@ Releases are fully automated. When a PR is merged to `main`:
36
36
  1. CI runs lint + type checking + tests (across Python 3.10-3.13)
37
37
  2. If CI passes, [semantic-release](https://github.com/semantic-release/semantic-release) analyzes commit messages since the last release
38
38
  3. If there are `fix:` or `feat:` commits, it automatically:
39
- - Bumps the version in `pyproject.toml`
39
+ - Bumps the version in `pyproject.toml` and `src/archunitpython/__init__.py`
40
40
  - Updates `CHANGELOG.md`
41
41
  - Publishes to PyPI
42
42
  - Creates a GitHub release with release notes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archunitpython
3
- Version: 1.0.1
3
+ Version: 1.1.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
@@ -189,7 +189,7 @@ Ensure services/modules don't have forbidden cross-dependencies.
189
189
 
190
190
  Here is a repository with a fully functioning example that uses ArchUnitPython to ensure architectural rules:
191
191
 
192
- - **[RAG Pipeline Example](https://github.com/LukasNiessen/ArchUnitPython-Example-RAG)**: A mock AI/RAG pipeline with layered architecture and intentional violations demonstrating ArchUnitPython catching real problems
192
+ - **[RAG Pipeline Test Showcase](https://github.com/LukasNiessen/ArchUnitPython-TestRepo-RAG)**: A test showcase demonstrating ArchUnitPython's architecture testing capabilities on a RAG pipeline
193
193
 
194
194
  ## 🐣 Features
195
195
 
@@ -157,7 +157,7 @@ Ensure services/modules don't have forbidden cross-dependencies.
157
157
 
158
158
  Here is a repository with a fully functioning example that uses ArchUnitPython to ensure architectural rules:
159
159
 
160
- - **[RAG Pipeline Example](https://github.com/LukasNiessen/ArchUnitPython-Example-RAG)**: A mock AI/RAG pipeline with layered architecture and intentional violations demonstrating ArchUnitPython catching real problems
160
+ - **[RAG Pipeline Test Showcase](https://github.com/LukasNiessen/ArchUnitPython-TestRepo-RAG)**: A test showcase demonstrating ArchUnitPython's architecture testing capabilities on a RAG pipeline
161
161
 
162
162
  ## 🐣 Features
163
163
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "archunitpython"
7
- version = "1.0.1"
7
+ version = "1.1.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"
@@ -67,13 +67,13 @@ python_classes = ["Test*"]
67
67
  python_functions = ["test_*"]
68
68
 
69
69
  [tool.mypy]
70
- python_version = "1.0.1"
70
+ python_version = "3.10"
71
71
  strict = true
72
72
  warn_return_any = true
73
73
  warn_unused_configs = true
74
74
 
75
75
  [tool.ruff]
76
- target-version = "1.0.1"
76
+ target-version = "py310"
77
77
  line-length = 100
78
78
 
79
79
  [tool.ruff.lint]
@@ -0,0 +1,56 @@
1
+ # Architecture Testing Landscape
2
+
3
+ Research date: 2026-05-23
4
+
5
+ This document compares ArchUnitPython with architecture testing tools in the broader ecosystem and with Python-specific alternatives. It focuses on user-facing features that can inform product direction.
6
+
7
+ ## Current ArchUnitPython Position
8
+
9
+ ArchUnitPython already covers three valuable surfaces:
10
+
11
+ - File-level architecture rules through `project_files(...)`: dependency direction, cycle detection, naming/path conventions, external dependency checks, and custom file predicates.
12
+ - Slice-level architecture rules through `project_slices(...)`: slice extraction from path patterns or regexes, PlantUML diagram adherence, and forbidden slice dependencies.
13
+ - Code metrics through `metrics(...)`: count metrics, LCOM variants, abstractness, instability, distance from the main sequence, zone checks, and custom metrics.
14
+
15
+ That breadth makes the project closer to ArchUnit-style libraries than to pure import linters. The main gaps are around configuration ergonomics, reporting, incremental adoption, IDE/CI workflow polish, and deeper Python import semantics.
16
+
17
+ ## General Architecture Testing Players
18
+
19
+ | Tool | Ecosystem | Rule definition style | Dependency/layer rules | Cycles | Diagram support | Metrics | Reports/visualization | Notable strengths | ArchUnitPython comparison |
20
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
21
+ | [ArchUnit](https://www.archunit.org/userguide/html/000_Index.html) | Java | Fluent Java API, JUnit integrations | Yes: packages, classes, layers, slices | Yes | PlantUML component diagrams | Yes: software architecture metrics | Strong test failure output; mature JUnit support | Mature reference implementation with a broad rule model and mature JUnit workflow | ArchUnitPython mirrors the core idea and already has files/slices/metrics, but lacks class/member/annotation richness and mature import options |
22
+ | [ArchUnitNET](https://github.com/TNG/ArchUnitNET) | .NET | Fluent C# API with xUnit/NUnit/MSTest integrations | Yes: assemblies, namespaces, types, members | Yes | PlantUML support and diagram generation in the project ecosystem | Limited compared with ArchUnit | Test framework integrations | Strong .NET analogue to ArchUnit with many framework adapters | ArchUnitPython has stronger built-in code metrics, but less type/member-level expressiveness |
23
+ | [NetArchTest](https://github.com/BenMorris/NetArchTest) | .NET | Fluent C# API | Yes: namespace/type dependency and conventions | Not a primary differentiator | No first-class PlantUML in core | No | Policy results; test framework agnostic | Simple, widely adopted dependency/convention tests for .NET | ArchUnitPython is broader on cycles, slices, PlantUML, and metrics |
24
+ | [ts-arch](https://github.com/ts-arch/ts-arch) | TypeScript/JavaScript | Fluent API, Jest matcher | Yes: files, folders, slices | Yes | PlantUML diagram adherence | No | Jest-oriented failure output | Very close conceptual sibling: file API, slice API, PlantUML, NX monorepo support | ArchUnitPython has a similar surface plus metrics; ts-arch has stronger monorepo/NX positioning |
25
+ | [jQAssistant](https://jqassistant.github.io/jqassistant/current/) | Mostly JVM, plugin-based | Scanner + graph database + rule concepts/constraints | Yes, via graph rules and plugins | Yes, rule-dependent | Documentation validation and living documentation workflows | Software analytics via graph queries | Reports, server/explore mode, graph-backed analysis | Enterprise-scale scanning, graph exploration, living documentation, baseline management | ArchUnitPython is much lighter and easier to embed in tests, but lacks graph database exploration and living-doc workflows |
26
+
27
+ ## Python Architecture Testing Libraries
28
+
29
+ | Tool | Rule definition style | Dependency/layer rules | Cycles | External dependency rules | Diagram support | Metrics | Reports/visualization | Adoption workflow | How ArchUnitPython stacks up |
30
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
31
+ | [ArchUnitPython](https://github.com/LukasNiessen/ArchUnitPython) | Fluent Python API used from pytest/unittest/any test framework | Yes: file dependencies, external modules, slices, forbidden slice dependencies | Yes: file-level cycles | Yes: external module matching | Yes: PlantUML slice adherence | Yes: count, LCOM, distance, custom metrics | Experimental metric export; no full HTML report yet | Plain test functions; `assert_passes()` or `.check()` | Broadest feature mix among the Python tools reviewed, especially because metrics and PlantUML are built in |
32
+ | [Import Linter](https://import-linter.readthedocs.io/en/stable/index.html) | `.importlinter` configuration contracts plus CLI | Yes: forbidden, protected, layers, independence, acyclic siblings, custom contracts | Yes: acyclic siblings | Primarily first-party import contracts | No first-class PlantUML | No | Browser-based architecture UI | CLI, CI, config file, caching | More mature for config-driven import contracts and exploration; ArchUnitPython is more test-native and broader on metrics/PlantUML |
33
+ | [Tach](https://docs.gauge.sh/getting-started/introduction/) | `tach.toml` plus CLI commands | Yes: module dependencies and public interfaces | Dependency graph focused | Yes: `check-external` for third-party imports | No | No | `show`, `map`, `report`, VS Code integration | Incremental CLI workflow, sync, pre-commit/CI, public interface enforcement | Tach is stronger for modular-boundary workflow and public APIs; ArchUnitPython is stronger for test-suite rules, metrics, and diagram validation |
34
+ | [PyTestArch](https://zyskarch.github.io/pytestarch/latest/features/module_import_checks/) | Python query language evaluated in pytest | Yes: module dependency query language and layered architecture rules | Implicit through dependency rules; not positioned as a primary cycle API | Not a main focus | Yes: generates rules from PlantUML component diagrams | No | Optional matplotlib visualization | Pytest-centered evaluation structures | Similar to ArchUnitPython on Python tests and PlantUML; ArchUnitPython has simpler fluent API and built-in metrics |
35
+ | [pytest-archon](https://github.com/jwbargsten/pytest-archon) | Pytest-oriented architecture assertions | Yes: forbidden dependencies and project structure rules | Yes, positioned around avoiding cycles | Not a main differentiator | No | No | Pytest failure output | Lightweight pytest helper | ArchUnitPython is broader and more ArchUnit-like; pytest-archon is smaller and focused |
36
+
37
+ ## Product Takeaways
38
+
39
+ - ArchUnitPython should lean into being the ArchUnit-style Python test library, not just another import linter.
40
+ - The nearest feature gap against Python tools is not raw rule breadth; it is workflow: config files, ignore files, public interfaces, incremental adoption, reporting, and IDE/CI polish.
41
+ - Against ArchUnit and ArchUnitNET, the biggest long-term gaps are richer semantic model support, import semantics, and mature documentation/reporting.
42
+ - Metrics are a meaningful differentiator in the Python space and should be kept visible in docs and examples.
43
+
44
+ ## Sources
45
+
46
+ - ArchUnit User Guide: https://www.archunit.org/userguide/html/000_Index.html
47
+ - ArchUnitNET repository: https://github.com/TNG/ArchUnitNET
48
+ - NetArchTest repository: https://github.com/BenMorris/NetArchTest
49
+ - ts-arch repository: https://github.com/ts-arch/ts-arch
50
+ - jQAssistant User Manual: https://jqassistant.github.io/jqassistant/current/
51
+ - Import Linter documentation: https://import-linter.readthedocs.io/en/stable/index.html
52
+ - Tach documentation: https://docs.gauge.sh/getting-started/introduction/
53
+ - PyTestArch documentation: https://zyskarch.github.io/pytestarch/latest/features/module_import_checks/
54
+ - PyTestArch PlantUML documentation: https://zyskarch.github.io/pytestarch/latest/features/plantuml/
55
+ - PyTestArch visualization documentation: https://zyskarch.github.io/pytestarch/latest/features/visualization/
56
+ - pytest-archon repository: https://github.com/jwbargsten/pytest-archon
@@ -1,6 +1,6 @@
1
1
  """ArchUnitPython - Architecture testing library for Python projects."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.1.1"
4
4
 
5
5
  # Files API
6
6
  # Common
@@ -8,7 +8,9 @@ import os
8
8
  from archunitpython.common.extraction.graph import Edge, Graph, ImportKind
9
9
  from archunitpython.common.fluentapi.checkable import CheckOptions
10
10
 
11
- _graph_cache: dict[str, Graph] = {}
11
+ GraphCacheKey = tuple[str, tuple[str, ...], bool]
12
+
13
+ _graph_cache: dict[GraphCacheKey, Graph] = {}
12
14
 
13
15
  _DEFAULT_EXCLUDE = [
14
16
  "__pycache__",
@@ -56,7 +58,13 @@ def extract_graph(
56
58
  project_path = os.getcwd()
57
59
 
58
60
  project_path = os.path.abspath(project_path)
59
- cache_key = project_path
61
+ excludes = list(exclude_patterns) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
62
+ ignore_type_checking_imports = bool(
63
+ options and options.ignore_type_checking_imports
64
+ )
65
+ cache_key = _build_cache_key(
66
+ project_path, excludes, ignore_type_checking_imports
67
+ )
60
68
 
61
69
  if options and options.clear_cache:
62
70
  _graph_cache.pop(cache_key, None)
@@ -64,18 +72,36 @@ def extract_graph(
64
72
  if cache_key in _graph_cache:
65
73
  return _graph_cache[cache_key]
66
74
 
67
- result = _extract_graph_uncached(project_path, exclude_patterns)
75
+ result = _extract_graph_uncached(
76
+ project_path,
77
+ excludes,
78
+ ignore_type_checking_imports=ignore_type_checking_imports,
79
+ )
68
80
  _graph_cache[cache_key] = result
69
81
  return result
70
82
 
71
83
 
84
+ def _build_cache_key(
85
+ project_path: str,
86
+ exclude_patterns: list[str],
87
+ ignore_type_checking_imports: bool,
88
+ ) -> GraphCacheKey:
89
+ """Build a stable cache key for graph extraction options."""
90
+ return (
91
+ project_path,
92
+ tuple(sorted(exclude_patterns)),
93
+ ignore_type_checking_imports,
94
+ )
95
+
96
+
72
97
  def _extract_graph_uncached(
73
98
  project_path: str,
74
- exclude_patterns: list[str] | None = None,
99
+ exclude_patterns: list[str],
100
+ *,
101
+ ignore_type_checking_imports: bool = False,
75
102
  ) -> Graph:
76
103
  """Extract graph without caching."""
77
- excludes = exclude_patterns if exclude_patterns is not None else _DEFAULT_EXCLUDE
78
- py_files = _find_python_files(project_path, excludes)
104
+ py_files = _find_python_files(project_path, exclude_patterns)
79
105
 
80
106
  edges: list[Edge] = []
81
107
  py_files_set = set(py_files)
@@ -93,6 +119,11 @@ def _extract_graph_uncached(
93
119
  # Extract and resolve imports
94
120
  imports = _extract_imports(file_path)
95
121
  for module_name, import_kind in imports:
122
+ if (
123
+ ignore_type_checking_imports
124
+ and import_kind == ImportKind.TYPE_IMPORT
125
+ ):
126
+ continue
96
127
  resolved, is_external = _resolve_import(
97
128
  module_name, file_path, project_path, import_kind
98
129
  )
@@ -16,6 +16,7 @@ class CheckOptions:
16
16
  allow_empty_tests: bool = False
17
17
  logging: LoggingOptions | None = None
18
18
  clear_cache: bool = False
19
+ ignore_type_checking_imports: bool = False
19
20
 
20
21
 
21
22
  class Checkable(Protocol):
@@ -34,3 +34,20 @@ def per_edge() -> MapFunction:
34
34
  return MappedEdge(source_label=edge.source, target_label=edge.target)
35
35
 
36
36
  return mapper
37
+
38
+
39
+ def per_external_edge() -> MapFunction:
40
+ """Create a mapper that only passes external edges.
41
+
42
+ Self-referencing edges are filtered out, though they are not expected for
43
+ external imports.
44
+ """
45
+
46
+ def mapper(edge: Edge) -> MappedEdge | None:
47
+ if not edge.external:
48
+ return None
49
+ if edge.source == edge.target:
50
+ return None
51
+ return MappedEdge(source_label=edge.source, target_label=edge.target)
52
+
53
+ return mapper
@@ -5,6 +5,10 @@ from archunitpython.files.assertion.custom_file_logic import (
5
5
  gather_custom_file_violations,
6
6
  )
7
7
  from archunitpython.files.assertion.cycle_free import ViolatingCycle, gather_cycle_violations
8
+ from archunitpython.files.assertion.depend_on_external_modules import (
9
+ ViolatingExternalModuleDependency,
10
+ gather_depend_on_external_module_violations,
11
+ )
8
12
  from archunitpython.files.assertion.depend_on_files import (
9
13
  ViolatingFileDependency,
10
14
  gather_depend_on_file_violations,
@@ -20,9 +24,11 @@ __all__ = [
20
24
  "FileInfo",
21
25
  "ViolatingCycle",
22
26
  "ViolatingFileDependency",
27
+ "ViolatingExternalModuleDependency",
23
28
  "ViolatingNode",
24
29
  "gather_custom_file_violations",
25
30
  "gather_cycle_violations",
26
31
  "gather_depend_on_file_violations",
32
+ "gather_depend_on_external_module_violations",
27
33
  "gather_regex_matching_violations",
28
34
  ]
@@ -0,0 +1,64 @@
1
+ """Violation gathering for external module dependency rules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from archunitpython.common.assertion.violation import Violation
8
+ from archunitpython.common.pattern_matching import matches_pattern
9
+ from archunitpython.common.projection.types import ProjectedEdge
10
+ from archunitpython.common.types import Filter
11
+
12
+
13
+ @dataclass
14
+ class ViolatingExternalModuleDependency(Violation):
15
+ """An external module dependency that violates a rule."""
16
+
17
+ dependency: ProjectedEdge
18
+ is_negated: bool = False
19
+
20
+
21
+ def gather_depend_on_external_module_violations(
22
+ edges: list[ProjectedEdge],
23
+ subject_filters: list[Filter],
24
+ module_filters: list[Filter],
25
+ is_negated: bool,
26
+ ) -> list[Violation]:
27
+ """Check if files depend on forbidden/allowed external modules.
28
+
29
+ Subject filters use AND semantics. Module filters use OR semantics, which
30
+ makes it possible to express useful allowlists or blocklists for external
31
+ module names.
32
+ """
33
+ violations: list[Violation] = []
34
+
35
+ for edge in edges:
36
+ source_matches = all(
37
+ matches_pattern(edge.source_label, filter_)
38
+ for filter_ in subject_filters
39
+ )
40
+ if not source_matches:
41
+ continue
42
+
43
+ target_matches = (
44
+ any(matches_pattern(edge.target_label, filter_) for filter_ in module_filters)
45
+ if module_filters
46
+ else False
47
+ )
48
+
49
+ if is_negated:
50
+ if target_matches:
51
+ violations.append(
52
+ ViolatingExternalModuleDependency(
53
+ dependency=edge, is_negated=True
54
+ )
55
+ )
56
+ else:
57
+ if not target_matches:
58
+ violations.append(
59
+ ViolatingExternalModuleDependency(
60
+ dependency=edge, is_negated=False
61
+ )
62
+ )
63
+
64
+ return violations
@@ -16,7 +16,10 @@ from archunitpython.common.assertion.violation import EmptyTestViolation, Violat
16
16
  from archunitpython.common.extraction.extract_graph import extract_graph
17
17
  from archunitpython.common.fluentapi.checkable import CheckOptions
18
18
  from archunitpython.common.pattern_matching import matches_all_patterns
19
- from archunitpython.common.projection.edge_projections import per_internal_edge
19
+ from archunitpython.common.projection.edge_projections import (
20
+ per_external_edge,
21
+ per_internal_edge,
22
+ )
20
23
  from archunitpython.common.projection.project_cycles import project_cycles
21
24
  from archunitpython.common.projection.project_edges import project_edges
22
25
  from archunitpython.common.projection.project_nodes import project_to_nodes
@@ -28,6 +31,9 @@ from archunitpython.files.assertion.custom_file_logic import (
28
31
  gather_custom_file_violations,
29
32
  )
30
33
  from archunitpython.files.assertion.cycle_free import gather_cycle_violations
34
+ from archunitpython.files.assertion.depend_on_external_modules import (
35
+ gather_depend_on_external_module_violations,
36
+ )
31
37
  from archunitpython.files.assertion.depend_on_files import gather_depend_on_file_violations
32
38
  from archunitpython.files.assertion.matching_files import gather_regex_matching_violations
33
39
 
@@ -133,6 +139,14 @@ class PositiveMatchPatternFileConditionBuilder:
133
139
  self._project_path, self._filters, is_negated=False
134
140
  )
135
141
 
142
+ def depend_on_external_modules(
143
+ self,
144
+ ) -> "DependOnExternalModuleConditionBuilder":
145
+ """Begin external dependency assertion for module names."""
146
+ return DependOnExternalModuleConditionBuilder(
147
+ self._project_path, self._filters, is_negated=False
148
+ )
149
+
136
150
  def be_in_folder(self, folder: Pattern) -> "MatchPatternFileCondition":
137
151
  """Assert that files are in a certain folder."""
138
152
  return MatchPatternFileCondition(
@@ -182,6 +196,14 @@ class NegatedMatchPatternFileConditionBuilder:
182
196
  self._project_path, self._filters, is_negated=True
183
197
  )
184
198
 
199
+ def depend_on_external_modules(
200
+ self,
201
+ ) -> "DependOnExternalModuleConditionBuilder":
202
+ """Begin negative external dependency assertion for module names."""
203
+ return DependOnExternalModuleConditionBuilder(
204
+ self._project_path, self._filters, is_negated=True
205
+ )
206
+
185
207
  def be_in_folder(self, folder: Pattern) -> "MatchPatternFileCondition":
186
208
  """Assert that files are NOT in a certain folder."""
187
209
  return MatchPatternFileCondition(
@@ -260,6 +282,31 @@ class DependOnFileConditionBuilder:
260
282
  )
261
283
 
262
284
 
285
+ class DependOnExternalModuleConditionBuilder:
286
+ """Configure external module dependency target patterns."""
287
+
288
+ def __init__(
289
+ self, project_path: str | None, filters: list[Filter], is_negated: bool
290
+ ) -> None:
291
+ self._project_path = project_path
292
+ self._filters = filters
293
+ self._is_negated = is_negated
294
+ self._module_filters: list[Filter] = []
295
+
296
+ def matching(self, module_name: Pattern) -> "DependOnExternalModuleCondition":
297
+ """Target external modules by dotted module name pattern.
298
+
299
+ Multiple calls are combined with OR semantics.
300
+ """
301
+ self._module_filters.append(RegexFactory.path_matcher(module_name))
302
+ return DependOnExternalModuleCondition(
303
+ self._project_path,
304
+ self._filters,
305
+ list(self._module_filters),
306
+ self._is_negated,
307
+ )
308
+
309
+
263
310
  def _get_filtered_nodes(
264
311
  project_path: str | None,
265
312
  filters: list[Filter],
@@ -346,6 +393,38 @@ class DependOnFileCondition:
346
393
  )
347
394
 
348
395
 
396
+ class DependOnExternalModuleCondition:
397
+ """Checkable that verifies external module dependency rules."""
398
+
399
+ def __init__(
400
+ self,
401
+ project_path: str | None,
402
+ subject_filters: list[Filter],
403
+ module_filters: list[Filter],
404
+ is_negated: bool,
405
+ ) -> None:
406
+ self._project_path = project_path
407
+ self._subject_filters = subject_filters
408
+ self._module_filters = module_filters
409
+ self._is_negated = is_negated
410
+
411
+ def matching(self, module_name: Pattern) -> "DependOnExternalModuleCondition":
412
+ """Add another external module pattern using OR semantics."""
413
+ self._module_filters.append(RegexFactory.path_matcher(module_name))
414
+ return self
415
+
416
+ def check(self, options: CheckOptions | None = None) -> list[Violation]:
417
+ graph = extract_graph(self._project_path, options=options)
418
+ edges = project_edges(graph, per_external_edge())
419
+
420
+ return gather_depend_on_external_module_violations(
421
+ edges,
422
+ self._subject_filters,
423
+ self._module_filters,
424
+ self._is_negated,
425
+ )
426
+
427
+
349
428
  class MatchPatternFileCondition:
350
429
  """Checkable that verifies files match/don't match patterns."""
351
430
 
@@ -7,6 +7,9 @@ from dataclasses import dataclass
7
7
  from archunitpython.common.assertion.violation import EmptyTestViolation, Violation
8
8
  from archunitpython.files.assertion.custom_file_logic import CustomFileViolation
9
9
  from archunitpython.files.assertion.cycle_free import ViolatingCycle
10
+ from archunitpython.files.assertion.depend_on_external_modules import (
11
+ ViolatingExternalModuleDependency,
12
+ )
10
13
  from archunitpython.files.assertion.depend_on_files import ViolatingFileDependency
11
14
  from archunitpython.files.assertion.matching_files import ViolatingNode
12
15
  from archunitpython.metrics.assertion.metric_thresholds import (
@@ -52,6 +55,15 @@ class ViolationFactory:
52
55
  f"'{edge.target_label}'",
53
56
  )
54
57
 
58
+ if isinstance(violation, ViolatingExternalModuleDependency):
59
+ edge = violation.dependency
60
+ return TestViolation(
61
+ message="External module dependency violation",
62
+ details=f"'{edge.source_label}' "
63
+ f"{'depends on' if violation.is_negated else 'does not depend on'} "
64
+ f"external module '{edge.target_label}'",
65
+ )
66
+
55
67
  if isinstance(violation, ViolatingCycle):
56
68
  cycle_str = " -> ".join(
57
69
  e.source_label for e in violation.cycle
@@ -101,6 +101,7 @@ class TestCheckOptions:
101
101
  assert opts.allow_empty_tests is False
102
102
  assert opts.logging is None
103
103
  assert opts.clear_cache is False
104
+ assert opts.ignore_type_checking_imports is False
104
105
 
105
106
  def test_custom(self):
106
107
  logging = LoggingOptions(enabled=True, level="debug")
@@ -108,12 +109,14 @@ class TestCheckOptions:
108
109
  allow_empty_tests=True,
109
110
  logging=logging,
110
111
  clear_cache=True,
112
+ ignore_type_checking_imports=True,
111
113
  )
112
114
  assert opts.allow_empty_tests is True
113
115
  assert opts.logging is not None
114
116
  assert opts.logging.enabled is True
115
117
  assert opts.logging.level == "debug"
116
118
  assert opts.clear_cache is True
119
+ assert opts.ignore_type_checking_imports is True
117
120
 
118
121
 
119
122
  class TestLoggingOptions: