archunitpython 1.2.1__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.
@@ -1,6 +1,6 @@
1
1
  """ArchUnitPython - Architecture testing library for Python projects."""
2
2
 
3
- __version__ = "1.2.1"
3
+ __version__ = "1.4.0"
4
4
 
5
5
  # Files API
6
6
  # Common
@@ -1,6 +1,10 @@
1
1
  from archunitpython.common.assertion.violation import EmptyTestViolation, Violation
2
2
  from archunitpython.common.error.errors import TechnicalError, UserError
3
- from archunitpython.common.fluentapi.checkable import Checkable, CheckOptions
3
+ from archunitpython.common.fluentapi.checkable import (
4
+ Checkable,
5
+ CheckOptions,
6
+ RuleRationaleMixin,
7
+ )
4
8
  from archunitpython.common.logging.types import LoggingOptions
5
9
  from archunitpython.common.types import Filter, Pattern, PatternMatchingOptions
6
10
 
@@ -11,6 +15,7 @@ __all__ = [
11
15
  "UserError",
12
16
  "Checkable",
13
17
  "CheckOptions",
18
+ "RuleRationaleMixin",
14
19
  "LoggingOptions",
15
20
  "Pattern",
16
21
  "Filter",
@@ -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
- is_external = True
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[:] = [d for d in dirnames if not _should_exclude(d, exclude)]
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
- if filename.endswith(".py") and not _should_exclude(filename, exclude):
191
- full_path = os.path.join(dirpath, filename)
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 _should_exclude(name: str, patterns: list[str]) -> bool:
198
- """Check if a name matches any exclude pattern."""
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
- for pattern in patterns:
202
- if fnmatch.fnmatch(name, pattern):
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
 
@@ -1,3 +1,7 @@
1
- from archunitpython.common.fluentapi.checkable import Checkable, CheckOptions
1
+ from archunitpython.common.fluentapi.checkable import (
2
+ Checkable,
3
+ CheckOptions,
4
+ RuleRationaleMixin,
5
+ )
2
6
 
3
- __all__ = ["Checkable", "CheckOptions"]
7
+ __all__ = ["Checkable", "CheckOptions", "RuleRationaleMixin"]
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
- from typing import Protocol
6
+ from typing import Protocol, TypeVar
7
7
 
8
8
  from archunitpython.common.assertion.violation import Violation
9
9
  from archunitpython.common.logging.types import LoggingOptions
@@ -19,6 +19,28 @@ class CheckOptions:
19
19
  ignore_type_checking_imports: bool = False
20
20
 
21
21
 
22
+ T = TypeVar("T", bound="RuleRationaleMixin")
23
+
24
+
25
+ class RuleRationaleMixin:
26
+ """Mixin for checkable rules that can carry a human-readable rationale."""
27
+
28
+ _because_reason: str | None = None
29
+
30
+ def because(self: T, reason: str) -> T:
31
+ """Attach a rationale explaining why the rule exists."""
32
+ reason = reason.strip()
33
+ if not reason:
34
+ raise ValueError("Rule rationale must not be empty.")
35
+ self._because_reason = reason
36
+ return self
37
+
38
+ @property
39
+ def because_reason(self) -> str | None:
40
+ """Return the rationale attached with because(), if any."""
41
+ return self._because_reason
42
+
43
+
22
44
  class Checkable(Protocol):
23
45
  """Protocol for any architecture rule that can be checked.
24
46
 
@@ -14,7 +14,7 @@ from collections.abc import Sequence
14
14
 
15
15
  from archunitpython.common.assertion.violation import EmptyTestViolation, Violation
16
16
  from archunitpython.common.extraction.extract_graph import extract_graph
17
- from archunitpython.common.fluentapi.checkable import CheckOptions
17
+ from archunitpython.common.fluentapi.checkable import CheckOptions, RuleRationaleMixin
18
18
  from archunitpython.common.pattern_matching import matches_all_patterns
19
19
  from archunitpython.common.projection.edge_projections import (
20
20
  per_external_edge,
@@ -322,7 +322,7 @@ def _check_empty_test(
322
322
  return None
323
323
 
324
324
 
325
- class CycleFreeFileCondition:
325
+ class CycleFreeFileCondition(RuleRationaleMixin):
326
326
  """Checkable that verifies no cycles exist among filtered files."""
327
327
 
328
328
  def __init__(self, project_path: str | None, filters: list[Filter]) -> None:
@@ -350,7 +350,7 @@ class CycleFreeFileCondition:
350
350
  return gather_cycle_violations(cycles)
351
351
 
352
352
 
353
- class DependOnFileCondition:
353
+ class DependOnFileCondition(RuleRationaleMixin):
354
354
  """Checkable that verifies file dependency rules."""
355
355
 
356
356
  def __init__(
@@ -377,7 +377,7 @@ class DependOnFileCondition:
377
377
  )
378
378
 
379
379
 
380
- class DependOnExternalModuleCondition:
380
+ class DependOnExternalModuleCondition(RuleRationaleMixin):
381
381
  """Checkable that verifies external module dependency rules."""
382
382
 
383
383
  def __init__(
@@ -409,7 +409,7 @@ class DependOnExternalModuleCondition:
409
409
  )
410
410
 
411
411
 
412
- class MatchPatternFileCondition:
412
+ class MatchPatternFileCondition(RuleRationaleMixin):
413
413
  """Checkable that verifies files match/don't match patterns."""
414
414
 
415
415
  def __init__(
@@ -434,7 +434,7 @@ class MatchPatternFileCondition:
434
434
  return gather_regex_matching_violations(nodes, self._check_filters, self._is_negated)
435
435
 
436
436
 
437
- class CustomFileCheckableCondition:
437
+ class CustomFileCheckableCondition(RuleRationaleMixin):
438
438
  """Checkable that evaluates a custom condition on files."""
439
439
 
440
440
  def __init__(
@@ -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 _DEFAULT_EXCLUDE, _find_python_files
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 = exclude_patterns if exclude_patterns is not None else _DEFAULT_EXCLUDE
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 = exclude_patterns if exclude_patterns is not None else _DEFAULT_EXCLUDE
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] = []
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
  from typing import Any, Callable
12
12
 
13
13
  from archunitpython.common.assertion.violation import Violation
14
- from archunitpython.common.fluentapi.checkable import CheckOptions
14
+ from archunitpython.common.fluentapi.checkable import CheckOptions, RuleRationaleMixin
15
15
  from archunitpython.common.pattern_matching import matches_pattern_classname
16
16
  from archunitpython.common.regex_factory import RegexFactory
17
17
  from archunitpython.common.types import Filter, Pattern
@@ -170,7 +170,7 @@ class ClassMetricThresholdBuilder:
170
170
  )
171
171
 
172
172
 
173
- class ClassMetricCondition:
173
+ class ClassMetricCondition(RuleRationaleMixin):
174
174
  """Checkable that verifies a class-level metric threshold."""
175
175
 
176
176
  def __init__(
@@ -230,7 +230,7 @@ class FileMetricThresholdBuilder:
230
230
  )
231
231
 
232
232
 
233
- class FileMetricCondition:
233
+ class FileMetricCondition(RuleRationaleMixin):
234
234
  """Checkable that verifies a file-level metric threshold."""
235
235
 
236
236
  def __init__(
@@ -251,13 +251,13 @@ class FileMetricCondition:
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, _DEFAULT_EXCLUDE)
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:
@@ -364,7 +364,7 @@ class DistanceThresholdBuilder:
364
364
  )
