Repository: zyskarch/pytestarch (language: python)

Issue:
Hide module internals via __init__
e.g. Rule is deeply nested within the module hierarchy, and this essentially exposes this as an API
Configurable: return all violations, not just one



Discussion / hints:

Fix patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 7e14b17..1355528 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -5,6 +5,13 @@ This project uses semantic versioning and follows [keep a changelog](https://kee
 
 ## [Unreleased]
 
+## [1.1.0] -- 2022-10-02
+### Added
+- Rule objects can be passed in as a list
+
+### Fixed
+- All violations not always returned
+
 ## [1.0.3] -- 2022-09-28
 ### Changed
 - Simplified calculation of whether imports are within the project
diff --git a/docs/details.md b/docs/details.md
index cc803b9..0811185 100644
--- a/docs/details.md
+++ b/docs/details.md
@@ -138,6 +138,18 @@ Currently, the following markers are supported by PyTestArch:
 #### RULE_OBJECT
 same as RULE_SUBJECT
 
+In addition, RULE_OBJECTS can be passed in as a list. The rule is fulfilled if it applies to all rule objects.
+For example, the rule
+```
+modules_that()
+    .are_named("1")
+    .should_only()
+    .be_imported_by_modules_that()
+    .are_named(["2", "3"])
+```
+is fulfilled, it the module "1" is not imported by any module other than "2" and "3", and if both "2" and "3" do import "1".
+
+
 #### VERB_MARKER_1
 * should()
 * should_only()
@@ -152,18 +164,24 @@ same as RULE_SUBJECT
 VERB_MARKER_2 and IMPORT_TYPE have been conflated into one expression to improve readability.
 
 Markers from each category can be combined freely with all markers of all other categories. Example rules could be <br>
-```modules_that()
+```
+modules_that()
     .are_sub_modules_of("A")
     .should_only()
     .be_imported_by_modules_that()
-    .are_sub_modules_of("B")``` (True in the above example).
+    .are_sub_modules_of("B")
+```
+(True in the above example)
 
 or <br>
-```modules_that()
+```
+modules_that()
     .are_named("C")
     .should_only()
     .be_imported_by_modules_that()
-    .are_named("A2")``` (False, also imported by module "A").
+    .are_named("A2")
+```
+(False, also imported by module "A")
 
 
 Most rules are so close to the English language that a detailed explanation seems unnecessary. An exception might be the
@@ -183,11 +201,16 @@ as reference (M1, M2 are used as RULE_SUBJECT and RULE_OBJECT respectively; pseu
 ## Generating the evaluable architecture representation
 When scanning and processing the requested modules, PyTestArch executes the following step:
 1) Parse all files starting at the requested `module_path`. This only takes python source files into account that are not explicitly excluded.
+
 2) Convert the generated AST into custom dependency representations. In this step, it is ensured that all internal modules (either importing or imported) receive their fully 
 qualified name.
+
 3) Generate a list of all modules that were parsed. This list is used to differentiate between external and internal dependencies (external dependencies will not have been parsed).
+
 4) If not requested otherwise, external dependencies will be filtered out.
+
 5) If external dependencies should be included, they will be added to the list of modules.
+
 6) The dependency representations are converted to a graph structure.
 
 
diff --git a/docs/eval_structure.md b/docs/eval_structure.md
index 66a0934..6b71b4d 100644
--- a/docs/eval_structure.md
+++ b/docs/eval_structure.md
@@ -2,8 +2,10 @@
 
 ## ::: src.pytestarch.eval_structure
 
-## ::: src.pytestarch.eval_structure.eval_structure_types
+## ::: src.pytestarch.eval_structure.evaluable_architecture
+
+## ::: src.pytestarch.eval_structure.evaluable_structures
 
 ## ::: src.pytestarch.eval_structure.evaluable_graph
 
-## ::: src.pytestarch.eval_structure.graph
\ No newline at end of file
+## ::: src.pytestarch.eval_structure_impl.networkxgraph
\ No newline at end of file
diff --git a/docs/parsing.md b/docs/parsing.md
index 2b2c7bc..d73b912 100644
--- a/docs/parsing.md
+++ b/docs/parsing.md
@@ -6,8 +6,6 @@
 
 ## ::: src.pytestarch.importer.import_filter
 
-## ::: src.pytestarch.importer.import_path_padder
-
 ## ::: src.pytestarch.importer.import_types
 
 ## ::: src.pytestarch.importer.importee_module_calculator
diff --git a/docs/query_language.md b/docs/query_language.md
index 63e6785..9df1b32 100644
--- a/docs/query_language.md
+++ b/docs/query_language.md
@@ -2,4 +2,6 @@
 
 ## ::: src.pytestarch.query_language.base_language
 
-## ::: src.pytestarch.query_language.rule_matcher
\ No newline at end of file
+## ::: src.pytestarch.query_language.rule_matcher
+
+## ::: src.pytestarch.query_language.message_generator
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 0f145ee..fabb34e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "PyTestArch"
-version = "1.0.3"
+version = "1.1.0"
 description = "Test framework for software architecture based on imports between modules"
 authors = ["zyskarch <zyskarch@gmail.com>"]
 maintainers = ["zyskarch <zyskarch@gmail.com>"]


Test patch:
diff --git a/src/pytestarch/__init__.py b/src/pytestarch/__init__.py
index e69de29..4e54c9f 100644
--- a/src/pytestarch/__init__.py
+++ b/src/pytestarch/__init__.py
@@ -0,0 +1,6 @@
+from .eval_structure.evaluable_architecture import EvaluableArchitecture  # noqa: F401
+from .pytestarch import get_evaluable_architecture  # noqa: F401 E401
+from .pytestarch import (  # noqa: F401 E401; noqa: F401 E401
+    get_evaluable_architecture_for_module_objects,
+)
+from .query_language.base_language import Rule  # noqa: F401
diff --git a/src/pytestarch/eval_structure/eval_structure_types.py b/src/pytestarch/eval_structure/evaluable_architecture.py
similarity index 54%
rename from src/pytestarch/eval_structure/eval_structure_types.py
rename to src/pytestarch/eval_structure/evaluable_architecture.py
index 98c920a..1b2152c 100644
--- a/src/pytestarch/eval_structure/eval_structure_types.py
+++ b/src/pytestarch/eval_structure/evaluable_architecture.py
@@ -1,17 +1,38 @@
 """Abstract types to specify the interface of evaluable objects."""
-from __future__ import annotations
 
 from dataclasses import dataclass
-from typing import Any, List, Optional, Protocol, Tuple
+from typing import Any, Dict, List, Optional, Protocol, Tuple, Union
 
-from pytestarch.eval_structure.graph import Node
+
+@dataclass(frozen=True)
+class Module:
+    """Represents a python module.
+    The module can either be identified by its name or by the name of a parent module.
+
+    Attributes:
+        name: full name of the module
+        parent_module: full name of the parent module
+    """
+
+    name: Optional[str] = None
+    parent_module: Optional[str] = None
+
+
+StrictDependency = Tuple[Module, Module]
+# if value is not None, it contains the actual (sub) modules of the key-modules that are connected
+StrictDependenciesByBaseModules = Dict[StrictDependency, List[StrictDependency]]
+# key: module that either has dependencies or that other are dependent on that were not expected
+# value: importer and importee that were not expected
+LaxDependenciesByBaseModule = Dict[Module, List[StrictDependency]]
 
 
 class EvaluableArchitecture(Protocol):
-    def get_dependency(
-        self, dependent: Module, dependent_upon: Module
-    ) -> Optional[Tuple[str, str]]:
-        """Returns True if the dependent module is indeed
+    def get_dependencies(
+        self,
+        dependents: Union[Module, List[Module]],
+        dependent_upons: Union[Module, List[Module]],
+    ) -> StrictDependenciesByBaseModules:
+        """Returns tuple of importer and importee per dependent and depending module if the dependent module is indeed
         depending on the dependent_upon module.
         Submodules of dependent towards are taken into account, but submodules of dependent_upon are not.
         If one or both of the modules are defined by their parent module, this parent module is excluded from possible
