archunitpython 1.0.0__py3-none-any.whl

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 (75) hide show
  1. archunitpython/__init__.py +45 -0
  2. archunitpython/common/__init__.py +18 -0
  3. archunitpython/common/assertion/__init__.py +3 -0
  4. archunitpython/common/assertion/violation.py +21 -0
  5. archunitpython/common/error/__init__.py +3 -0
  6. archunitpython/common/error/errors.py +13 -0
  7. archunitpython/common/extraction/__init__.py +13 -0
  8. archunitpython/common/extraction/extract_graph.py +345 -0
  9. archunitpython/common/extraction/graph.py +39 -0
  10. archunitpython/common/fluentapi/__init__.py +3 -0
  11. archunitpython/common/fluentapi/checkable.py +28 -0
  12. archunitpython/common/logging/__init__.py +3 -0
  13. archunitpython/common/logging/types.py +18 -0
  14. archunitpython/common/pattern_matching.py +80 -0
  15. archunitpython/common/projection/__init__.py +30 -0
  16. archunitpython/common/projection/cycles/__init__.py +4 -0
  17. archunitpython/common/projection/cycles/cycle_utils.py +49 -0
  18. archunitpython/common/projection/cycles/cycles.py +26 -0
  19. archunitpython/common/projection/cycles/johnsons_apsp.py +110 -0
  20. archunitpython/common/projection/cycles/model.py +22 -0
  21. archunitpython/common/projection/cycles/tarjan_scc.py +86 -0
  22. archunitpython/common/projection/edge_projections.py +36 -0
  23. archunitpython/common/projection/project_cycles.py +85 -0
  24. archunitpython/common/projection/project_edges.py +43 -0
  25. archunitpython/common/projection/project_nodes.py +49 -0
  26. archunitpython/common/projection/types.py +40 -0
  27. archunitpython/common/regex_factory.py +76 -0
  28. archunitpython/common/types.py +29 -0
  29. archunitpython/common/util/__init__.py +3 -0
  30. archunitpython/common/util/declaration_detector.py +115 -0
  31. archunitpython/common/util/logger.py +100 -0
  32. archunitpython/files/__init__.py +3 -0
  33. archunitpython/files/assertion/__init__.py +28 -0
  34. archunitpython/files/assertion/custom_file_logic.py +107 -0
  35. archunitpython/files/assertion/cycle_free.py +29 -0
  36. archunitpython/files/assertion/depend_on_files.py +67 -0
  37. archunitpython/files/assertion/matching_files.py +64 -0
  38. archunitpython/files/fluentapi/__init__.py +3 -0
  39. archunitpython/files/fluentapi/files.py +403 -0
  40. archunitpython/metrics/__init__.py +3 -0
  41. archunitpython/metrics/assertion/__init__.py +0 -0
  42. archunitpython/metrics/assertion/metric_thresholds.py +51 -0
  43. archunitpython/metrics/calculation/__init__.py +0 -0
  44. archunitpython/metrics/calculation/count.py +148 -0
  45. archunitpython/metrics/calculation/distance.py +110 -0
  46. archunitpython/metrics/calculation/lcom.py +177 -0
  47. archunitpython/metrics/common/__init__.py +19 -0
  48. archunitpython/metrics/common/types.py +67 -0
  49. archunitpython/metrics/extraction/__init__.py +0 -0
  50. archunitpython/metrics/extraction/extract_class_info.py +246 -0
  51. archunitpython/metrics/fluentapi/__init__.py +3 -0
  52. archunitpython/metrics/fluentapi/export_utils.py +89 -0
  53. archunitpython/metrics/fluentapi/metrics.py +589 -0
  54. archunitpython/metrics/projection/__init__.py +0 -0
  55. archunitpython/py.typed +0 -0
  56. archunitpython/slices/__init__.py +3 -0
  57. archunitpython/slices/assertion/__init__.py +13 -0
  58. archunitpython/slices/assertion/admissible_edges.py +108 -0
  59. archunitpython/slices/fluentapi/__init__.py +3 -0
  60. archunitpython/slices/fluentapi/slices.py +220 -0
  61. archunitpython/slices/projection/__init__.py +8 -0
  62. archunitpython/slices/projection/slicing_projections.py +128 -0
  63. archunitpython/slices/uml/__init__.py +4 -0
  64. archunitpython/slices/uml/export_diagram.py +31 -0
  65. archunitpython/slices/uml/generate_rules.py +71 -0
  66. archunitpython/testing/__init__.py +3 -0
  67. archunitpython/testing/assertion.py +47 -0
  68. archunitpython/testing/common/__init__.py +4 -0
  69. archunitpython/testing/common/color_utils.py +57 -0
  70. archunitpython/testing/common/violation_factory.py +97 -0
  71. archunitpython/testing/pytest_plugin/__init__.py +0 -0
  72. archunitpython-1.0.0.dist-info/METADATA +660 -0
  73. archunitpython-1.0.0.dist-info/RECORD +75 -0
  74. archunitpython-1.0.0.dist-info/WHEEL +4 -0
  75. archunitpython-1.0.0.dist-info/licenses/LICENSE +7 -0
