pytestarch 3.0.0__tar.gz → 3.1.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-3.0.0 → pytestarch-3.1.0}/PKG-INFO +1 -1
- {pytestarch-3.0.0 → pytestarch-3.1.0}/pyproject.toml +1 -1
- {pytestarch-3.0.0 → pytestarch-3.1.0}/setup.py +1 -1
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/diagram_parser.py +8 -8
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/config.py +1 -0
- pytestarch-3.1.0/src/pytestarch/eval_structure_generation/file_import/file_filter.py +39 -0
- pytestarch-3.1.0/src/pytestarch/eval_structure_generation/file_import/import_filter.py +72 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/graph_generation/graph_generator.py +31 -11
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/pytestarch.py +28 -3
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/partial_match_to_regex_converter.py +1 -1
- pytestarch-3.0.0/src/pytestarch/eval_structure_generation/file_import/file_filter.py +0 -34
- pytestarch-3.0.0/src/pytestarch/eval_structure_generation/file_import/import_filter.py +0 -39
- {pytestarch-3.0.0 → pytestarch-3.1.0}/LICENSE +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/README.md +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/dependency_to_rule_converter.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/diagram_rule.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/exceptions.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/parsed_dependencies.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/breadth_first_searches.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_architecture.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_graph.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_structures.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/exceptions.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/module_name_converter.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/networkxgraph.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/types.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/utils.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/converter.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/exceptions.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/import_types.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/importee_module_calculator.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/parser.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/graph_generation/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/py.typed +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/LANGUAGE_DEFINTION.md +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/base_language.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/exceptions.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/layered_architecture_rule.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/multiple_rule_applier.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/rule.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/error_message/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/error_message/message_generator.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/exceptions.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/behavior_requirement.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/layer_rule_violation_detector.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/module_requirement.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_matcher.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_violation_detector.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_violations.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/__init__.py +0 -0
- {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/decorators.py +0 -0
|
@@ -28,7 +28,7 @@ extras_require = \
|
|
|
28
28
|
|
|
29
29
|
setup_kwargs = {
|
|
30
30
|
'name': 'pytestarch',
|
|
31
|
-
'version': '3.
|
|
31
|
+
'version': '3.1.0',
|
|
32
32
|
'description': 'Test framework for software architecture based on imports between modules',
|
|
33
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',
|
|
34
34
|
'author': 'zyskarch',
|
|
@@ -25,18 +25,18 @@ class Module:
|
|
|
25
25
|
PUML_START_MARKER = "@startuml"
|
|
26
26
|
PUML_END_MARKER = "@enduml"
|
|
27
27
|
ANYTHING = ".*"
|
|
28
|
-
ANY_ARROW_TEXT = "(-?|\w+-)"
|
|
28
|
+
ANY_ARROW_TEXT = r"(-?|\w+-)"
|
|
29
29
|
NON_EMPTY_STRING = ".+"
|
|
30
30
|
COMPONENT_MARKER = "component"
|
|
31
|
-
NON_EMPTY_WHITESPACE = "\s+"
|
|
32
|
-
NON_EMPTY_CHAR_OR_DIGIT = "(\w|\d)+"
|
|
33
|
-
NON_EMPTY_CHAR_OR_DIGIT_OR_WHITESPACE = "(\w|\d|\s)+"
|
|
31
|
+
NON_EMPTY_WHITESPACE = r"\s+"
|
|
32
|
+
NON_EMPTY_CHAR_OR_DIGIT = r"(\w|\d)+"
|
|
33
|
+
NON_EMPTY_CHAR_OR_DIGIT_OR_WHITESPACE = r"(\w|\d|\s)+"
|
|
34
34
|
START_LINE = "^"
|
|
35
35
|
END_LINE = "$"
|
|
36
|
-
BRACKET_OPEN = "\["
|
|
37
|
-
BRACKET_CLOSE = "\]"
|
|
38
|
-
OPTIONAL_BRACKET_OPEN = "(\[)?"
|
|
39
|
-
OPTIONAL_BRACKET_CLOSE = "(\])?"
|
|
36
|
+
BRACKET_OPEN = r"\["
|
|
37
|
+
BRACKET_CLOSE = r"\]"
|
|
38
|
+
OPTIONAL_BRACKET_OPEN = r"(\[)?"
|
|
39
|
+
OPTIONAL_BRACKET_CLOSE = r"(\])?"
|
|
40
40
|
ALIAS_MARKER = "as"
|
|
41
41
|
ARROW_BODY = "-"
|
|
42
42
|
ARROW_HEAD_LEFT = "<"
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/config.py
RENAMED
|
@@ -10,6 +10,7 @@ class Config:
|
|
|
10
10
|
Attributes:
|
|
11
11
|
excluded_directories: Directory paths not to include in parsing specified via regex. Can be used to exclude
|
|
12
12
|
e.g. test directories.
|
|
13
|
+
excluded_external_dependencies: All external dependencies matching these patterns shall be excluded.
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
excluded_directories: tuple[str, ...]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from functools import singledispatchmethod
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from pytestarch.eval_structure_generation.file_import.config import Config
|
|
8
|
+
|
|
9
|
+
ALL_MARKER = "*"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileFilter:
|
|
13
|
+
"""Uses a regex pattern to determine whether a file or directory should be excluded."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, config: Config) -> None:
|
|
16
|
+
self._excluded_directories = [
|
|
17
|
+
re.compile(directory_pattern)
|
|
18
|
+
for directory_pattern in config.excluded_directories
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
@singledispatchmethod
|
|
22
|
+
def is_excluded(self, obj: str | Path) -> bool:
|
|
23
|
+
"""Returns True if the object matches one of the pre-configured exclusion patterns."""
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
@is_excluded.register(Path)
|
|
27
|
+
def _(self, obj: Path) -> bool:
|
|
28
|
+
path_as_str = str(obj)
|
|
29
|
+
|
|
30
|
+
return self.is_excluded(path_as_str)
|
|
31
|
+
|
|
32
|
+
@is_excluded.register(str)
|
|
33
|
+
def _(self, obj: str) -> bool:
|
|
34
|
+
return any(
|
|
35
|
+
re.match(pattern, obj) is not None for pattern in self._excluded_directories
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def has_filter(self) -> bool:
|
|
39
|
+
return len(self._excluded_directories) > 0
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
from pytestarch.eval_structure.types import Import
|
|
6
|
+
from pytestarch.eval_structure_generation.file_import.config import Config
|
|
7
|
+
from pytestarch.eval_structure_generation.file_import.file_filter import FileFilter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExternalImportFilter:
|
|
11
|
+
"""Filters out imports of (some) external modules from the list of all imports.
|
|
12
|
+
External modules are all modules that are not submodules of the configured module to search for imports.
|
|
13
|
+
|
|
14
|
+
Two possibilities: either all external modules are filtered out, or only those matching given regex patterns.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
exclude_external_libraries: bool,
|
|
20
|
+
root_module_name: str,
|
|
21
|
+
external_exclusions: tuple[str, ...],
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Args:
|
|
25
|
+
exclude_external_libraries: If True, external modules will be filtered out, otherwise this class implements a no-op.
|
|
26
|
+
root_module_name: name of the module that determines which modules are considered external. If a module
|
|
27
|
+
is a submodule of this module, it is considered internal.
|
|
28
|
+
external_exclusions: regex pattern: all external modules that match it shall be filtered out.
|
|
29
|
+
"""
|
|
30
|
+
self._exclude_external_libraries = exclude_external_libraries
|
|
31
|
+
self._root_module_name = root_module_name
|
|
32
|
+
self._external_exclusion_filter = FileFilter(Config(external_exclusions))
|
|
33
|
+
|
|
34
|
+
def filter(self, imports: Sequence[Import]) -> Sequence[Import]:
|
|
35
|
+
"""According to the configuration, imports will be filtered.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
imports: list of imports to be filtered
|
|
39
|
+
Returns:
|
|
40
|
+
filtered list of imports
|
|
41
|
+
"""
|
|
42
|
+
if (
|
|
43
|
+
not self._exclude_external_libraries
|
|
44
|
+
and not self._external_exclusion_filter.has_filter()
|
|
45
|
+
):
|
|
46
|
+
return imports
|
|
47
|
+
|
|
48
|
+
# is this is set, then _exclude_external_libraries is False, as asserted by previously executed code
|
|
49
|
+
if self._external_exclusion_filter.has_filter():
|
|
50
|
+
return [
|
|
51
|
+
i for i in imports if self._is_internal_or_retained_external_import(i)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
return [i for i in imports if self._is_internal_import(i)]
|
|
55
|
+
|
|
56
|
+
def _is_internal_import(self, i: Import) -> bool:
|
|
57
|
+
importee = i.importee()
|
|
58
|
+
|
|
59
|
+
return importee.startswith(self._root_module_name)
|
|
60
|
+
|
|
61
|
+
def _is_internal_or_retained_external_import(self, i: Import) -> bool:
|
|
62
|
+
if self._is_internal_import(i):
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
importee = i.importee()
|
|
66
|
+
|
|
67
|
+
excluded = self._external_exclusion_filter.is_excluded(importee)
|
|
68
|
+
any_parent_excluded = any(
|
|
69
|
+
self._external_exclusion_filter.is_excluded(parent)
|
|
70
|
+
for parent in i.importee_parent_modules()
|
|
71
|
+
)
|
|
72
|
+
return not (excluded or any_parent_excluded)
|
|
@@ -10,7 +10,9 @@ from pytestarch.eval_structure.types import Import
|
|
|
10
10
|
from pytestarch.eval_structure_generation.file_import.config import Config
|
|
11
11
|
from pytestarch.eval_structure_generation.file_import.converter import ImportConverter
|
|
12
12
|
from pytestarch.eval_structure_generation.file_import.file_filter import FileFilter
|
|
13
|
-
from pytestarch.eval_structure_generation.file_import.import_filter import
|
|
13
|
+
from pytestarch.eval_structure_generation.file_import.import_filter import (
|
|
14
|
+
ExternalImportFilter,
|
|
15
|
+
)
|
|
14
16
|
from pytestarch.eval_structure_generation.file_import.import_types import NamedModule
|
|
15
17
|
from pytestarch.eval_structure_generation.file_import.importee_module_calculator import (
|
|
16
18
|
ImporteeModuleCalculator,
|
|
@@ -36,6 +38,7 @@ def generate_graph(
|
|
|
36
38
|
exclusions: tuple[str, ...],
|
|
37
39
|
exclude_external_libraries: bool,
|
|
38
40
|
level_limit: int | None,
|
|
41
|
+
external_exclusions: tuple[str, ...] | None,
|
|
39
42
|
) -> EvaluableArchitectureGraph:
|
|
40
43
|
level_limit = _add_extra_levels_to_limit_if_root_and_module_path_differ(
|
|
41
44
|
level_limit,
|
|
@@ -56,12 +59,15 @@ def generate_graph(
|
|
|
56
59
|
_get_all_internal_modules(all_modules, internal_module_prefix),
|
|
57
60
|
)
|
|
58
61
|
|
|
62
|
+
if external_exclusions is None:
|
|
63
|
+
external_exclusions = ()
|
|
64
|
+
|
|
59
65
|
imports = _remove_excluded_imports(
|
|
60
|
-
exclude_external_libraries, imports, internal_module_prefix
|
|
66
|
+
exclude_external_libraries, imports, internal_module_prefix, external_exclusions
|
|
61
67
|
)
|
|
62
68
|
|
|
63
69
|
all_modules = _append_external_modules_to_module_list(
|
|
64
|
-
all_modules, exclude_external_libraries, imports, root_path
|
|
70
|
+
all_modules, exclude_external_libraries, imports, root_path, external_exclusions
|
|
65
71
|
)
|
|
66
72
|
return EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports, level_limit))
|
|
67
73
|
|
|
@@ -71,24 +77,36 @@ def _append_external_modules_to_module_list(
|
|
|
71
77
|
exclude_external_libraries: bool,
|
|
72
78
|
imports: Sequence[Import],
|
|
73
79
|
root_path: Path,
|
|
80
|
+
external_exclusions: tuple[str, ...],
|
|
74
81
|
) -> list[Node]:
|
|
75
82
|
"""External modules are not detected as modules when importing the source folder - but they will of course show up
|
|
76
83
|
in the imports. To ensure that all edges in the graph have nodes attached, the external modules need to be added to
|
|
77
84
|
the list of modules/nodes."""
|
|
78
|
-
if
|
|
79
|
-
all_modules
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if exclude_external_libraries:
|
|
86
|
+
return all_modules
|
|
87
|
+
|
|
88
|
+
all_modules = ImporteeModuleCalculator(root_path).calculate_importee_modules(
|
|
89
|
+
imports,
|
|
90
|
+
all_modules,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
file_filter = FileFilter(Config(external_exclusions))
|
|
94
|
+
|
|
95
|
+
if not file_filter.has_filter():
|
|
96
|
+
return all_modules
|
|
97
|
+
|
|
98
|
+
return [module for module in all_modules if not file_filter.is_excluded(module)]
|
|
84
99
|
|
|
85
100
|
|
|
86
101
|
def _remove_excluded_imports(
|
|
87
102
|
exclude_external_libraries: bool,
|
|
88
103
|
imports: Sequence[Import],
|
|
89
104
|
internal_module_prefix: str,
|
|
105
|
+
external_exclusions: tuple[str, ...],
|
|
90
106
|
) -> Sequence[Import]:
|
|
91
|
-
import_filter =
|
|
107
|
+
import_filter = ExternalImportFilter(
|
|
108
|
+
exclude_external_libraries, internal_module_prefix, external_exclusions
|
|
109
|
+
)
|
|
92
110
|
return import_filter.filter(imports)
|
|
93
111
|
|
|
94
112
|
|
|
@@ -139,7 +157,9 @@ def _get_imports_from_ast(
|
|
|
139
157
|
|
|
140
158
|
|
|
141
159
|
def _get_all_ast_modules(
|
|
142
|
-
module_path: Path,
|
|
160
|
+
module_path: Path,
|
|
161
|
+
root_path: Path,
|
|
162
|
+
exclusions: tuple[str, ...],
|
|
143
163
|
) -> tuple[list[str], list[NamedModule]]:
|
|
144
164
|
config = Config(exclusions)
|
|
145
165
|
file_filter = FileFilter(config)
|
|
@@ -28,32 +28,52 @@ def get_evaluable_architecture(
|
|
|
28
28
|
exclude_external_libraries: bool = True,
|
|
29
29
|
level_limit: int | None = None,
|
|
30
30
|
regex_exclusions: tuple[str, ...] | None = None,
|
|
31
|
+
external_exclusions: tuple[str, ...] | None = None,
|
|
32
|
+
regex_external_exclusions: tuple[str, ...] | None = None,
|
|
31
33
|
) -> EvaluableArchitecture:
|
|
32
34
|
"""Constructs an evaluable object based on the given module.
|
|
33
35
|
|
|
34
36
|
Args:
|
|
35
37
|
root_path: root directory of the source code. Should not be set to a submodule of the top level module.
|
|
36
38
|
module_path: path of module to generate the evaluable for. Must be a submodule of the root_path module.
|
|
37
|
-
exclusions: pseudo-regex to exclude files and directories from being integrated into the evaluable, e.g. *Test.py
|
|
39
|
+
exclusions: pseudo-regex to exclude files and directories from being integrated into the evaluable, e.g. *Test.py. Can only be specified if regex_exclusions is not specified.
|
|
40
|
+
external_exclusions: pseudo-regex version of 'external_exclusions' to exclude certain external dependencies from being integrated into the evaluable. Can only be specified if exclude_external_libraries is False and regex_external_exclusions is not specified. Note that child dependencies are not automatically excluded, e.g. if exclusion regex is 'logging', then 'logging.handler' will not be excluded. For exclude this as well, use 'logging.*'.
|
|
38
41
|
exclude_external_libraries: if True, external dependencies (defined as all dependencies to module outside the
|
|
39
|
-
module_path, including Python built-in modules) will not be taken into account for the dependency analysis.
|
|
40
|
-
Can only be specified if regex_exclusions is not specified.
|
|
42
|
+
module_path, including Python built-in modules) will not be taken into account for the dependency analysis. If a parent module (e.g. 'logging') is excluded, so will be child modules (e.g. 'logging.handlers').
|
|
41
43
|
level_limit: if not None, specifies the depth of the graph. For example, a limit of 1 will result in only the
|
|
42
44
|
modules one level below the module path to be added as nodes. Note that this only applies to the final graph;
|
|
43
45
|
all modules will be parsed, the graph will simply be flattened: if a submodule of X imports Y, this import is
|
|
44
46
|
then assigned to X instead, if Y is above the level limit.
|
|
45
47
|
regex_exclusions: Proper regex version of 'exclusions'. Can only be specified if regex_exclusions is not specified.
|
|
48
|
+
regex_external_exclusions: Proper regex version of 'external_exclusions' to exclude certain external dependencies from being integrated into the evaluable. Can only be specified if exclude_external_libraries is False and external_exclusions is not specified. If a parent module (e.g. 'logging') is excluded, so will be child modules (e.g. 'logging.handlers').
|
|
46
49
|
"""
|
|
47
50
|
if regex_exclusions and exclusions:
|
|
48
51
|
raise ImproperlyConfigured(
|
|
49
52
|
"Partial match exclusions and regex exclusions cannot both be specified."
|
|
50
53
|
)
|
|
51
54
|
|
|
55
|
+
if regex_external_exclusions and external_exclusions:
|
|
56
|
+
raise ImproperlyConfigured(
|
|
57
|
+
"Partial match external_exclusions and regex external_exclusions cannot both be specified."
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if exclude_external_libraries and (
|
|
61
|
+
external_exclusions or regex_external_exclusions
|
|
62
|
+
):
|
|
63
|
+
raise ImproperlyConfigured(
|
|
64
|
+
"If external libraries are excluded, no exclusion patterns can be defined for them."
|
|
65
|
+
)
|
|
66
|
+
|
|
52
67
|
if exclusions:
|
|
53
68
|
regex_exclusions = tuple(
|
|
54
69
|
convert_partial_match_to_regex(pattern) for pattern in exclusions
|
|
55
70
|
)
|
|
56
71
|
|
|
72
|
+
if external_exclusions:
|
|
73
|
+
regex_external_exclusions = tuple(
|
|
74
|
+
convert_partial_match_to_regex(pattern) for pattern in external_exclusions
|
|
75
|
+
)
|
|
76
|
+
|
|
57
77
|
root_as_path = Path(root_path)
|
|
58
78
|
module_as_path = Path(module_path)
|
|
59
79
|
|
|
@@ -68,6 +88,7 @@ def get_evaluable_architecture(
|
|
|
68
88
|
regex_exclusions, # type: ignore
|
|
69
89
|
exclude_external_libraries,
|
|
70
90
|
level_limit,
|
|
91
|
+
regex_external_exclusions,
|
|
71
92
|
)
|
|
72
93
|
|
|
73
94
|
|
|
@@ -78,6 +99,8 @@ def get_evaluable_architecture_for_module_objects(
|
|
|
78
99
|
exclude_external_libraries: bool = True,
|
|
79
100
|
level_limit: int | None = None,
|
|
80
101
|
regex_exclusions: tuple[str, ...] | None = None,
|
|
102
|
+
external_exclusions: tuple[str, ...] | None = None,
|
|
103
|
+
regex_external_exclusions: tuple[str, ...] | None = None,
|
|
81
104
|
) -> EvaluableArchitecture:
|
|
82
105
|
"""Same functionality as get_evaluable_architecture, but root module and module to evaluate are passed in as module objects
|
|
83
106
|
instead of the absolute paths to them.
|
|
@@ -92,4 +115,6 @@ def get_evaluable_architecture_for_module_objects(
|
|
|
92
115
|
exclude_external_libraries,
|
|
93
116
|
level_limit,
|
|
94
117
|
regex_exclusions,
|
|
118
|
+
external_exclusions,
|
|
119
|
+
regex_external_exclusions,
|
|
95
120
|
)
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/partial_match_to_regex_converter.py
RENAMED
|
@@ -16,7 +16,7 @@ def convert_partial_match_to_regex(match: str) -> str:
|
|
|
16
16
|
Returns:
|
|
17
17
|
Regex version of the partial match string. This means that the partial match characters * have been replaced
|
|
18
18
|
by their regex equivalent .* and the rest of the string has been escaped to not contain any characters that
|
|
19
|
-
have a special meaning in a regex. In addition, $ will be added at the end if no * was present
|
|
19
|
+
have a special meaning in a regex. In addition, $ will be added at the end if no * was present there before.
|
|
20
20
|
This is done because partial matches are used as full string matches, unless * is present at the end.
|
|
21
21
|
"""
|
|
22
22
|
all_at_start = match.startswith(PARTIAL_MATCH_ALL_MARKER)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from pytestarch.eval_structure_generation.file_import.config import Config
|
|
7
|
-
|
|
8
|
-
ALL_MARKER = "*"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class FileFilter:
|
|
12
|
-
"""Uses a pseudo-regex pattern to determine whether a file or directory should be excluded.
|
|
13
|
-
|
|
14
|
-
Allowed patterns are:
|
|
15
|
-
- *A*: file or dir will be excluded, if 'A' is part of their name
|
|
16
|
-
- *A: file or dir will be excluded, if their name ends with 'A'
|
|
17
|
-
- A*: file or dir will be excluded, if their name starts with 'A'
|
|
18
|
-
- A: file or dir will be excluded, if their name is 'A'
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, config: Config) -> None:
|
|
22
|
-
self._excluded_directories = [
|
|
23
|
-
re.compile(directory_pattern)
|
|
24
|
-
for directory_pattern in config.excluded_directories
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
def is_excluded(self, path: Path) -> bool:
|
|
28
|
-
"""Returns True if the path matches one of the pre-configured exclusion patterns."""
|
|
29
|
-
path_as_str = str(path)
|
|
30
|
-
|
|
31
|
-
return any(
|
|
32
|
-
re.match(pattern, path_as_str) is not None
|
|
33
|
-
for pattern in self._excluded_directories
|
|
34
|
-
)
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Sequence
|
|
4
|
-
|
|
5
|
-
from pytestarch.eval_structure.types import Import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class ImportFilter:
|
|
9
|
-
"""Filters out imports of external modules from the list of all imports.
|
|
10
|
-
External modules are all modules that are not submodules of the configured module to search for imports.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
def __init__(self, exclude_external_libraries: bool, root_module_name: str) -> None:
|
|
14
|
-
"""
|
|
15
|
-
Args:
|
|
16
|
-
exclude_external_libraries: If True, external modules will be filtered out, otherwise this class implements a no-op.
|
|
17
|
-
root_module_name: name of the module that determines which modules are considered external. If a module
|
|
18
|
-
is a submodule of this module, it is considered internal.
|
|
19
|
-
"""
|
|
20
|
-
self._exclude_external_libraries = exclude_external_libraries
|
|
21
|
-
self._root_module_name = root_module_name
|
|
22
|
-
|
|
23
|
-
def filter(self, imports: Sequence[Import]) -> Sequence[Import]:
|
|
24
|
-
"""According to the configuration, imports will be filtered.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
imports: list of imports to be filtered
|
|
28
|
-
Returns:
|
|
29
|
-
filtered list of imports
|
|
30
|
-
"""
|
|
31
|
-
if not self._exclude_external_libraries:
|
|
32
|
-
return imports
|
|
33
|
-
|
|
34
|
-
return [i for i in imports if self._is_internal_import(i)]
|
|
35
|
-
|
|
36
|
-
def _is_internal_import(self, i: Import) -> bool:
|
|
37
|
-
importee = i.importee()
|
|
38
|
-
|
|
39
|
-
return importee.startswith(self._root_module_name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/parsed_dependencies.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/breadth_first_searches.py
RENAMED
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_architecture.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/module_name_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/layered_architecture_rule.py
RENAMED
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/multiple_rule_applier.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/error_message/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_matcher.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_violations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|