@@ -22,32 +43,36 @@ def get_dependency(
             dependent_upon: Module
 
         Returns:
-            Importer and importee if there are any that are sub modules of dependent and dependent_upon respectively.
+            Importer and importee per pair of dependent and dependent_upon module if there are any that are sub modules of dependent and dependent_upon respectively.
         """
         raise NotImplementedError()
 
-    def any_dependency_to_module_other_than(
-        self, dependent: Module, dependent_upon: Module
-    ) -> List[Module]:
-        """Returns True if the dependent module has any
-        dependency to a module other than the dependent_upon module
-        or any of its submodules.
-        If the dependent module is defined via a parent module, this parent module is not taken into account.
-        If the dependent upon module is defined via a parent module, this parent module counts as an 'other' dependency.
+    def any_dependencies_to_modules_other_than(
+        self,
+        dependents: Union[Module, List[Module]],
+        dependent_upons: Union[Module, List[Module]],
+    ) -> LaxDependenciesByBaseModule:
+        """Returns list of depending modules per dependent module if the dependent module has any
+        dependency to a module other than the dependent_upon modules
+        or any of their submodules.
+        If a dependent module is defined via a parent module, this parent module is not taken into account.
+        If a dependent upon module is defined via a parent module, this parent module counts as an 'other' dependency.
 
         Args:
             dependent: Module
             dependent_upon: Module
 
         Returns:
-            All modules other than dependent_upon on which dependent module as any dependency
+            All modules other than dependent_upon on which dependent module as any dependency per dependent module
         """
         raise NotImplementedError()
 
-    def any_other_dependency_to_module_than(
-        self, dependent: Module, dependent_upon: Module
-    ) -> List[Module]:
-        """Returns True if any module other than the dependent
+    def any_other_dependencies_to_modules_than(
+        self,
+        dependents: Union[Module, List[Module]],
+        dependent_upons: Union[Module, List[Module]],
+    ) -> LaxDependenciesByBaseModule:
+        """Returns list of depending modules per dependent_upon module if any module other than the dependent
         module and its submodules has any dependency to the
         dependent_upon module.
         If the dependent module is defined via a parent module, this parent module is taken into account. This means
@@ -60,7 +85,7 @@ def any_other_dependency_to_module_than(
             dependent_upon: Module
 
         Returns:
-            All modules other than dependent that have any dependency on the dependent upon module
+            All modules other than dependent that have any dependency on the dependent upon module per dependent_upon module
         """
         raise NotImplementedError()
 
@@ -73,17 +98,3 @@ def visualize(self, **kwargs: Any) -> None:
                 Exception: If 'spacing' is set, this will be interpreted as the parameter 'k' of the spring layout (https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html#networkx.drawing.layout.spring_layout).
         """
         raise NotImplementedError()
-
-
-@dataclass(frozen=True)
-class Module:
-    """Represents a python module.
-    The module can either be identified by its name or by the name of a parent module.
-
-    Attributes:
-        name: full name of the module
-        parent_module: full name of the parent module
-    """
-
-    name: Optional[str] = None
-    parent_module: Optional[Node] = None
diff --git a/src/pytestarch/eval_structure/evaluable_graph.py b/src/pytestarch/eval_structure/evaluable_graph.py
index 6e94b26..2e8a493 100644
--- a/src/pytestarch/eval_structure/evaluable_graph.py
+++ b/src/pytestarch/eval_structure/evaluable_graph.py
@@ -1,21 +1,44 @@
 """Base class for different graph implementations of an evaluable structure. Delegates direct access to graph nodes
 and edges to its subclasses in a template pattern.
 """
-from typing import Any, List, Optional, Set, Tuple
+from itertools import product
+from typing import Any, List, Set, Union
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture, Module
-from pytestarch.eval_structure.graph import Graph, Node
+from pytestarch.eval_structure.evaluable_architecture import (
+    EvaluableArchitecture,
+    LaxDependenciesByBaseModule,
+    Module,
+    StrictDependenciesByBaseModules,
+    StrictDependency,
+)
+from pytestarch.eval_structure.evaluable_structures import AbstractGraph, AbstractNode
 
 
 class EvaluableArchitectureGraph(EvaluableArchitecture):
     """Abstract implementation of an evaluable object that is based on a graph structure."""
 
-    def __init__(self, graph: Graph) -> None:
+    def __init__(self, graph: AbstractGraph) -> None:
         self._graph = graph
 
-    def get_dependency(
+    def get_dependencies(
+        self,
+        dependents: Union[Module, List[Module]],
+        dependent_upons: Union[Module, List[Module]],
+    ) -> StrictDependenciesByBaseModules:
+        result = {}
+
+        dependents = self._listify(dependents)
+        dependent_upons = self._listify(dependent_upons)
+
+        for dependent, dependent_upon in product(dependents, dependent_upons):
+            dependency = self._get_dependency_between_modules(dependent, dependent_upon)
+            result[(dependent, dependent_upon)] = dependency
+
+        return result
+
+    def _get_dependency_between_modules(
         self, dependent: Module, dependent_upon: Module
-    ) -> Optional[Tuple[str, str]]:
+    ) -> List[StrictDependency]:
         dependent_node = self._get_node(dependent)
         dependent_upon_nodes = self._get_all_submodules_of(dependent_upon)
 
@@ -24,6 +47,8 @@ def get_dependency(
         nodes_to_check = [dependent_node]
         checked_nodes = set()
 
+        dependencies = []
+
         while nodes_to_check:
             node = nodes_to_check.pop()
 
@@ -43,18 +68,39 @@ def get_dependency(
                     and node not in nodes_to_exclude
                     and child not in nodes_to_exclude
                 ):
-                    return node, child
-
-        return None
+                    dependencies.append(tuple(self._to_modules([node, child])))
+        return dependencies  # type: ignore
 
-    def _get_node(self, dependent: Module) -> Node:
+    def _get_node(self, dependent: Module) -> AbstractNode:
         return dependent.name or dependent.parent_module
 
-    def any_dependency_to_module_other_than(
-        self, dependent: Module, dependent_upon: Module
-    ) -> List[Module]:
-        # submodules of the dependent upon module do not count as an import that is not the dependent upon module
-        nodes_to_exclude = self._get_all_submodules_of(dependent_upon)
+    def any_dependencies_to_modules_other_than(
+        self,
+        dependents: Union[Module, List[Module]],
+        dependent_upons: Union[Module, List[Module]],
+    ) -> LaxDependenciesByBaseModule:
+        result = {}
+
+        dependents = self._listify(dependents)
+        dependent_upons = self._listify(dependent_upons)
+
+        for dependent in dependents:
+            dependencies = self._any_dependency_to_module_other_than(
+                dependent, dependent_upons
+            )
+            result[dependent] = dependencies
+
+        return result
+
+    def _listify(self, module: Union[Module, List[Module]]) -> List[Module]:
+        return [module] if not isinstance(module, list) else module
+
+    def _any_dependency_to_module_other_than(  # noqa: C901
+        self, dependent: Module, dependent_upons: List[Module]
+    ) -> List[StrictDependency]:
+        nodes_to_exclude = set()
+        for dependent_upon in dependent_upons:
+            nodes_to_exclude.update(self._get_all_submodules_of(dependent_upon))
 
         nodes_fulfilling_criteria = []
         # should submodules of the dependent module import each other, this does not count as a dependency
@@ -65,18 +111,16 @@ def any_dependency_to_module_other_than(
             # true submodules should be considered
             nodes_to_exclude.add(dependent.parent_module)
 
-        if dependent_upon.parent_module is not None:
-            # if there is a dependency to the parent module of dependent upon, this counts, as only dependencies to
-            # the true submodules are excluded
-            nodes_to_exclude.remove(dependent_upon.parent_module)
+        for dependent_upon in dependent_upons:
+            if dependent_upon.parent_module is not None:
+                # if there is a dependency to the parent module of dependent upon, this counts, as only dependencies to
+                # the true submodules are excluded
+                nodes_to_exclude.remove(dependent_upon.parent_module)
 
         nodes_to_check = list(nodes_that_do_not_fulfill_criterion)
         checked_nodes = set()
 
         while nodes_to_check:
-            if nodes_fulfilling_criteria:
-                break
-
             node = nodes_to_check.pop()
 
             if node in checked_nodes:
@@ -96,17 +140,40 @@ def any_dependency_to_module_other_than(
                         child not in nodes_to_exclude
                         and child not in nodes_that_do_not_fulfill_criterion
                     ):
-                        nodes_fulfilling_criteria.append(child)
-
-                    nodes_to_check.append(child)
+                        nodes_fulfilling_criteria.append(
+                            tuple(self._to_modules([node, child]))
+                        )
+                    else:
+                        nodes_to_check.append(child)
+
+        return nodes_fulfilling_criteria  # type: ignore
+
+    def any_other_dependencies_to_modules_than(
+        self,
+        dependents: Union[Module, List[Module]],
+        dependent_upons: Union[Module, List[Module]],
+    ) -> LaxDependenciesByBaseModule:
+        result = {}
+
+        dependents = self._listify(dependents)
+        dependent_upons = self._listify(dependent_upons)
+
+        for dependent_upon in dependent_upons:
+            dependencies = self._any_other_dependency_to_module_than(
+                dependents, dependent_upon
+            )
+            result[dependent_upon] = dependencies
 
-        return self._to_modules(nodes_fulfilling_criteria)
+        return result
 
-    def any_other_dependency_to_module_than(
-        self, dependent: Module, dependent_upon: Module
-    ) -> List[Module]:
+    def _any_other_dependency_to_module_than(  # noqa: C901
+        self, dependents: List[Module], dependent_upon: Module
+    ) -> List[StrictDependency]:
+        # submodules of the dependent upon module do not count as an import that is not the dependent upon module
         # submodules of and including the dependent do not count as allowed imports
-        nodes_to_exclude = self._get_all_submodules_of(dependent)
+        nodes_to_exclude = set()
+        for dependent in dependents:
+            nodes_to_exclude.update(self._get_all_submodules_of(dependent))
 
         nodes_fulfilling_criteria = []
         # submodules of the dependent upon do not count as allowed imports if they should import each other
@@ -123,9 +190,10 @@ def any_other_dependency_to_module_than(
 
         nodes_to_check = list(nodes_that_count_as_not_fulfilling_criterion)
 
-        if dependent.parent_module is not None:
-            # if the dependent module is defined via a parent module, this parent module counts as an allowed import
-            nodes_to_exclude.remove(dependent.parent_module)
+        for dependent in dependents:
+            if dependent.parent_module is not None:
+                # if the dependent module is defined via a parent module, this parent module counts as an allowed import
+                nodes_to_exclude.remove(dependent.parent_module)
 
         checked_nodes = set()
 
@@ -146,17 +214,19 @@ def any_other_dependency_to_module_than(
                         parent not in nodes_to_exclude
                         and parent not in nodes_that_count_as_not_fulfilling_criterion
                     ):
-                        nodes_fulfilling_criteria.append(parent)
+                        nodes_fulfilling_criteria.append(
+                            tuple(self._to_modules([parent, node]))
+                        )
 
                     if parent not in nodes_to_exclude:
                         nodes_to_check.append(parent)
 
-        return self._to_modules(nodes_fulfilling_criteria)
+        return nodes_fulfilling_criteria  # type: ignore
 
-    def _to_modules(self, nodes: List[Node]) -> List[Module]:
+    def _to_modules(self, nodes: List[AbstractNode]) -> List[Module]:
         return list(map(lambda node: Module(name=node), nodes))
 
-    def _get_all_submodules_of(self, module: Module) -> Set[Node]:
+    def _get_all_submodules_of(self, module: Module) -> Set[AbstractNode]:
         """Returns all submodules of a given module.
 
         Args:
@@ -192,6 +262,6 @@ def _get_all_submodules_of(self, module: Module) -> Set[Node]:
     def visualize(self, **kwargs: Any) -> None:
         self._graph.draw(**kwargs)
 
-    def _get_excluded_nodes(self, modules: List[Module]) -> List[Node]:
+    def _get_excluded_nodes(self, modules: List[Module]) -> List[AbstractNode]:
         result = [module.parent_module for module in modules]
         return [node for node in result if node is not None]
diff --git a/src/pytestarch/eval_structure/evaluable_structures.py b/src/pytestarch/eval_structure/evaluable_structures.py
new file mode 100644
index 0000000..26dde60
--- /dev/null
+++ b/src/pytestarch/eval_structure/evaluable_structures.py
@@ -0,0 +1,20 @@
+from abc import ABC, abstractmethod
+from typing import List
+
+AbstractNode = str
+
+
+class AbstractGraph(ABC):
+    @abstractmethod
+    def direct_successor_nodes(self, node: AbstractNode) -> List[AbstractNode]:
+        raise NotImplementedError()
+
+    @abstractmethod
+    def parent_child_relationship(
+        self, parent: AbstractNode, child: AbstractNode
+    ) -> bool:
+        raise NotImplementedError()
+
+    @abstractmethod
+    def direct_predecessor_nodes(self, node: AbstractNode) -> List[AbstractNode]:
+        raise NotImplementedError()
diff --git a/src/pytestarch/eval_structure_impl/__init__.py b/src/pytestarch/eval_structure_impl/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/pytestarch/eval_structure/graph.py b/src/pytestarch/eval_structure_impl/networkxgraph.py
similarity index 96%
rename from src/pytestarch/eval_structure/graph.py
rename to src/pytestarch/eval_structure_impl/networkxgraph.py
index 863e6f1..bd518ea 100644
--- a/src/pytestarch/eval_structure/graph.py
+++ b/src/pytestarch/eval_structure_impl/networkxgraph.py
@@ -1,18 +1,19 @@
 """Encapsulation of networkx graph functionality."""
-from typing import Any, List, Optional
+from typing import Any, List, Optional, Tuple
 
 import networkx as nx
 from networkx import draw_networkx, has_path, spring_layout
 
+from pytestarch.eval_structure.evaluable_structures import AbstractGraph, AbstractNode
 from pytestarch.importer.import_types import Import
 
 EXPECTED_EDGE_AND_NODE_TYPES = "Only str and tuple of two str supported."
 
 
-Node = str
+Node = AbstractNode
 
 
-class Graph:
+class NetworkxGraph(AbstractGraph):
     """Constructs eval_structure from list of imports.
 
     Each module passed to this object will be added as a node.
@@ -154,6 +155,10 @@ def nodes_number(self) -> int:
     def nodes(self) -> List[Node]:
         return list(self._graph.nodes)
 
+    @property
+    def edges(self) -> List[Tuple[Node, Node]]:
+        return list(self._graph.edges)
+
     def direct_predecessor_nodes(self, node: Node) -> List[Node]:
         """Returns all nodes that have a directed edge towards the given node.
 
diff --git a/src/pytestarch/query_language/exceptions.py b/src/pytestarch/exceptions.py
similarity index 100%
rename from src/pytestarch/query_language/exceptions.py
rename to src/pytestarch/exceptions.py
diff --git a/src/pytestarch/importer/import_types.py b/src/pytestarch/importer/import_types.py
index d284963..5699d23 100644
--- a/src/pytestarch/importer/import_types.py
+++ b/src/pytestarch/importer/import_types.py
@@ -3,7 +3,7 @@
 from dataclasses import dataclass
 from typing import List, Optional
 
-from pytestarch.query_language.exceptions import ImproperlyConfigured
+from pytestarch.exceptions import ImproperlyConfigured
 
 
 @dataclass
diff --git a/src/pytestarch/pytestarch.py b/src/pytestarch/pytestarch.py
index 0f80b80..4f1ab9b 100755
--- a/src/pytestarch/pytestarch.py
+++ b/src/pytestarch/pytestarch.py
@@ -8,9 +8,9 @@
 from typing import Optional, Tuple
 
 from pytestarch.config.config import Config
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture
+from pytestarch.eval_structure.evaluable_architecture import EvaluableArchitecture
 from pytestarch.eval_structure.evaluable_graph import EvaluableArchitectureGraph
-from pytestarch.eval_structure.graph import Graph
+from pytestarch.eval_structure_impl.networkxgraph import NetworkxGraph
 from pytestarch.importer.converter import ImportConverter
 from pytestarch.importer.file_filter import FileFilter
 from pytestarch.importer.import_filter import ImportFilter
@@ -74,7 +74,7 @@ def get_evaluable_architecture(
             imports,
             all_modules,
         )
-    return EvaluableArchitectureGraph(Graph(all_modules, imports, level_limit))
+    return EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports, level_limit))
 
 
 def get_evaluable_architecture_for_module_objects(
diff --git a/src/pytestarch/query_language/LANGUAGE_DEFINTION.md b/src/pytestarch/query_language/LANGUAGE_DEFINTION.md
index cb3ccb0..9b2cb89 100644
--- a/src/pytestarch/query_language/LANGUAGE_DEFINTION.md
+++ b/src/pytestarch/query_language/LANGUAGE_DEFINTION.md
@@ -53,6 +53,10 @@ M1 should only be_imported_from except M2   -> any edge from non-M2 to M1, neg(e
 M1 should not be_imported_from except M2    -> neg(any edge from non-M2 to M1)
 
 
+If M2 contains multiple modules, they can be treated separately for "edge", but need to be considered jointly for 
+"any edge".
+
+
 
 Operations: 
     any edge
@@ -72,3 +76,53 @@ simplified:
     edge        should, should only
     neg edge    should not, should only except
     neg any     should not except, should only
+
+
+# Rule Violation Messages
+### One M2
+| type                                        | message                                                                                                  | note                   |
+|---------------------------------------------|----------------------------------------------------------------------------------------------------------|------------------------|
+| should import                               | M1 does not import M2                                                                                    | -                      |
+| should only import -- forbidden             | Violator1 imports Violator2. Subviolator1 imports Violator3                                              | (submodule of M1)      |
+| should only import -- no import             | M1 does not import M2                                                                                    | -                      |
+| should only import -- both                  | Violator1 imports Violator2. Subviolator1 imports Violator3. M1 does not import M2                       | (submodule of M1)      |
+| should not import                           | Violator1 imports Violator2. Subviolator1 imports Subviolator2.                                          | (submodules of M1, M2) |
+| should import except                        | M1 does not import any that is not M2                                                                    | -                      |
+| should only import except -- forbidden      | Violator1 imports Violator2. Subviolator1 imports Subviolator2.                                          | (submodules of M1, M2) |
+| should only import except -- no import      | M1 does not import any that is not M2                                                                    | -                      |
+| should only import except -- both           | Violator1 imports Violator2. Subviolator1 imports Subviolator2. M1 does not import any that is not M2    | (submodules of M1, M2) |
+| should not import except                    | Violator1 imports Violator2. Subviolator1 imports Violator3.                                             | (submodule of M1)      |
+| should be imported                          | M1 is not imported by M2                                                                                 | -                      |
+| should only be imported -- forbidden        | Violator2 imports Violator1. Violator3 imports Subviolator1                                              | (submodule of M1)      |
+| should only be imported -- no import        | M1 is not imported by M2                                                                                 | -                      |
+| should only be imported -- both             | Violator2 imports Violator1. Violator3 imports Subviolator1. M1 is not imported by M2                    | (submodule of M1)      |
+| should not be imported                      | Violator2 imports Violator1. Violator 3 imports Subviolator1.                                            | (submodules of M1, M2) |
+| should be imported except                   | M1 is not imported by any that is not M2                                                                 | -                      |
+| should only be imported except -- forbidden | Violator2 imports Violator1. Subviolator2 imports Subviolator1.                                          | (submodules of M1, M2) |
+| should only be imported except -- no import | M1 is not imported by any that is not M2                                                                 | -                      |
+| should only be imported except -- both      | Violator2 imports Violator1. Subviolator2 imports Subviolator1. M1 is not imported by any that is not M2 | (submodules of M1, M2) |
+| should not be imported except               | Violator2 imports Violator1. Violator3 imports Subviolator1.                                             | (submodule of M1)      |
+
+
+### Multiple M2s
+| type                                        | message                                                                                                                                      | note                                                | diff to One M2 |
+|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|---------------|
+| should import                               | M1 does not import M2, M3                                                                                                                    | concatentation with ","                             | list all M2s  |
+| should only import -- forbidden             | Violator1 imports Violator2. Subviolator1 imports Violator3.                                                                                 | (submodule of M1)                                   | -             |
+| should only import -- no import             | M1 does not import M2(, M3)                                                                                                                  | concatentation with ","                             | -             |
+| should only import -- both                  | Violator1 imports Violator2. Subviolator1 imports Violator3. M1 does not import M2(, M3)                                                     | (submodule of M1); concatentation with ","          | -             |
+| should not import                           | Violator1 imports Violator2. Subviolator1 imports Subviolator2. Subviolator1 imports Subviolator2. Subviolator1 imports Violator3            | (submodules of M1, M2, M3); concatentation with "," | -             |
+| should import except                        | M1 does not import any that is not M2, M3                                                                                                    | concatentation with ","                             | list all M2s  |
+| should only import except -- forbidden      | Violator1 imports Violator2. Subviolator1 imports Subviolator2. Subviolator1 imports Violator3                                               | (submodules of M1, M2, M3); concatentation with "," | -             ||
+| should only import except -- no import      | M1 does not import any that is not M2, M3                                                                                                    | concatentation with ","                             | list all M2s  |
+| should only import except -- both           | Violator1 imports Violator2. Subviolator1 imports Subviolator2. Subviolator1 imports Violator3 . M1 does not import any that is not M2, M3   | (submodules of M1, M2, M3); concatentation with "," | list all M2s  |
+| should not import except                    | Violator1 imports Violator2. Subviolator1 imports Subviolator2. Subviolator1 imports Violator3                                               | (submodule of M1)                                   | -             |
+| should be imported                          | M1 is not imported by M2, M3                                                                                                                 | concatentation with ","                             | list all M2s  |
+| should only be imported -- forbidden        | Violator2 imports Violator1. Violator2 imports Subviolator1                                                                                  | (submodule of M1)                                   | -             |
+| should only be imported -- no import        | M1 is not imported by M2, M3                                                                                                                 | concatentation with ","                             | -             |
+| should not be imported                      | Violator2 imports Violator1. Violator3 imports Subviolator1.                                                                                 | (submodules of M1, M2, M3)                          | -             |
+| should be imported except                   | M1 is not imported by any that is not M2, M3                                                                                                 | concatentation with ","                             | list all M2s  |
+| should only be imported except -- forbidden | Violator2 imports Violator1. Subviolator2 imports Subviolator1. Violator3 imports Subviolator1.                                              | (submodules of M1, M2, M3)                          | -             |
+| should only be imported except -- no import | M1 is not imported by any that is not M2, M3                                                                                                 | concatentation with ","                             | list all M2s  |
+| should only be imported except -- both      | Violator2 imports Violator1. Subviolator2 imports Subviolator1. Violator3 imports Subviolator1. M1 is not imported by any that is not M2, M3 | (submodules of M1, M2, M3); concatentation with "," | list all M2s  |
+| should not be imported except               | Violator2 imports Violator1. Violator3 imports Subviolator1                                                                                  | (submodule of M1)                                   | -             |
diff --git a/src/pytestarch/query_language/base_language.py b/src/pytestarch/query_language/base_language.py
index d0ab139..126ae0a 100644
--- a/src/pytestarch/query_language/base_language.py
+++ b/src/pytestarch/query_language/base_language.py
@@ -1,12 +1,15 @@
 from __future__ import annotations
 
+import os
 from abc import ABC, abstractmethod
-from collections import defaultdict
-from typing import Generic, Optional, Type, TypeVar
+from typing import Callable, Generic, List, Optional, Type, TypeVar, Union
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture, Module
-from pytestarch.eval_structure.graph import Node
-from pytestarch.query_language.exceptions import ImproperlyConfigured
+from pytestarch.eval_structure.evaluable_architecture import (
+    EvaluableArchitecture,
+    Module,
+)
+from pytestarch.exceptions import ImproperlyConfigured
+from pytestarch.query_language.message_generator import RuleViolationMessageGenerator
 from pytestarch.query_language.rule_matcher import (
     BehaviorRequirement,
     DefaultRuleMatcher,
@@ -15,19 +18,6 @@
     RuleViolations,
 )
 
-# (Import, negated, singular)
-PREFIX_MAPPING = defaultdict(str)
-PREFIX_MAPPING.update(
-    {
-        (False, False, True): "is ",
-        (False, True, True): "is not ",
-        (True, True, True): "does not ",
-        (False, False, False): "are ",
-        (False, True, False): "are not ",
-        (True, True, False): "do not ",
-    }
-)
-
 
 class RuleApplier(ABC):
     @abstractmethod
@@ -71,11 +61,13 @@ class ModuleSpecification(Generic[ModuleSpecificationSuccessor], ABC):
     """Offers functionality to specify detail information about Rule Subjects or Objects."""
 
     @abstractmethod
-    def are_sub_modules_of(self, modules: Node) -> ModuleSpecificationSuccessor:
+    def are_sub_modules_of(
+        self, modules: Union[str, List[str]]
+    ) -> ModuleSpecificationSuccessor:
         pass
 
     @abstractmethod
-    def are_named(self, name: str) -> ModuleSpecificationSuccessor:
+    def are_named(self, names: Union[str, List[str]]) -> ModuleSpecificationSuccessor:
         pass
 
 
@@ -133,7 +125,7 @@ def __init__(
         self._rule_matcher_class = rule_matcher_class
 
         self._module_to_check: Optional[Module] = None
-        self._module_to_check_against: Optional[Module] = None
+        self._modules_to_check_against: Optional[List[Module]] = None
 
         self._should = False
         self._should_only = False
@@ -153,31 +145,38 @@ def modules_that(self) -> RuleSubject:
         self._modules_to_check_to_be_specified_next = True
         return self
 
-    def are_sub_modules_of(self, module: Node) -> BehaviorSpecification:
-        if self._modules_to_check_to_be_specified_next is None:
-            raise ImproperlyConfigured("Specify a RuleSubject or RuleObject first.")
-
-        module = Module(parent_module=module)
-
-        if self._modules_to_check_to_be_specified_next:
-            self._module_to_check = module
-        else:
-            self._module_to_check_against = module
+    def are_sub_modules_of(
+        self, modules: Union[str, List[str]]
+    ) -> BehaviorSpecification:
+        self._set_modules(modules, lambda name: Module(parent_module=name))
+        return self
 
+    def are_named(self, names: Union[str, List[str]]) -> BehaviorSpecification:
+        self._set_modules(names, lambda name: Module(name=name))
         return self
 
-    def are_named(self, name: str) -> BehaviorSpecification:
+    def _set_modules(
+        self,
+        module_names: Union[str, List[str]],
+        create_module_fn: Callable[[str], Module],
+    ) -> None:
         if self._modules_to_check_to_be_specified_next is None:
             raise ImproperlyConfigured("Specify a RuleSubject or RuleObject first.")
 
-        module = Module(name=name)
+        if self._modules_to_check_to_be_specified_next and isinstance(
+            module_names, list
+        ):
+            raise ImproperlyConfigured("Only rule subjects can be specified in batch.")
+
+        if not self._modules_to_check_to_be_specified_next and isinstance(
+            module_names, str
+        ):
+            module_names = [module_names]
 
         if self._modules_to_check_to_be_specified_next:
-            self._module_to_check = module
+            self._module_to_check = create_module_fn(module_names)  # type: ignore
         else:
-            self._module_to_check_against = module
-
-        return self
+            self._modules_to_check_against = [create_module_fn(n) for n in module_names]
 
     def should(self) -> DependencySpecification:
         self._should = True
@@ -216,8 +215,15 @@ def be_imported_by_modules_except_modules_that(self) -> RuleObject:
     def assert_applies(self, evaluable: EvaluableArchitecture) -> None:
         self._assert_required_configuration_present()
 
+        matcher = self._prepare_rule_matcher()
+        rule_violations = matcher.match(evaluable)
+
+        if rule_violations:
+            raise AssertionError(self._create_rule_violation_message(rule_violations))
+
+    def _prepare_rule_matcher(self) -> RuleMatcher:
         module_requirement = ModuleRequirement(
-            self._module_to_check, self._module_to_check_against, self._import
+            self._module_to_check, self._modules_to_check_against, self._import
         )
         behavior_requirement = BehaviorRequirement(
             self._should,
@@ -226,23 +232,23 @@ def assert_applies(self, evaluable: EvaluableArchitecture) -> None:
             self._except_present,
         )
 
-        rule_violations = self._rule_matcher_class(
-            module_requirement, behavior_requirement
-        ).match(evaluable)
-
-        if rule_violations:
-            raise AssertionError(self._create_rule_violation_message(rule_violations))
+        return self._rule_matcher_class(module_requirement, behavior_requirement)
 
     def __str__(self) -> str:
+        self._assert_required_configuration_present()
+
         method_name = f'{"should" if self._should else "should only" if self._should_only else "should not"}'
 
+        subject_prefix = (
+            "Sub modules of " if self._module_to_check.parent_module is not None else ""
+        )
         return (
-            f"{self._add_rule_subject_plural_marker_if_needed(self._get_module_name(self._module_to_check))} "
+            f'{subject_prefix}"{self._get_module_name(self._module_to_check)}" '
             f"{method_name} "
             f'{"import" if self._import else "be imported by"} '
             f'{"modules except " if self._except_present else ""}'
-            f'modules that are {"named" if self._module_to_check_against.name is not None else "sub modules of"} '
-            f'"{self._get_module_name(self._module_to_check_against)}".'
+            f'modules that are {"named" if self._modules_to_check_against[0].name is not None else "sub modules of"} '
+            f'"{", ".join(map(lambda module: self._get_module_name(module), self._modules_to_check_against))}".'
         )
 
     def _get_module_name(self, module: Module) -> str:
@@ -252,7 +258,7 @@ def _assert_required_configuration_present(self) -> None:
         behavior_missing = not any([self._should, self._should_only, self._should_not])
         dependency_missing = self._import is None
         subject_missing = not self._module_to_check
-        object_missing = not self._module_to_check_against
+        object_missing = not self._modules_to_check_against
 
         if any([behavior_missing, dependency_missing, subject_missing, object_missing]):
             subject_message = self._name_or_empty(subject_missing, RuleSubject)
@@ -282,128 +288,12 @@ def _name_or_empty(cls, empty: bool, clz: type) -> str:
         return f"a {clz.__name__}" if empty else ""
 
     def _create_rule_violation_message(self, rule_violations: RuleViolations) -> str:
-        rule_subject = self._create_rule_subject_for_rule_violation_message(
-            rule_violations
+        message_generator = RuleViolationMessageGenerator(
+            self._module_to_check,
+            self._modules_to_check_against,
+            self._import,
         )
-        verb = self._create_verb_for_rule_violation_message(rule_violations)
-        rule_object = self._create_rule_object_for_rule_violation_message(
+        single_violation_messages = message_generator.create_rule_violation_messages(
             rule_violations
         )
-        return f"{rule_subject} {verb}{rule_object}."
-
-    def _create_rule_subject_for_rule_violation_message(
-        self, rule_violations: RuleViolations
-    ) -> str:
-        module_name = self._get_module_name(self._module_to_check)
-
-        if self._forbidden_import(rule_violations):
-            if rule_violations.strict_dependency is not None:
-                module_name = self._get_subject_of_strict_dependency(rule_violations)
-
-                # no plural marker needed, as module is always singular
-                return f'"{module_name}"'
-
-        return self._add_rule_subject_plural_marker_if_needed(module_name)
-
-    def _get_subject_of_strict_dependency(self, rule_violations: RuleViolations) -> str:
-        # strict dependency is always reported in format (importer, importee)
-        # if rule is of format A ... imports B, the dependency will be (A, B)
-        idx = int(not self._import)
-        return rule_violations.strict_dependency[idx]
-
-    def _get_object_of_strict_dependency(self, rule_violations: RuleViolations) -> str:
-        # strict dependency is always reported in format (importer, importee)
-        # if rule is of format A ... be imported by B, the dependency will be (B, A)
-        # but in our rule, B is the object
-        idx = int(self._import)
-        return rule_violations.strict_dependency[idx]
-
-    def _add_rule_subject_plural_marker_if_needed(self, module_name: str) -> str:
-        prefix = ""
-        if not self._rule_subject_singular:
-            prefix = "Sub modules of "
-        return f'{prefix}"{module_name}"'
-
-    def _create_verb_for_rule_violation_message(
-        self, rule_violations: RuleViolations
-    ) -> str:
-        base_verb = "import" if self._import else "imported by"
-
-        suffix = " "
-
-        if rule_violations.should_violated:
-            negated = True
-
-        elif (
-            rule_violations.should_except_violated
-            or rule_violations.should_only_except_violated_by_no_import
-        ):
-            negated = True
-
-        elif (
-            rule_violations.should_only_except_violated_by_forbidden_import
-            or rule_violations.should_not_violated
-            or rule_violations.should_only_violated
-            or rule_violations.should_not_except_violated
-        ):
-
-            if self._import and (
-                self._rule_subject_singular or (self._forbidden_import(rule_violations))
-            ):
-                suffix = "s "
-
-            negated = False
-        else:
-            raise Exception("Unknown rule violation detected.")
-
-        prefix = PREFIX_MAPPING[
-            (
-                self._import,
-                negated,
-                self._rule_subject_singular
-                or (self._forbidden_import(rule_violations)),
-            )
-        ]
-
-        return f"{prefix}{base_verb}{suffix}"
-
-    def _forbidden_import(self, rule_violations: RuleViolations) -> bool:
-        return (
-            rule_violations.should_not_violated
-            or rule_violations.should_only_except_violated_by_forbidden_import
-        )
-
-    @property
-    def _rule_subject_singular(self) -> bool:
-        return self._module_to_check.name is not None
-
-    @property
-    def _rule_object_singular(self) -> bool:
-        return self._module_to_check_against.name is not None
-
-    def _create_rule_object_for_rule_violation_message(
-        self, rule_violations: RuleViolations
-    ) -> str:
-        object_name = self._get_module_name(self._module_to_check_against)
-        module_qualifier = "" if self._rule_object_singular else "a sub module of "
-
-        if (
-            rule_violations.should_except_violated
-            or rule_violations.should_only_except_violated_by_no_import
-        ):
-            module_qualifier = f"any module that is not {'' if self._rule_object_singular else 'a sub module of '}"
-        elif self._forbidden_import(rule_violations):
-            module_qualifier = ""
-
-            if rule_violations.strict_dependency is not None:
-                object_name = self._get_object_of_strict_dependency(rule_violations)
-
-        if (
-            rule_violations.should_not_except_violated
-            or rule_violations.should_only_violated
-        ):
-            object_name = ", ".join(
-                (module.name for module in rule_violations.lax_dependencies_found)
-            )
-
-        return f'{module_qualifier}"{object_name}"'
+        return os.sep.join(single_violation_messages)
diff --git a/src/pytestarch/query_language/message_generator.py b/src/pytestarch/query_language/message_generator.py
new file mode 100644
index 0000000..5fe3890
--- /dev/null
+++ b/src/pytestarch/query_language/message_generator.py
@@ -0,0 +1,381 @@
+"""Converts a RuleViolations object into a human-readable format."""
+
+from collections import defaultdict
+from dataclasses import dataclass
+from typing import Iterable, List, Optional, Tuple
+
+from pytestarch.eval_structure.evaluable_architecture import Module, StrictDependency
+from pytestarch.query_language.rule_matcher import RuleViolations
+
+IMPORT = "import"
+IMPORTED_BY = "imported by"
+
+THIRD_PERSON_SINGULAR = "s"
+
+RULE_OBJECT_IS_SUBMODULE_MARKER = "a sub module of "
+ANY_MODULE_THAT_IS_NOT = "any module that is not "
+
+
+@dataclass
+class RuleViolatedMessage:
+    rule_subject: str
+    rule_verb: str
+    rule_object: str
+
+
+# (Import, negated, subject singular)
+PREFIX_MAPPING = defaultdict(str)
+PREFIX_MAPPING.update(
+    {
+        (False, False, True): "is ",
+        (False, True, True): "is not ",
+        (True, True, True): "does not ",
+        (False, False, False): "are ",
+        (False, True, False): "are not ",
+        (True, True, False): "do not ",
+    }
+)
+
+
+class RuleViolationMessageGenerator:
+    """Generates a message for each violated rule."""
+
+    def __init__(
+        self,
+        original_rule_subject: Module,
+        original_rule_objects: List[Module],
+        import_rule: bool,
+    ) -> None:
+        """
+        Args:
+            original_rule_subject: module as originally specified
+            original_rule_objects: modules as originally specified
+            import_rule: True if the underlying rule is a "import" instead of an "is imported" rule
+        """
+        self._original_rule_subject_name = self._generate_rule_subject_name(
+            original_rule_subject
+        )
+        self._original_rule_subject_singular = self._is_singular_module(
+            original_rule_subject
+        )
+
+        (
+            self._original_rule_object_names,
+            self._original_rule_object_singular,
+        ) = self._generate_rule_object_names_and_count(original_rule_objects)
+
+        self._import_rule = import_rule
+
+        self._base_verb = IMPORT if self._import_rule else IMPORTED_BY
+
+    def create_rule_violation_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[str]:
+        """Create a message about each rule violation.
+
+        Args:
+            rule_violations: to convert to human-readable format
+
+        Returns:
+            list of messages, each representing a rule violation
+        """
+        messages = []
+        for message in self._create_violation_messages(rule_violations):
+            messages.append(
+                f"{message.rule_subject} {message.rule_verb} {message.rule_object}."
+            )
+
+        return sorted(messages)
+
+    def _create_violation_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        messages = []
+
+        self._extend(
+            messages, self._create_should_import_violated_messages(rule_violations)
+        )
+        self._extend(
+            messages, self._create_should_only_import_violated_messages(rule_violations)
+        )
+        self._extend(
+            messages, self._create_should_not_import_violated_messages(rule_violations)
+        )
+        self._extend(
+            messages,
+            self._create_should_import_except_violated_messages(rule_violations),
+        )
+        self._extend(
+            messages,
+            self._create_should_only_import_except_violated_messages(rule_violations),
+        )
+        self._extend(
+            messages,
+            self._create_should_not_import_except_violated_messages(rule_violations),
+        )
+
+        return messages
+
+    def _extend(
+        self,
+        messages: List[RuleViolatedMessage],
+        new_messages: Optional[List[RuleViolatedMessage]],
+    ) -> None:
+        if new_messages is not None:
+            messages.extend(new_messages)
+
+    def _create_should_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_violated:
+            return []
+
+        return self._create_no_import_between_original_subject_and_objects_message()
+
+    def _create_no_import_between_original_subject_and_objects_message(
+        self,
+    ) -> List[RuleViolatedMessage]:
+        messages = []
+
+        rule_subject = self._original_rule_subject_name
+
+        verb_prefix = self._get_verb_prefix(
+            negated=True, subject_singular=self._original_rule_subject_singular
+        )
+        rule_verb = self._concatenate_verb(prefix=verb_prefix, verb=self._base_verb)
+
+        rule_objects = []
+
+        for rule_object, is_singular in zip(
+            self._original_rule_object_names, self._original_rule_object_singular
+        ):
+            rule_objects.append(self._get_rule_object(rule_object, is_singular))
+
+        combined_rule_object = ", ".join(rule_objects)
+        messages.append(
+            RuleViolatedMessage(rule_subject, rule_verb, combined_rule_object)
+        )
+
+        return messages
+
+    def _create_should_only_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        messages = []
+
+        self._extend(
+            messages,
+            self._create_should_only_import_forbidden_import_violated_messages(
+                rule_violations
+            ),
+        )
+        self._extend(
+            messages,
+            self._create_should_only_import_no_import_violated_messages(
+                rule_violations
+            ),
+        )
+
+        return messages
+
+    def _create_should_only_import_forbidden_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_only_violated_by_forbidden_import:
+            return []
+
+        violating_dependencies = rule_violations.lax_dependencies.values()
+        return self._create_other_violating_dependencies_message(violating_dependencies)
+
+    def _create_other_violating_dependencies_message(
+        self,
+        violating_dependencies: Iterable[List[StrictDependency]],
+    ) -> List[RuleViolatedMessage]:
+        # messages are always of the type "module x imports module y"
+        messages = []
+
+        verb_prefix = self._get_verb_prefix(negated=False, subject_singular=True)
+        suffix = self._get_verb_suffix(subject_singular=True)
+        rule_verb = self._concatenate_verb(
+            prefix=verb_prefix, verb=self._base_verb, suffix=suffix
+        )
+
+        for strict_dependencies in violating_dependencies:
+            for strict_dependency in strict_dependencies:
+                (
+                    rule_subject,
+                    rule_object,
+                ) = self._get_rule_subject_and_object_of_strict_dependency(
+                    strict_dependency
+                )
+                messages.append(
+                    RuleViolatedMessage(rule_subject, rule_verb, rule_object)
+                )
+
+        return messages
+
+    def _create_should_only_import_no_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_only_violated_by_no_import:
+            return []
+
+        return self._create_no_import_between_original_subject_and_objects_message()
+
+    def _create_should_not_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_not_violated:
+            return []
+
+        violating_dependencies = rule_violations.strict_dependencies.values()
+        return self._create_other_violating_dependencies_message(violating_dependencies)
+
+    def _create_should_import_except_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_except_violated:
+            return []
+
+        return (
+            self._create_no_import_other_than_between_original_subject_and_objects_message()
+        )
+
+    def _create_no_import_other_than_between_original_subject_and_objects_message(
+        self,
+    ) -> List[RuleViolatedMessage]:
+        messages = []
+
+        rule_subject = self._original_rule_subject_name
+
+        verb_prefix = self._get_verb_prefix(
+            negated=True, subject_singular=self._original_rule_subject_singular
+        )
+        rule_verb = self._concatenate_verb(prefix=verb_prefix, verb=self._base_verb)
+
+        rule_objects = []
+
+        for rule_object, is_singular in zip(
+            self._original_rule_object_names, self._original_rule_object_singular
+        ):
+            rule_objects.append(
+                self._get_rule_object(rule_object, is_singular),
+            )
+
+        combined_rule_object = ANY_MODULE_THAT_IS_NOT + ", ".join(rule_objects)
+        messages.append(
+            RuleViolatedMessage(rule_subject, rule_verb, combined_rule_object)
+        )
+
+        return messages
+
+    def _create_should_only_import_except_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        messages = []
+
+        self._extend(
+            messages,
+            self._create_should_only_import_except_forbidden_import_violated_messages(
+                rule_violations
+            ),
+        )
+        self._extend(
+            messages,
+            self._create_should_only_import_except_no_import_violated_messages(
+                rule_violations
+            ),
+        )
+
+        return messages
+
+    def _create_should_only_import_except_forbidden_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_only_except_violated_by_forbidden_import:
+            return []
+
+        violating_dependencies = rule_violations.strict_dependencies.values()
+        return self._create_other_violating_dependencies_message(violating_dependencies)
+
+    def _create_should_only_import_except_no_import_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_only_except_violated_by_no_import:
+            return []
+
+        return (
+            self._create_no_import_other_than_between_original_subject_and_objects_message()
+        )
+
+    def _create_should_not_import_except_violated_messages(
+        self, rule_violations: RuleViolations
+    ) -> List[RuleViolatedMessage]:
+        if not rule_violations.should_not_except_violated:
+            return []
+
+        violating_dependencies = rule_violations.lax_dependencies.values()
+        return self._create_other_violating_dependencies_message(violating_dependencies)
+
+    def _generate_rule_subject_name(self, module: Module) -> str:
+        name = self._get_module_name(module)
+        prefix = ""
+        if not self._is_singular_module(module):
+            prefix = "Sub modules of "
+        return f"{prefix}{self._quoted_name(name)}"
+
+    def _generate_rule_object_names_and_count(
+        self, original_rule_objects: List[Module]
+    ) -> Tuple[List[str], List[bool]]:
+        names = []
+        is_singular = []
+
+        for rule_object in original_rule_objects:
+            names.append(self._get_module_name(rule_object))
+            is_singular.append(self._is_singular_module(rule_object))
+
+        return names, is_singular
+
+    def _get_module_name(self, module: Module) -> str:
+        return module.name if module.name is not None else module.parent_module
+
+    def _is_singular_module(self, module: Module) -> bool:
+        return module.name is not None
+
+    def _get_verb_prefix(self, negated: bool, subject_singular: bool) -> str:
+        return PREFIX_MAPPING[(self._import_rule, negated, subject_singular)]
+
+    def _concatenate_verb(self, verb: str, prefix: str = "", suffix="") -> str:
+        return f"{prefix}{verb}{suffix}"
+
+    def _get_rule_object(self, rule_object: str, is_singular: bool) -> str:
+        module_qualifier = "" if is_singular else RULE_OBJECT_IS_SUBMODULE_MARKER
+        return f"{module_qualifier}{self._quoted_name(rule_object)}"
+
+    def _quoted_name(self, name: str) -> str:
+        return f'"{name}"'
+
+    def _get_rule_subject_and_object_of_strict_dependency(
+        self, strict_dependency: StrictDependency
+    ) -> Tuple[str, str]:
+        # strict dependency is always reported in format (importer, importee)
+        # if rule is of format A ... imports B, the dependency will be (A, B)
+        if self._import_rule:
+            rule_subject, rule_object = strict_dependency
+        else:
+            rule_object, rule_subject = strict_dependency
+
+        rule_subject_name = self._get_module_name(rule_subject)
+        rule_object_name = self._get_module_name(rule_object)
+
+        return self._quoted_name(rule_subject_name), self._quoted_name(rule_object_name)
+
+    def _get_verb_suffix(self, subject_singular: bool) -> str:
+        if not subject_singular:
+            return ""
+
+        if self._import_rule:
+            return THIRD_PERSON_SINGULAR
+
+        # "is imported by" does not need an additional 's'
+        return ""
diff --git a/src/pytestarch/query_language/rule_matcher.py b/src/pytestarch/query_language/rule_matcher.py
index 2f9fad2..161edb6 100644
--- a/src/pytestarch/query_language/rule_matcher.py
+++ b/src/pytestarch/query_language/rule_matcher.py
@@ -1,59 +1,69 @@
 from abc import ABC, abstractmethod
 from dataclasses import dataclass, fields
-from typing import List, Optional, Set, Tuple
+from typing import List, Optional, Union
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture, Module
-from pytestarch.query_language.exceptions import ImproperlyConfigured
+from pytestarch.eval_structure.evaluable_architecture import (
+    EvaluableArchitecture,
+    LaxDependenciesByBaseModule,
+    Module,
+    StrictDependenciesByBaseModules,
+)
+from pytestarch.exceptions import ImproperlyConfigured
 
 
 class ModuleRequirement:
     """Stores information about which module is supposed to be checked against which module."""
 
     def __init__(
-        self, left_hand_module: Module, right_hand_module: Module, import_relation: bool
+        self,
+        left_hand_module: Module,
+        right_hand_modules: List[Module],
+        import_relation: bool,
     ) -> None:
-        self._left_hand_module = left_hand_module
-        self._right_hand_module = right_hand_module
+        self._left_hand_modules = left_hand_module
+        self._right_hand_modules = right_hand_modules
 
         self._right_hand_module_has_specifier = import_relation
 
         if self.left_hand_module_has_specifier:
-            self._left_hand_module, self._right_hand_module = (
-                self._right_hand_module,
-                self._left_hand_module,
+            self._left_hand_modules, self._right_hand_modules = (
+                self._right_hand_modules,
+                self._left_hand_modules,
             )
 
     @property
-    def left_hand_module(self) -> Module:
-        return self._left_hand_module
+    def left_hand_modules(self) -> Union[Module, List[Module]]:
+        return self._left_hand_modules
 
     @property
-    def right_hand_module(self) -> Module:
-        return self._right_hand_module
+    def right_hand_modules(self) -> Union[Module, List[Module]]:
+        return self._right_hand_modules
 
     @property
     def left_hand_module_has_specifier(self) -> bool:
+        # True if rule is of format "is imported by"
         return not self._right_hand_module_has_specifier
 
 
 @dataclass
 class RuleViolations:
-    lax_dependencies_found: List[Module]
-    strict_dependency: Optional[Tuple[str, str]]
+    lax_dependencies: Optional[LaxDependenciesByBaseModule]
+    strict_dependencies: Optional[StrictDependenciesByBaseModules]
 
-    should_not_except_violated: bool = False
-    should_only_except_violated_by_forbidden_import: bool = False
+    should_violated: bool = False
+    should_only_violated_by_forbidden_import: bool = False
+    should_only_violated_by_no_import: bool = False
     should_not_violated: bool = False
-    should_only_violated: bool = False
-    should_only_except_violated_by_no_import: bool = False
     should_except_violated: bool = False
-    should_violated: bool = False
+    should_only_except_violated_by_forbidden_import: bool = False
+    should_only_except_violated_by_no_import: bool = False
+    should_not_except_violated: bool = False
 
     def __bool__(self) -> bool:
-        return any(self.__dict__[a] for a in self._bool_types())
+        return any(self.__dict__[a] for a in self._bool_field_names())
 
     @classmethod
-    def _bool_types(cls) -> List[str]:
+    def _bool_field_names(cls) -> List[str]:
         return [field.name for field in fields(cls) if field.type == bool]
 
 
@@ -114,12 +124,15 @@ def _validate(self) -> None:
 
     def generate_rule_violation(
         self,
-        strict_dependency: Optional[Tuple[str, str]],
-        lax_dependencies: Set[Module],
+        strict_dependencies: Optional[StrictDependenciesByBaseModules],
+        lax_dependencies: Optional[LaxDependenciesByBaseModule],
+        import_rule: bool,
+        left_hand_side_module: Union[Module, List[Module]],
+        right_hand_side_module: Union[Module, List[Module]],
     ) -> RuleViolations:
         """Translate from the detected types of dependencies back to which behavior and dependency requirements are violated by them.
         Args:
-            strict_dependency: dependency between the two specified modules found
+            strict_dependencies: dependency between the two specified modules found, grouped by requested modules
             lax_dependencies: other dependencies found beside the two specified modules
         Returns:
             overview of all rule violations
@@ -136,26 +149,30 @@ def generate_rule_violation(
         should_except_expected = self._should and self._behavior_exception
         should_expected = self._should and not self._behavior_exception
 
-        lax_dependencies_present = (
-            lax_dependencies is not None and len(lax_dependencies) > 0
+        lax_dependencies_present = lax_dependencies is not None and any(
+            lax_dependency for lax_dependency in lax_dependencies.values()
+        )
+        strict_dependency_present = strict_dependencies is not None and any(
+            strict_dependency for strict_dependency in strict_dependencies.values()
         )
-        strict_dependency_present = strict_dependency is not None
 
         return RuleViolations(
             should_not_except_violated=should_not_except_expected
             and lax_dependencies_present,
             should_only_except_violated_by_forbidden_import=should_only_except_violated_by_forbidden_expected
-            and (strict_dependency_present and lax_dependencies_present),
+            and strict_dependency_present,
             should_not_violated=should_not_expected and strict_dependency_present,
-            should_only_violated=should_only_expected
-            and (not strict_dependency_present or lax_dependencies_present),
+            should_only_violated_by_no_import=should_only_expected
+            and any(dependency == [] for dependency in strict_dependencies.values()),
+            should_only_violated_by_forbidden_import=should_only_expected
+            and lax_dependencies_present,
             should_only_except_violated_by_no_import=should_only_except_violated_by_no_expected
             and not lax_dependencies_present,
             should_except_violated=should_except_expected
             and not lax_dependencies_present,
             should_violated=should_expected and not strict_dependency_present,
-            lax_dependencies_found=lax_dependencies,
-            strict_dependency=strict_dependency,
+            lax_dependencies=lax_dependencies,
+            strict_dependencies=strict_dependencies,
         )
 
 
@@ -185,39 +202,52 @@ def match(self, evaluable: EvaluableArchitecture) -> RuleViolations:
 
 class DefaultRuleMatcher(RuleMatcher):
     def match(self, evaluable: EvaluableArchitecture) -> RuleViolations:
-        strict_dependency_required = (
-            self._behavior_requirement.strict_dependency_required
-        )
-        lax_dependency_required = self._behavior_requirement.lax_dependency_required
-        no_strict_dependency_allowed = (
-            self._behavior_requirement.strict_dependency_not_allowed
-        )
-        no_lax_dependency_allowed = (
-            self._behavior_requirement.lax_dependency_not_allowed
-        )
+        strict_dependencies = self._get_strict_dependencies(evaluable)
 
-        left_hand_module = self._module_requirement.left_hand_module
-        right_hand_module = self._module_requirement.right_hand_module
+        lax_dependencies = self._get_lax_dependencies(evaluable)
 
-        strict_dependency = None
-        lax_dependencies = None
-
-        if strict_dependency_required or no_strict_dependency_allowed:
-            strict_dependency = evaluable.get_dependency(
-                left_hand_module, right_hand_module
-            )
+        return self._behavior_requirement.generate_rule_violation(
+            strict_dependencies,
+            lax_dependencies,
+            not self._module_requirement.left_hand_module_has_specifier,
+            self._module_requirement.left_hand_modules,
+            self._module_requirement.right_hand_modules,
+        )
 
-        if lax_dependency_required or no_lax_dependency_allowed:
+    def _get_lax_dependencies(
+        self, evaluable: EvaluableArchitecture
+    ) -> Optional[LaxDependenciesByBaseModule]:
+        if (
+            self._behavior_requirement.lax_dependency_required
+            or self._behavior_requirement.lax_dependency_not_allowed
+        ):
             if not self._module_requirement.left_hand_module_has_specifier:
-                dependency_check_method = evaluable.any_dependency_to_module_other_than
+                lax_dependency_check_method = (
+                    evaluable.any_dependencies_to_modules_other_than
+                )
             else:
-                dependency_check_method = evaluable.any_other_dependency_to_module_than
+                lax_dependency_check_method = (
+                    evaluable.any_other_dependencies_to_modules_than
+                )
 
-            lax_dependencies = dependency_check_method(
-                left_hand_module,
-                right_hand_module,
+            return lax_dependency_check_method(
+                self._module_requirement.left_hand_modules,
+                self._module_requirement.right_hand_modules,
             )
 
-        return self._behavior_requirement.generate_rule_violation(
-            strict_dependency, lax_dependencies
-        )
+        return None
+
+    def _get_strict_dependencies(
+        self,
+        evaluable: EvaluableArchitecture,
+    ) -> Optional[StrictDependenciesByBaseModules]:
+        if (
+            self._behavior_requirement.strict_dependency_required
+            or self._behavior_requirement.strict_dependency_not_allowed
+        ):
+            return evaluable.get_dependencies(
+                self._module_requirement.left_hand_modules,
+                self._module_requirement.right_hand_modules,
+            )
+
+        return None
diff --git a/tests/conftest.py b/tests/conftest.py
index b91f618..c0911f4 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@
 import pytest
 from resources.test_project import src
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture
+from pytestarch.eval_structure.evaluable_architecture import EvaluableArchitecture
 from pytestarch.pytestarch import (
     get_evaluable_architecture,
     get_evaluable_architecture_for_module_objects,
diff --git a/tests/eval_structure/test_evaluable_graph.py b/tests/eval_structure/test_evaluable_graph.py
index c8dc169..868a786 100644
--- a/tests/eval_structure/test_evaluable_graph.py
+++ b/tests/eval_structure/test_evaluable_graph.py
@@ -2,17 +2,26 @@
 
 import pytest
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture, Module
+from pytestarch.eval_structure.evaluable_architecture import (
+    EvaluableArchitecture,
+    Module,
+)
 from pytestarch.eval_structure.evaluable_graph import EvaluableArchitectureGraph
-from pytestarch.eval_structure.graph import Graph
+from pytestarch.eval_structure_impl.networkxgraph import NetworkxGraph
 from pytestarch.importer.import_types import AbsoluteImport
 
 MODULE_1 = "Module1"
+MODULE_OBJECT_1 = Module(name=MODULE_1)
 MODULE_2 = "Module2"
+MODULE_OBJECT_2 = Module(name=MODULE_2)
 MODULE_3 = "Module3"
+MODULE_OBJECT_3 = Module(name=MODULE_3)
 MODULE_4 = "Module4"
+MODULE_OBJECT_4 = Module(name=MODULE_4)
 MODULE_6 = "Module6"
+MODULE_OBJECT_6 = Module(name=MODULE_6)
 SUB_MODULE_OF_2 = "Module2.SubModule1"
+SUB_MODULE_OBJECT_2 = Module(name=SUB_MODULE_OF_2)
 SUB_MODULE_OF_1 = "Module1.SubModule1"
 
 MODULE_A = "E.A"  # submodule of E
@@ -35,7 +44,7 @@ def evaluable() -> EvaluableArchitecture:
         AbsoluteImport(MODULE_3, MODULE_6),
     ]
 
-    return EvaluableArchitectureGraph(Graph(all_modules, imports))
+    return EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
 
 @pytest.fixture(scope="module")
@@ -47,259 +56,301 @@ def submodule_evaluable() -> EvaluableArchitecture:
         AbsoluteImport(MODULE_C, MODULE_B),
     ]
 
-    return EvaluableArchitectureGraph(Graph(all_modules, imports))
+    return EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
 
 def test_a_depends_on_non_b(evaluable: EvaluableArchitecture) -> None:
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(name=MODULE_2)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(name=MODULE_3)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(name=MODULE_4)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_2), Module(name=MODULE_1)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_2), Module(name=MODULE_3)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_2), Module(name=MODULE_4)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_2), Module(name=SUB_MODULE_OF_2)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_2), Module(name=MODULE_6)
-    )
-
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_3), Module(name=MODULE_1)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_3), Module(name=MODULE_2)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_3), Module(name=MODULE_4)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_3), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_3), Module(name=MODULE_6)
-    )
-
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_4), Module(name=MODULE_1)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_4), Module(name=MODULE_2)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_4), Module(name=MODULE_3)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_4), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_4), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_1)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_2)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_3)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_4)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_6), Module(name=MODULE_1)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_6), Module(name=MODULE_2)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_6), Module(name=MODULE_3)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_6), Module(name=MODULE_4)
-    )
-    assert not evaluable.any_dependency_to_module_other_than(
-        Module(name=MODULE_6), Module(name=SUB_MODULE_OF_2)
-    )
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_2
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_3
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_4
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_1, SUB_MODULE_OBJECT_2
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_6
+    )[MODULE_OBJECT_1]
+
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_1
+    )[MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_3
+    )[MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_4
+    )[MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_2, SUB_MODULE_OBJECT_2
+    )[MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_6
+    )[MODULE_OBJECT_2]
+
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_1
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_2
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_4
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_3, SUB_MODULE_OBJECT_2
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_6
+    )[MODULE_OBJECT_3]
+
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_1
+    )[MODULE_OBJECT_4]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_2
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_3
+    )[MODULE_OBJECT_4]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_4, SUB_MODULE_OBJECT_2
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_6
+    )[MODULE_OBJECT_4]
+
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_1
+    )[SUB_MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_2
+    )[SUB_MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_3
+    )[SUB_MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_4
+    )[SUB_MODULE_OBJECT_2]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_6
+    )[SUB_MODULE_OBJECT_2]
+
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_1
+    )[MODULE_OBJECT_6]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_2
+    )[MODULE_OBJECT_6]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_3
+    )[MODULE_OBJECT_6]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_4
+    )[MODULE_OBJECT_6]
+    assert not evaluable.any_dependencies_to_modules_other_than(
+        MODULE_OBJECT_6, SUB_MODULE_OBJECT_2
+    )[MODULE_OBJECT_6]
 
 
 def test_non_a_depends_on_b(evaluable: EvaluableArchitecture) -> None:
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(name=MODULE_2)
-    )
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(name=MODULE_3)
-    )
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(name=MODULE_4)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_2), Module(name=MODULE_1)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_2), Module(name=MODULE_3)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_2), Module(name=MODULE_4)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_2), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_2), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_3), Module(name=MODULE_1)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_3), Module(name=MODULE_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_3), Module(name=MODULE_4)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_3), Module(name=SUB_MODULE_OF_2)
-    )
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_3), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_4), Module(name=MODULE_1)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_4), Module(name=MODULE_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_4), Module(name=MODULE_3)
-    )
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_4), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_4), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_1)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_3)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_4)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_6), Module(name=MODULE_1)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_6), Module(name=MODULE_2)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_6), Module(name=MODULE_3)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_6), Module(name=MODULE_4)
-    )
-    assert evaluable.any_other_dependency_to_module_than(
-        Module(name=MODULE_6), Module(name=SUB_MODULE_OF_2)
-    )
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_2
+    )[MODULE_OBJECT_2]
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_3
+    )[MODULE_OBJECT_3]
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_4
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_1, SUB_MODULE_OBJECT_2
+    )[SUB_MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_1, MODULE_OBJECT_6
+    )[MODULE_OBJECT_6]
+
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_1
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_3
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_4
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_2, SUB_MODULE_OBJECT_2
+    )[SUB_MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_2, MODULE_OBJECT_6
+    )[MODULE_OBJECT_6]
+
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_1
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_2
+    )[MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_4
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_3, SUB_MODULE_OBJECT_2
+    )[SUB_MODULE_OBJECT_2]
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_3, MODULE_OBJECT_6
+    )[MODULE_OBJECT_6]
+
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_1
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_2
+    )[MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_3
+    )[MODULE_OBJECT_3]
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_4, SUB_MODULE_OBJECT_2
+    )[SUB_MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_4, MODULE_OBJECT_6
+    )[MODULE_OBJECT_6]
+
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_1
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_2
+    )[MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_3
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_4
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        SUB_MODULE_OBJECT_2, MODULE_OBJECT_6
+    )[MODULE_OBJECT_6]
+
+    assert not evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_1
+    )[MODULE_OBJECT_1]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_2
+    )[MODULE_OBJECT_2]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_3
+    )[MODULE_OBJECT_3]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_6, MODULE_OBJECT_4
+    )[MODULE_OBJECT_4]
+    assert evaluable.any_other_dependencies_to_modules_than(
+        MODULE_OBJECT_6, SUB_MODULE_OBJECT_2
+    )[SUB_MODULE_OBJECT_2]
 
 
 def test_depends_on(evaluable: EvaluableArchitecture) -> None:
-    assert evaluable.get_dependency(Module(name=MODULE_1), Module(name=MODULE_2))
-    assert evaluable.get_dependency(Module(name=MODULE_1), Module(name=MODULE_3))
-    assert evaluable.get_dependency(Module(name=MODULE_1), Module(name=MODULE_4))
-    assert not evaluable.get_dependency(
-        Module(name=MODULE_1), Module(name=SUB_MODULE_OF_2)
-    )
-    assert not evaluable.get_dependency(Module(name=MODULE_1), Module(name=MODULE_6))
-
-    assert not evaluable.get_dependency(Module(name=MODULE_2), Module(name=MODULE_1))
-    assert not evaluable.get_dependency(Module(name=MODULE_2), Module(name=MODULE_3))
-    assert not evaluable.get_dependency(Module(name=MODULE_2), Module(name=MODULE_4))
-    assert not evaluable.get_dependency(
-        Module(name=MODULE_2), Module(name=SUB_MODULE_OF_2)
-    )
-    assert not evaluable.get_dependency(Module(name=MODULE_2), Module(name=MODULE_6))
-
-    assert not evaluable.get_dependency(Module(name=MODULE_3), Module(name=MODULE_1))
-    assert evaluable.get_dependency(Module(name=MODULE_3), Module(name=MODULE_2))
-    assert not evaluable.get_dependency(Module(name=MODULE_3), Module(name=MODULE_4))
-    assert not evaluable.get_dependency(
-        Module(name=MODULE_3), Module(name=SUB_MODULE_OF_2)
-    )
-    assert evaluable.get_dependency(Module(name=MODULE_3), Module(name=MODULE_6))
-
-    assert not evaluable.get_dependency(Module(name=MODULE_4), Module(name=MODULE_1))
-    assert evaluable.get_dependency(Module(name=MODULE_4), Module(name=MODULE_2))
-    assert not evaluable.get_dependency(Module(name=MODULE_4), Module(name=MODULE_3))
-    assert evaluable.get_dependency(Module(name=MODULE_4), Module(name=SUB_MODULE_OF_2))
-    assert not evaluable.get_dependency(Module(name=MODULE_4), Module(name=MODULE_6))
-
-    assert not evaluable.get_dependency(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_1)
-    )
-    assert not evaluable.get_dependency(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_2)
-    )
-    assert not evaluable.get_dependency(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_3)
-    )
-    assert not evaluable.get_dependency(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_4)
-    )
-    assert not evaluable.get_dependency(
-        Module(name=SUB_MODULE_OF_2), Module(name=MODULE_6)
-    )
-
-    assert not evaluable.get_dependency(Module(name=MODULE_6), Module(name=MODULE_1))
-    assert not evaluable.get_dependency(Module(name=MODULE_6), Module(name=MODULE_2))
-    assert not evaluable.get_dependency(Module(name=MODULE_6), Module(name=MODULE_3))
-    assert not evaluable.get_dependency(Module(name=MODULE_6), Module(name=MODULE_4))
-    assert not evaluable.get_dependency(
-        Module(name=MODULE_6), Module(name=SUB_MODULE_OF_2)
-    )
+    assert evaluable.get_dependencies(MODULE_OBJECT_1, MODULE_OBJECT_2)[
+        (MODULE_OBJECT_1, MODULE_OBJECT_2)
+    ]
+    assert evaluable.get_dependencies(MODULE_OBJECT_1, MODULE_OBJECT_3)[
+        (MODULE_OBJECT_1, MODULE_OBJECT_3)
+    ]
+    assert evaluable.get_dependencies(MODULE_OBJECT_1, MODULE_OBJECT_4)[
+        (MODULE_OBJECT_1, MODULE_OBJECT_4)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_1, SUB_MODULE_OBJECT_2)[
+        (MODULE_OBJECT_1, SUB_MODULE_OBJECT_2)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_1, MODULE_OBJECT_6)[
+        (MODULE_OBJECT_1, MODULE_OBJECT_6)
+    ]
+
+    assert not evaluable.get_dependencies(MODULE_OBJECT_2, MODULE_OBJECT_1)[
+        (MODULE_OBJECT_2, MODULE_OBJECT_1)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_2, MODULE_OBJECT_3)[
+        MODULE_OBJECT_2, MODULE_OBJECT_3
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_2, MODULE_OBJECT_4)[
+        MODULE_OBJECT_2, MODULE_OBJECT_4
+    ]
+    assert not evaluable.get_dependencies(
+        MODULE_OBJECT_2, Module(name=SUB_MODULE_OF_2)
+    )[MODULE_OBJECT_2, SUB_MODULE_OBJECT_2]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_2, MODULE_OBJECT_6)[
+        (MODULE_OBJECT_2, MODULE_OBJECT_6)
+    ]
+
+    assert not evaluable.get_dependencies(MODULE_OBJECT_3, MODULE_OBJECT_1)[
+        (MODULE_OBJECT_3, MODULE_OBJECT_1)
+    ]
+    assert evaluable.get_dependencies(MODULE_OBJECT_3, MODULE_OBJECT_2)[
+        (MODULE_OBJECT_3, MODULE_OBJECT_2)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_3, MODULE_OBJECT_4)[
+        MODULE_OBJECT_3, MODULE_OBJECT_4
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_3, SUB_MODULE_OBJECT_2)[
+        (MODULE_OBJECT_3, SUB_MODULE_OBJECT_2)
+    ]
+    assert evaluable.get_dependencies(MODULE_OBJECT_3, MODULE_OBJECT_6)[
+        (MODULE_OBJECT_3, MODULE_OBJECT_6)
+    ]
+
+    assert not evaluable.get_dependencies(MODULE_OBJECT_4, MODULE_OBJECT_1)[
+        (MODULE_OBJECT_4, MODULE_OBJECT_1)
+    ]
+    assert evaluable.get_dependencies(MODULE_OBJECT_4, MODULE_OBJECT_2)[
+        MODULE_OBJECT_4, MODULE_OBJECT_2
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_4, MODULE_OBJECT_3)[
+        MODULE_OBJECT_4, MODULE_OBJECT_3
+    ]
+    assert evaluable.get_dependencies(MODULE_OBJECT_4, SUB_MODULE_OBJECT_2)[
+        (MODULE_OBJECT_4, SUB_MODULE_OBJECT_2)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_4, MODULE_OBJECT_6)[
+        (MODULE_OBJECT_4, MODULE_OBJECT_6)
+    ]
+
+    assert not evaluable.get_dependencies(SUB_MODULE_OBJECT_2, MODULE_OBJECT_1)[
+        (SUB_MODULE_OBJECT_2, MODULE_OBJECT_1)
+    ]
+    assert not evaluable.get_dependencies(SUB_MODULE_OBJECT_2, MODULE_OBJECT_2)[
+        (SUB_MODULE_OBJECT_2, MODULE_OBJECT_2)
+    ]
+    assert not evaluable.get_dependencies(SUB_MODULE_OBJECT_2, MODULE_OBJECT_3)[
+        (SUB_MODULE_OBJECT_2, MODULE_OBJECT_3)
+    ]
+    assert not evaluable.get_dependencies(SUB_MODULE_OBJECT_2, MODULE_OBJECT_4)[
+        (SUB_MODULE_OBJECT_2, MODULE_OBJECT_4)
+    ]
+    assert not evaluable.get_dependencies(SUB_MODULE_OBJECT_2, MODULE_OBJECT_6)[
+        (SUB_MODULE_OBJECT_2, MODULE_OBJECT_6)
+    ]
+
+    assert not evaluable.get_dependencies(MODULE_OBJECT_6, MODULE_OBJECT_1)[
+        (MODULE_OBJECT_6, MODULE_OBJECT_1)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_6, MODULE_OBJECT_2)[
+        (MODULE_OBJECT_6, MODULE_OBJECT_2)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_6, MODULE_OBJECT_3)[
+        (MODULE_OBJECT_6, MODULE_OBJECT_3)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_6, MODULE_OBJECT_4)[
+        (MODULE_OBJECT_6, MODULE_OBJECT_4)
+    ]
+    assert not evaluable.get_dependencies(MODULE_OBJECT_6, SUB_MODULE_OBJECT_2)[
+        (MODULE_OBJECT_6, SUB_MODULE_OBJECT_2)
+    ]
 
 
 def test_submodule_calculation(submodule_evaluable: EvaluableArchitectureGraph) -> None:
@@ -338,9 +389,11 @@ def test_submodule_calculation(submodule_evaluable: EvaluableArchitectureGraph)
 )
 def test_is_dependent_between_named_modules(imports: List[AbsoluteImport]) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.get_dependency(Module(name=MODULE_1), Module(name=MODULE_2))
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -354,11 +407,11 @@ def test_is_dependent_between_named_and_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.get_dependency(
-        Module(name=MODULE_1), Module(parent_module=MODULE_2)
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -372,11 +425,11 @@ def test_is_dependent_between_submodule_and_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.get_dependency(
-        Module(parent_module=MODULE_1), Module(name=MODULE_2)
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -387,11 +440,11 @@ def test_is_dependent_between_submodule_and_named_modules(
 )
 def test_is_dependent_between_submodule_modules(imports: List[AbsoluteImport]) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.get_dependency(
-        Module(parent_module=MODULE_1), Module(parent_module=MODULE_2)
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -403,12 +456,11 @@ def test_is_dependent_between_submodule_modules(imports: List[AbsoluteImport]) -
 )
 def test_is_not_dependent_between_named_modules(imports: List[AbsoluteImport]) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.get_dependency(Module(name=MODULE_1), Module(name=MODULE_2))
-        is None
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert not architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -422,14 +474,11 @@ def test_is_not_dependent_between_named_and_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.get_dependency(
-            Module(name=MODULE_1), Module(parent_module=MODULE_2)
-        )
-        is None
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert not architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -443,14 +492,11 @@ def test_is_not_dependent_between_submodule_and_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.get_dependency(
-            Module(parent_module=MODULE_1), Module(name=MODULE_2)
-        )
-        is None
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert not architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -465,14 +511,11 @@ def test_is_not_dependent_between_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.get_dependency(
-            Module(parent_module=MODULE_1), Module(parent_module=MODULE_2)
-        )
-        is None
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert not architecture.get_dependencies(module_1, module_2)[(module_1, module_2)]
 
 
 @pytest.mark.parametrize(
@@ -484,11 +527,13 @@ def test_is_not_dependent_between_submodule_modules(
 )
 def test_any_to_other_between_named_modules(imports: List[AbsoluteImport]) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(name=MODULE_2)
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -504,11 +549,13 @@ def test_any_to_other_between_named_and_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_dependency_to_module_other_than(
-        Module(name=MODULE_1), Module(parent_module=MODULE_2)
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -521,11 +568,13 @@ def test_any_to_other_between_submodule_and_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_dependency_to_module_other_than(
-        Module(parent_module=MODULE_1), Module(name=MODULE_2)
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -537,11 +586,13 @@ def test_any_to_other_between_submodule_and_named_modules(
 )
 def test_any_to_other_between_submodule_modules(imports: List[AbsoluteImport]) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_dependency_to_module_other_than(
-        Module(parent_module=MODULE_1), Module(parent_module=MODULE_2)
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -555,14 +606,13 @@ def test_any_to_other_between_submodule_modules(imports: List[AbsoluteImport]) -
 )
 def test_not_any_to_other_between_named_modules(imports: List[AbsoluteImport]) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_dependency_to_module_other_than(
-            Module(name=MODULE_1), Module(name=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert not architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -576,14 +626,13 @@ def test_not_any_to_other_between_named_and_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_dependency_to_module_other_than(
-            Module(name=MODULE_1), Module(parent_module=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert not architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -598,14 +647,13 @@ def test_not_any_to_other_between_submodule_and_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_dependency_to_module_other_than(
-            Module(parent_module=MODULE_1), Module(name=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert not architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -619,14 +667,13 @@ def test_not_any_to_other_between_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_dependency_to_module_other_than(
-            Module(parent_module=MODULE_1), Module(parent_module=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert not architecture.any_dependencies_to_modules_other_than(module_1, module_2)[
+        module_1
+    ]
 
 
 @pytest.mark.parametrize(
@@ -640,11 +687,13 @@ def test_other_to_module_than_between_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(name=MODULE_2)
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -657,11 +706,13 @@ def test_other_to_module_than_between_named_and_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_other_dependency_to_module_than(
-        Module(name=MODULE_1), Module(parent_module=MODULE_2)
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -677,11 +728,13 @@ def test_other_to_module_than_between_submodule_and_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_other_dependency_to_module_than(
-        Module(parent_module=MODULE_1), Module(name=MODULE_2)
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -695,11 +748,13 @@ def test_other_to_module_than_between_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert architecture.any_other_dependency_to_module_than(
-        Module(parent_module=MODULE_1), Module(parent_module=MODULE_2)
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -715,14 +770,13 @@ def test_not_other_to_module_than_between_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_other_dependency_to_module_than(
-            Module(name=MODULE_1), Module(name=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert not architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -737,14 +791,13 @@ def test_not_other_to_module_than_between_named_and_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_other_dependency_to_module_than(
-            Module(name=MODULE_1), Module(parent_module=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(name=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert not architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -758,14 +811,13 @@ def test_not_other_to_module_than_between_submodule_and_named_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
-    assert (
-        architecture.any_other_dependency_to_module_than(
-            Module(parent_module=MODULE_1), Module(name=MODULE_2)
-        )
-        == []
-    )
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(name=MODULE_2)
+    assert not architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
 
 
 @pytest.mark.parametrize(
@@ -781,11 +833,10 @@ def test_not_other_to_module_than_between_submodule_modules(
     imports: List[AbsoluteImport],
 ) -> None:
     all_modules = [MODULE_1, MODULE_2, SUB_MODULE_OF_1, SUB_MODULE_OF_2, MODULE_3]
-    architecture = EvaluableArchitectureGraph(Graph(all_modules, imports))
-
-    assert (
-        architecture.any_other_dependency_to_module_than(
-            Module(parent_module=MODULE_1), Module(parent_module=MODULE_2)
-        )
-        == []
-    )
+    architecture = EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
+
+    module_1 = Module(parent_module=MODULE_1)
+    module_2 = Module(parent_module=MODULE_2)
+    assert not architecture.any_other_dependencies_to_modules_than(module_1, module_2)[
+        module_2
+    ]
diff --git a/tests/graph/test_graph_initialisation.py b/tests/graph/test_graph_initialisation.py
index 35358d2..5848c1c 100644
--- a/tests/graph/test_graph_initialisation.py
+++ b/tests/graph/test_graph_initialisation.py
@@ -1,9 +1,9 @@
-from pytestarch.eval_structure.graph import Graph
+from pytestarch.eval_structure_impl.networkxgraph import NetworkxGraph
 from pytestarch.importer.import_types import AbsoluteImport
 
 
 def test_empty_graph_allowed() -> None:
-    Graph([], [])
+    NetworkxGraph([], [])
 
     assert True
 
@@ -14,7 +14,7 @@ def test_graph_as_expected() -> None:
         AbsoluteImport("B", "C"),
         AbsoluteImport("A", "C"),
     ]
-    graph = Graph(["A", "B", "C"], imports)
+    graph = NetworkxGraph(["A", "B", "C"], imports)
 
     assert "A" in graph
     assert "B" in graph
@@ -27,7 +27,7 @@ def test_graph_as_expected() -> None:
 
 def test_duplicate_edges_do_not_raise_error() -> None:
     imports = [AbsoluteImport("A", "B"), AbsoluteImport("A", "B")]
-    graph = Graph(["A", "B"], imports)
+    graph = NetworkxGraph(["A", "B"], imports)
 
     assert len(graph._graph.nodes) == 2
     assert len(graph._graph.edges) == 1
diff --git a/tests/graph/test_graph_reduction.py b/tests/graph/test_graph_reduction.py
index 287d32a..dace89c 100644
--- a/tests/graph/test_graph_reduction.py
+++ b/tests/graph/test_graph_reduction.py
@@ -2,7 +2,7 @@
 import resources
 from resources import importer
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture
+from pytestarch.eval_structure.evaluable_architecture import EvaluableArchitecture
 from pytestarch.pytestarch import get_evaluable_architecture_for_module_objects
 
 
diff --git a/tests/graph/test_module_graph.py b/tests/graph/test_module_graph.py
index 4a0c792..bf906e9 100644
--- a/tests/graph/test_module_graph.py
+++ b/tests/graph/test_module_graph.py
@@ -4,7 +4,7 @@
 import pytest
 
 from pytestarch.config.config import Config
-from pytestarch.eval_structure.graph import Graph
+from pytestarch.eval_structure_impl.networkxgraph import NetworkxGraph
 from pytestarch.importer.converter import ImportConverter
 from pytestarch.importer.file_filter import FileFilter
 from pytestarch.importer.import_types import Import, NamedModule
@@ -41,16 +41,16 @@ def all_modules(
 
 
 @pytest.fixture(scope="module")
-def module_graph(all_modules: List[str], imports: List[Import]) -> Graph:
-    return Graph(all_modules, imports)
+def module_graph(all_modules: List[str], imports: List[Import]) -> NetworkxGraph:
+    return NetworkxGraph(all_modules, imports)
 
 
-def test_node_edge_count_as_expected(module_graph: Graph) -> None:
+def test_node_edge_count_as_expected(module_graph: NetworkxGraph) -> None:
     assert module_graph.nodes_number == 33
     assert module_graph.edges_number == 23
 
 
-def test_expected_nodes_present(module_graph: Graph) -> None:
+def test_expected_nodes_present(module_graph: NetworkxGraph) -> None:
     assert "pytestarch.tests.resources.importer" in module_graph
     assert "pytestarch.tests.resources.importer.level0" in module_graph
     assert "pytestarch.tests.resources.importer.level0.level1" in module_graph
@@ -87,7 +87,7 @@ def test_expected_nodes_present(module_graph: Graph) -> None:
 
 
 def test_edges_between_parent_and_child_modules_as_expected(
-    module_graph: Graph,
+    module_graph: NetworkxGraph,
 ) -> None:
     assert (
         "pytestarch.tests.resources.importer",
@@ -126,7 +126,9 @@ def test_edges_between_parent_and_child_modules_as_expected(
     assert ("pytestarch", "pytestarch.pytestarch") in module_graph
 
 
-def test_edges_between_importers_and_importees_as_expected(module_graph: Graph) -> None:
+def test_edges_between_importers_and_importees_as_expected(
+    module_graph: NetworkxGraph,
+) -> None:
     assert (
         "pytestarch.tests.resources.importer.level0.level1.level2."
         "level3.level4.level5.module_level_5",
@@ -175,7 +177,7 @@ def test_edges_between_importers_and_importees_as_expected(module_graph: Graph)
     ) in module_graph
 
 
-def test_graph_connections_as_expected(module_graph: Graph) -> None:
+def test_graph_connections_as_expected(module_graph: NetworkxGraph) -> None:
     assert (
         "pytestarch.tests.resources.importer.level0.test_dummy",
         "pytest",
diff --git a/tests/query_language/test_RuleViolationMessageGenerator.py b/tests/query_language/test_RuleViolationMessageGenerator.py
new file mode 100644
index 0000000..2d1f2e3
--- /dev/null
+++ b/tests/query_language/test_RuleViolationMessageGenerator.py
@@ -0,0 +1,681 @@
+from typing import Dict, List
+
+import pytest
+
+from pytestarch.eval_structure.evaluable_architecture import (
+    LaxDependenciesByBaseModule,
+    Module,
+    StrictDependenciesByBaseModules,
+)
+from pytestarch.query_language.message_generator import RuleViolationMessageGenerator
+from pytestarch.query_language.rule_matcher import RuleViolations
+
+ORIGINAL_SUBJECT = "A"
+ORIGINAL_OBJECT_1 = "B"
+ORIGINAL_OBJECT_2 = "C"
+OTHER_OBJECT_1 = "Y"
+OTHER_OBJECT_2 = "Z"
+
+ORIGINAL_SUBJECT_MODULE = Module(name=ORIGINAL_SUBJECT)
+ORIGINAL_OBJECT_MODULE_1 = Module(name=ORIGINAL_OBJECT_1)
+ORIGINAL_OBJECT_MODULE_2 = Module(name=ORIGINAL_OBJECT_2)
+OTHER_OBJECT_MODULE_1 = Module(name=OTHER_OBJECT_1)
+OTHER_OBJECT_MODULE_2 = Module(name=OTHER_OBJECT_2)
+
+single_object_import_generator = RuleViolationMessageGenerator(
+    ORIGINAL_SUBJECT_MODULE,
+    [ORIGINAL_OBJECT_MODULE_1],
+    True,
+)
+single_object_be_imported_generator = RuleViolationMessageGenerator(
+    ORIGINAL_SUBJECT_MODULE,
+    [ORIGINAL_OBJECT_MODULE_1],
+    False,
+)
+
+multiple_object_import_generator = RuleViolationMessageGenerator(
+    ORIGINAL_SUBJECT_MODULE,
+    [ORIGINAL_OBJECT_MODULE_1, ORIGINAL_OBJECT_MODULE_2],
+    True,
+)
+multiple_object_be_imported_generator = RuleViolationMessageGenerator(
+    ORIGINAL_SUBJECT_MODULE,
+    [ORIGINAL_OBJECT_MODULE_1, ORIGINAL_OBJECT_MODULE_2],
+    False,
+)
+
+
+message_content_test_cases = [
+    pytest.param(
+        single_object_import_generator,
+        {"should_violated": True},
+        f'"{ORIGINAL_SUBJECT}" does not import "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should import",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_violated": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should be imported",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_violated_by_forbidden_import": True},
+        f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+        None,
+        {ORIGINAL_SUBJECT_MODULE: [(ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)]},
+        id="should only import -- forbidden",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" does not import "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should only import -- no import",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_violated_by_forbidden_import": True},
+        f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_1}".',
+        None,
+        {ORIGINAL_SUBJECT_MODULE: [(OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)]},
+        id="should only be imported -- forbidden",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should only be imported -- no import",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_not_violated": True},
+        f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)
+            ]
+        },
+        None,
+        id="should not import",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_not_violated": True},
+        f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_1}".',
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)
+            ]
+        },
+        None,
+        id="should not be imported",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_except_violated": True},
+        f'"{ORIGINAL_SUBJECT}" does not import any module that is not "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should except import",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_except_violated": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by any module that is not "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should except be imported",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_not_except_violated": True},
+        f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+        None,
+        {(ORIGINAL_SUBJECT): [(ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)]},
+        id="should not except import",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_not_except_violated": True},
+        f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_1}".',
+        None,
+        {(ORIGINAL_SUBJECT): [(OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)]},
+        id="should not except be imported",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_except_violated_by_forbidden_import": True},
+        f'"{ORIGINAL_SUBJECT}" imports "{ORIGINAL_OBJECT_1}".',
+        {ORIGINAL_SUBJECT: [(ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_1)]},
+        None,
+        id="should only import except -- forbidden",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_except_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" does not import any module that is not "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should only import except -- no import",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_except_violated_by_forbidden_import": True},
+        f'"{ORIGINAL_SUBJECT}" is imported by "{ORIGINAL_OBJECT_1}".',
+        {ORIGINAL_SUBJECT: [(ORIGINAL_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)]},
+        None,
+        id="should only be imported except -- forbidden",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_except_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by any module that is not "{ORIGINAL_OBJECT_1}".',
+        None,
+        None,
+        id="should only be imported except -- no import",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "generator, violation, expected_message, strict_dependencies, lax_dependencies",
+    message_content_test_cases,
+)
+def test_rule_violation_message_content(
+    generator: RuleViolationMessageGenerator,
+    violation: Dict[str, bool],
+    expected_message: str,
+    strict_dependencies: StrictDependenciesByBaseModules,
+    lax_dependencies: LaxDependenciesByBaseModule,
+) -> None:
+    violations = RuleViolations(
+        strict_dependencies=strict_dependencies,
+        lax_dependencies=lax_dependencies,
+        **violation,
+    )
+    messages = generator.create_rule_violation_messages(violations)
+
+    assert len(messages) == 1
+    assert messages[0] == expected_message
+
+
+message_only_present_if_rule_violated_test_cases = [
+    pytest.param(
+        single_object_import_generator,
+        {"should_violated": True},
+        None,
+        None,
+        id="should import present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_violated": False},
+        None,
+        None,
+        id="should import not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_violated_by_forbidden_import": True},
+        None,
+        {ORIGINAL_SUBJECT_MODULE: [(ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)]},
+        id="should only import -- forbidden present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_violated_by_forbidden_import": False},
+        None,
+        {ORIGINAL_SUBJECT_MODULE: [(OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)]},
+        id="should only import -- forbidden not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_violated_by_no_import": True},
+        {
+            (ORIGINAL_OBJECT_1, ORIGINAL_SUBJECT_MODULE): [
+                (OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)
+            ]
+        },
+        None,
+        id="should only import -- no import present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_violated_by_no_import": False},
+        None,
+        None,
+        id="should only import -- no import not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_not_violated": True},
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)
+            ]
+        },
+        None,
+        id="should not import present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_not_violated": False},
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)
+            ]
+        },
+        None,
+        id="should not import not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_except_violated": True},
+        None,
+        None,
+        id="should except import present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_except_violated": False},
+        None,
+        None,
+        id="should except import not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_not_except_violated": True},
+        None,
+        {
+            (ORIGINAL_SUBJECT): [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1),
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_2),
+            ]
+        },
+        id="should not except import present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_not_except_violated": False},
+        None,
+        {
+            (ORIGINAL_SUBJECT): [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1),
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_2),
+            ]
+        },
+        id="should not except import not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_except_violated_by_forbidden_import": True},
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_1)
+            ]
+        },
+        None,
+        id="should only import -- forbidden present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_except_violated_by_forbidden_import": False},
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (ORIGINAL_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE)
+            ]
+        },
+        None,
+        id="should only import -- forbidden not present",
+    ),
+    pytest.param(
+        single_object_import_generator,
+        {"should_only_except_violated_by_no_import": True},
+        None,
+        None,
+        id="should only import except -- no import present",
+    ),
+    pytest.param(
+        single_object_be_imported_generator,
+        {"should_only_except_violated_by_no_import": False},
+        None,
+        None,
+        id="should only import except -- no import not present",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "generator, violation, strict_dependencies, lax_dependencies",
+    message_only_present_if_rule_violated_test_cases,
+)
+def test_violated_message_only_present_if_rule_actually_violated(
+    generator: RuleViolationMessageGenerator,
+    violation: Dict[str, bool],
+    strict_dependencies: StrictDependenciesByBaseModules,
+    lax_dependencies: LaxDependenciesByBaseModule,
+) -> None:
+    violations = RuleViolations(
+        strict_dependencies=strict_dependencies,
+        lax_dependencies=lax_dependencies,
+        **violation,
+    )
+    messages = generator.create_rule_violation_messages(violations)
+
+    assert bool(messages) is (True in violation.values())
+
+
+multiple_objects_in_one_message_test_cases = [
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_violated": True},
+        f'"{ORIGINAL_SUBJECT}" does not import "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should import",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_violated": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should be imported",
+    ),
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_only_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" does not import "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should only import -- no import",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_only_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should only be imported -- no import",
+    ),
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_except_violated": True},
+        f'"{ORIGINAL_SUBJECT}" does not import any module that is not "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should except import",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_except_violated": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by any module that is not "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should except be imported",
+    ),
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_only_except_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" does not import any module that is not "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should only import except -- no import",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_only_except_violated_by_no_import": True},
+        f'"{ORIGINAL_SUBJECT}" is not imported by any module that is not "{ORIGINAL_OBJECT_1}", "{ORIGINAL_OBJECT_2}".',
+        None,
+        None,
+        id="should only be imported except -- no import",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "generator, violation, expected_message, strict_dependencies, lax_dependencies",
+    multiple_objects_in_one_message_test_cases,
+)
+def test_multiple_rule_objects_combined_in_one_message(
+    generator: RuleViolationMessageGenerator,
+    violation: Dict[str, bool],
+    expected_message: str,
+    strict_dependencies: StrictDependenciesByBaseModules,
+    lax_dependencies: LaxDependenciesByBaseModule,
+) -> None:
+    violations = RuleViolations(
+        strict_dependencies=strict_dependencies,
+        lax_dependencies=lax_dependencies,
+        **violation,
+    )
+    messages = generator.create_rule_violation_messages(violations)
+
+    assert len(messages) == 1
+    assert messages[0] == expected_message
+
+
+multiple_objects_in_multiple_messages_test_cases = [
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_only_violated_by_forbidden_import": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_2}".',
+        ],
+        None,
+        {
+            ORIGINAL_SUBJECT_MODULE: [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1),
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_2),
+            ]
+        },
+        id="should only import -- forbidden",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_only_violated_by_forbidden_import": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_2}".',
+        ],
+        None,
+        {
+            ORIGINAL_SUBJECT_MODULE: [
+                (OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE),
+                (OTHER_OBJECT_MODULE_2, ORIGINAL_SUBJECT_MODULE),
+            ]
+        },
+        id="should only be imported -- forbidden",
+    ),
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_not_violated": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_2}".',
+        ],
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1),
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_2),
+            ]
+        },
+        None,
+        id="should not import",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_not_violated": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_2}".',
+        ],
+        {
+            (ORIGINAL_SUBJECT, ORIGINAL_OBJECT_1): [
+                (OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE),
+                (OTHER_OBJECT_MODULE_2, ORIGINAL_SUBJECT_MODULE),
+            ]
+        },
+        None,
+        id="should not be imported",
+    ),
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_not_except_violated": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_2}".',
+        ],
+        None,
+        {
+            (ORIGINAL_SUBJECT): [
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1),
+                (ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_2),
+            ]
+        },
+        id="should not import except",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_not_except_violated": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" is imported by "{OTHER_OBJECT_2}".',
+        ],
+        None,
+        {
+            (ORIGINAL_SUBJECT): [
+                (OTHER_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE),
+                (OTHER_OBJECT_MODULE_2, ORIGINAL_SUBJECT_MODULE),
+            ]
+        },
+        id="should not be imported except",
+    ),
+    pytest.param(
+        multiple_object_import_generator,
+        {"should_only_except_violated_by_forbidden_import": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" imports "{ORIGINAL_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" imports "{ORIGINAL_OBJECT_2}".',
+        ],
+        {
+            (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_1): [
+                (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_1),
+            ],
+            (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_2): [
+                (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_2),
+            ],
+        },
+        None,
+        id="should only import -- forbidden",
+    ),
+    pytest.param(
+        multiple_object_be_imported_generator,
+        {"should_only_except_violated_by_forbidden_import": True},
+        2,
+        [
+            f'"{ORIGINAL_SUBJECT}" is imported by "{ORIGINAL_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" is imported by "{ORIGINAL_OBJECT_2}".',
+        ],
+        {
+            (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_1): [
+                (ORIGINAL_OBJECT_MODULE_1, ORIGINAL_SUBJECT_MODULE),
+            ],
+            (ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_2): [
+                (ORIGINAL_OBJECT_MODULE_2, ORIGINAL_SUBJECT_MODULE),
+            ],
+        },
+        None,
+        id="should only be imported -- forbidden",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "generator, violation, expected_message_count, expected_messages, strict_dependencies, lax_dependencies",
+    multiple_objects_in_multiple_messages_test_cases,
+)
+def test_multiple_rule_objects_in_multiple_message(
+    generator: RuleViolationMessageGenerator,
+    violation: Dict[str, bool],
+    expected_message_count: int,
+    expected_messages: List[str],
+    strict_dependencies: StrictDependenciesByBaseModules,
+    lax_dependencies: LaxDependenciesByBaseModule,
+) -> None:
+    violations = RuleViolations(
+        strict_dependencies=strict_dependencies,
+        lax_dependencies=lax_dependencies,
+        **violation,
+    )
+    messages = generator.create_rule_violation_messages(violations)
+
+    assert len(messages) == expected_message_count
+    assert messages == expected_messages
+
+
+forbidden_and_no_import_test_cases = [
+    pytest.param(
+        {
+            "should_only_violated_by_forbidden_import": True,
+            "should_only_violated_by_no_import": True,
+        },
+        [
+            f'"{ORIGINAL_SUBJECT}" does not import "{ORIGINAL_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" imports "{OTHER_OBJECT_1}".',
+        ],
+        None,
+        {ORIGINAL_SUBJECT: [(ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)]},
+        id="should only",
+    ),
+    pytest.param(
+        {
+            "should_only_except_violated_by_forbidden_import": True,
+            "should_only_except_violated_by_no_import": True,
+        },
+        [
+            f'"{ORIGINAL_SUBJECT}" does not import any module that is not "{ORIGINAL_OBJECT_1}".',
+            f'"{ORIGINAL_SUBJECT}" imports "{ORIGINAL_OBJECT_1}".',
+        ],
+        {ORIGINAL_SUBJECT: [(ORIGINAL_SUBJECT_MODULE, ORIGINAL_OBJECT_MODULE_1)]},
+        {ORIGINAL_SUBJECT_MODULE: [(ORIGINAL_SUBJECT_MODULE, OTHER_OBJECT_MODULE_1)]},
+        id="should only except",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "violation, expected_messages, strict_dependencies, lax_dependencies",
+    forbidden_and_no_import_test_cases,
+)
+def test_multiple_messages_if_forbidden_and_no_import_both_present(
+    violation: Dict[str, bool],
+    expected_messages: List[str],
+    strict_dependencies: StrictDependenciesByBaseModules,
+    lax_dependencies: LaxDependenciesByBaseModule,
+) -> None:
+    violations = RuleViolations(
+        strict_dependencies=strict_dependencies,
+        lax_dependencies=lax_dependencies,
+        **violation,
+    )
+    messages = single_object_import_generator.create_rule_violation_messages(violations)
+
+    assert len(messages) == 2
+    assert messages == expected_messages
diff --git a/tests/query_language/test_base_language.py b/tests/query_language/test_base_language.py
index 49da38a..16a6a9a 100644
--- a/tests/query_language/test_base_language.py
+++ b/tests/query_language/test_base_language.py
@@ -1,11 +1,11 @@
 import pytest
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture
+from pytestarch.eval_structure.evaluable_architecture import EvaluableArchitecture
 from pytestarch.eval_structure.evaluable_graph import EvaluableArchitectureGraph
-from pytestarch.eval_structure.graph import Graph
+from pytestarch.eval_structure_impl.networkxgraph import NetworkxGraph
+from pytestarch.exceptions import ImproperlyConfigured
 from pytestarch.importer.import_types import AbsoluteImport
 from pytestarch.query_language.base_language import Rule
-from pytestarch.query_language.exceptions import ImproperlyConfigured
 
 MODULE_1 = "Module1"
 MODULE_2 = "Module2"
@@ -77,7 +77,7 @@ def evaluable1() -> EvaluableArchitecture:
         AbsoluteImport(MODULE_7, SUB_MODULE_OF_7),
     ]
 
-    return EvaluableArchitectureGraph(Graph(all_modules, imports))
+    return EvaluableArchitectureGraph(NetworkxGraph(all_modules, imports))
 
 
 def test_partially_configured_rule_raises_error(
diff --git a/tests/query_language/test_rule_matcher.py b/tests/query_language/test_rule_matcher.py
index 8ebed3a..4120856 100644
--- a/tests/query_language/test_rule_matcher.py
+++ b/tests/query_language/test_rule_matcher.py
@@ -1,582 +1,676 @@
+from typing import List
+
 import pytest
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture
-from pytestarch.query_language.base_language import Rule
+from pytestarch import Rule
+from pytestarch.eval_structure.evaluable_architecture import (
+    LaxDependenciesByBaseModule,
+    Module,
+    StrictDependenciesByBaseModules,
+)
+from pytestarch.eval_structure.evaluable_graph import EvaluableArchitectureGraph
+from pytestarch.eval_structure_impl.networkxgraph import NetworkxGraph
+from pytestarch.importer.import_types import AbsoluteImport
 
-C = "src.moduleC"
-B = "src.moduleB"
-A = "src.moduleA"
-FILE_A2 = f"{A}.submoduleA2.fileA2"
-A2 = f"{A}.submoduleA2"
-A1 = f"{A}.submoduleA1"
-A11 = f"{A}.submoduleA1.submoduleA11"
-B1_MODULE = f"{B}.submoduleB1"
-B2 = f"{B}.submoduleB1.fileB2"
+MODULE_1 = "1"
+MODULE_2 = "2"
+MODULE_3 = "3"
+MODULE_4 = "4"
 
-test_cases = [
-    pytest.param(
-        Rule().modules_that().are_named(C).should().import_modules_that().are_named(A),
-        '"src.moduleC" does not import "src.moduleA".',
-        id="named should import named",
-    ),
+ALL_MODULES = [MODULE_1, MODULE_2, MODULE_3, MODULE_4]
+
+
+multiple_violations_test_cases = [
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_1, MODULE_3),
+            AbsoluteImport(MODULE_1, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(C)
+        .are_named(MODULE_1)
         .should()
         .import_modules_that()
-        .are_sub_modules_of(A),
-        '"src.moduleC" does not import a sub module of "src.moduleA".',
-        id="named should import submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(C)
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [
+                (Module(name=MODULE_1), Module(name=MODULE_3))
+            ],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [
+                (Module(name=MODULE_1), Module(name=MODULE_2))
+            ],
+        },
+        None,
+        id="should import -- true",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should()
         .import_modules_that()
-        .are_named(A),
-        'Sub modules of "src.moduleC" do not import "src.moduleA".',
-        id="submodule should import named",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_violated"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+        },
+        None,
+        id="should import -- false",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_4, MODULE_1),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(C)
+        .are_named(MODULE_1)
         .should()
-        .import_modules_that()
-        .are_sub_modules_of(A),
-        'Sub modules of "src.moduleC" do not import a sub module of "src.moduleA".',
-        id="submodule should import submodule",
+        .be_imported_by_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [
+                (Module(name=MODULE_2), Module(name=MODULE_1))
+            ],
+        },
+        None,
+        id="should be imported -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_4, MODULE_1),
+        ],
         Rule()
         .modules_that()
