archunitpython 1.0.1__py3-none-any.whl → 1.1.1__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.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
@@ -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
 
@@ -1,4 +1,4 @@
1
- archunitpython/__init__.py,sha256=Y7boq0Xo0uyEXzfP30N_Jt1ij6Ofy8q8XzinC6E1crU,926
1
+ archunitpython/__init__.py,sha256=0CNl66vkf6m597p2KMuJLP6D1CtgSfO1Y6VMxlMScWk,926
2
2
  archunitpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  archunitpython/common/__init__.py,sha256=u8jwbfALwIT_5ZqTrRchpZqkFHG5tTwM21LX5hR9ZSc,593
4
4
  archunitpython/common/pattern_matching.py,sha256=zhfglETSJfe5XPzueVflM14EKfllXuazqzd2XH5fuxw,2675
@@ -9,14 +9,14 @@ 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=2guw1RA5CnXXR08x2UEzOtfUVfT3wLZcG8xNYIUtCrs,11114
12
+ archunitpython/common/extraction/extract_graph.py,sha256=LNh3gohN3hkzoxBGrMOxqFUVPL3-oYiOU2W6Hja0Mbg,11979
13
13
  archunitpython/common/extraction/graph.py,sha256=Rk-0eDDOLoHvScO27J2JzXjyMq3e5eKtrHnFBh5B6SU,1052
14
14
  archunitpython/common/fluentapi/__init__.py,sha256=J7PDgqBXQOpXqCMCGRyFNil0dCulufmhfirzvYvh-gk,119
15
- archunitpython/common/fluentapi/checkable.py,sha256=bdbG8zRgi9pt0xh0LkPzUq5aN8geFlb60KEViAGZc1w,845
15
+ archunitpython/common/fluentapi/checkable.py,sha256=6Tsxtycc27-LaeE6cTtQF6NfepFzhgmCrFJAosN_chs,892
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
19
- archunitpython/common/projection/edge_projections.py,sha256=xyQQNh8NnACR9mDRauTmro1_IgZouyTwX7tUl4Zt2xI,1053
19
+ archunitpython/common/projection/edge_projections.py,sha256=Mud8DL6Y8H66CObqyikKYAxdXMRENuAFRtwhCxpwxoU,1524
20
20
  archunitpython/common/projection/project_cycles.py,sha256=SA6eXzy599dJMvAOA6K1ONkOaFLiEXw8qixpgybtW-I,3105
21
21
  archunitpython/common/projection/project_edges.py,sha256=v7hDMSeghiLz0xHbG4BNF7jNQphcWobU7rgoiuw3fh4,1268
22
22
  archunitpython/common/projection/project_nodes.py,sha256=U6sLYvJd6NBgg39V0Ry7r_-57gdXaaXsbH-D12nipCg,1497
@@ -31,13 +31,14 @@ archunitpython/common/util/__init__.py,sha256=g-W8kWiVqwdohxm74J3yUonPkb_yDUJrp7
31
31
  archunitpython/common/util/declaration_detector.py,sha256=XgrpgaUIQa9MNFlnhVQz2X_uW7zCO9nrMbGa3tVFyPM,3407
32
32
  archunitpython/common/util/logger.py,sha256=QT6ir8caMXu4VQombe0NjaT2NUm58kFdlnps4T5ECCE,3348
33
33
  archunitpython/files/__init__.py,sha256=wgl8IOqTeQGpNISu-Q8b_j6rTn8aEgoz6t1S5j8z13g,108
34
- archunitpython/files/assertion/__init__.py,sha256=U2nJkByFzNHkDGaNXJaCef3ywrDaBLNf9biWNJ_9Yu8,814
34
+ archunitpython/files/assertion/__init__.py,sha256=K2qiEFfmo6SCgxfOKNrPzYgT5ScKUaw2W2CrDa3jBTw,1068
35
35
  archunitpython/files/assertion/custom_file_logic.py,sha256=ygpc2NZghHBD-GTGrQW_dWzGZr_9DSvi0denC-cdwuo,3054