@@ -0,0 +1,660 @@
1
+ Metadata-Version: 2.4
2
+ Name: archunitpython
3
+ Version: 1.0.0
4
+ Summary: Architecture testing library for Python projects. Enforce dependency rules, detect cycles, validate metrics.
5
+ Project-URL: Homepage, https://github.com/LukasNiessen/ArchUnitPython
6
+ Project-URL: Repository, https://github.com/LukasNiessen/ArchUnitPython.git
7
+ Project-URL: Documentation, https://lukasniessen.github.io/ArchUnitPython/
8
+ Project-URL: Issues, https://github.com/LukasNiessen/ArchUnitPython/issues
9
+ Project-URL: Changelog, https://github.com/LukasNiessen/ArchUnitPython/blob/main/CHANGELOG.md
10
+ Author-email: Lukas Niessen <lks.niessen@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: architectural-testing,architecture,archunit,circular-dependencies,clean-code,code-metrics,code-quality,code-standards,dependency-analysis,developer-tools,linting,pytest,python,software-architecture,static-analysis,technical-debt,testing
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.0; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
29
+ Requires-Dist: pytest>=7.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.4; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # ArchUnitPython - Architecture Testing
34
+
35
+ <div align="center" name="top">
36
+
37
+ <!-- spacer -->
38
+ <p></p>
39
+
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
+ [![PyPI version](https://img.shields.io/pypi/v/archunitpython.svg)](https://pypi.org/project/archunitpython/)
42
+ [![Python versions](https://img.shields.io/pypi/pyversions/archunitpython.svg)](https://pypi.org/project/archunitpython/)
43
+ [![GitHub stars](https://img.shields.io/github/stars/LukasNiessen/ArchUnitPython.svg)](https://github.com/LukasNiessen/ArchUnitPython)
44
+
45
+ </div>
46
+
47
+ 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.
48
+
49
+ _The Python port of [ArchUnitTS](https://github.com/LukasNiessen/ArchUnitTS). Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit._
50
+
51
+ [Setup](#-setup) â€ĸ [Use Cases](#-use-cases) â€ĸ [Features](#-features) â€ĸ [Contributing](CONTRIBUTING.md)
52
+
53
+ ## ⚡ 5 min Quickstart
54
+
55
+ ### Installation
56
+
57
+ ```bash
58
+ pip install archunitpython
59
+ ```
60
+
61
+ ### Add tests
62
+
63
+ Simply add tests to your existing test suites. The following is an example using pytest. First we ensure that we have no circular dependencies.
64
+
65
+ ```python
66
+ from archunitpython import project_files, metrics, assert_passes
67
+
68
+ def test_no_circular_dependencies():
69
+ rule = project_files("src/").in_folder("src/**").should().have_no_cycles()
70
+ assert_passes(rule)
71
+ ```
72
+
73
+ Next we ensure that our layered architecture is respected.
74
+
75
+ ```python
76
+ def test_presentation_should_not_depend_on_database():
77
+ rule = (
78
+ project_files("src/")
79
+ .in_folder("**/presentation/**")
80
+ .should_not()
81
+ .depend_on_files()
82
+ .in_folder("**/database/**")
83
+ )
84
+ assert_passes(rule)
85
+
86
+ def test_business_should_not_depend_on_database():
87
+ rule = (
88
+ project_files("src/")
89
+ .in_folder("**/business/**")
90
+ .should_not()
91
+ .depend_on_files()
92
+ .in_folder("**/database/**")
93
+ )
94
+ assert_passes(rule)
95
+
96
+ # More layers ...
97
+ ```
98
+
99
+ Lastly we ensure that some code metric rules are met.
100
+
101
+ ```python
102
+ def test_no_large_files():
103
+ rule = metrics("src/").count().lines_of_code().should_be_below(1000)
104
+ assert_passes(rule)
105
+
106
+ def test_high_cohesion():
107
+ # LCOM metric (lack of cohesion of methods), low = high cohesion
108
+ rule = metrics("src/").lcom().lcom96b().should_be_below(0.3)
109
+ assert_passes(rule)
110
+ ```
111
+
112
+ ### CI Integration
113
+
114
+ These tests run automatically in your testing setup, for example in your CI pipeline, so that's basically it. This setup ensures that the architectural rules you have defined are always adhered to!
115
+
116
+ ```yaml
117
+ # GitHub Actions
118
+ - name: Run Architecture Tests
119
+ run: pytest tests/test_architecture.py -v
120
+ ```
121
+
122
+ ## 🚐 Setup
123
+
124
+ Installation:
125
+
126
+ ```bash
127
+ pip install archunitpython
128
+ ```
129
+
130
+ That's it. Works with **pytest**, **unittest**, or any Python testing framework.
131
+
132
+ ### pytest (Recommended)
133
+
134
+ Use `assert_passes()` for clean assertion messages:
135
+
136
+ ```python
137
+ from archunitpython import project_files, assert_passes
138
+
139
+ def test_my_architecture():
140
+ rule = project_files("src/").should().have_no_cycles()
141
+ assert_passes(rule)
142
+ ```
143
+
144
+ ### Any Other Framework
145
+
146
+ Use `.check()` directly and assert on the violations list:
147
+
148
+ ```python
149
+ from archunitpython import project_files
150
+
151
+ rule = project_files("src/").should().have_no_cycles()
152
+ violations = rule.check()
153
+ assert len(violations) == 0
154
+ ```
155
+
156
+ ### Configuration Options
157
+
158
+ Both `assert_passes()` and `.check()` accept configuration options:
159
+
160
+ ```python
161
+ from archunitpython import CheckOptions
162
+
163
+ options = CheckOptions(
164
+ allow_empty_tests=True, # Don't fail when no files match
165
+ clear_cache=True, # Clear the graph cache
166
+ )
167
+
168
+ violations = rule.check(options)
169
+ ```
170
+
171
+ ## 🐹 Use Cases
172
+
173
+ Here is an overview of common use cases.
174
+
175
+ **Layered Architecture:**
176
+
177
+ Enforce that higher layers don't depend on lower layers and vice versa.
178
+
179
+ **Clean Architecture / Hexagonal:**
180
+
181
+ Validate that domain logic doesn't depend on infrastructure.
182
+
183
+ **Microservices / Modular:**
184
+
185
+ Ensure services/modules don't have forbidden cross-dependencies.
186
+
187
+ ## 🐲 Example Repository
188
+
189
+ Here is a repository with a fully functioning example that uses ArchUnitPython to ensure architectural rules:
190
+
191
+ - **[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
+
193
+ ## đŸŖ Features
194
+
195
+ This is an overview of what you can do with ArchUnitPython.
196
+
197
+ ### Circular Dependencies
198
+
199
+ ```python
200
+ def test_services_cycle_free():
201
+ rule = project_files("src/").in_folder("**/services/**").should().have_no_cycles()
202
+ assert_passes(rule)
203
+ ```
204
+
205
+ ### Layer Dependencies
206
+
207
+ ```python
208
+ def test_clean_architecture_layers():
209
+ rule = (
210
+ project_files("src/")
211
+ .in_folder("**/presentation/**")
212
+ .should_not()
213
+ .depend_on_files()
214
+ .in_folder("**/database/**")
215
+ )
216
+ assert_passes(rule)
217
+
218
+ def test_business_not_depend_on_presentation():
219
+ rule = (
220
+ project_files("src/")
221
+ .in_folder("**/business/**")
222
+ .should_not()
223
+ .depend_on_files()
224
+ .in_folder("**/presentation/**")
225
+ )
226
+ assert_passes(rule)
227
+ ```
228
+
229
+ ### Naming Conventions
230
+
231
+ ```python
232
+ def test_naming_patterns():
233
+ rule = (
234
+ project_files("src/")
235
+ .in_folder("**/services/**")
236
+ .should()
237
+ .have_name("*_service.py")
238
+ )
239
+ assert_passes(rule)
240
+ ```
241
+
242
+ ### Code Metrics
243
+
244
+ ```python
245
+ def test_no_large_files():
246
+ rule = metrics("src/").count().lines_of_code().should_be_below(1000)
247
+ assert_passes(rule)
248
+
249
+ def test_high_class_cohesion():
250
+ rule = metrics("src/").lcom().lcom96b().should_be_below(0.3)
251
+ assert_passes(rule)
252
+
253
+ def test_method_count():
254
+ rule = metrics("src/").count().method_count().should_be_below(20)
255
+ assert_passes(rule)
256
+
257
+ def test_field_count_for_data_classes():
258
+ rule = (
259
+ metrics("src/")
260
+ .for_classes_matching("*Data*")
261
+ .count()
262
+ .field_count()
263
+ .should_be(3)
264
+ )
265
+ assert_passes(rule)
266
+ ```
267
+
268
+ ### Distance Metrics
269
+
270
+ ```python
271
+ def test_proper_coupling():
272
+ rule = metrics("src/").distance().distance_from_main_sequence().should_be_below(0.3)
273
+ assert_passes(rule)
274
+
275
+ def test_not_in_zone_of_pain():
276
+ rule = metrics("src/").distance().not_in_zone_of_pain()
277
+ assert_passes(rule)
278
+ ```
279
+
280
+ ### Custom Rules
281
+
282
+ You can define your own custom rules.
283
+
284
+ ```python
285
+ rule_desc = "Python files should have docstrings"
286
+
287
+ def has_docstring(file):
288
+ return '"""' in file.content or "'''" in file.content
289
+
290
+ violations = (
291
+ project_files("src/")
292
+ .with_name("*.py")
293
+ .should()
294
+ .adhere_to(has_docstring, rule_desc)
295
+ .check()
296
+ )
297
+
298
+ assert len(violations) == 0
299
+ ```
300
+
301
+ ### Custom Metrics
302
+
303
+ You can define your own metrics as well.
304
+
305
+ ```python
306
+ def test_method_field_ratio():
307
+ rule = (
308
+ metrics("src/")
309
+ .custom_metric(
310
+ "methodFieldRatio",
311
+ "Ratio of methods to fields",
312
+ lambda ci: len(ci.methods) / max(len(ci.fields), 1),
313
+ )
314
+ .should_be_below(10)
315
+ )
316
+ assert_passes(rule)
317
+ ```
318
+
319
+ ### Architecture Slices
320
+
321
+ ```python
322
+ import re
323
+ from archunitpython import project_slices
324
+
325
+ def test_adhere_to_diagram():
326
+ diagram = """
327
+ @startuml
328
+ component [controllers]
329
+ component [services]
330
+ [controllers] --> [services]
331
+ @enduml"""
332
+
333
+ rule = (
334
+ project_slices("src/")
335
+ .defined_by_regex(re.compile(r"/([^/]+)/[^/]+\.py$"))
336
+ .should()
337
+ .adhere_to_diagram(diagram)
338
+ )
339
+ assert_passes(rule)
340
+
341
+ def test_no_forbidden_dependency():
342
+ rule = (
343
+ project_slices("src/")
344
+ .defined_by("src/(**)/**")
345
+ .should_not()
346
+ .contain_dependency("services", "controllers")
347
+ )
348
+ assert_passes(rule)
349
+ ```
350
+
351
+ ### Reports
352
+
353
+ Generate HTML reports for your metrics. _Note that this feature is in beta._
354
+
355
+ ```python
356
+ from archunitpython.metrics.fluentapi.export_utils import MetricsExporter, ExportOptions
357
+
358
+ MetricsExporter.export_as_html(
359
+ {"MethodCount": 5, "FieldCount": 3, "LinesOfCode": 150},
360
+ ExportOptions(
361
+ output_path="reports/metrics.html",
362
+ title="Architecture Metrics Dashboard",
363
+ ),
364
+ )
365
+ ```
366
+
367
+ ## 🔎 Pattern Matching System
368
+
369
+ We offer three targeting options for pattern matching across all modules:
370
+
371
+ - **`with_name(pattern)`** - Pattern is checked against the filename (e.g. `service.py` from `src/services/service.py`)
372
+ - **`in_path(pattern)`** - Pattern is checked against the full relative path (e.g. `src/services/service.py`)
373
+ - **`in_folder(pattern)`** - Pattern is checked against the path without filename (e.g. `src/services` from `src/services/service.py`)
374
+
375
+ For the metrics module there is an additional one:
376
+
377
+ - **`for_classes_matching(pattern)`** - Pattern is checked against class names. The filepath or filename does not matter here
378
+
379
+ ### Pattern Types
380
+
381
+ We support string patterns and regular expressions. String patterns support glob.
382
+
383
+ ```python
384
+ # String patterns with glob support (case sensitive)
385
+ .with_name("*_service.py") # All files ending with _service.py
386
+ .in_folder("**/services") # All files in any services folder
387
+ .in_path("src/api/**/*.py") # All Python files under src/api
388
+
389
+ # Regular expressions
390
+ import re
391
+ .with_name(re.compile(r".*Service\.py$"))
392
+ .in_folder(re.compile(r"services$"))
393
+
394
+ # For metrics module: Class name matching
395
+ .for_classes_matching("*Service*")
396
+ .for_classes_matching(re.compile(r"^User.*"))
397
+ ```
398
+
399
+ ### Glob Patterns Guide
400
+
401
+ #### Basic Wildcards
402
+
403
+ - `*` - Matches any characters within a single path segment (except `/`)
404
+ - `**` - Matches any characters across multiple path segments
405
+ - `?` - Matches exactly one character
406
+
407
+ #### Common Glob Examples
408
+
409
+ ```python
410
+ # Filename patterns
411
+ .with_name("*.py") # All Python files
412
+ .with_name("*_service.py") # Files ending with _service.py
413
+ .with_name("test_*.py") # Files starting with test_
414
+
415
+ # Folder patterns
416
+ .in_folder("**/services") # Any services folder at any depth
417
+ .in_folder("src/services") # Exact src/services folder
418
+ .in_folder("**/test/**") # Any folder containing test in path
419
+
420
+ # Path patterns
421
+ .in_path("src/**/*.py") # Python files anywhere under src
422
+ .in_path("**/test/**/*_test.py") # Test files in any test folder
423
+ ```
424
+
425
+ ### Recommendation
426
+
427
+ We generally recommend using string patterns with glob support unless you need very special cases. Regular expressions add extra complexity that is not necessary for most cases.
428
+
429
+ ### Supported Metric Types
430
+
431
+ #### LCOM (Lack of Cohesion of Methods)
432
+
433
+ The LCOM metrics measure how well the methods and fields of a class are connected. Lower values indicate better cohesion.
434
+
435
+ ```python
436
+ # LCOM96a (Henderson et al.)
437
+ metrics("src/").lcom().lcom96a().should_be_below(0.8)
438
+
439
+ # LCOM96b (Henderson et al.) - most commonly used
440
+ metrics("src/").lcom().lcom96b().should_be_below(0.7)
441
+ ```
442
+
443
+ All 8 LCOM variants are available: `lcom96a()`, `lcom96b()`, `lcom1()` through `lcom5()`, and `lcomstar()`.
444
+
445
+ The LCOM96b metric is calculated as:
446
+
447
+ ```
448
+ LCOM96b = (1/a) * sum((1/m) * (m - mu(Ai)))
449
+ ```
450
+
451
+ Where:
452
+
453
+ - `m` is the number of methods in the class
454
+ - `a` is the number of attributes (fields) in the class
455
+ - `mu(Ai)` is the number of methods that access attribute Ai
456
+
457
+ The result is a value between 0 and 1:
458
+
459
+ - 0: perfect cohesion (all methods access all attributes)
460
+ - 1: complete lack of cohesion (each method accesses its own attribute)
461
+
462
+ #### Count Metrics
463
+
464
+ ```python
465
+ metrics("src/").count().method_count().should_be_below(20)
466
+ metrics("src/").count().field_count().should_be_below(15)
467
+ metrics("src/").count().lines_of_code().should_be_below(200)
468
+ metrics("src/").count().statements().should_be_below(100)
469
+ metrics("src/").count().imports().should_be_below(20)
470
+ ```
471
+
472
+ #### Distance Metrics
473
+
474
+ ```python
475
+ metrics("src/").distance().abstractness().should_be_above(0.3)
476
+ metrics("src/").distance().instability().should_be_below(0.8)
477
+ metrics("src/").distance().distance_from_main_sequence().should_be_below(0.5)
478
+ ```
479
+
480
+ #### Custom Metrics
481
+
482
+ ```python
483
+ metrics("src/").custom_metric(
484
+ "complexityRatio",
485
+ "Ratio of methods to fields",
486
+ lambda ci: len(ci.methods) / max(len(ci.fields), 1),
487
+ ).should_be_below(3.0)
488
+ ```
489
+
490
+ ## 📐 UML Diagram Support
491
+
492
+ ArchUnitPython can validate your architecture against PlantUML diagrams, ensuring your code matches your architectural designs.
493
+
494
+ ### Component Diagrams
495
+
496
+ ```python
497
+ def test_component_architecture():
498
+ diagram = """
499
+ @startuml
500
+ component [UserInterface]
501
+ component [BusinessLogic]
502
+ component [DataAccess]
503
+
504
+ [UserInterface] --> [BusinessLogic]
505
+ [BusinessLogic] --> [DataAccess]
506
+ @enduml"""
507
+
508
+ rule = (
509
+ project_slices("src/")
510
+ .defined_by("src/(**)/**")
511
+ .should()
512
+ .adhere_to_diagram(diagram)
513
+ )
514
+ assert_passes(rule)
515
+ ```
516
+
517
+ ### Diagram from File
518
+
519
+ ```python
520
+ def test_from_file():
521
+ rule = (
522
+ project_slices("src/")
523
+ .defined_by("src/(**)/**")
524
+ .should()
525
+ .adhere_to_diagram_in_file("docs/architecture.puml")
526
+ )
527
+ assert_passes(rule)
528
+ ```
529
+
530
+ ## đŸ“ĸ Informative Error Messages
531
+
532
+ When tests fail, you get helpful output with file paths and violation details:
533
+
534
+ ```
535
+ Found 2 architecture violation(s):
536
+
537
+ 1. File dependency violation
538
+ 'src/api/bad_shortcut.py' depends on 'src/retrieval/vector_store.py'
539
+
540
+ 2. File dependency violation
541
+ 'src/api/bad_shortcut.py' depends on 'src/retrieval/embedder.py'
542
+ ```
543
+
544
+ ## 📝 Debug Logging & Configuration
545
+
546
+ We support logging to help you understand what files are being analyzed and troubleshoot test failures. Logging is disabled by default to keep test output clean.
547
+
548
+ ### Enabling Debug Logging
549
+
550
+ ```python
551
+ from archunitpython import CheckOptions
552
+ from archunitpython.common.logging.types import LoggingOptions
553
+
554
+ options = CheckOptions(
555
+ logging=LoggingOptions(
556
+ enabled=True,
557
+ level="debug", # "error" | "warn" | "info" | "debug"
558
+ log_file=True, # Creates logs/archunit-YYYY-MM-DD_HH-MM-SS.log
559
+ ),
560
+ )
561
+
562
+ violations = rule.check(options)
563
+ ```
564
+
565
+ ### CI Pipeline Integration
566
+
567
+ ```yaml
568
+ # GitHub Actions
569
+ - name: Run Architecture Tests
570
+ run: pytest tests/test_architecture.py -v
571
+
572
+ - name: Upload Test Logs
573
+ if: always()
574
+ uses: actions/upload-artifact@v3
575
+ with:
576
+ name: architecture-test-logs
577
+ path: logs/
578
+ ```
579
+
580
+ ## 🏈 Architecture Fitness Functions
581
+
582
+ The features of ArchUnitPython can very well be used as architectural fitness functions. See [here](https://www.thoughtworks.com/en-de/insights/articles/fitness-function-driven-development) for more information about that topic.
583
+
584
+ ## 🔲 Core Modules
585
+
586
+ | Module | Description | Status |
587
+ | ----------- | ------------------------------ | ------------ |
588
+ | **Files** | File and folder based rules | Stable |
589
+ | **Metrics** | Code quality metrics | Stable |
590
+ | **Slices** | Architecture slicing | Stable |
591
+ | **Testing** | Test framework integration | Stable |
592
+ | **Common** | Shared utilities | Stable |
593
+ | **Reports** | Generate HTML reports | Experimental |
594
+
595
+ ### ArchUnitPython uses ArchUnitPython
596
+
597
+ We use ourselves to ensure the architectural rules for this repository.
598
+
599
+ ## đŸĻŠ Contributing
600
+
601
+ 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).
602
+
603
+ ## â„šī¸ FAQ
604
+
605
+ **Q: What Python testing frameworks are supported?**
606
+
607
+ ArchUnitPython works with pytest, unittest, and any other testing framework. We recommend pytest with `assert_passes()`.
608
+
609
+ **Q: What Python versions are supported?**
610
+
611
+ Python 3.10 and above.
612
+
613
+ **Q: Does ArchUnitPython have any runtime dependencies?**
614
+
615
+ No. ArchUnitPython uses only the Python standard library. Development dependencies (pytest, mypy, ruff) are optional.
616
+
617
+ **Q: How does it analyze Python imports?**
618
+
619
+ ArchUnitPython uses Python's built-in `ast` module to parse source files and resolve imports. It handles absolute imports, relative imports, and package imports.
620
+
621
+ **Q: How do I handle false positives in architecture rules?**
622
+
623
+ Use the filtering and targeting capabilities to exclude specific files or patterns. You can filter by file paths, class names, or custom predicates to fine-tune your rules.
624
+
625
+ ## 📅 Plans
626
+
627
+ ArchUnitPython is the Python port of ArchUnitTS. We plan to keep it in sync with the TypeScript version's features, and extend it with Python-specific capabilities.
628
+
629
+ ## đŸŖ Origin Story
630
+
631
+ ArchUnitPython started as the Python port of [ArchUnitTS](https://github.com/LukasNiessen/ArchUnitTS). With the rise of LLMs and AI integration, enforcing architectural boundaries and QA in general has become more critical than ever -- especially in Python, the dominant language in the AI/ML ecosystem.
632
+
633
+ ## 💟 Community
634
+
635
+ ### Maintainers
636
+
637
+ - **[LukasNiessen](https://github.com/LukasNiessen)** - Creator and main maintainer
638
+
639
+ ### Questions
640
+
641
+ Found a bug? Want to discuss features?
642
+
643
+ - Submit an [issue on GitHub](https://github.com/LukasNiessen/ArchUnitPython/issues/new/choose)
644
+ - Join our [GitHub Discussions](https://github.com/LukasNiessen/ArchUnitPython/discussions)
645
+
646
+ If ArchUnitPython helps your project, please consider:
647
+
648
+ - Starring the repository
649
+ - Suggesting new features
650
+ - Contributing code or documentation
651
+
652
+ ## 📄 License
653
+
654
+ This project is under the **MIT** license.
655
+
656
+ ---
657
+
658
+ <p align="center">
659
+ <a href="#top"><strong>Go Back to Top</strong></a>
660
+ </p>