-        .are_named(A)
-        .should_only()
+        .are_named(MODULE_1)
+        .should()
         .import_modules_that()
-        .are_named(B),
-        '"src.moduleA" imports "src.moduleC.fileC".',
-        id="named should only import named",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_violated"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+        },
+        None,
+        id="should be imported -- false",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_1, MODULE_3),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(A)
+        .are_named(MODULE_1)
         .should_only()
         .import_modules_that()
-        .are_sub_modules_of(B),
-        '"src.moduleA" imports a sub module of "src.moduleC.fileC".',
-        id="named should only import submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [
+                (Module(name=MODULE_1), Module(name=MODULE_3))
+            ],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [
+                (Module(name=MODULE_1), Module(name=MODULE_2))
+            ],
+        },
+        {Module(name=MODULE_1): []},
+        id="should only import -- true",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_1, MODULE_3),
+            AbsoluteImport(MODULE_1, MODULE_4),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .import_modules_that()
-        .are_named(B),
-        'Sub modules of "src.moduleA" import "src.moduleC.fileC".',
-        id="submodule should only import named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_violated_by_forbidden_import"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [
+                (Module(name=MODULE_1), Module(name=MODULE_3))
+            ],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [
+                (Module(name=MODULE_1), Module(name=MODULE_2))
+            ],
+        },
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_1), Module(name=MODULE_4))],
+        },
+        id="should only import -- false forbidden",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .import_modules_that()
-        .are_sub_modules_of(B),
-        'Sub modules of "src.moduleA" import a sub module of "src.moduleC.fileC".',
-        id="submodule should only import submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(B)
-        .should_not()
-        .import_modules_that()
-        .are_named(A),
-        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11',
-        id="named should not import named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(B)
-        .should_not()
-        .import_modules_that()
-        .are_sub_modules_of(A),
-        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
-        id="named should not import submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(B)
-        .should_not()
-        .import_modules_that()
-        .are_named(A),
-        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
-        id="submodule should not import named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(B)
-        .should_not()
-        .import_modules_that()
-        .are_sub_modules_of(A),
-        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
-        id="submodule should not import submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(FILE_A2)
-        .should()
-        .import_modules_except_modules_that()
-        .are_named(C),
-        '"src.moduleA.submoduleA2.fileA2" does not import any module that is not "src.moduleC".',
-        id="named should import except named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(FILE_A2)
-        .should()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(C),
-        '"src.moduleA.submoduleA2.fileA2" does not import any module that is not a sub module of "src.moduleC".',
-        id="named should import except submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(FILE_A2)
-        .should()
-        .import_modules_except_modules_that()
-        .are_named(C),
-        'Sub modules of "src.moduleA.submoduleA2.fileA2" do not import any module that is not "src.moduleC".',
-        id="submodule should import except named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(FILE_A2)
-        .should()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(C),
-        'Sub modules of "src.moduleA.submoduleA2.fileA2" do not import any module that is not a sub module of "src.moduleC".',
-        id="submodule should import except submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_named(C),
-        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
-        id="named should only import except named - forbidden import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(C),
-        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
-        id="named should only import except submodule - forbidden import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_named(C),
-        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
-        id="submodule should only import except named - forbidden import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(C),
-        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
-        id="submodule should only import except submodule - forbidden import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_named(B),
-        '"src.moduleC" does not import any module that is not "src.moduleB".',
-        id="named should only import except named - no import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(B),
-        '"src.moduleC" does not import any module that is not a sub module of "src.moduleB".',
-        id="named should only import except submodule - no import",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_violated_by_no_import"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+        },
+        {Module(name=MODULE_1): []},
+        id="should only import -- false no import",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_4),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(C)
+        .are_named(MODULE_1)
         .should_only()