36
36
  archunitpython/files/assertion/cycle_free.py,sha256=Ib3tmVVErmTpORmRn_rZqOqY0gk54crN_d_IapeWqdI,738
37
+ archunitpython/files/assertion/depend_on_external_modules.py,sha256=d16mK6lpr5MhxndigSZsGEOPF51UY0Gl5HddqP_MdXs,1949
37
38
  archunitpython/files/assertion/depend_on_files.py,sha256=B-zyFyXCt2ywCzO0BIH1lRGsre6PkAItv2ZfpM_u2Js,2132
38
39
  archunitpython/files/assertion/matching_files.py,sha256=qNbvU2o_MM6rzM-YUdJZzkltVbenWGC0u5kLH7su6CA,2003
39
40
  archunitpython/files/fluentapi/__init__.py,sha256=wgl8IOqTeQGpNISu-Q8b_j6rTn8aEgoz6t1S5j8z13g,108
40
- archunitpython/files/fluentapi/files.py,sha256=olBZQ6lVMjovMnCEQVzzE4QilvK6w4ojTgbW_nL01tw,14476
41
+ archunitpython/files/fluentapi/files.py,sha256=uX__VDed_TLNiibK2pTMaPQ8RaCyyFf2ZClqdymvs98,17214
41
42
  archunitpython/metrics/__init__.py,sha256=HD72MWF0hCh2LzKfyk3mMJp2tf7TJebpwTtZ_kppjso,84
42
43
  archunitpython/metrics/assertion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
44
  archunitpython/metrics/assertion/metric_thresholds.py,sha256=dijCzOr5-dhkdK-QAE3DAkfiSBgknZlxtp-ZrBj_Dmk,1268
@@ -67,9 +68,9 @@ archunitpython/testing/__init__.py,sha256=MJm6CHGhBGFg1um2L20-GD2DEHKqg8SiUoI-sR
67
68
  archunitpython/testing/assertion.py,sha256=YTiNWHIQu4xlSqmlvqj1L-Z2Oo1Z-wOvN0VyZQB-CZE,1430
68
69
  archunitpython/testing/common/__init__.py,sha256=Wc4bC-N4t6giChKjj6wuTGKkb39thcYS_xKWG9paYeY,220
69
70
  archunitpython/testing/common/color_utils.py,sha256=2I8Z1SZfWhhgudMgmXY6PPydGxGl35cK_To-GXnSyJg,1226
70
- archunitpython/testing/common/violation_factory.py,sha256=jo6jWzy6-ia-Jc1wU_LA8NVjWMzIHbbrLe7H3ngY-vQ,3681
71
+ archunitpython/testing/common/violation_factory.py,sha256=wrhsQoTmu70cmgXjjTIJNdXFeN9iQerQCZR4-vaAwuQ,4209
71
72
  archunitpython/testing/pytest_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- archunitpython-1.0.1.dist-info/METADATA,sha256=RN8jrD8bVkMWGVQ-2hMJxHSVJ2GZVXJG5O1WkPX8MWo,19832
73
- archunitpython-1.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
74
- archunitpython-1.0.1.dist-info/licenses/LICENSE,sha256=kaQWfzfHk45CNIx4sIW7Uf1sNW5rmo6BpZ-R8GruuK0,1102
75
- archunitpython-1.0.1.dist-info/RECORD,,
73
+ archunitpython-1.1.1.dist-info/METADATA,sha256=Qm_crmBhrWg9c5xdnwYxQrqbs3OsSWlMnpZcSIh4PtM,19810
74
+ archunitpython-1.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
75
+ archunitpython-1.1.1.dist-info/licenses/LICENSE,sha256=kaQWfzfHk45CNIx4sIW7Uf1sNW5rmo6BpZ-R8GruuK0,1102
76
+ archunitpython-1.1.1.dist-info/RECORD,,