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.
Files changed (58) hide show
  1. {pytestarch-2.0.2 → pytestarch-3.0.0}/PKG-INFO +4 -10
  2. {pytestarch-2.0.2 → pytestarch-3.0.0}/pyproject.toml +16 -17
  3. {pytestarch-2.0.2 → pytestarch-3.0.0}/setup.py +4 -8
  4. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/dependency_to_rule_converter.py +4 -6
  5. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/diagram_parser.py +11 -12
  6. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/diagram_rule.py +6 -7
  7. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/parsed_dependencies.py +2 -3
  8. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/breadth_first_searches.py +6 -8
  9. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_architecture.py +10 -20
  10. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_graph.py +3 -2
  11. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/evaluable_structures.py +3 -4
  12. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/module_name_converter.py +5 -4
  13. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/networkxgraph.py +15 -14
  14. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/types.py +4 -5
  15. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/utils.py +3 -3
  16. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/config.py +1 -2
  17. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/converter.py +20 -23
  18. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/import_filter.py +1 -1
  19. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/import_types.py +4 -5
  20. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/importee_module_calculator.py +4 -4
  21. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/parser.py +3 -4
  22. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/graph_generation/graph_generator.py +30 -20
  23. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/pytestarch.py +6 -7
  24. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/base_language.py +7 -9
  25. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/layered_architecture_rule.py +11 -11
  26. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/multiple_rule_applier.py +1 -3
  27. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/rule.py +15 -14
  28. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/error_message/message_generator.py +44 -43
  29. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/layer_rule_violation_detector.py +42 -42
  30. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/module_requirement.py +1 -1
  31. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/rule_matcher.py +9 -10
  32. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/rule_violation_detector.py +76 -76
  33. pytestarch-3.0.0/src/pytestarch/rule_assessment/rule_check/rule_violations.py +20 -0
  34. pytestarch-2.0.2/src/pytestarch/rule_assessment/rule_check/rule_violations.py +0 -21
  35. {pytestarch-2.0.2 → pytestarch-3.0.0}/LICENSE +0 -0
  36. {pytestarch-2.0.2 → pytestarch-3.0.0}/README.md +0 -0
  37. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/__init__.py +0 -0
  38. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/__init__.py +0 -0
  39. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/diagram_extension/exceptions.py +0 -0
  40. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/__init__.py +0 -0
  41. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure/exceptions.py +0 -0
  42. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/__init__.py +0 -0
  43. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/__init__.py +0 -0
  44. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/exceptions.py +0 -0
  45. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/file_import/file_filter.py +0 -0
  46. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/eval_structure_generation/graph_generation/__init__.py +0 -0
  47. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/py.typed +0 -0
  48. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/LANGUAGE_DEFINTION.md +0 -0
  49. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/__init__.py +0 -0
  50. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/query_language/exceptions.py +0 -0
  51. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/__init__.py +0 -0
  52. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/error_message/__init__.py +0 -0
  53. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/exceptions.py +0 -0
  54. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/__init__.py +0 -0
  55. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/rule_assessment/rule_check/behavior_requirement.py +0 -0
  56. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/utils/__init__.py +0 -0
  57. {pytestarch-2.0.2 → pytestarch-3.0.0}/src/pytestarch/utils/decorators.py +0 -0
  58. {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: 2.0.2
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.8,<4.0
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: griffe (==0.26.0)
30
- Requires-Dist: matplotlib (>=3.5.3,<4.0.0) ; extra == "visualization"
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 = "2.0.2"
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.8"
31
- networkx = "^3.0"
32
- matplotlib = { version = "^3.5.3", optional = true }
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.4.2"
43
- mkdocstrings-python = "0.9.0"
44
- mkdocs-material = "9.1.6"
45
- nox = "2022.8.7"
46
- pre-commit = "2.20.0"
47
- pytest = "^7.1.2"
48
- coverage = "^7.0.5"
49
- mike = "1.1.2"
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
- ['griffe==0.26.0',
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.5.3,<4.0.0']}
27
+ {'visualization': ['matplotlib>=3.9,<4.0']}
32
28
 
33
29
  setup_kwargs = {
34
30
  'name': 'pytestarch',
35
- 'version': '2.0.2',
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.8,<4.0',
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) -> List[RuleApplier]:
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
- ) -> List[RuleApplier]:
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: Set[str]) -> RuleApplier:
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
- ) -> List[RuleApplier]:
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: Optional[str] = None
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
- ) -> Set[Module]:
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
- ) -> Dict[str, Set[str]]:
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: Set[Module],
172
- dependencies: Dict[str, Set[str]],
173
- ) -> Tuple[Set[str], Dict[str, Set[str]]]:
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: Set[Module],
194
- unified_dependencies: Dict[str, Set[str]],
195
- ) -> Set[str]:
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: Set[Module]) -> Dict[str, str]:
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: Dict[str, str]) -> str:
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: Optional[str]
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: Optional[str]) -> str:
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: Optional[Path] = None
63
- self._name_relative_to_root: Optional[str] = None
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) -> List[RuleApplier]:
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: List[RuleApplier], evaluable: EvaluableArchitecture
101
+ cls, rule_appliers: list[RuleApplier], evaluable: EvaluableArchitecture
103
102
  ) -> None:
104
103
  MultipleRuleApplier(rule_appliers).assert_applies(evaluable)
@@ -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: Set[str]
10
- dependencies: Dict[str, Set[str]]
8
+ all_modules: set[str]
9
+ dependencies: dict[str, set[str]]
@@ -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
- ) -> List[Dependency]:
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: Set[ModuleFilter]
48
- ) -> List[Dependency]:
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: Set[ModuleFilter], dependent_upon: ModuleFilter
113
- ) -> List[Dependency]:
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
- ) -> Set[AbstractNode]:
178
+ ) -> set[AbstractNode]:
181
179
  """Returns all submodules of a given module.
182
180
 
183
181
  Args:
@@ -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, Union[Sequence[ModuleFilter], Sequence[Module]]
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[Union[ModuleFilter, Module], Layer] = {
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) -> Optional[Layer]:
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) -> Optional[Layer]:
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 = Tuple[Module, Module]
212
+ Dependency = tuple[Module, Module]
223
213
  # key: user-requested dependency
224
214
  # values: list of exact modules that show this dependency
225
- ExplicitlyRequestedDependenciesByBaseModules = Dict[Dependency, List[Dependency]]
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 = Dict[Module, List[Dependency]]
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) -> List[str]:
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, Iterable, List, Sequence
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) -> List[str]:
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) -> List[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) -> List[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) -> List[AbstractNode]:
25
+ def nodes(self) -> list[AbstractNode]:
27
26
  raise NotImplementedError()
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from collections import defaultdict
5
- from typing import Dict, List, Sequence, Tuple, cast
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
- ) -> Tuple[Sequence[ModuleFilter], Dict[str, List[Module]]]:
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
- ) -> Tuple[List[ModuleNameRegexFilter], Sequence[ModuleFilter]]:
92
- modules_with_regex_name_pattern: List[ModuleNameRegexFilter] = []
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 typing import Any, Dict, List, Optional, Sequence, Tuple
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: List[Node],
37
+ all_modules: list[Node],
37
38
  imports: Sequence[Import],
38
- level_limit: Optional[int] = None,
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: List[Node],
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) -> List[Node]:
167
+ def nodes(self) -> list[Node]:
167
168
  return list(self._graph.nodes)
168
169
 
169
170
  @property
170
- def edges(self) -> List[Tuple[Node, Node]]:
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) -> List[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) -> List[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: Dict[str, str]) -> Dict[str, str]:
249
- module_names: List[str] = list(self._graph.nodes)
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: Dict[str, str],
269
- module_names: List[str],
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: List[str],
282
- aliases: Dict[str, str],
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) -> List[str]:
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) -> List[str]:
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) -> List[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: List[str] = []
68
+ parent: list[str] = []
70
69
 
71
70
  for char in module:
72
71
  if char == ".":