365
365
 
366
366
 
367
- class DistanceCondition:
367
+ class DistanceCondition(RuleRationaleMixin):
368
368
  """Checkable for distance metric thresholds."""
369
369
 
370
370
  def __init__(
@@ -404,7 +404,7 @@ class DistanceCondition:
404
404
  return violations
405
405
 
406
406
 
407
- class ZoneCondition:
407
+ class ZoneCondition(RuleRationaleMixin):
408
408
  """Checkable for zone detection (pain/uselessness)."""
409
409
 
410
410
  def __init__(self, project_path: str | None, filters: list[Filter], zone_type: str) -> None:
@@ -485,7 +485,7 @@ class CustomMetricsBuilder:
485
485
  )
486
486
 
487
487
 
488
- class CustomMetricCondition:
488
+ class CustomMetricCondition(RuleRationaleMixin):
489
489
  """Checkable for custom metric thresholds."""
490
490
 
491
491
  def __init__(
@@ -525,7 +525,7 @@ class CustomMetricCondition:
525
525
  return violations
526
526
 
527
527
 
528
- class CustomAssertionCondition:
528
+ class CustomAssertionCondition(RuleRationaleMixin):
529
529
  """Checkable for custom metric assertions."""
530
530
 
531
531
  def __init__(
@@ -14,7 +14,7 @@ import re
14
14
 
15
15
  from archunitpython.common.assertion.violation import Violation
16
16
  from archunitpython.common.extraction.extract_graph import extract_graph
17
- from archunitpython.common.fluentapi.checkable import CheckOptions
17
+ from archunitpython.common.fluentapi.checkable import CheckOptions, RuleRationaleMixin
18
18
  from archunitpython.common.projection.project_edges import project_edges
19
19
  from archunitpython.common.projection.types import MapFunction
20
20
  from archunitpython.slices.assertion.admissible_edges import (
@@ -140,7 +140,7 @@ class NegativeConditionBuilder:
140
140
  )
141
141
 
142
142
 
143
- class PositiveSliceCondition:
143
+ class PositiveSliceCondition(RuleRationaleMixin):
144
144
  """Checkable that verifies slices adhere to a diagram."""
145
145
 
146
146
  def __init__(
@@ -176,7 +176,7 @@ class PositiveSliceCondition:
176
176
  return identity()
177
177
 
178
178
 
179
- class NegativeSliceCondition:
179
+ class NegativeSliceCondition(RuleRationaleMixin):
180
180
  """Checkable that verifies a specific dependency does NOT exist."""
181
181
 
182
182
  def __init__(
@@ -7,7 +7,11 @@ from archunitpython.common.fluentapi.checkable import Checkable, CheckOptions
7
7
  from archunitpython.testing.common.violation_factory import ViolationFactory
8
8
 
9
9
 
10
- def format_violations(violations: list[Violation]) -> str:
10
+ def format_violations(
11
+ violations: list[Violation],
12
+ *,
13
+ because: str | None = None,
14
+ ) -> str:
11
15
  """Format violations into a human-readable string.
12
16
 
13
17
  Args:
@@ -19,7 +23,10 @@ def format_violations(violations: list[Violation]) -> str:
19
23
  if not violations:
20
24
  return "No violations found."
21
25
 
22
- lines = [f"Found {len(violations)} architecture violation(s):", ""]
26
+ lines = [f"Found {len(violations)} architecture violation(s):"]
27
+ if because:
28
+ lines.extend(["", f"Because: {because}"])
29
+ lines.append("")
23
30
  for i, violation in enumerate(violations, 1):
24
31
  tv = ViolationFactory.from_violation(violation)
25
32
  lines.append(f" {i}. {tv.message}")
@@ -44,4 +51,5 @@ def assert_passes(
44
51
  """
45
52
  violations = checkable.check(options)
46
53
  if violations:
47
- raise AssertionError(format_violations(violations))
54
+ because = getattr(checkable, "because_reason", None)
55
+ raise AssertionError(format_violations(violations, because=because))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: archunitpython
3
- Version: 1.2.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,43 @@ 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 `/`.
209
+ ### Explaining Rules With `.because(...)`
210
+
211
+ Attach a rationale to a rule so failing assertions explain why the rule exists:
212
+
213
+ ```python
214
+ rule = (
215
+ project_files("src/")
216
+ .in_folder("**/controllers/**")
217
+ .should_not()
218
+ .depend_on_files()
219
+ .in_folder("**/database/**")
220
+ .because("controllers should stay thin and delegate persistence")
221
+ )
222
+
223
+ assert_passes(rule)
224
+ ```
225
+
226
+ When the rule fails, the rationale is included in the assertion message.
227
+
191
228
  ## 🐹 Use Cases
192
229
 
193
230
  Here is an overview of common use cases.
@@ -431,21 +468,113 @@ def test_no_forbidden_dependency():
431
468
 
432
469
  Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.
433
470
 
471
+ **Using [`requests`](https://github.com/psf/requests) library repo for example**
472
+
434
473
  ```python
435
474
  from archunitpython import project_graph
436
475
 
437
476
  def test_export_dependency_graph_reports():
438
- graph = project_graph("src/").titled("Application Architecture")
439
-
440
- graph.collapse_to_folder_depth(2).export_as_mermaid(
441
- "reports/dependencies.mmd"
442
- )
443
-
444
- graph.focus_on("**/domain/**", 1).export_as_html(
445
- "reports/domain-dependencies.html"
446
- )
447
-
448
- assert graph.snapshot().summary.node_count >= 0
477
+ graph = project_graph("src/requests").titled("Application Architecture")
478
+
479
+ graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.md")
480
+
481
+ if __name__ == "__main__":
482
+ test_export_dependency_graph_reports()
483
+ ```
484
+ **Exported mermaid diagram**
485
+ ``` mermaid
486
+ flowchart LR
487
+ n0["__init__.py"]
488
+ n1["__version__.py"]
489
+ n2["_internal_utils.py"]
490
+ n3["_types.py"]
491
+ n4["adapters.py"]
492
+ n5["api.py"]
493
+ n6["auth.py"]
494
+ n7["certs.py"]
495
+ n8["compat.py"]
496
+ n9["cookies.py"]
497
+ n10["exceptions.py"]
498
+ n11["help.py"]
499
+ n12["hooks.py"]
500
+ n13["models.py"]
501
+ n14["packages.py"]
502
+ n15["sessions.py"]
503
+ n16["status_codes.py"]
504
+ n17["structures.py"]
505
+ n18["utils.py"]
506
+ n0 --> n1
507
+ n0 --> n5
508
+ n0 --> n10
509
+ n0 --> n13
510
+ n0 --> n15
511
+ n0 --> n16
512
+ n2 --> n8
513
+ n3 --> n6
514
+ n3 --> n9
515
+ n3 --> n13
516
+ n3 --> n17
517
+ n4 --> n0
518
+ n4 --> n3
519
+ n4 --> n6
520
+ n4 --> n8
521
+ n4 --> n9
522
+ n4 --> n10
523
+ n4 --> n13
524
+ n4 --> n17
525
+ n4 --> n18
526
+ n5 --> n0
527
+ n5 --> n13
528
+ n6 --> n2
529
+ n6 --> n8
530
+ n6 --> n9
531
+ n6 --> n13
532
+ n6 --> n18
533
+ n9 --> n2
534
+ n9 --> n3
535
+ n9 --> n8
536
+ n9 --> n13
537
+ n10 --> n8
538
+ n10 --> n13
539
+ n11 --> n0
540
+ n12 --> n0
541
+ n12 --> n13
542
+ n13 --> n0
543
+ n13 --> n2
544
+ n13 --> n4
545
+ n13 --> n6
546
+ n13 --> n8
547
+ n13 --> n9
548
+ n13 --> n10
549
+ n13 --> n12
550
+ n13 --> n16
551
+ n13 --> n17
552
+ n13 --> n18
553
+ n14 --> n8
554
+ n15 --> n0
555
+ n15 --> n2
556
+ n15 --> n3
557
+ n15 --> n4
558
+ n15 --> n6
559
+ n15 --> n8
560
+ n15 --> n9
561
+ n15 --> n10
562
+ n15 --> n12
563
+ n15 --> n13
564
+ n15 --> n16
565
+ n15 --> n17
566
+ n15 --> n18
567
+ n16 --> n17
568
+ n17 --> n8
569
+ n18 --> n0
570
+ n18 --> n1
571
+ n18 --> n2
572
+ n18 --> n3
573
+ n18 --> n8
574
+ n18 --> n9
575
+ n18 --> n10
576
+ n18 --> n13
577
+ n18 --> n17
449
578
  ```
450
579
 
451
580
  Supported formats:
@@ -1,6 +1,6 @@
1
- archunitpython/__init__.py,sha256=w__UDj9GojwOMtYX5eSZCpd4WYiIKjWGJ6c-8nRlPSI,1154
1
+ archunitpython/__init__.py,sha256=8MQMKFXjGNHJdCtifcWJcAgXLkjqJhDiXYxqp2fmxlQ,1154
2
2
  archunitpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- archunitpython/common/__init__.py,sha256=u8jwbfALwIT_5ZqTrRchpZqkFHG5tTwM21LX5hR9ZSc,593
3
+ archunitpython/common/__init__.py,sha256=TKL39Z0kBpWqMH99jU4LsDSUZ5lQ_rcAJfLnmUTDTOI,656
4
4
  archunitpython/common/pattern_matching.py,sha256=HMAfo8GsooHvA4d2IxCi3btrYTdwfOQXw4bDNr117P0,2669
5
5
  archunitpython/common/regex_factory.py,sha256=UtlElM0Uty3SuD7tPZeoDnpbFipFUuYDpWfuV-Xq4c4,2459
6
6
  archunitpython/common/types.py,sha256=aA6hxCvmOy7_q74pE0sDRILNB8KxmHx9G4j5ql87mWs,755
@@ -9,10 +9,10 @@ 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=HIfAZLZiv4Zbi1Nm8jmNnnu8ggn0wQXunBlZP_QK1x8,15254
12
+ archunitpython/common/extraction/extract_graph.py,sha256=PcKInkTFLBxWO5RqppcGJKDii2LrTXgkxm7a0G16ZAg,17186
13
13
  archunitpython/common/extraction/graph.py,sha256=Rk-0eDDOLoHvScO27J2JzXjyMq3e5eKtrHnFBh5B6SU,1052
14
- archunitpython/common/fluentapi/__init__.py,sha256=J7PDgqBXQOpXqCMCGRyFNil0dCulufmhfirzvYvh-gk,119
15
- archunitpython/common/fluentapi/checkable.py,sha256=6Tsxtycc27-LaeE6cTtQF6NfepFzhgmCrFJAosN_chs,892
14
+ archunitpython/common/fluentapi/__init__.py,sha256=LeS7qS2p9-FqqBhDL8xRPex__W5Qk0UWfcpl7nVVLfI,178
15
+ archunitpython/common/fluentapi/checkable.py,sha256=BJFYibVhHLMFlnWdISPCIL3DYI1_3JN2-1jEQOaVaGE,1547
16
16
  archunitpython/common/logging/__init__.py,sha256=u3-2_jYmiyoQ-C534SmQXvR1L9h3lbJqA9Yt1Nmv9HA,115
17
17
  archunitpython/common/logging/types.py,sha256=wUriZeaUawA3JoBfJVLC6lIMglbLG955nXWdJGwxW4I,425
18
18
  archunitpython/common/projection/__init__.py,sha256=-Sx-b80b2N3ap3ahAcLUCXqYYK3ccgTDpV-K1jSOSYQ,797
@@ -38,7 +38,7 @@ archunitpython/files/assertion/depend_on_external_modules.py,sha256=BXGpN-301YOc
38
38
  archunitpython/files/assertion/depend_on_files.py,sha256=EGMtDD8KT09BJQwOEMoKj-yTQfY3KLLYWN5SM1BCmpY,2012
39
39
  archunitpython/files/assertion/matching_files.py,sha256=qNbvU2o_MM6rzM-YUdJZzkltVbenWGC0u5kLH7su6CA,2003
40
40
  archunitpython/files/fluentapi/__init__.py,sha256=wgl8IOqTeQGpNISu-Q8b_j6rTn8aEgoz6t1S5j8z13g,108
41
- archunitpython/files/fluentapi/files.py,sha256=MsXY-763W9wVrtU50mDs_cuaErWRHk1MehbbAit4Je8,17032
41
+ archunitpython/files/fluentapi/files.py,sha256=oZG0lWV6p0AiGANdoZfYd51H8zY-J4eTSPRTRPBYhs0,17152
42
42
  archunitpython/graph/__init__.py,sha256=vm1jMOoN35IIU7YDs59qPtf_F82YV-QrEGfXhozAOek,756
43
43
  archunitpython/graph/graph_reporter.py,sha256=D_KH01CM7TzeMFB6o5NIQfLVJq0rvKZn9l7WumXV6lo,25598
44
44
  archunitpython/layers/__init__.py,sha256=DCRxqLhluTxTaT9ZM2MbfAdJVgqcOO9jtkZHzBbeLOw,164
@@ -56,28 +56,28 @@ 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=IFokUFwNaExmPtKnHW5hgnYWodRtBM3cWhXBGb7lcjs,7751
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=tvaMctIJAjqHhGT62u5LbZImJFCGIsoJ3Z6cdiY6zTs,19198
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
66
66
  archunitpython/slices/assertion/admissible_edges.py,sha256=-3o5mGtl81i5I_572x3Z4ZDfqPCyFqjGd7Dg8GRSKhY,3119
67
67
  archunitpython/slices/fluentapi/__init__.py,sha256=tb8MoZiEqIdmWy7uyhmEtMNm5Fxjuw6atSvDsD131DE,96
68
- archunitpython/slices/fluentapi/slices.py,sha256=5Q6HbUqzz3xSyeeI3iLqMYRX-LXbu5xOc806MWqqYro,7103
68
+ archunitpython/slices/fluentapi/slices.py,sha256=vNoyqmleTDTjnf2i35i2nte-Ycjm-oapfgaf3AhN0QI,7163
69
69
  archunitpython/slices/projection/__init__.py,sha256=tVgcK4gw-bqhUq98vU7_iPE0NEhOPUr7vr1-p7JI1A4,237
70
70
  archunitpython/slices/projection/slicing_projections.py,sha256=BHBkuVG80mqzT7N4FlCCfY5U_NvlXmAgTmHf_SPTXVM,4215
71
71
  archunitpython/slices/uml/__init__.py,sha256=GQFA5WBKfi_YElWiNWNm9c9IM-VaW83_Cg6kpwkzxkw,196
72
72
  archunitpython/slices/uml/export_diagram.py,sha256=FeBRV8Vt8LuhLqEbg5JzZOmEk2i49JPkozMcI5r335g,818
73
73
  archunitpython/slices/uml/generate_rules.py,sha256=D9vvqDzHAM0F80z4u5yBalEqIiNfJJ5HGXP0fVlSzHw,2136
74
74
  archunitpython/testing/__init__.py,sha256=MJm6CHGhBGFg1um2L20-GD2DEHKqg8SiUoI-sRsoXQk,128
75
- archunitpython/testing/assertion.py,sha256=YTiNWHIQu4xlSqmlvqj1L-Z2Oo1Z-wOvN0VyZQB-CZE,1430
75
+ archunitpython/testing/assertion.py,sha256=7cXlNwBklIGAyUJlPGFGAw97xZ9UgpkZvoO_QBdqm1c,1637
76
76
  archunitpython/testing/common/__init__.py,sha256=Wc4bC-N4t6giChKjj6wuTGKkb39thcYS_xKWG9paYeY,220
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.2.1.dist-info/METADATA,sha256=0kFzgCIGfYVHduyDa7U4njL_cGzoMoThpd255uf_Eh8,28498
81
- archunitpython-1.2.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
82
- archunitpython-1.2.1.dist-info/licenses/LICENSE,sha256=kaQWfzfHk45CNIx4sIW7Uf1sNW5rmo6BpZ-R8GruuK0,1102
83
- archunitpython-1.2.1.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any