-        .import_modules_except_modules_that()
-        .are_named(B),
-        'Sub modules of "src.moduleC" do not import any module that is not "src.moduleB".',
-        id="submodule should only import except named - no import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(C)
-        .should_only()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(B),
-        'Sub modules of "src.moduleC" do not import any module that is not a sub module of "src.moduleB".',
-        id="submodule should only import except submodule - no import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
-        .should_not()
-        .import_modules_except_modules_that()
-        .are_named(B),
-        '"src.moduleA" imports "src.moduleC.fileC".',
-        id="named should not import except named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
-        .should_not()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(B),
-        '"src.moduleA" imports a sub module of "src.moduleC.fileC".',
-        id="named should not import except submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
-        .should_not()
-        .import_modules_except_modules_that()
-        .are_named(B),
-        'Sub modules of "src.moduleA" import "src.moduleC.fileC".',
-        id="submodule should not import except named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
-        .should_not()
-        .import_modules_except_modules_that()
-        .are_sub_modules_of(B),
-        'Sub modules of "src.moduleA" import a sub module of "src.moduleC.fileC".',
-        id="submodule should not import except submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
-        .should()
-        .be_imported_by_modules_that()
-        .are_named(C),
-        '"src.moduleA" is not imported by "src.moduleC".',
-        id="named should be imported by named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
-        .should()
-        .be_imported_by_modules_that()
-        .are_sub_modules_of(C),
-        '"src.moduleA" is not imported by a sub module of "src.moduleC".',
-        id="named should be imported by submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
-        .should()
-        .be_imported_by_modules_that()
-        .are_named(C),
-        'Sub modules of "src.moduleA" are not imported by "src.moduleC".',
-        id="submodule should be imported by named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
-        .should()
-        .be_imported_by_modules_that()
-        .are_sub_modules_of(C),
-        'Sub modules of "src.moduleA" are not imported by a sub module of "src.moduleC".',
-        id="submodule should be imported by submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
+        .import_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        [
+            "should_only_violated_by_forbidden_import",
+            "should_only_violated_by_no_import",
+        ],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+        },
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_1), Module(name=MODULE_4))],
+        },
+        id="should only import -- false forbidden and no import",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_3, MODULE_1),
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_that()
-        .are_named(B),
-        '"src.moduleC" is imported by "src.moduleA.',
-        id="named should only be imported by named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [
+                (Module(name=MODULE_3), Module(name=MODULE_1))
+            ],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [
+                (Module(name=MODULE_2), Module(name=MODULE_1))
+            ],
+        },
+        {Module(name=MODULE_1): []},
+        id="should only be imported -- true",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_1),
+            AbsoluteImport(MODULE_4, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_that()
-        .are_sub_modules_of(B),
-        '"src.moduleC" is imported by a sub module of "src.moduleA',
-        id="named should only be imported by submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(C)
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_violated_by_forbidden_import"],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [
+                (Module(name=MODULE_3), Module(name=MODULE_1))
+            ],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [
+                (Module(name=MODULE_2), Module(name=MODULE_1))
+            ],
+        },
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_4), Module(name=MODULE_1))],
+        },
+        id="should only be imported -- false forbidden",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_that()
-        .are_named(B),
-        'Sub modules of "src.moduleC" are imported by "src.moduleA',
-        id="submodule should only be imported by named",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_violated_by_no_import"],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [],
+        },
+        {Module(name=MODULE_1): []},
+        id="should only be imported -- false no import",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_4, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(C)
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_that()
-        .are_sub_modules_of(B),
-        'Sub modules of "src.moduleC" are imported by a sub module of "src.moduleA',
-        id="submodule should only be imported by submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
+        .are_named([MODULE_2, MODULE_3]),
+        [
+            "should_only_violated_by_forbidden_import",
+            "should_only_violated_by_no_import",
+        ],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [],
+        },
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_4), Module(name=MODULE_1))],
+        },
+        id="should only be imported  -- false forbidden and no import",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_not()
-        .be_imported_by_modules_that()
-        .are_named(A),
-        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
-        id="named should not be imported by named",
+        .import_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+        },
+        None,
+        id="should not import -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_1, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(C)
+        .are_named(MODULE_1)
         .should_not()
