pytestarch 2.0.2__tar.gz → 3.0.0__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.
- {pytestarch-2.0.2 → pytestarch-3.0.0}/PKG-INFO +4 -10
- {pytestarch-2.0.2 → pytestarch-3.0.0}/pyproject.toml +16 -17
- {pytestarch-2.0.2 → pytestarch-3.0.0}/setup.py +4 -8
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/dependency_to_rule_converter.py +4 -6
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/diagram_parser.py +11 -12
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/diagram_rule.py +6 -7
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/parsed_dependencies.py +2 -3
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/breadth_first_searches.py +6 -8
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_architecture.py +10 -20
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_graph.py +3 -2
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_structures.py +3 -4
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/module_name_converter.py +5 -4
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/networkxgraph.py +15 -14
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/types.py +4 -5
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/utils.py +3 -3
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/config.py +1 -2
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/converter.py +20 -23
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/import_filter.py +1 -1
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/import_types.py +4 -5
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/importee_module_calculator.py +4 -4
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/parser.py +3 -4
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/graph_generation/graph_generator.py +30 -20
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/pytestarch.py +6 -7
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/base_language.py +7 -9
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/layered_architecture_rule.py +11 -11
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/multiple_rule_applier.py +1 -3
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/rule.py +15 -14
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/error_message/message_generator.py +44 -43
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/layer_rule_violation_detector.py +42 -42
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/module_requirement.py +1 -1
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/rule_matcher.py +9 -10
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/rule_violation_detector.py +76 -76
- pytestarch-3.0.0/src/pytestarch/rule_assessment/rule_check/rule_violations.py +20 -0
- pytestarch-2.0.2/src/pytestarch/rule_assessment/rule_check/rule_violations.py +0 -21
- {pytestarch-2.0.2 → pytestarch-3.0.0}/LICENSE +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/README.md +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/exceptions.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/exceptions.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/exceptions.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/file_filter.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/graph_generation/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/py.typed +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/LANGUAGE_DEFINTION.md +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/exceptions.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/error_message/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/exceptions.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/behavior_requirement.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/utils/__init__.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/utils/decorators.py +0 -0
- {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/utils/partial_match_to_regex_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytestarch
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Test framework for software architecture based on imports between modules
|
|
5
5
|
Home-page: https://github.com/zyskarch/pytestarch
|
|
6
6
|
License: Apache-2.0
|
|
@@ -9,29 +9,23 @@ Author: zyskarch
|
|
|
9
9
|
Author-email: zyskarch@gmail.com
|
|
10
10
|
Maintainer: zyskarch
|
|
11
11
|
Maintainer-email: zyskarch@gmail.com
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.9,<4.0
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
25
23
|
Classifier: Programming Language :: Python :: 3.9
|
|
26
24
|
Classifier: Topic :: Software Development :: Testing
|
|
27
25
|
Classifier: Topic :: Software Development :: Testing :: Unit
|
|
28
26
|
Provides-Extra: visualization
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist: mkdocs-autorefs (==0.4.1)
|
|
32
|
-
Requires-Dist: mkdocs-material-extensions (==1.3.1)
|
|
33
|
-
Requires-Dist: mkdocstrings (==0.21.2)
|
|
34
|
-
Requires-Dist: networkx (>=3.0,<4.0)
|
|
27
|
+
Requires-Dist: matplotlib (>=3.9,<4.0) ; extra == "visualization"
|
|
28
|
+
Requires-Dist: networkx (>=3.2,<4.0)
|
|
35
29
|
Project-URL: Bug Tracker, https://github.com/zyskarch/pytestarch/issues
|
|
36
30
|
Project-URL: Documentation, https://zyskarch.github.io/pytestarch/
|
|
37
31
|
Project-URL: Repository, https://github.com/zyskarch/pytestarch
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "PyTestArch"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.0.0"
|
|
4
4
|
description = "Test framework for software architecture based on imports between modules"
|
|
5
5
|
authors = ["zyskarch <zyskarch@gmail.com>"]
|
|
6
6
|
maintainers = ["zyskarch <zyskarch@gmail.com>"]
|
|
@@ -15,7 +15,6 @@ classifiers = [
|
|
|
15
15
|
"Programming Language :: Python :: 3.11",
|
|
16
16
|
"Programming Language :: Python :: 3.10",
|
|
17
17
|
"Programming Language :: Python :: 3.9",
|
|
18
|
-
"Programming Language :: Python :: 3.8",
|
|
19
18
|
"License :: OSI Approved :: Apache Software License",
|
|
20
19
|
"Operating System :: OS Independent",
|
|
21
20
|
"Intended Audience :: Developers",
|
|
@@ -27,26 +26,26 @@ classifiers = [
|
|
|
27
26
|
"Bug Tracker" = "https://github.com/zyskarch/pytestarch/issues"
|
|
28
27
|
|
|
29
28
|
[tool.poetry.dependencies]
|
|
30
|
-
python = "^3.
|
|
31
|
-
networkx = "^3.
|
|
32
|
-
matplotlib = { version = "^3.
|
|
33
|
-
mkdocs-autorefs = "0.4.1"
|
|
34
|
-
mkdocs-material-extensions = "1.3.1"
|
|
35
|
-
mkdocstrings = "0.21.2"
|
|
36
|
-
griffe = "0.26.0"
|
|
29
|
+
python = "^3.9"
|
|
30
|
+
networkx = "^3.2"
|
|
31
|
+
matplotlib = { version = "^3.9", optional = true }
|
|
37
32
|
|
|
38
33
|
[tool.poetry.extras]
|
|
39
34
|
visualization = ["matplotlib"]
|
|
40
35
|
|
|
41
36
|
[tool.poetry.dev-dependencies]
|
|
42
|
-
mkdocs = "1.
|
|
43
|
-
mkdocstrings-python = "
|
|
44
|
-
mkdocs-material = "9.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
mkdocs = "1.6.0"
|
|
38
|
+
mkdocstrings-python = "1.10.3"
|
|
39
|
+
mkdocs-material = "9.5.25"
|
|
40
|
+
mkdocs-autorefs = "1.0.1"
|
|
41
|
+
mkdocs-material-extensions = "1.3.1"
|
|
42
|
+
mkdocstrings = "0.25.1"
|
|
43
|
+
griffe = "0.45.2"
|
|
44
|
+
nox = "2024.4.15"
|
|
45
|
+
pre-commit = "3.7.1"
|
|
46
|
+
pytest = "^8.2.1"
|
|
47
|
+
coverage = "^7.5.2"
|
|
48
|
+
mike = "2.1.1"
|
|
50
49
|
|
|
51
50
|
[build-system]
|
|
52
51
|
requires = ["poetry-core>=1.0.0"]
|
|
@@ -21,18 +21,14 @@ package_data = \
|
|
|
21
21
|
{'': ['*']}
|
|
22
22
|
|
|
23
23
|
install_requires = \
|
|
24
|
-
['
|
|
25
|
-
'mkdocs-autorefs==0.4.1',
|
|
26
|
-
'mkdocs-material-extensions==1.3.1',
|
|
27
|
-
'mkdocstrings==0.21.2',
|
|
28
|
-
'networkx>=3.0,<4.0']
|
|
24
|
+
['networkx>=3.2,<4.0']
|
|
29
25
|
|
|
30
26
|
extras_require = \
|
|
31
|
-
{'visualization': ['matplotlib>=3.
|
|
27
|
+
{'visualization': ['matplotlib>=3.9,<4.0']}
|
|
32
28
|
|
|
33
29
|
setup_kwargs = {
|
|
34
30
|
'name': 'pytestarch',
|
|
35
|
-
'version': '
|
|
31
|
+
'version': '3.0.0',
|
|
36
32
|
'description': 'Test framework for software architecture based on imports between modules',
|
|
37
33
|
'long_description': '# Welcome to PyTestArch\n\nPyTestArch is an open source library that allows users to define architectural rules and test their code against them. It is \ngenerally inspired by [ArchUnit](https://www.archunit.org/).\n\n## Installation Guide\nPyTestArch is available via [PyPI](https://pypi.org/project/pytestarch/) and can be installed e.g. via pip: `pip install pytestarch`. To also install the\noptional dependency matplotlib, which is required to draw the created dependency graphs, install `pytestarch[visualization]`\n\n## Usage Guide\nThree steps are required to test an architectural rule:\n\n1) Create an evaluable representation of the source code you want to test\n\n```\nfrom pytestarch import get_evaluable_architecture\n\nevaluable = get_evaluable_architecture("/home/dummy/project", "/home/dummy/project/src")\n```\nThis will scan all python files under /home/dummy/project/src for imports and build an internal representation that can\nlater be queried. The first parameter /home/dummy/project helps PyTestArch to differentiate between internal and external \ndependencies. This evaluable can be used for multiple architectural rule checks; if you are using [pytest](https://docs.pytest.org/en/7.1.x/),\nyou could use a fixture for this evaluable object.\n\n2) Define an architectural rule\n```\nfrom pytestarch import Rule\n\nrule = (\n Rule() \n .modules_that() \n .are_named("project.src.moduleB") \n .should_not() \n .be_imported_by_modules_that() \n .are_sub_modules_of("project.src.moduleA") \n)\n```\n\nThis rule represents the architectural requirements that a module named "project.src.moduleB" should not be imported by any module\nthat is a submodule of "project.src.moduleA", excluding "project.src.moduleA" itself.\n\n3) Evaluate your code against this rule\n\n```\nrule.assert_applies(evaluable)\n```\nThat\'s it!\n',
|
|
38
34
|
'author': 'zyskarch',
|
|
@@ -45,7 +41,7 @@ setup_kwargs = {
|
|
|
45
41
|
'package_data': package_data,
|
|
46
42
|
'install_requires': install_requires,
|
|
47
43
|
'extras_require': extras_require,
|
|
48
|
-
'python_requires': '>=3.
|
|
44
|
+
'python_requires': '>=3.9,<4.0',
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Set
|
|
4
|
-
|
|
5
3
|
from pytestarch import Rule
|
|
6
4
|
from pytestarch.diagram_extension.parsed_dependencies import ParsedDependencies
|
|
7
5
|
from pytestarch.query_language.base_language import RuleApplier
|
|
@@ -11,7 +9,7 @@ class DependencyToRuleConverter:
|
|
|
11
9
|
def __init__(self, should_only_rule: bool) -> None:
|
|
12
10
|
self._should_only_rule = should_only_rule
|
|
13
11
|
|
|
14
|
-
def convert(self, dependencies: ParsedDependencies) ->
|
|
12
|
+
def convert(self, dependencies: ParsedDependencies) -> list[RuleApplier]:
|
|
15
13
|
"""Converts a parsed dependency object to a list of RuleAppliers.
|
|
16
14
|
All explicit dependencies in the given object are converted to should (only) rules.
|
|
17
15
|
All missing, but possible dependencies between the given modules are converted to 'should not' rules.
|
|
@@ -27,13 +25,13 @@ class DependencyToRuleConverter:
|
|
|
27
25
|
|
|
28
26
|
def _convert_should_rules(
|
|
29
27
|
self, dependencies: ParsedDependencies
|
|
30
|
-
) ->
|
|
28
|
+
) -> list[RuleApplier]:
|
|
31
29
|
return [
|
|
32
30
|
self._generate_rule(importer, importees)
|
|
33
31
|
for importer, importees in dependencies.dependencies.items()
|
|
34
32
|
]
|
|
35
33
|
|
|
36
|
-
def _generate_rule(self, importer: str, importees:
|
|
34
|
+
def _generate_rule(self, importer: str, importees: set[str]) -> RuleApplier:
|
|
37
35
|
rule_subject = Rule().modules_that().are_named(importer)
|
|
38
36
|
|
|
39
37
|
if self._should_only_rule:
|
|
@@ -46,7 +44,7 @@ class DependencyToRuleConverter:
|
|
|
46
44
|
@classmethod
|
|
47
45
|
def _convert_should_not_rules(
|
|
48
46
|
cls, parsed_dependencies: ParsedDependencies
|
|
49
|
-
) ->
|
|
47
|
+
) -> list[RuleApplier]:
|
|
50
48
|
# sorting in this method is necessary to generate a reliable order of returned rules
|
|
51
49
|
# this helps with testing and overall consistency
|
|
52
50
|
rules = []
|
|
@@ -5,7 +5,6 @@ from abc import ABC, abstractmethod
|
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Dict, Optional, Set, Tuple
|
|
9
8
|
|
|
10
9
|
from pytestarch.diagram_extension.exceptions import PumlParsingError
|
|
11
10
|
from pytestarch.diagram_extension.parsed_dependencies import ParsedDependencies
|
|
@@ -20,7 +19,7 @@ class DiagramParser(ABC):
|
|
|
20
19
|
@dataclass(frozen=True)
|
|
21
20
|
class Module:
|
|
22
21
|
name: str
|
|
23
|
-
alias:
|
|
22
|
+
alias: str | None = None
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
PUML_START_MARKER = "@startuml"
|
|
@@ -101,7 +100,7 @@ class PumlParser(DiagramParser):
|
|
|
101
100
|
@classmethod
|
|
102
101
|
def _retrieve_modules_declared_outside_dependencies(
|
|
103
102
|
cls, content: str
|
|
104
|
-
) ->
|
|
103
|
+
) -> set[Module]:
|
|
105
104
|
module_group_1 = "m1"
|
|
106
105
|
module_group_2 = "m2"
|
|
107
106
|
alias_group = "alias"
|
|
@@ -135,7 +134,7 @@ class PumlParser(DiagramParser):
|
|
|
135
134
|
def _retrieve_dependencies_and_inline_modules(
|
|
136
135
|
cls,
|
|
137
136
|
content: str,
|
|
138
|
-
) ->
|
|
137
|
+
) -> dict[str, set[str]]:
|
|
139
138
|
dependor_group_1 = "dependor1"
|
|
140
139
|
dependor_group_2 = "dependor2"
|
|
141
140
|
|
|
@@ -168,9 +167,9 @@ class PumlParser(DiagramParser):
|
|
|
168
167
|
|
|
169
168
|
def _unify(
|
|
170
169
|
self,
|
|
171
|
-
modules:
|
|
172
|
-
dependencies:
|
|
173
|
-
) ->
|
|
170
|
+
modules: set[Module],
|
|
171
|
+
dependencies: dict[str, set[str]],
|
|
172
|
+
) -> tuple[set[str], dict[str, set[str]]]:
|
|
174
173
|
unified_dependencies = {}
|
|
175
174
|
|
|
176
175
|
all_aliases = self._get_modules_by_alias(modules)
|
|
@@ -190,9 +189,9 @@ class PumlParser(DiagramParser):
|
|
|
190
189
|
@classmethod
|
|
191
190
|
def _get_unified_modules(
|
|
192
191
|
cls,
|
|
193
|
-
modules:
|
|
194
|
-
unified_dependencies:
|
|
195
|
-
) ->
|
|
192
|
+
modules: set[Module],
|
|
193
|
+
unified_dependencies: dict[str, set[str]],
|
|
194
|
+
) -> set[str]:
|
|
196
195
|
all_modules = {m.name for m in modules}
|
|
197
196
|
|
|
198
197
|
all_modules.update(set(unified_dependencies.keys()))
|
|
@@ -203,11 +202,11 @@ class PumlParser(DiagramParser):
|
|
|
203
202
|
return all_modules
|
|
204
203
|
|
|
205
204
|
@classmethod
|
|
206
|
-
def _get_modules_by_alias(cls, modules:
|
|
205
|
+
def _get_modules_by_alias(cls, modules: set[Module]) -> dict[str, str]:
|
|
207
206
|
return {
|
|
208
207
|
module.alias: module.name for module in modules if module.alias is not None
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
@classmethod
|
|
212
|
-
def _unify_module(cls, module: str, all_aliases:
|
|
211
|
+
def _unify_module(cls, module: str, all_aliases: dict[str, str]) -> str:
|
|
213
212
|
return all_aliases.get(module, module)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import List, Optional
|
|
5
4
|
|
|
6
5
|
from pytestarch import EvaluableArchitecture
|
|
7
6
|
from pytestarch.diagram_extension.dependency_to_rule_converter import (
|
|
@@ -21,7 +20,7 @@ from pytestarch.query_language.multiple_rule_applier import MultipleRuleApplier
|
|
|
21
20
|
class ModulePrefixer:
|
|
22
21
|
@classmethod
|
|
23
22
|
def prefix(
|
|
24
|
-
cls, parsed_dependencies: ParsedDependencies, prefix:
|
|
23
|
+
cls, parsed_dependencies: ParsedDependencies, prefix: str | None
|
|
25
24
|
) -> ParsedDependencies:
|
|
26
25
|
modules = parsed_dependencies.all_modules
|
|
27
26
|
dependencies = parsed_dependencies.dependencies
|
|
@@ -37,7 +36,7 @@ class ModulePrefixer:
|
|
|
37
36
|
)
|
|
38
37
|
|
|
39
38
|
@classmethod
|
|
40
|
-
def _add_prefix_to_module(cls, module_name: str, prefix:
|
|
39
|
+
def _add_prefix_to_module(cls, module_name: str, prefix: str | None) -> str:
|
|
41
40
|
if prefix is None:
|
|
42
41
|
return module_name
|
|
43
42
|
|
|
@@ -59,8 +58,8 @@ class DiagramRule(FileRule, BaseModuleSpecifier, RuleApplier):
|
|
|
59
58
|
should_only_rule: if True, edges between components will be converted into 'should only import' rules.
|
|
60
59
|
If False, 'should import' rules will be generated instead.
|
|
61
60
|
"""
|
|
62
|
-
self._file_path:
|
|
63
|
-
self._name_relative_to_root:
|
|
61
|
+
self._file_path: Path | None = None
|
|
62
|
+
self._name_relative_to_root: str | None = None
|
|
64
63
|
self._should_only_rule = should_only_rule
|
|
65
64
|
|
|
66
65
|
def from_file(self, file_path: Path) -> BaseModuleSpecifier:
|
|
@@ -94,11 +93,11 @@ class DiagramRule(FileRule, BaseModuleSpecifier, RuleApplier):
|
|
|
94
93
|
) -> ParsedDependencies:
|
|
95
94
|
return ModulePrefixer.prefix(parsed_dependencies, self._name_relative_to_root)
|
|
96
95
|
|
|
97
|
-
def _convert_to_rules(self, dependencies: ParsedDependencies) ->
|
|
96
|
+
def _convert_to_rules(self, dependencies: ParsedDependencies) -> list[RuleApplier]:
|
|
98
97
|
return DependencyToRuleConverter(self._should_only_rule).convert(dependencies)
|
|
99
98
|
|
|
100
99
|
@classmethod
|
|
101
100
|
def _apply_rules(
|
|
102
|
-
cls, rule_appliers:
|
|
101
|
+
cls, rule_appliers: list[RuleApplier], evaluable: EvaluableArchitecture
|
|
103
102
|
) -> None:
|
|
104
103
|
MultipleRuleApplier(rule_appliers).assert_applies(evaluable)
|
{pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/parsed_dependencies.py
RENAMED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Dict, Set
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
@dataclass(frozen=True)
|
|
8
7
|
class ParsedDependencies:
|
|
9
|
-
all_modules:
|
|
10
|
-
dependencies:
|
|
8
|
+
all_modules: set[str]
|
|
9
|
+
dependencies: dict[str, set[str]]
|
{pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/breadth_first_searches.py
RENAMED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Set
|
|
4
|
-
|
|
5
3
|
from pytestarch.eval_structure.evaluable_architecture import Dependency, ModuleFilter
|
|
6
4
|
from pytestarch.eval_structure.evaluable_structures import AbstractGraph, AbstractNode
|
|
7
5
|
from pytestarch.eval_structure.utils import get_node, get_parent_nodes, to_modules
|
|
@@ -9,7 +7,7 @@ from pytestarch.eval_structure.utils import get_node, get_parent_nodes, to_modul
|
|
|
9
7
|
|
|
10
8
|
def get_dependency_between_modules(
|
|
11
9
|
graph: AbstractGraph, dependent: ModuleFilter, dependent_upon: ModuleFilter
|
|
12
|
-
) ->
|
|
10
|
+
) -> list[Dependency]:
|
|
13
11
|
dependent_node = get_node(dependent)
|
|
14
12
|
dependent_upon_nodes = get_all_submodules_of(graph, dependent_upon)
|
|
15
13
|
|
|
@@ -44,8 +42,8 @@ def get_dependency_between_modules(
|
|
|
44
42
|
|
|
45
43
|
|
|
46
44
|
def any_dependency_to_module_other_than( # noqa: C901
|
|
47
|
-
graph: AbstractGraph, dependent: ModuleFilter, dependent_upons:
|
|
48
|
-
) ->
|
|
45
|
+
graph: AbstractGraph, dependent: ModuleFilter, dependent_upons: set[ModuleFilter]
|
|
46
|
+
) -> list[Dependency]:
|
|
49
47
|
# nodes to exclude are nodes that once reached will not have their submodules analysed next
|
|
50
48
|
nodes_to_exclude = set()
|
|
51
49
|
for dependent_upon in dependent_upons:
|
|
@@ -109,8 +107,8 @@ def any_dependency_to_module_other_than( # noqa: C901
|
|
|
109
107
|
|
|
110
108
|
|
|
111
109
|
def any_other_dependency_to_module_than( # noqa: C901
|
|
112
|
-
graph: AbstractGraph, dependents:
|
|
113
|
-
) ->
|
|
110
|
+
graph: AbstractGraph, dependents: set[ModuleFilter], dependent_upon: ModuleFilter
|
|
111
|
+
) -> list[Dependency]:
|
|
114
112
|
# submodules of the dependent upon module do not count as an import that is not the dependent upon module
|
|
115
113
|
# submodules of and including the dependent do not count as allowed imports
|
|
116
114
|
|
|
@@ -177,7 +175,7 @@ def any_other_dependency_to_module_than( # noqa: C901
|
|
|
177
175
|
|
|
178
176
|
def get_all_submodules_of(
|
|
179
177
|
graph: AbstractGraph, module: ModuleFilter
|
|
180
|
-
) ->
|
|
178
|
+
) -> set[AbstractNode]:
|
|
181
179
|
"""Returns all submodules of a given module.
|
|
182
180
|
|
|
183
181
|
Args:
|
{pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_architecture.py
RENAMED
|
@@ -4,19 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from bisect import bisect
|
|
7
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
7
8
|
from dataclasses import dataclass
|
|
8
|
-
from typing import
|
|
9
|
-
Any,
|
|
10
|
-
Dict,
|
|
11
|
-
Iterable,
|
|
12
|
-
List,
|
|
13
|
-
Mapping,
|
|
14
|
-
Optional,
|
|
15
|
-
Protocol,
|
|
16
|
-
Sequence,
|
|
17
|
-
Tuple,
|
|
18
|
-
Union,
|
|
19
|
-
)
|
|
9
|
+
from typing import Any, Protocol
|
|
20
10
|
|
|
21
11
|
from pytestarch.eval_structure.exceptions import LayerMismatch
|
|
22
12
|
|
|
@@ -28,11 +18,11 @@ class LayerMapping:
|
|
|
28
18
|
def __init__(
|
|
29
19
|
self,
|
|
30
20
|
layer_mapping_for_module_filters: Mapping[
|
|
31
|
-
Layer,
|
|
21
|
+
Layer, Sequence[ModuleFilter] | Sequence[Module]
|
|
32
22
|
],
|
|
33
23
|
) -> None:
|
|
34
24
|
self._layer_mapping_for_module_filters = layer_mapping_for_module_filters
|
|
35
|
-
self._module_filter_mapping: Mapping[
|
|
25
|
+
self._module_filter_mapping: Mapping[ModuleFilter | Module, Layer] = {
|
|
36
26
|
module: layer
|
|
37
27
|
for layer, modules in layer_mapping_for_module_filters.items()
|
|
38
28
|
for module in modules
|
|
@@ -54,7 +44,7 @@ class LayerMapping:
|
|
|
54
44
|
if key.identifier == module.identifier
|
|
55
45
|
][0]
|
|
56
46
|
|
|
57
|
-
def _get_layer_or_none(self, module_name: str) ->
|
|
47
|
+
def _get_layer_or_none(self, module_name: str) -> Layer | None:
|
|
58
48
|
layers = [
|
|
59
49
|
self._module_filter_mapping[key]
|
|
60
50
|
for key in self._module_filter_mapping
|
|
@@ -66,7 +56,7 @@ class LayerMapping:
|
|
|
66
56
|
|
|
67
57
|
return None
|
|
68
58
|
|
|
69
|
-
def get_layer_for_module_name(self, module_name: str) ->
|
|
59
|
+
def get_layer_for_module_name(self, module_name: str) -> Layer | None:
|
|
70
60
|
"""Attempts to find the layer the given module belongs to. If the module does not appear in the
|
|
71
61
|
layer definition itself, it is checked whether the module is a submodule of one of the modules in the layer
|
|
72
62
|
definition. If so, the layer of the parent module is returned. Otherwise, None is returned.
|
|
@@ -219,14 +209,14 @@ class ModuleGroup(Module):
|
|
|
219
209
|
return False
|
|
220
210
|
|
|
221
211
|
|
|
222
|
-
Dependency =
|
|
212
|
+
Dependency = tuple[Module, Module]
|
|
223
213
|
# key: user-requested dependency
|
|
224
214
|
# values: list of exact modules that show this dependency
|
|
225
|
-
ExplicitlyRequestedDependenciesByBaseModules =
|
|
215
|
+
ExplicitlyRequestedDependenciesByBaseModules = dict[Dependency, list[Dependency]]
|
|
226
216
|
# key: module that either has dependencies or that others are dependent on that were not explicitly requested
|
|
227
217
|
# via the rule the user has specified
|
|
228
218
|
# value: importer and importee that were not found
|
|
229
|
-
NotExplicitlyRequestedDependenciesByBaseModule =
|
|
219
|
+
NotExplicitlyRequestedDependenciesByBaseModule = dict[Module, list[Dependency]]
|
|
230
220
|
|
|
231
221
|
|
|
232
222
|
class EvaluableArchitecture(Protocol):
|
|
@@ -311,6 +301,6 @@ class EvaluableArchitecture(Protocol):
|
|
|
311
301
|
raise NotImplementedError()
|
|
312
302
|
|
|
313
303
|
@property
|
|
314
|
-
def modules(self) ->
|
|
304
|
+
def modules(self) -> list[str]:
|
|
315
305
|
"""Return names of all modules that are present in this architecture."""
|
|
316
306
|
raise NotImplementedError()
|
|
@@ -4,8 +4,9 @@ and edges to its subclasses in a template pattern.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
from collections.abc import Iterable, Sequence
|
|
7
8
|
from itertools import product
|
|
8
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
11
|
from pytestarch.eval_structure.breadth_first_searches import (
|
|
11
12
|
any_dependency_to_module_other_than,
|
|
@@ -91,5 +92,5 @@ class EvaluableArchitectureGraph(EvaluableArchitecture):
|
|
|
91
92
|
self._graph.draw(**kwargs) # type: ignore
|
|
92
93
|
|
|
93
94
|
@property
|
|
94
|
-
def modules(self) ->
|
|
95
|
+
def modules(self) -> list[str]:
|
|
95
96
|
return self._graph.nodes
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import List
|
|
5
4
|
|
|
6
5
|
AbstractNode = str
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class AbstractGraph(ABC):
|
|
10
9
|
@abstractmethod
|
|
11
|
-
def direct_successor_nodes(self, node: AbstractNode) ->
|
|
10
|
+
def direct_successor_nodes(self, node: AbstractNode) -> list[AbstractNode]:
|
|
12
11
|
raise NotImplementedError()
|
|
13
12
|
|
|
14
13
|
@abstractmethod
|
|
@@ -18,10 +17,10 @@ class AbstractGraph(ABC):
|
|
|
18
17
|
raise NotImplementedError()
|
|
19
18
|
|
|
20
19
|
@abstractmethod
|
|
21
|
-
def direct_predecessor_nodes(self, node: AbstractNode) ->
|
|
20
|
+
def direct_predecessor_nodes(self, node: AbstractNode) -> list[AbstractNode]:
|
|
22
21
|
raise NotImplementedError()
|
|
23
22
|
|
|
24
23
|
@property
|
|
25
24
|
@abstractmethod
|
|
26
|
-
def nodes(self) ->
|
|
25
|
+
def nodes(self) -> list[AbstractNode]:
|
|
27
26
|
raise NotImplementedError()
|
{pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/module_name_converter.py
RENAMED
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from collections import defaultdict
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import cast
|
|
6
7
|
|
|
7
8
|
from pytestarch import EvaluableArchitecture
|
|
8
9
|
from pytestarch.eval_structure.breadth_first_searches import get_all_submodules_of
|
|
@@ -22,7 +23,7 @@ class ModuleNameConverter:
|
|
|
22
23
|
@classmethod
|
|
23
24
|
def convert(
|
|
24
25
|
cls, modules: Sequence[ModuleFilter], arch: EvaluableArchitecture
|
|
25
|
-
) ->
|
|
26
|
+
) -> tuple[Sequence[ModuleFilter], dict[str, list[Module]]]:
|
|
26
27
|
"""Converts each regex pattern that serves to identify module names into actual modules that match this pattern.
|
|
27
28
|
|
|
28
29
|
Args:
|
|
@@ -88,8 +89,8 @@ class ModuleNameConverter:
|
|
|
88
89
|
@classmethod
|
|
89
90
|
def _split_modules_by_presence_of_regex_pattern(
|
|
90
91
|
cls, modules: Sequence[ModuleFilter]
|
|
91
|
-
) ->
|
|
92
|
-
modules_with_regex_name_pattern:
|
|
92
|
+
) -> tuple[list[ModuleNameRegexFilter], Sequence[ModuleFilter]]:
|
|
93
|
+
modules_with_regex_name_pattern: list[ModuleNameRegexFilter] = []
|
|
93
94
|
other_modules = []
|
|
94
95
|
|
|
95
96
|
for module in modules:
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any
|
|
7
8
|
|
|
8
9
|
import networkx as nx
|
|
9
10
|
from networkx import draw_networkx, has_path, spring_layout
|
|
@@ -33,9 +34,9 @@ class NetworkxGraph(AbstractGraph):
|
|
|
33
34
|
|
|
34
35
|
def __init__(
|
|
35
36
|
self,
|
|
36
|
-
all_modules:
|
|
37
|
+
all_modules: list[Node],
|
|
37
38
|
imports: Sequence[Import],
|
|
38
|
-
level_limit:
|
|
39
|
+
level_limit: int | None = None,
|
|
39
40
|
) -> None:
|
|
40
41
|
"""
|
|
41
42
|
Args:
|
|
@@ -85,7 +86,7 @@ class NetworkxGraph(AbstractGraph):
|
|
|
85
86
|
|
|
86
87
|
def _add_edges_within_module_hierarchy(
|
|
87
88
|
self,
|
|
88
|
-
parent_modules:
|
|
89
|
+
parent_modules: list[Node],
|
|
89
90
|
child: Node,
|
|
90
91
|
) -> None:
|
|
91
92
|
"""Create edges between a node and its parent recursively until the parent-less parent is reached.
|
|
@@ -163,14 +164,14 @@ class NetworkxGraph(AbstractGraph):
|
|
|
163
164
|
return self._graph.number_of_nodes()
|
|
164
165
|
|
|
165
166
|
@property
|
|
166
|
-
def nodes(self) ->
|
|
167
|
+
def nodes(self) -> list[Node]:
|
|
167
168
|
return list(self._graph.nodes)
|
|
168
169
|
|
|
169
170
|
@property
|
|
170
|
-
def edges(self) ->
|
|
171
|
+
def edges(self) -> list[tuple[Node, Node]]:
|
|
171
172
|
return list(self._graph.edges)
|
|
172
173
|
|
|
173
|
-
def direct_predecessor_nodes(self, node: Node) ->
|
|
174
|
+
def direct_predecessor_nodes(self, node: Node) -> list[Node]:
|
|
174
175
|
"""Returns all nodes that have a directed edge towards the given node.
|
|
175
176
|
|
|
176
177
|
Args:
|
|
@@ -181,7 +182,7 @@ class NetworkxGraph(AbstractGraph):
|
|
|
181
182
|
"""
|
|
182
183
|
return sorted(self._graph.predecessors(node))
|
|
183
184
|
|
|
184
|
-
def direct_successor_nodes(self, node: Node) ->
|
|
185
|
+
def direct_successor_nodes(self, node: Node) -> list[Node]:
|
|
185
186
|
"""Returns all nodes that the given node has a directed edge towards.
|
|
186
187
|
|
|
187
188
|
Args:
|
|
@@ -245,8 +246,8 @@ class NetworkxGraph(AbstractGraph):
|
|
|
245
246
|
|
|
246
247
|
draw_networkx(self._graph, **kwargs)
|
|
247
248
|
|
|
248
|
-
def _create_plot_labels_with_alias(self, aliases:
|
|
249
|
-
module_names:
|
|
249
|
+
def _create_plot_labels_with_alias(self, aliases: dict[str, str]) -> dict[str, str]:
|
|
250
|
+
module_names: list[str] = list(self._graph.nodes)
|
|
250
251
|
self._assert_aliased_modules_exist(aliases, module_names)
|
|
251
252
|
|
|
252
253
|
# longest name first so aliases for submodule take priority over aliases for
|
|
@@ -265,8 +266,8 @@ class NetworkxGraph(AbstractGraph):
|
|
|
265
266
|
|
|
266
267
|
def _assert_aliased_modules_exist(
|
|
267
268
|
self,
|
|
268
|
-
aliases:
|
|
269
|
-
module_names:
|
|
269
|
+
aliases: dict[str, str],
|
|
270
|
+
module_names: list[str],
|
|
270
271
|
) -> None:
|
|
271
272
|
for module in aliases:
|
|
272
273
|
if module not in module_names:
|
|
@@ -278,8 +279,8 @@ class NetworkxGraph(AbstractGraph):
|
|
|
278
279
|
def _create_label(
|
|
279
280
|
self,
|
|
280
281
|
module_name: str,
|
|
281
|
-
sorted_aliased_modules:
|
|
282
|
-
aliases:
|
|
282
|
+
sorted_aliased_modules: list[str],
|
|
283
|
+
aliases: dict[str, str],
|
|
283
284
|
) -> str:
|
|
284
285
|
try:
|
|
285
286
|
most_specific_aliased_module = next(
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import List
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class Import(ABC):
|
|
@@ -18,7 +17,7 @@ class Import(ABC):
|
|
|
18
17
|
"""
|
|
19
18
|
return self._importer
|
|
20
19
|
|
|
21
|
-
def importer_parent_modules(self) ->
|
|
20
|
+
def importer_parent_modules(self) -> list[str]:
|
|
22
21
|
"""Returns names of all parent modules of the importing module.
|
|
23
22
|
|
|
24
23
|
Returns:
|
|
@@ -36,7 +35,7 @@ class Import(ABC):
|
|
|
36
35
|
raise NotImplementedError()
|
|
37
36
|
|
|
38
37
|
@abstractmethod
|
|
39
|
-
def importee_parent_modules(self) ->
|
|
38
|
+
def importee_parent_modules(self) -> list[str]:
|
|
40
39
|
"""Returns names of all parent modules of the imported module.
|
|
41
40
|
|
|
42
41
|
Returns:
|
|
@@ -51,7 +50,7 @@ class Import(ABC):
|
|
|
51
50
|
return f"{prefix}.{value}"
|
|
52
51
|
|
|
53
52
|
|
|
54
|
-
def get_parent_modules(module: str) ->
|
|
53
|
+
def get_parent_modules(module: str) -> list[str]:
|
|
55
54
|
"""Calculates all parent modules of a given module.
|
|
56
55
|
|
|
57
56
|
Example: source root is a
|
|
@@ -66,7 +65,7 @@ def get_parent_modules(module: str) -> List[str]:
|
|
|
66
65
|
"""
|
|
67
66
|
parent_modules = []
|
|
68
67
|
|
|
69
|
-
parent:
|
|
68
|
+
parent: list[str] = []
|
|
70
69
|
|
|
71
70
|
for char in module:
|
|
72
71
|
if char == ".":
|