archunitpython 1.3.0__py3-none-any.whl → 1.4.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.
- archunitpython/__init__.py +1 -1
- archunitpython/common/extraction/extract_graph.py +79 -11
- archunitpython/metrics/extraction/extract_class_info.py +6 -3
- archunitpython/metrics/fluentapi/metrics.py +2 -2
- {archunitpython-1.3.0.dist-info → archunitpython-1.4.0.dist-info}/METADATA +21 -3
- {archunitpython-1.3.0.dist-info → archunitpython-1.4.0.dist-info}/RECORD +8 -8
- {archunitpython-1.3.0.dist-info → archunitpython-1.4.0.dist-info}/WHEEL +1 -1
- {archunitpython-1.3.0.dist-info → archunitpython-1.4.0.dist-info}/licenses/LICENSE +0 -0
archunitpython/__init__.py
CHANGED
|
@@ -29,6 +29,8 @@ _DEFAULT_EXCLUDE = [
|
|
|
29
29
|
"*.egg-info",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
+
_ARCHIGNORE_FILE = ".archignore"
|
|
33
|
+
|
|
32
34
|
_IGNORE_DIRECTIVE_REGEX = re.compile(
|
|
33
35
|
r"#\s*archunit(?::|-)\s*ignore"
|
|
34
36
|
r"(?:\([^)]*\))?"
|
|
@@ -88,9 +90,7 @@ def extract_graph(
|
|
|
88
90
|
project_path = os.getcwd()
|
|
89
91
|
|
|
90
92
|
project_path = os.path.abspath(project_path)
|
|
91
|
-
excludes = (
|
|
92
|
-
list(set(exclude_patterns)) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
|
|
93
|
-
)
|
|
93
|
+
excludes = _resolve_exclude_patterns(project_path, exclude_patterns)
|
|
94
94
|
ignore_type_checking_imports = bool(options and options.ignore_type_checking_imports)
|
|
95
95
|
cache_key = _build_cache_key(project_path, excludes, ignore_type_checking_imports)
|
|
96
96
|
|
|
@@ -122,6 +122,34 @@ def _build_cache_key(
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def _resolve_exclude_patterns(
|
|
126
|
+
project_path: str,
|
|
127
|
+
exclude_patterns: list[str] | None,
|
|
128
|
+
) -> list[str]:
|
|
129
|
+
"""Resolve exclude patterns (explicit or defaults) plus any .archignore patterns."""
|
|
130
|
+
excludes = list(exclude_patterns) if exclude_patterns is not None else list(_DEFAULT_EXCLUDE)
|
|
131
|
+
excludes.extend(_load_archignore_patterns(project_path))
|
|
132
|
+
return excludes
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _load_archignore_patterns(project_path: str) -> list[str]:
|
|
136
|
+
"""Load .archignore patterns from a project root, if present."""
|
|
137
|
+
archignore_path = os.path.join(project_path, _ARCHIGNORE_FILE)
|
|
138
|
+
try:
|
|
139
|
+
with open(archignore_path, "r", encoding="utf-8", errors="replace") as f:
|
|
140
|
+
lines = f.readlines()
|
|
141
|
+
except OSError:
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
patterns: list[str] = []
|
|
145
|
+
for line in lines:
|
|
146
|
+
pattern = line.strip()
|
|
147
|
+
if not pattern or pattern.startswith("#"):
|
|
148
|
+
continue
|
|
149
|
+
patterns.append(pattern)
|
|
150
|
+
return patterns
|
|
151
|
+
|
|
152
|
+
|
|
125
153
|
def _extract_graph_uncached(
|
|
126
154
|
project_path: str,
|
|
127
155
|
exclude_patterns: list[str],
|
|
@@ -160,7 +188,7 @@ def _extract_graph_uncached(
|
|
|
160
188
|
if resolved and resolved != _normalize(file_path):
|
|
161
189
|
# Check if the resolved path is in our project
|
|
162
190
|
if not is_external and resolved not in normalized_py_file_set:
|
|
163
|
-
|
|
191
|
+
continue
|
|
164
192
|
|
|
165
193
|
edges.append(
|
|
166
194
|
Edge(
|
|
@@ -182,25 +210,65 @@ def _normalize(path: str) -> str:
|
|
|
182
210
|
def _find_python_files(root: str, exclude: list[str]) -> list[str]:
|
|
183
211
|
"""Recursively find all .py files, excluding specified patterns."""
|
|
184
212
|
py_files: list[str] = []
|
|
213
|
+
root = os.path.abspath(root)
|
|
185
214
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
186
215
|
# Filter out excluded directories in-place
|
|
187
|
-
dirnames[:] = [
|
|
216
|
+
dirnames[:] = [
|
|
217
|
+
d
|
|
218
|
+
for d in dirnames
|
|
219
|
+
if not _should_exclude_path(os.path.join(dirpath, d), root, exclude, is_dir=True)
|
|
220
|
+
]
|
|
188
221
|
|
|
189
222
|
for filename in filenames:
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
full_path = os.path.join(dirpath, filename)
|
|
224
|
+
if filename.endswith(".py") and not _should_exclude_path(
|
|
225
|
+
full_path, root, exclude, is_dir=False
|
|
226
|
+
):
|
|
192
227
|
py_files.append(os.path.abspath(full_path))
|
|
193
228
|
|
|
194
229
|
return py_files
|
|
195
230
|
|
|
196
231
|
|
|
197
|
-
def
|
|
198
|
-
|
|
232
|
+
def _should_exclude_path(
|
|
233
|
+
path: str,
|
|
234
|
+
root: str,
|
|
235
|
+
patterns: list[str],
|
|
236
|
+
*,
|
|
237
|
+
is_dir: bool,
|
|
238
|
+
) -> bool:
|
|
239
|
+
"""Check if a path matches any exclude pattern."""
|
|
199
240
|
import fnmatch
|
|
200
241
|
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
rel_path = _normalize(os.path.relpath(path, root))
|
|
243
|
+
name = os.path.basename(path)
|
|
244
|
+
|
|
245
|
+
for raw_pattern in patterns:
|
|
246
|
+
pattern = raw_pattern.strip().replace("\\", "/")
|
|
247
|
+
if not pattern or pattern.startswith("#"):
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
pattern = pattern.removeprefix("./")
|
|
251
|
+
anchored = pattern.startswith("/")
|
|
252
|
+
if anchored:
|
|
253
|
+
pattern = pattern[1:]
|
|
254
|
+
|
|
255
|
+
dir_only = pattern.endswith("/")
|
|
256
|
+
if dir_only:
|
|
257
|
+
pattern = pattern.rstrip("/")
|
|
258
|
+
if not is_dir:
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
if not pattern:
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
if "/" in pattern or anchored:
|
|
265
|
+
if fnmatch.fnmatch(rel_path, pattern):
|
|
266
|
+
return True
|
|
267
|
+
if is_dir and rel_path == pattern:
|
|
268
|
+
return True
|
|
269
|
+
elif fnmatch.fnmatch(name, pattern):
|
|
203
270
|
return True
|
|
271
|
+
|
|
204
272
|
return False
|
|
205
273
|
|
|
206
274
|
|
|
@@ -5,7 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
import ast
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
-
from archunitpython.common.extraction.extract_graph import
|
|
8
|
+
from archunitpython.common.extraction.extract_graph import (
|
|
9
|
+
_find_python_files,
|
|
10
|
+
_resolve_exclude_patterns,
|
|
11
|
+
)
|
|
9
12
|
from archunitpython.metrics.common.types import (
|
|
10
13
|
ClassInfo,
|
|
11
14
|
EnhancedClassInfo,
|
|
@@ -33,7 +36,7 @@ def extract_class_info(
|
|
|
33
36
|
project_path = os.getcwd()
|
|
34
37
|
|
|
35
38
|
project_path = os.path.abspath(project_path)
|
|
36
|
-
excludes =
|
|
39
|
+
excludes = _resolve_exclude_patterns(project_path, exclude_patterns)
|
|
37
40
|
py_files = _find_python_files(project_path, excludes)
|
|
38
41
|
|
|
39
42
|
classes: list[ClassInfo] = []
|
|
@@ -53,7 +56,7 @@ def extract_enhanced_class_info(
|
|
|
53
56
|
project_path = os.getcwd()
|
|
54
57
|
|
|
55
58
|
project_path = os.path.abspath(project_path)
|
|
56
|
-
excludes =
|
|
59
|
+
excludes = _resolve_exclude_patterns(project_path, exclude_patterns)
|
|
57
60
|
py_files = _find_python_files(project_path, excludes)
|
|
58
61
|
|
|
59
62
|
results: list[FileAnalysisResult] = []
|
|
@@ -251,13 +251,13 @@ class FileMetricCondition(RuleRationaleMixin):
|
|
|
251
251
|
import os
|
|
252
252
|
|
|
253
253
|
from archunitpython.common.extraction.extract_graph import (
|
|
254
|
-
_DEFAULT_EXCLUDE,
|
|
255
254
|
_find_python_files,
|
|
255
|
+
_resolve_exclude_patterns,
|
|
256
256
|
)
|
|
257
257
|
|
|
258
258
|
project = self._project_path or os.getcwd()
|
|
259
259
|
project = os.path.abspath(project)
|
|
260
|
-
files = _find_python_files(project,
|
|
260
|
+
files = _find_python_files(project, _resolve_exclude_patterns(project, None))
|
|
261
261
|
violations: list[Violation] = []
|
|
262
262
|
|
|
263
263
|
for file_path in files:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: archunitpython
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -188,6 +188,24 @@ options = CheckOptions(
|
|
|
188
188
|
violations = rule.check(options)
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
+
### Excluding Files With `.archignore`
|
|
192
|
+
|
|
193
|
+
Add a `.archignore` file to your project root to permanently exclude generated or
|
|
194
|
+
irrelevant files from architecture checks and file-based metrics:
|
|
195
|
+
|
|
196
|
+
```gitignore
|
|
197
|
+
# Generated code
|
|
198
|
+
generated/
|
|
199
|
+
|
|
200
|
+
# Migration scripts
|
|
201
|
+
migrations/*.py
|
|
202
|
+
|
|
203
|
+
# A single root-level file
|
|
204
|
+
/legacy_adapter.py
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Patterns support comments, blank lines, glob syntax, root-relative paths, path
|
|
208
|
+
patterns, and directory patterns with a trailing `/`.
|
|
191
209
|
### Explaining Rules With `.because(...)`
|
|
192
210
|
|
|
193
211
|
Attach a rationale to a rule so failing assertions explain why the rule exists:
|
|
@@ -450,7 +468,7 @@ def test_no_forbidden_dependency():
|
|
|
450
468
|
|
|
451
469
|
Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
|
|
452
470
|
|
|
453
|
-
**Using `requests` library repo for example**
|
|
471
|
+
**Using [`requests`](https://github.com/psf/requests) library repo for example**
|
|
454
472
|
|
|
455
473
|
```python
|
|
456
474
|
from archunitpython import project_graph
|
|
@@ -463,7 +481,7 @@ def test_export_dependency_graph_reports():
|
|
|
463
481
|
if __name__ == "__main__":
|
|
464
482
|
test_export_dependency_graph_reports()
|
|
465
483
|
```
|
|
466
|
-
**
|
|
484
|
+
**Exported mermaid diagram**
|
|
467
485
|
``` mermaid
|
|
468
486
|
flowchart LR
|
|
469
487
|
n0["__init__.py"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
archunitpython/__init__.py,sha256=
|
|
1
|
+
archunitpython/__init__.py,sha256=8MQMKFXjGNHJdCtifcWJcAgXLkjqJhDiXYxqp2fmxlQ,1154
|
|
2
2
|
archunitpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
archunitpython/common/__init__.py,sha256=TKL39Z0kBpWqMH99jU4LsDSUZ5lQ_rcAJfLnmUTDTOI,656
|
|
4
4
|
archunitpython/common/pattern_matching.py,sha256=HMAfo8GsooHvA4d2IxCi3btrYTdwfOQXw4bDNr117P0,2669
|
|
@@ -9,7 +9,7 @@ archunitpython/common/assertion/violation.py,sha256=TnMOykN3kPoGrSf1KcxBXYRfaxfX
|
|
|
9
9
|
archunitpython/common/error/__init__.py,sha256=UWcdIKpGAJvo4WEGOVSaUjQQnKoPi8R496uDGXBWKsE,116
|
|
10
10
|
archunitpython/common/error/errors.py,sha256=y7mcXoZPyK7uD2dMMO1qBt1y7KcIeGlRYiVeI6bjms4,249
|
|
11
11
|
archunitpython/common/extraction/__init__.py,sha256=RkJOcxJoLYQxEagh3uWhjsUCprK3Nr3Bn3o7nmzk4_Q,284
|
|
12
|
-
archunitpython/common/extraction/extract_graph.py,sha256=
|
|
12
|
+
archunitpython/common/extraction/extract_graph.py,sha256=PcKInkTFLBxWO5RqppcGJKDii2LrTXgkxm7a0G16ZAg,17186
|
|
13
13
|
archunitpython/common/extraction/graph.py,sha256=Rk-0eDDOLoHvScO27J2JzXjyMq3e5eKtrHnFBh5B6SU,1052
|
|
14
14
|
archunitpython/common/fluentapi/__init__.py,sha256=LeS7qS2p9-FqqBhDL8xRPex__W5Qk0UWfcpl7nVVLfI,178
|
|
15
15
|
archunitpython/common/fluentapi/checkable.py,sha256=BJFYibVhHLMFlnWdISPCIL3DYI1_3JN2-1jEQOaVaGE,1547
|
|
@@ -56,10 +56,10 @@ archunitpython/metrics/calculation/lcom.py,sha256=LK_kNT-BM6yuR5TUOLWQLkVvMpqR6q
|
|
|
56
56
|
archunitpython/metrics/common/__init__.py,sha256=xtEyAhS4X0UbJXHxnpdiXljVZmKIKKekZAZ1zflSPFE,335
|
|
57
57
|
archunitpython/metrics/common/types.py,sha256=w1BUlw6p_3K8qZDoQXv50P_CcKixGYiyS_YBGYILySA,1601
|
|
58
58
|
archunitpython/metrics/extraction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
-
archunitpython/metrics/extraction/extract_class_info.py,sha256=
|
|
59
|
+
archunitpython/metrics/extraction/extract_class_info.py,sha256=u-i_AkRRdIbOVXhH7LVCCVIxRrz4IX6nOqukPxbB5Iw,7747
|
|
60
60
|
archunitpython/metrics/fluentapi/__init__.py,sha256=HD72MWF0hCh2LzKfyk3mMJp2tf7TJebpwTtZ_kppjso,84
|
|
61
61
|
archunitpython/metrics/fluentapi/export_utils.py,sha256=1r-zTQAUqn_agp4_lhbcffdzfdASNY3LebYvKAG9kcQ,2333
|
|
62
|
-
archunitpython/metrics/fluentapi/metrics.py,sha256=
|
|
62
|
+
archunitpython/metrics/fluentapi/metrics.py,sha256=8OBOL3jVKbM9v_CjNNM9FMfV1JBfl5UEd1-PPOEScRs,19371
|
|
63
63
|
archunitpython/metrics/projection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
64
|
archunitpython/slices/__init__.py,sha256=tb8MoZiEqIdmWy7uyhmEtMNm5Fxjuw6atSvDsD131DE,96
|
|
65
65
|
archunitpython/slices/assertion/__init__.py,sha256=MRwV3d69ljAJ7hFYdxI6UDNC8U4Dm1ImeuLUVKEC9To,280
|
|
@@ -77,7 +77,7 @@ archunitpython/testing/common/__init__.py,sha256=Wc4bC-N4t6giChKjj6wuTGKkb39thcY
|
|
|
77
77
|
archunitpython/testing/common/color_utils.py,sha256=2I8Z1SZfWhhgudMgmXY6PPydGxGl35cK_To-GXnSyJg,1226
|
|
78
78
|
archunitpython/testing/common/violation_factory.py,sha256=yWlvv2U7u-7KoTMfp1QCeJXxTLpPEagKqDHZmPSyrlk,4677
|
|
79
79
|
archunitpython/testing/pytest_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
|
-
archunitpython-1.
|
|
81
|
-
archunitpython-1.
|
|
82
|
-
archunitpython-1.
|
|
83
|
-
archunitpython-1.
|
|
80
|
+
archunitpython-1.4.0.dist-info/METADATA,sha256=z6zirY281brtJcOrmADYb_Mrll-MNzEC6GKuTPQRkA4,30762
|
|
81
|
+
archunitpython-1.4.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
82
|
+
archunitpython-1.4.0.dist-info/licenses/LICENSE,sha256=kaQWfzfHk45CNIx4sIW7Uf1sNW5rmo6BpZ-R8GruuK0,1102
|
|
83
|
+
archunitpython-1.4.0.dist-info/RECORD,,
|
|
File without changes
|