-        .be_imported_by_modules_that()
-        .are_sub_modules_of(A),
-        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
-        id="named should not be imported by submodule",
+        .import_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_not_violated"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [
+                (Module(name=MODULE_1), Module(name=MODULE_2))
+            ],
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+        },
+        None,
+        id="should not import -- false",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_4, MODULE_1),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(C)
+        .are_named(MODULE_1)
         .should_not()
         .be_imported_by_modules_that()
-        .are_named(A),
-        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
-        id="submodule should not be imported by named",
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+        },
+        None,
+        id="should not be imported -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_4, MODULE_1),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(C)
+        .are_named(MODULE_1)
         .should_not()
         .be_imported_by_modules_that()
-        .are_sub_modules_of(A),
-        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
-        id="submodule should not be imported by submodule",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_not_violated"],
+        {
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [
+                (Module(name=MODULE_2), Module(name=MODULE_1))
+            ],
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+        },
+        None,
+        id="should not be imported -- false",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_1, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(B)
+        .are_named(MODULE_1)
         .should()
-        .be_imported_by_modules_except_modules_that()
-        .are_named(A),
-        '"src.moduleB" is not imported by any module that is not "src.moduleA".',
-        id="named should be imported by except named",
+        .import_modules_except_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        None,
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_1), Module(name=MODULE_4))],
+        },
+        id="should import except -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+        ],
         Rule()
         .modules_that()
-        .are_named(B)
+        .are_named(MODULE_1)
         .should()
-        .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(A),
-        '"src.moduleB" is not imported by any module that is not a sub module of "src.moduleA".',
-        id="named should be imported by except submodule",
+        .import_modules_except_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_except_violated"],
+        None,
+        {
+            Module(name=MODULE_1): [],
+        },
+        id="should import except -- false",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_4, MODULE_1),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(B)
+        .are_named(MODULE_1)
         .should()
         .be_imported_by_modules_except_modules_that()
-        .are_named(A),
-        'Sub modules of "src.moduleB" are not imported by any module that is not "src.moduleA".',
-        id="submodule should be imported by except named",
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        None,
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_4), Module(name=MODULE_1))],
+        },
+        id="should be imported except -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(B)
+        .are_named(MODULE_1)
         .should()
         .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(A),
-        'Sub modules of "src.moduleB" are not imported by any module that is not a sub module of "src.moduleA".',
-        id="submodule should be imported by except submodule",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_except_violated"],
+        None,
+        {
+            Module(name=MODULE_1): [],
+        },
+        id="should be imported except -- false",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_4),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(A11)
+        .are_named(MODULE_1)
         .should_only()
-        .be_imported_by_modules_except_modules_that()
-        .are_named(B2),
-        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.fileB2".',
-        id="named should only be imported by except named - forbidden import",
+        .import_modules_except_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+        },
+        {Module(name=MODULE_1): [(Module(name=MODULE_1), Module(name=MODULE_4))]},
+        id="should only import except -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_1, MODULE_4),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(A11)
+        .are_named(MODULE_1)
         .should_only()
-        .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(B1_MODULE),
-        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.submoduleB11.fileB11".',
-        id="named should only be imported by except submodule - forbidden import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A11)
+        .import_modules_except_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_except_violated_by_forbidden_import"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [
+                (Module(name=MODULE_1), Module(name=MODULE_2))
+            ],
+        },
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_1), Module(name=MODULE_4))],
+        },
+        id="should only import except -- false forbidden",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
-        .be_imported_by_modules_except_modules_that()
-        .are_named(B2),
-        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.fileB2".',
-        id="submodule should only be imported by except named - forbidden import",
+        .import_modules_except_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_except_violated_by_no_import"],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [],
+        },
+        {Module(name=MODULE_1): []},
+        id="should only import except -- false no import",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_1, MODULE_2),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(A1)
+        .are_named(MODULE_1)
         .should_only()
-        .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(B1_MODULE),
-        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.submoduleB11.fileB11".',
-        id="submodule should only be imported by except submodule - forbidden import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(A)
+        .import_modules_except_modules_that()
+        .are_named([MODULE_2, MODULE_3]),
+        [
+            "should_only_except_violated_by_forbidden_import",
+            "should_only_except_violated_by_no_import",
+        ],
+        {
+            (Module(name=MODULE_1), Module(name=MODULE_3)): [],
+            (Module(name=MODULE_1), Module(name=MODULE_2)): [
+                (Module(name=MODULE_1), Module(name=MODULE_2))
+            ],
+        },
+        {
+            Module(name=MODULE_1): [],
+        },
+        id="should only import except  -- false forbidden and no import",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_4, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_except_modules_that()
-        .are_named(B),
-        '"src.moduleA" is not imported by any module that is not "src.moduleB".',
-        id="named should only be imported by except named - no import",
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [],
+        },
+        {Module(name=MODULE_1): [(Module(name=MODULE_4), Module(name=MODULE_1))]},
+        id="should only be imported except -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_4, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_named(A)
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(B),
-        '"src.moduleA" is not imported by any module that is not a sub module of "src.moduleB"',
-        id="named should only be imported by except submodule - no import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(A)
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_except_violated_by_forbidden_import"],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [
+                (Module(name=MODULE_2), Module(name=MODULE_1))
+            ],
+        },
+        {
+            Module(name=MODULE_1): [(Module(name=MODULE_4), Module(name=MODULE_1))],
+        },
+        id="should only be imported except -- false forbidden",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_except_modules_that()
-        .are_named(B),
-        'Sub modules of "src.moduleA" are not imported by any module that is not "src.moduleB"',
-        id="submodule should only be imported by except named - no import",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_only_except_violated_by_no_import"],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [],
+        },
+        {Module(name=MODULE_1): []},
+        id="should only be imported except -- false no import",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(A)
+        .are_named(MODULE_1)
         .should_only()
         .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(B),
-        'Sub modules of "src.moduleA" are not imported by any module that is not a sub module of "src.moduleB"',
-        id="submodule should only be imported by except submodule - no import",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
-        .should_not()
-        .be_imported_by_modules_except_modules_that()
-        .are_named(FILE_A2),
-        '"src.moduleC" is imported by "src.moduleA.fileA".',
-        id="named should not be imported by except named",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_named(C)
-        .should_not()
-        .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(A2),
-        '"src.moduleC" is imported by a sub module of "src.moduleA.fileA".',
-        id="named should not be imported by except submodule",
-    ),
-    pytest.param(
-        Rule()
-        .modules_that()
-        .are_sub_modules_of(C)
+        .are_named([MODULE_2, MODULE_3]),
+        [
+            "should_only_except_violated_by_forbidden_import",
+            "should_only_except_violated_by_no_import",
+        ],
+        {
+            (Module(name=MODULE_3), Module(name=MODULE_1)): [],
+            (Module(name=MODULE_2), Module(name=MODULE_1)): [
+                (Module(name=MODULE_2), Module(name=MODULE_1))
+            ],
+        },
+        {
+            Module(name=MODULE_1): [],
+        },
+        id="should only be imported except  -- false forbidden and no import",
+    ),
+    pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
+        Rule()
+        .modules_that()
+        .are_named(MODULE_1)
         .should_not()
         .be_imported_by_modules_except_modules_that()
