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.
Files changed (59) hide show
  1. {pytestarch-3.0.0 → pytestarch-3.1.0}/PKG-INFO +1 -1
  2. {pytestarch-3.0.0 → pytestarch-3.1.0}/pyproject.toml +1 -1
  3. {pytestarch-3.0.0 → pytestarch-3.1.0}/setup.py +1 -1
  4. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/diagram_parser.py +8 -8
  5. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/config.py +1 -0
  6. pytestarch-3.1.0/src/pytestarch/eval_structure_generation/file_import/file_filter.py +39 -0
  7. pytestarch-3.1.0/src/pytestarch/eval_structure_generation/file_import/import_filter.py +72 -0
  8. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/graph_generation/graph_generator.py +31 -11
  9. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/pytestarch.py +28 -3
  10. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/partial_match_to_regex_converter.py +1 -1
  11. pytestarch-3.0.0/src/pytestarch/eval_structure_generation/file_import/file_filter.py +0 -34
  12. pytestarch-3.0.0/src/pytestarch/eval_structure_generation/file_import/import_filter.py +0 -39
  13. {pytestarch-3.0.0 → pytestarch-3.1.0}/LICENSE +0 -0
  14. {pytestarch-3.0.0 → pytestarch-3.1.0}/README.md +0 -0
  15. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/__init__.py +0 -0
  16. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/__init__.py +0 -0
  17. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/dependency_to_rule_converter.py +0 -0
  18. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/diagram_rule.py +0 -0
  19. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/exceptions.py +0 -0
  20. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/diagram_extension/parsed_dependencies.py +0 -0
  21. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/__init__.py +0 -0
  22. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/breadth_first_searches.py +0 -0
  23. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_architecture.py +0 -0
  24. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_graph.py +0 -0
  25. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/evaluable_structures.py +0 -0
  26. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/exceptions.py +0 -0
  27. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/module_name_converter.py +0 -0
  28. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/networkxgraph.py +0 -0
  29. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/types.py +0 -0
  30. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure/utils.py +0 -0
  31. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/__init__.py +0 -0
  32. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/__init__.py +0 -0
  33. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/converter.py +0 -0
  34. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/exceptions.py +0 -0
  35. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/import_types.py +0 -0
  36. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/importee_module_calculator.py +0 -0
  37. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/file_import/parser.py +0 -0
  38. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/eval_structure_generation/graph_generation/__init__.py +0 -0
  39. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/py.typed +0 -0
  40. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/LANGUAGE_DEFINTION.md +0 -0
  41. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/__init__.py +0 -0
  42. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/base_language.py +0 -0
  43. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/exceptions.py +0 -0
  44. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/layered_architecture_rule.py +0 -0
  45. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/multiple_rule_applier.py +0 -0
  46. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/query_language/rule.py +0 -0
  47. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/__init__.py +0 -0
  48. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/error_message/__init__.py +0 -0
  49. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/error_message/message_generator.py +0 -0
  50. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/exceptions.py +0 -0
  51. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/__init__.py +0 -0
  52. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/behavior_requirement.py +0 -0
  53. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/layer_rule_violation_detector.py +0 -0
  54. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/module_requirement.py +0 -0
  55. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_matcher.py +0 -0
  56. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_violation_detector.py +0 -0
  57. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/rule_assessment/rule_check/rule_violations.py +0 -0
  58. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/__init__.py +0 -0
  59. {pytestarch-3.0.0 → pytestarch-3.1.0}/src/pytestarch/utils/decorators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytestarch
3
- Version: 3.0.0
3
+ Version: 3.1.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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "PyTestArch"
3
- version = "3.0.0"
3
+ version = "3.1.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>"]
@@ -28,7 +28,7 @@ extras_require = \
28
28
 
29
29
  setup_kwargs = {
30
30
  'name': 'pytestarch',
31
- 'version': '3.0.0',
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+-)" # noqa: W605
28
+ ANY_ARROW_TEXT = r"(-?|\w+-)"
29
29
  NON_EMPTY_STRING = ".+"
30
30
  COMPONENT_MARKER = "component"
31
- NON_EMPTY_WHITESPACE = "\s+" # noqa: W605
32
- NON_EMPTY_CHAR_OR_DIGIT = "(\w|\d)+" # noqa: W605
33
- NON_EMPTY_CHAR_OR_DIGIT_OR_WHITESPACE = "(\w|\d|\s)+" # noqa: W605
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 = "\[" # noqa: W605
37
- BRACKET_CLOSE = "\]" # noqa: W605
38
- OPTIONAL_BRACKET_OPEN = "(\[)?" # noqa: W605
39
- OPTIONAL_BRACKET_CLOSE = "(\])?" # noqa: W605
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 = "<"
@@ -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 ImportFilter
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 not exclude_external_libraries:
79
- all_modules = ImporteeModuleCalculator(root_path).calculate_importee_modules(
80
- imports,
81
- all_modules,
82
- )
83
- return all_modules
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 = ImportFilter(exclude_external_libraries, internal_module_prefix)
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, root_path: Path, exclusions: tuple[str, ...]
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
  )
@@ -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 their before.
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