-        .are_named(A2),
-        'Sub modules of "src.moduleC" are imported by "src.moduleA.fileA".',
-        id="submodule should not be imported by except named",
+        .are_named([MODULE_2, MODULE_3]),
+        [],
+        None,
+        {Module(name=MODULE_1): []},
+        id="should not import except -- true",
     ),
     pytest.param(
+        [
+            AbsoluteImport(MODULE_2, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_1),
+            AbsoluteImport(MODULE_4, MODULE_1),
+            AbsoluteImport(MODULE_3, MODULE_4),
+        ],
         Rule()
         .modules_that()
-        .are_sub_modules_of(C)
+        .are_named(MODULE_1)
         .should_not()
         .be_imported_by_modules_except_modules_that()
-        .are_sub_modules_of(FILE_A2),
-        'Sub modules of "src.moduleC" are imported by a sub module of "src.moduleA.fileA, src.moduleA.submoduleA2.fileA2".',
-        id="submodule should not be imported by except submodule",
+        .are_named([MODULE_2, MODULE_3]),
+        ["should_not_except_violated"],
+        None,
+        {Module(name=MODULE_1): [(Module(name=MODULE_4), Module(name=MODULE_1))]},
+        id="should not import except -- false",
     ),
 ]
 
 
-@pytest.mark.parametrize("rule, expected_error_message", test_cases)
-def test_rule_violated_raises_proper_error_message(
+@pytest.mark.parametrize(
+    "imports, rule, expected_violated, strict_dependencies, lax_dependencies",
+    multiple_violations_test_cases,
+)
+def test_multiple_violations_due_to_multiple_rule_objects(
+    imports: List[AbsoluteImport],
     rule: Rule,
-    expected_error_message: str,
-    graph_based_on_string_module_names: EvaluableArchitecture,
+    expected_violated: List[str],
+    strict_dependencies: StrictDependenciesByBaseModules,
+    lax_dependencies: LaxDependenciesByBaseModule,
 ) -> None:
-    with pytest.raises(AssertionError, match=expected_error_message):
-        rule.assert_applies(graph_based_on_string_module_names)
+    evaluable = EvaluableArchitectureGraph(NetworkxGraph(ALL_MODULES, imports))
+    matcher = rule._prepare_rule_matcher()
+    violations = matcher.match(evaluable)
+
+    flags = violations._bool_field_names()
+
+    for flag in flags:
+        violated = getattr(violations, flag)
+        if flag in expected_violated:
+            assert violated
+        else:
+            assert not violated
+
+    assert violations.strict_dependencies == strict_dependencies
+    assert violations.lax_dependencies == lax_dependencies
diff --git a/tests/test_integration.py b/tests/test_integration.py
index dbe9122..94f49a3 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -5,9 +5,9 @@
 from resources.test_project.src import moduleA
 from resources.test_project.src.moduleA import submoduleA1
 
-from pytestarch.eval_structure.eval_structure_types import EvaluableArchitecture
+from pytestarch import Rule
+from pytestarch.eval_structure.evaluable_architecture import EvaluableArchitecture
 from pytestarch.pytestarch import get_evaluable_architecture
-from pytestarch.query_language.base_language import Rule
 
 # from matplotlib import pyplot as plt
 # from matplotlib.pyplot import figure
@@ -331,3 +331,1181 @@ def test_edges_correctly_calculated_for_level_3_module_path_no_external_dependen
         ("src.moduleB.submoduleB1", "src.moduleB.submoduleB1.fileB1"),
     ]:
         assert edge in level_3_graph._graph
+
+
+C = "src.moduleC"
+B = "src.moduleB"
+A = "src.moduleA"
+FILE_A2 = f"{A}.submoduleA2.fileA2"
+FILE_C = f"{C}.fileC"
+A2 = f"{A}.submoduleA2"
+A1 = f"{A}.submoduleA1"
+A11 = f"{A}.submoduleA1.submoduleA11"
+B1_MODULE = f"{B}.submoduleB1"
+B2 = f"{B}.submoduleB1.fileB2"
+
+test_cases = [
+    pytest.param(
+        Rule().modules_that().are_named(C).should().import_modules_that().are_named(A),
+        '"src.moduleC" does not import "src.moduleA".',
+        id="named should import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should()
+        .import_modules_that()
+        .are_sub_modules_of(A),
+        '"src.moduleC" does not import a sub module of "src.moduleA".',
+        id="named should import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should()
+        .import_modules_that()
+        .are_named(A),
+        'Sub modules of "src.moduleC" do not import "src.moduleA".',
+        id="submodule should import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should()
+        .import_modules_that()
+        .are_sub_modules_of(A),
+        'Sub modules of "src.moduleC" do not import a sub module of "src.moduleA".',
+        id="submodule should import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_that()
+        .are_named(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="named should only import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="named should only import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_that()
+        .are_named(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="submodule should only import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should only import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should_not()
+        .import_modules_that()
+        .are_named(A),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11',
+        id="named should not import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should_not()
+        .import_modules_that()
+        .are_sub_modules_of(A),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
+        id="named should not import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should_not()
+        .import_modules_that()
+        .are_named(A),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
+        id="submodule should not import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should_not()
+        .import_modules_that()
+        .are_sub_modules_of(A),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
+        id="submodule should not import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_named(C),
+        '"src.moduleA.submoduleA2.fileA2" does not import any module that is not "src.moduleC".',
+        id="named should import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(C),
+        '"src.moduleA.submoduleA2.fileA2" does not import any module that is not a sub module of "src.moduleC".',
+        id="named should import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_named(C),
+        'Sub modules of "src.moduleA.submoduleA2.fileA2" do not import any module that is not "src.moduleC".',
+        id="submodule should import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(C),
+        'Sub modules of "src.moduleA.submoduleA2.fileA2" do not import any module that is not a sub module of "src.moduleC".',
+        id="submodule should import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named(C),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="named should only import except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(C),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="named should only import except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named(C),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should only import except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(C),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should only import except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named(B),
+        '"src.moduleC" does not import any module that is not "src.moduleB".',
+        id="named should only import except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleC" does not import any module that is not a sub module of "src.moduleB".',
+        id="named should only import except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named(B),
+        'Sub modules of "src.moduleC" do not import any module that is not "src.moduleB".',
+        id="submodule should only import except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(B),
+        'Sub modules of "src.moduleC" do not import any module that is not a sub module of "src.moduleB".',
+        id="submodule should only import except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_named(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="named should not import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC"',
+        id="named should not import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_named(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="submodule should not import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should not import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_named(C),
+        '"src.moduleA" is not imported by "src.moduleC".',
+        id="named should be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of(C),
+        '"src.moduleA" is not imported by a sub module of "src.moduleC".',
+        id="named should be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_named(C),
+        'Sub modules of "src.moduleA" are not imported by "src.moduleC".',
+        id="submodule should be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of(C),
+        'Sub modules of "src.moduleA" are not imported by a sub module of "src.moduleC".',
+        id="submodule should be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_named(B),
+        '"src.moduleC" is not imported by "src.moduleB".',
+        id="named should only be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleC" is not imported by a sub module of "src.moduleB',
+        id="named should only be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_named(B),
+        'Sub modules of "src.moduleC" are not imported by "src.moduleB',
+        id="submodule should only be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of(B),
+        'Sub modules of "src.moduleC" are not imported by a sub module of "src.moduleB',
+        id="submodule should only be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_named(A),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="named should not be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of(A),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="named should not be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_named(A),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="submodule should not be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of(A),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="submodule should not be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(A),
+        '"src.moduleB" is not imported by any module that is not "src.moduleA".',
+        id="named should be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(A),
+        '"src.moduleB" is not imported by any module that is not a sub module of "src.moduleA".',
+        id="named should be imported by except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(A),
+        'Sub modules of "src.moduleB" are not imported by any module that is not "src.moduleA".',
+        id="submodule should be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(A),
+        'Sub modules of "src.moduleB" are not imported by any module that is not a sub module of "src.moduleA".',
+        id="submodule should be imported by except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A11)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(B2),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.fileB2".',
+        id="named should only be imported by except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A11)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(B1_MODULE),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.submoduleB11.fileB11".',
+        id="named should only be imported by except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A11)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(B2),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.fileB2".',
+        id="submodule should only be imported by except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A1)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(B1_MODULE),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.submoduleB11.fileB11".',
+        id="submodule should only be imported by except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(B),
+        '"src.moduleA" is not imported by any module that is not "src.moduleB".',
+        id="named should only be imported by except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(B),
+        '"src.moduleA" is not imported by any module that is not a sub module of "src.moduleB"',
+        id="named should only be imported by except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(B),
+        'Sub modules of "src.moduleA" are not imported by any module that is not "src.moduleB"',
+        id="submodule should only be imported by except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(B),
+        'Sub modules of "src.moduleA" are not imported by any module that is not a sub module of "src.moduleB"',
+        id="submodule should only be imported by except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(FILE_A2),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".',
+        id="named should not be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(A2),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".',
+        id="named should not be imported by except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_named(A2),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".',
+        id="submodule should not be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of(FILE_A2),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".'
+        + os.sep
+        + '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="submodule should not be imported by except submodule",
+    ),
+]
+
+
+@pytest.mark.parametrize("rule, expected_error_message", test_cases)
+def test_rule_violated_raises_proper_error_message(
+    rule: Rule,
+    expected_error_message: str,
+    graph_based_on_string_module_names: EvaluableArchitecture,
+) -> None:
+    with pytest.raises(AssertionError, match=expected_error_message):
+        rule.assert_applies(graph_based_on_string_module_names)
+
+
+multiple_rule_objects_test_cases = [
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should()
+        .import_modules_that()
+        .are_named([A, B]),
+        '"src.moduleC" does not import "src.moduleA", "src.moduleB".',
+        id="named should import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should()
+        .import_modules_that()
+        .are_sub_modules_of([A, B]),
+        '"src.moduleC" does not import a sub module of "src.moduleA", a sub module of "src.moduleB".',
+        id="named should import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should()
+        .import_modules_that()
+        .are_named([A]),
+        'Sub modules of "src.moduleC" do not import "src.moduleA".',
+        id="submodule should import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should()
+        .import_modules_that()
+        .are_sub_modules_of([A]),
+        'Sub modules of "src.moduleC" do not import a sub module of "src.moduleA".',
+        id="submodule should import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_that()
+        .are_named([B, B2]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="named should only import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_that()
+        .are_sub_modules_of([B, B2]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="named should only import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_that()
+        .are_named([B]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="submodule should only import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_that()
+        .are_sub_modules_of([B]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should only import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should_not()
+        .import_modules_that()
+        .are_named([A, C]),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11',
+        id="named should not import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should_not()
+        .import_modules_that()
+        .are_sub_modules_of([A, C]),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
+        id="named should not import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should_not()
+        .import_modules_that()
+        .are_named([A]),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
+        id="submodule should not import named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should_not()
+        .import_modules_that()
+        .are_sub_modules_of([A]),
+        '"src.moduleB.submoduleB1.submoduleB11.fileB11" imports "src.moduleA.submoduleA1.submoduleA11.fileA11".',
+        id="submodule should not import submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_named([C, FILE_C]),
+        '"src.moduleA.submoduleA2.fileA2" does not import any module that is not "src.moduleC".',
+        id="named should import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([C, B]),
+        '"src.moduleA.submoduleA2.fileA2" does not import any module that is not a sub module of "src.moduleC", a sub module of "src.moduleB".',
+        id="named should import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_named([C]),
+        'Sub modules of "src.moduleA.submoduleA2.fileA2" do not import any module that is not "src.moduleC".',
+        id="submodule should import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(FILE_A2)
+        .should()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([C]),
+        'Sub modules of "src.moduleA.submoduleA2.fileA2" do not import any module that is not a sub module of "src.moduleC".',
+        id="submodule should import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named([C, B]),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="named should only import except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([C, B]),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="named should only import except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named([C]),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should only import except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([C]),
+        '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should only import except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named([B, B2]),
+        '"src.moduleC" does not import any module that is not "src.moduleB".',
+        id="named should only import except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([B, B2]),
+        '"src.moduleC" does not import any module that is not a sub module of "src.moduleB".',
+        id="named should only import except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_named([B]),
+        'Sub modules of "src.moduleC" do not import any module that is not "src.moduleB".',
+        id="submodule should only import except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([B]),
+        'Sub modules of "src.moduleC" do not import any module that is not a sub module of "src.moduleB".',
+        id="submodule should only import except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_named([B, B2]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="named should not import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([B, B2]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC"',
+        id="named should not import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_named([B]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".',
+        id="submodule should not import except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_not()
+        .import_modules_except_modules_that()
+        .are_sub_modules_of([B]),
+        '"src.moduleA.fileA" imports "src.moduleC.fileC".'
+        + os.sep
+        + '"src.moduleA.submoduleA2.fileA2" imports "src.moduleC.fileC".',
+        id="submodule should not import except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_named([C, FILE_C]),
+        '"src.moduleA" is not imported by "src.moduleC", "src.moduleC.fileC".',
+        id="named should be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of([C, FILE_C]),
+        '"src.moduleA" is not imported by a sub module of "src.moduleC", a sub module of "src.moduleC.fileC".',
+        id="named should be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_named([C]),
+        'Sub modules of "src.moduleA" are not imported by "src.moduleC".',
+        id="submodule should be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of([C]),
+        'Sub modules of "src.moduleA" are not imported by a sub module of "src.moduleC".',
+        id="submodule should be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_named([B, B2]),
+        '"src.moduleC" is not imported by "src.moduleB", "src.moduleB.submoduleB1.fileB2".',
+        id="named should only be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of([B]),
+        '"src.moduleC" is not imported by a sub module of "src.moduleB',
+        id="named should only be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_named([B]),
+        'Sub modules of "src.moduleC" are not imported by "src.moduleB',
+        id="submodule should only be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_only()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of([B]),
+        'Sub modules of "src.moduleC" are not imported by a sub module of "src.moduleB',
+        id="submodule should only be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_named([A, FILE_A2]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".'
+        + os.sep
+        + '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="named should not be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of([A]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="named should not be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_named([A]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="submodule should not be imported by named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_that()
+        .are_sub_modules_of([A]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="submodule should not be imported by submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([A, A2]),
+        '"src.moduleB" is not imported by any module that is not "src.moduleA", "src.moduleA.submoduleA2".',
+        id="named should be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([A]),
+        '"src.moduleB" is not imported by any module that is not a sub module of "src.moduleA".',
+        id="named should be imported by except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([A]),
+        'Sub modules of "src.moduleB" are not imported by any module that is not "src.moduleA".',
+        id="submodule should be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(B)
+        .should()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([A]),
+        'Sub modules of "src.moduleB" are not imported by any module that is not a sub module of "src.moduleA".',
+        id="submodule should be imported by except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A11)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([B2, B]),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.fileB2".',
+        id="named should only be imported by except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A11)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([B1_MODULE, B]),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.submoduleB11.fileB11".',
+        id="named should only be imported by except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A11)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([B2]),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.fileB2".',
+        id="submodule should only be imported by except named - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A1)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([B1_MODULE]),
+        '"src.moduleA.submoduleA1.submoduleA11.fileA11" is imported by "src.moduleB.submoduleB1.submoduleB11.fileB11".',
+        id="submodule should only be imported by except submodule - forbidden import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([B, B2]),
+        '"src.moduleA" is not imported by any module that is not "src.moduleB", "src.moduleB.submoduleB1.',
+        id="named should only be imported by except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([B]),
+        '"src.moduleA" is not imported by any module that is not a sub module of "src.moduleB"',
+        id="named should only be imported by except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([B]),
+        'Sub modules of "src.moduleA" are not imported by any module that is not "src.moduleB"',
+        id="submodule should only be imported by except named - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(A)
+        .should_only()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([B]),
+        'Sub modules of "src.moduleA" are not imported by any module that is not a sub module of "src.moduleB"',
+        id="submodule should only be imported by except submodule - no import",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([FILE_A2, B]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".',
+        id="named should not be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_named(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([A2]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".',
+        id="named should not be imported by except submodule",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_named([A2]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".',
+        id="submodule should not be imported by except named",
+    ),
+    pytest.param(
+        Rule()
+        .modules_that()
+        .are_sub_modules_of(C)
+        .should_not()
+        .be_imported_by_modules_except_modules_that()
+        .are_sub_modules_of([FILE_A2]),
+        '"src.moduleC.fileC" is imported by "src.moduleA.fileA".'
+        + os.sep
+        + '"src.moduleC.fileC" is imported by "src.moduleA.submoduleA2.fileA2".',
+        id="submodule should not be imported by except submodule",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "rule, expected_error_message", multiple_rule_objects_test_cases
+)
+def test_rule_violated_with_multiple_rule_objects_raises_proper_error_message(
+    rule: Rule,
+    expected_error_message: str,
+    graph_based_on_string_module_names: EvaluableArchitecture,
+) -> None:
+    with pytest.raises(AssertionError, match=expected_error_message):
+        rule.assert_applies(graph_based_on_string_module_names)