baldertest 0.1.0b10__py3-none-any.whl → 0.1.0b12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. _balder/_version.py +1 -1
  2. _balder/cnnrelations/__init__.py +7 -0
  3. _balder/cnnrelations/and_connection_relation.py +149 -0
  4. _balder/cnnrelations/base_connection_relation.py +270 -0
  5. _balder/cnnrelations/or_connection_relation.py +65 -0
  6. _balder/collector.py +10 -16
  7. _balder/connection.py +400 -881
  8. _balder/connection_metadata.py +255 -0
  9. _balder/controllers/device_controller.py +37 -16
  10. _balder/controllers/feature_controller.py +63 -99
  11. _balder/controllers/normal_scenario_setup_controller.py +5 -5
  12. _balder/controllers/scenario_controller.py +6 -6
  13. _balder/controllers/setup_controller.py +2 -3
  14. _balder/decorator_connect.py +12 -10
  15. _balder/decorator_for_vdevice.py +17 -25
  16. _balder/decorator_gateway.py +3 -3
  17. _balder/executor/testcase_executor.py +0 -1
  18. _balder/executor/variation_executor.py +212 -199
  19. _balder/feature.py +1 -1
  20. _balder/feature_replacement_mapping.py +69 -0
  21. _balder/feature_vdevice_mapping.py +88 -0
  22. _balder/fixture_manager.py +10 -9
  23. _balder/objects/connections/osi_3_network.py +2 -2
  24. _balder/objects/connections/osi_4_transport.py +2 -2
  25. _balder/routing_path.py +27 -31
  26. _balder/solver.py +1 -1
  27. _balder/testresult.py +1 -1
  28. _balder/utils.py +27 -1
  29. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/METADATA +2 -2
  30. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/RECORD +34 -27
  31. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/WHEEL +1 -1
  32. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/LICENSE +0 -0
  33. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/entry_points.txt +0 -0
  34. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,255 @@
1
+ from __future__ import annotations
2
+ from typing import Union, Type, Tuple
3
+ from .device import Device
4
+
5
+
6
+ class ConnectionMetadata:
7
+ """
8
+ Describes the metadata of a connection.
9
+ """
10
+
11
+ def __init__(
12
+ self,
13
+ from_device: Union[Type[Device], None] = None,
14
+ to_device: Union[Type[Device], None] = None,
15
+ from_device_node_name: Union[str, None] = None,
16
+ to_device_node_name: Union[str, None] = None,
17
+ bidirectional: bool = True,
18
+ ):
19
+
20
+ self._from_device = None
21
+ self._from_device_node_name = None
22
+ self.set_from(from_device, from_device_node_name)
23
+
24
+ self._to_device = None
25
+ self._to_device_node_name = None
26
+ self.set_to(to_device, to_device_node_name)
27
+
28
+ if not ((from_device is None and to_device is None and from_device_node_name is None
29
+ and to_device_node_name is None) or (
30
+ from_device is not None and to_device is not None and from_device_node_name is not None and
31
+ to_device_node_name is not None)):
32
+ raise ValueError(
33
+ "you have to provide all or none of the following items: `from_device`, `from_device_node_name`, "
34
+ "`to_device` or `to_device_node_name`")
35
+
36
+ # describes if the connection is uni or bidirectional
37
+ self._bidirectional = bidirectional
38
+
39
+ def __eq__(self, other: ConnectionMetadata):
40
+ return self.equal_with(other)
41
+
42
+ def __hash__(self):
43
+ all_hashes = hash(self._from_device) + hash(self._to_device) + hash(self._from_device_node_name) + \
44
+ hash(self._to_device_node_name) + hash(self._bidirectional)
45
+ return hash(all_hashes)
46
+
47
+ def __compare_with(self, other: ConnectionMetadata, allow_single_unidirectional_for_both_directions: bool) -> bool:
48
+ """
49
+ This method checks, if the metadata of this object is the metadata of the other object.
50
+
51
+ The method returns true in the following situations:
52
+ * both connections are bidirectional / the FROM and TO elements (device and node name) are the same
53
+ * both connections are bidirectional / the FROM is the TO and the TO is the FROM
54
+ * both connections are unidirectional and have the same from and to elements
55
+
56
+ If the parameter `allow_single_unidirectional_for_both_directions` is True, it additionally checks the following
57
+ situations:
58
+ * one is unidirectional / the other is bidirectional / the FROM and TO elements are the same
59
+ * one is unidirectional / the other is bidirectional / the FROM is the TO and the TO is the FROM
60
+ """
61
+ def check_same() -> bool:
62
+ return (self.from_device == other.from_device and self.from_node_name == other.from_node_name and
63
+ self.to_device == other.to_device and self.to_node_name == other.to_node_name)
64
+
65
+ def check_twisted() -> bool:
66
+ return (self.from_device == other.to_device and self.from_node_name == other.to_node_name and
67
+ self.to_device == other.from_device and self.to_node_name == other.from_node_name)
68
+
69
+ # CHECK: both connections are bidirectional / the FROM and TO elements (device and node name) are the same
70
+ # CHECK: both connections are bidirectional / the FROM is the TO and the TO is the FROM
71
+ if self.bidirectional and other.bidirectional:
72
+ return check_same() or check_twisted()
73
+ # CHECK: both connections are unidirectional and have the same from and to elements
74
+ if not self.bidirectional and not other.bidirectional:
75
+ return check_same()
76
+
77
+ if allow_single_unidirectional_for_both_directions:
78
+ # CHECK: one is unidirectional / the other is bidirectional / the FROM and TO elements are the same
79
+ # CHECK: one is unidirectional / the other is bidirectional / the FROM is the TO and the TO is the FROM
80
+ if self.bidirectional and not other.bidirectional or not self.bidirectional and other.bidirectional:
81
+ return check_same() or check_twisted()
82
+ return False
83
+
84
+ def set_from(self, from_device: Union[Type[Device], None], from_device_node_name: Union[str, None] = None):
85
+ """
86
+ This method sets the FROM device and node for this connection.
87
+
88
+ :param from_device: The FROM device of this connection.
89
+ :param from_device_node_name: The FROM node of this connection (if it should be set, otherwise None).
90
+ """
91
+ if from_device is not None and isinstance(from_device, type) and not issubclass(from_device, Device):
92
+ raise TypeError(f"detect illegal argument element {str(from_device)} for given attribute "
93
+ f"`from_device` - should be a subclasses of `balder.Device`")
94
+ self._from_device = from_device
95
+
96
+ if from_device_node_name is not None and not isinstance(from_device_node_name, str):
97
+ raise TypeError(f"detect illegal argument type {type(from_device_node_name)} for given attribute "
98
+ f"`from_device_node_name` - should be a string value")
99
+ self._from_device_node_name = from_device_node_name
100
+
101
+ def set_to(self, to_device: Union[Type[Device], None], to_device_node_name: Union[str, None] = None):
102
+ """
103
+ This method sets the TO device and node of this connection.
104
+
105
+ :param to_device: The TO device of this connection.
106
+ :param to_device_node_name: The TO node of this connection (if it should be set, otherwise None).
107
+ """
108
+ if to_device is not None and isinstance(to_device, type) and not issubclass(to_device, Device):
109
+ raise TypeError(f"detect illegal argument element {str(to_device)} for given attribute "
110
+ f"`to_device` - should be a subclasses of `balder.Device`")
111
+ self._to_device = to_device
112
+
113
+ if to_device_node_name is not None and not isinstance(to_device_node_name, str):
114
+ raise TypeError(f"detect illegal argument type {type(to_device_node_name)} for given attribute "
115
+ f"`to_device_node_name` - should be a string value")
116
+ self._to_device_node_name = to_device_node_name
117
+
118
+ def get_conn_partner_of(self, device: Type[Device], node: Union[str, None] = None) -> Tuple[Type[Device], str]:
119
+ """
120
+ This method returns the connection partner of this connection - it always returns the other not given side
121
+
122
+ :param device: the device itself - the other will be returned
123
+
124
+ :param node: the node name of the device itself (only required if the connection starts and ends with the same
125
+ device)
126
+ """
127
+ if device not in (self.from_device, self.to_device):
128
+ raise ValueError(f"the given device `{device.__qualname__}` is no component of this connection")
129
+ if node is None:
130
+ # check that the from_device and to_device are not the same
131
+ if self.from_device == self.to_device:
132
+ raise ValueError("the connection is a inner-device connection (start and end is the same device) - you "
133
+ "have to provide the `node` string too")
134
+ if device == self.from_device:
135
+ return self.to_device, self.to_node_name
136
+
137
+ return self.from_device, self.from_node_name
138
+
139
+ if node not in (self.from_node_name, self.to_node_name):
140
+ raise ValueError(f"the given node `{node}` is no component of this connection")
141
+
142
+ if device == self.from_device and node == self.from_node_name:
143
+ return self.to_device, self.to_node_name
144
+
145
+ if device == self.to_device and node == self.to_node_name:
146
+ return self.from_device, self.from_node_name
147
+
148
+ raise ValueError(f"the given node `{node}` is no component of the given device `{device.__qualname__}`")
149
+
150
+ def has_connection_from_to(
151
+ self,
152
+ start_device: Type[Device],
153
+ start_device_node_name: Union[str, None] = None,
154
+ end_device: Union[Type[Device], None] = None,
155
+ end_device_node_name: Union[str, None] = None,
156
+ ) -> bool:
157
+ """
158
+ This method checks if there is a connection from ``start_device`` to ``end_device``. This will return
159
+ true if the ``start_device`` and ``end_device`` given in this method are also the ``start_device`` and
160
+ ``end_device`` mentioned in this connection object. If this is a bidirectional connection, ``start_device`` and
161
+ ``end_device`` can switch places.
162
+
163
+
164
+ :param start_device: the device for which the method should check whether it is a communication partner (for
165
+ non-bidirectional connection, this has to be the start device)
166
+ :param start_device_node_name: the node name that start device should have or None if it should be ignored
167
+ :param end_device: the other device for which the method should check whether it is a communication partner (for
168
+ non-bidirectional connection, this has to be the end device - this is optional if only the
169
+ start device should be checked)
170
+ :param end_device_node_name: the node name that start device should have or None if it should be ignored
171
+
172
+ :return: returns true if the given direction is possible
173
+ """
174
+ if not (isinstance(start_device, type) and issubclass(start_device, Device)):
175
+ raise TypeError("argument `start_device` needs to be a device")
176
+ if not (start_device_node_name is None or isinstance(start_device_node_name, str)):
177
+ raise TypeError("argument `start_device_node_name` needs to be a string or None if it should be ignored")
178
+ if not (end_device is None or isinstance(end_device, type) and issubclass(end_device, Device)):
179
+ raise TypeError("argument `end_device` needs to be a device or None if it should be ignored")
180
+ if not (end_device_node_name is None or isinstance(end_device_node_name, str)):
181
+ raise TypeError("argument `end_device_node_name` needs to be a string or None if it should be ignored")
182
+
183
+ def check(start_dev, start_node_name, end_dev, end_node_name):
184
+ if start_device != start_dev:
185
+ return False
186
+ if start_device_node_name is not None and start_device_node_name != start_node_name:
187
+ return False
188
+ if end_device is not None and end_device != end_dev:
189
+ return False
190
+ if end_device_node_name is not None and end_device_node_name != end_node_name:
191
+ return False
192
+ return True
193
+
194
+ if self.bidirectional:
195
+ if check(start_dev=self.to_device, start_node_name=self.to_node_name,
196
+ end_dev=self.from_device, end_node_name=self.from_node_name):
197
+ return True
198
+ return check(start_dev=self.from_device, start_node_name=self.from_node_name,
199
+ end_dev=self.to_device, end_node_name=self.to_node_name)
200
+
201
+ def equal_with(self, other: ConnectionMetadata) -> bool:
202
+ """
203
+ This method returns true if the metadata of the current connection is equal with the metadata of the given
204
+ connection.
205
+
206
+ The method returns true in the following situations:
207
+ * both connections are bidirectional and the from and to elements (device and node name) are the same
208
+ * both connections are unidirectional and have the same from and to elements
209
+ * both connections are bidirectional and the from is the to and the to is the from
210
+
211
+ :return: true if the metadata of the current connection is contained in the metadata of the given one
212
+ """
213
+ return self.__compare_with(other, allow_single_unidirectional_for_both_directions=False)
214
+
215
+ def contained_in(self, other: ConnectionMetadata) -> bool:
216
+ """
217
+ This method returns true if the metadata of the current connection is contained in the given one.
218
+
219
+ The method returns true in the following situations:
220
+ * both connections are bidirectional and the from and to elements (device and node name) are the same
221
+ * both connections are unidirectional and have the same from and to elements
222
+ * both connections are bidirectional and the from is the to and the to is the from
223
+ * one connection is unidirectional and the other is bidirectional and the from and to elements are the same
224
+ * one connection is unidirectional and the other is bidirectional and the from is the to and the to is the from
225
+
226
+ :return: true if the metadata of the current connection is contained in the metadata of the given one
227
+ """
228
+ return self.__compare_with(other, allow_single_unidirectional_for_both_directions=True)
229
+
230
+ @property
231
+ def from_device(self):
232
+ """device from which the connection starts"""
233
+ return self._from_device
234
+
235
+ @property
236
+ def to_device(self):
237
+ """device at which the connection ends"""
238
+ return self._to_device
239
+
240
+ @property
241
+ def from_node_name(self):
242
+ """the name of the node in the `Device` from which the connection starts"""
243
+ return self._from_device_node_name
244
+
245
+ @property
246
+ def to_node_name(self):
247
+ """the name of the node in the `Device` at which the connection ends"""
248
+ return self._to_device_node_name
249
+
250
+ @property
251
+ def bidirectional(self) -> bool:
252
+ """
253
+ returns true if the connection is bidirectional (can go in both directions) otherwise false
254
+ """
255
+ return self._bidirectional
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
- from typing import Dict, List, Type, Tuple, Union, TYPE_CHECKING
2
+
3
+ import copy
4
+ from typing import Dict, List, Type, Union, TYPE_CHECKING
3
5
 
4
6
  import sys
5
7
  import logging
@@ -16,6 +18,7 @@ from _balder.exceptions import DeviceScopeError, DeviceResolvingException, Inner
16
18
  if TYPE_CHECKING:
17
19
  from _balder.connection import Connection
18
20
  from _balder.controllers import ScenarioController, SetupController
21
+ from _balder.node_gateway import NodeGateway
19
22
 
20
23
  logger = logging.getLogger(__file__)
21
24
 
@@ -61,6 +64,8 @@ class DeviceController(BaseDeviceController, ABC):
61
64
  #: describes the absolute connections from the related device to another device
62
65
  self._absolute_connections: Dict[Type[Device], List[Connection]] = {}
63
66
 
67
+ self._gateways: List[NodeGateway] = []
68
+
64
69
  # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
65
70
 
66
71
  @staticmethod
@@ -146,6 +151,17 @@ class DeviceController(BaseDeviceController, ABC):
146
151
 
147
152
  self._connections[own_node].append(connection)
148
153
 
154
+ def add_new_raw_gateway(self, gateway: NodeGateway):
155
+ """
156
+ This method adds a new raw gateway to the internal property `_gateways`.
157
+
158
+ :param gateway: the gateway object (the related device has to be part of it)
159
+ """
160
+ if gateway.device != self.related_cls:
161
+ raise ValueError("the given gateway does not have the current device as component")
162
+
163
+ self._gateways.append(gateway)
164
+
149
165
  def add_new_absolute_connection(self, connection: Connection):
150
166
  """
151
167
  This method adds a new absolute connection to the internal property `absolute_connections`.
@@ -257,7 +273,7 @@ class DeviceController(BaseDeviceController, ABC):
257
273
  # for cur_gateway in self._gateways:
258
274
  # cur_gateway.validate_given_node_names()
259
275
 
260
- def get_node_types(self) -> Dict[str, List[Connection, Tuple[Connection]]]:
276
+ def get_node_types(self) -> Dict[str, List[Connection | None]]:
261
277
  """
262
278
  This method returns a dictionary with the node name as key and a connection class as value. This class
263
279
  describes the common connection sub-tree, that all incoming and outgoing connections of the related device have
@@ -342,16 +358,16 @@ class DeviceController(BaseDeviceController, ABC):
342
358
  if cur_conn.to_device in all_inner_classes_of_outer.keys():
343
359
  meta = cur_conn.metadata
344
360
 
345
- meta["to_device"] = all_inner_classes_of_outer[cur_conn.to_device]
346
- if meta["to_device_node_name"] is None:
347
- # no unique node was given -> create one
348
- meta["to_device_node_name"] = \
349
- DeviceController.get_for(meta["to_device"]).get_new_empty_auto_node()
361
+ to_device = all_inner_classes_of_outer[cur_conn.to_device]
362
+ # if there was given no unique node -> create one
363
+ to_device_node_name = DeviceController.get_for(to_device).get_new_empty_auto_node() \
364
+ if meta.to_node_name is None else meta.to_node_name
365
+
366
+ meta.set_to(
367
+ to_device=to_device,
368
+ to_device_node_name=to_device_node_name)
350
369
 
351
- # first reset whole metadata
352
- cur_conn.metadata = {}
353
- # now set metadata
354
- cur_conn.metadata = meta
370
+ cur_conn.set_metadata_for_all_subitems(meta)
355
371
  else:
356
372
  raise DeviceResolvingException(
357
373
  f"cannot resolve the str for the given device class `{cur_conn.to_device}` for "
@@ -429,10 +445,15 @@ class DeviceController(BaseDeviceController, ABC):
429
445
  if all_instanced_features is None:
430
446
  # has no features -> skip
431
447
  return
432
- for _, cur_feature in all_instanced_features.items():
433
- if cur_feature.active_vdevices != {}:
448
+ for cur_attr_name, cur_feature in all_instanced_features.items():
449
+ # clone feature and its active_device dict to make sure that shared instances in parent classes are handled
450
+ # correctly
451
+ new_feature = copy.copy(cur_feature)
452
+ new_feature.active_vdevices = {**cur_feature.active_vdevices}
453
+ setattr(self.related_cls, cur_attr_name, new_feature)
454
+ if new_feature.active_vdevices != {}:
434
455
  # do something only if there exists an internal mapping
435
- for cur_mapped_vdevice, cur_mapped_device in cur_feature.active_vdevices.items():
456
+ for cur_mapped_vdevice, cur_mapped_device in new_feature.active_vdevices.items():
436
457
  if isinstance(cur_mapped_device, str):
437
458
  resolved_device = \
438
459
  scenario_or_setup_controller.get_inner_device_class_by_string(cur_mapped_device)
@@ -440,5 +461,5 @@ class DeviceController(BaseDeviceController, ABC):
440
461
  raise RuntimeError(
441
462
  f"found no possible matching name while trying to resolve "
442
463
  f"the given vDevice string `{cur_mapped_vdevice}` in feature "
443
- f"`{cur_feature.__class__.__name__}`")
444
- cur_feature.active_vdevices[cur_mapped_vdevice] = resolved_device
464
+ f"`{new_feature.__class__.__name__}`")
465
+ new_feature.active_vdevices[cur_mapped_vdevice] = resolved_device
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
+
3
+ import copy
2
4
  from typing import Type, Dict, Union, List, Callable, Tuple
3
5
 
4
6
  import logging
5
7
  import inspect
8
+ from _balder.cnnrelations import OrConnectionRelation
6
9
  from _balder.device import Device
7
10
  from _balder.vdevice import VDevice
8
11
  from _balder.feature import Feature
@@ -43,16 +46,16 @@ class FeatureController(Controller):
43
46
  self._related_cls = related_cls
44
47
 
45
48
  #: holds the defined **Class-Based-Binding** for the related feature class sorted by VDevice types
46
- self._cls_for_vdevice: Dict[Type[VDevice], List[Connection, Type[Connection]]] = {}
49
+ self._cls_for_vdevice: Dict[Type[VDevice], Connection] = {}
47
50
 
48
51
  #: holds the absolute calculated **Class-Based-Binding** for the related feature class sorted by VDevice types
49
52
  #: (will be calculated by :meth:`FeatureController.determine_absolute_class_based_for_vdevice`, which will be
50
53
  #: called during collecting)
51
- self._abs_cls_for_vdevice: Union[Dict[Type[VDevice], List[Connection]], None] = None
54
+ self._abs_cls_for_vdevice: Union[Dict[Type[VDevice], Connection], None] = None
52
55
 
53
56
  #: contains the **Method-Based-Binding** information for the current feature type (will be automatically set by
54
57
  #: executor)
55
- self._for_vdevice: Union[Dict[str, Dict[Callable, Dict[Type[VDevice], List[Connection]]]], None] = None
58
+ self._for_vdevice: Union[Dict[str, Dict[Callable, Dict[Type[VDevice], Connection]]], None] = None
56
59
 
57
60
  #: contains the original defined :class:`VDevice` objects for this feature (will be automatically set by
58
61
  #: :class:`Collector`)
@@ -90,14 +93,13 @@ class FeatureController(Controller):
90
93
  # now check if a definition for this class exists
91
94
  all_vdevices = self.get_abs_inner_vdevice_classes()
92
95
  # check the class based @for_vdevice and check the used vDevice classes here
93
- if self.get_class_based_for_vdevice() is not None:
94
- for cur_decorated_vdevice in self.get_class_based_for_vdevice().keys():
95
- if cur_decorated_vdevice not in all_vdevices:
96
- raise VDeviceResolvingError(
97
- f"you assign a vDevice to the class based decorator `@for_vdevice()` of the feature class "
98
- f"`{self.related_cls.__name__}` which is no direct member of this feature - note that you have "
99
- f"to define the vDevice in your feature before using it in the decorator - if necessary "
100
- f"overwrite it")
96
+ for cur_decorated_vdevice in self.get_class_based_for_vdevice().keys():
97
+ if cur_decorated_vdevice not in all_vdevices:
98
+ raise VDeviceResolvingError(
99
+ f"you assign a vDevice to the class based decorator `@for_vdevice()` of the feature class "
100
+ f"`{self.related_cls.__name__}` which is no direct member of this feature - note that you have "
101
+ f"to define the vDevice in your feature before using it in the decorator - if necessary "
102
+ f"overwrite it")
101
103
  # check the method based @for_vdevice and check the used vDevice classes here
102
104
  if self.get_method_based_for_vdevice() is not None:
103
105
  for cur_method_name, cur_method_data in self.get_method_based_for_vdevice().items():
@@ -143,12 +145,18 @@ class FeatureController(Controller):
143
145
  VDevice
144
146
  :return: a dictionary that holds all available method variation that matches here
145
147
  """
148
+ all_vdevice_method_variations = self.get_method_based_for_vdevice()
149
+
150
+ if all_vdevice_method_variations is None:
151
+ raise ValueError("the current feature has no method variations")
152
+
153
+ if of_method_name not in all_vdevice_method_variations.keys():
154
+ raise ValueError(f"can not find the method `{of_method_name}` in method variation data dictionary")
155
+
146
156
  all_possible_method_variations = {}
147
- for cur_impl_method, cur_method_impl_dict in self.get_method_based_for_vdevice()[of_method_name].items():
157
+ for cur_impl_method, cur_method_impl_dict in all_vdevice_method_variations[of_method_name].items():
148
158
  if for_vdevice in cur_method_impl_dict.keys():
149
- cur_impl_method_cnns = []
150
- for cur_cnn in cur_method_impl_dict[for_vdevice]:
151
- cur_impl_method_cnns += cur_cnn.get_singles()
159
+ cur_impl_method_cnns = cur_method_impl_dict[for_vdevice].get_singles()
152
160
  for cur_single_impl_method_cnn in cur_impl_method_cnns:
153
161
  if cur_single_impl_method_cnn.contained_in(with_connection, ignore_metadata=True):
154
162
  # this variation is possible
@@ -158,29 +166,19 @@ class FeatureController(Controller):
158
166
  # COMBINE IT if it is already available
159
167
  else:
160
168
  all_possible_method_variations[cur_impl_method] = Connection.based_on(
161
- all_possible_method_variations[cur_impl_method], cur_single_impl_method_cnn)
169
+ OrConnectionRelation(all_possible_method_variations[cur_impl_method],
170
+ cur_single_impl_method_cnn))
162
171
  return all_possible_method_variations
163
172
 
164
-
165
173
  # ---------------------------------- METHODS -----------------------------------------------------------------------
166
174
 
167
- def get_class_based_for_vdevice(self) -> Union[Dict[Type[VDevice], List[Connection]], None]:
175
+ def get_class_based_for_vdevice(self) -> Dict[Type[VDevice], Connection]:
168
176
  """
169
- This method returns the class based data for the `@for_vdevice` decorator or None, if there is no decorator
170
- given
177
+ This method returns the class based data for the `@for_vdevice` decorator.
171
178
  """
172
- result = {}
173
-
174
- for cur_device, cnn_list in self._cls_for_vdevice.items():
175
- result[cur_device] = []
176
- for cur_cnn in cnn_list:
177
- if isinstance(cur_cnn, type) and issubclass(cur_cnn, Connection):
178
- result[cur_device].append(cur_cnn())
179
- else:
180
- result[cur_device].append(cur_cnn)
181
- return result
179
+ return copy.copy(self._cls_for_vdevice)
182
180
 
183
- def get_abs_class_based_for_vdevice(self) -> Dict[Type[VDevice], List[Union[Connection]]]:
181
+ def get_abs_class_based_for_vdevice(self) -> Dict[Type[VDevice], Connection]:
184
182
  """
185
183
  This method returns the absolute calculated class-based-for-vdevice data for this feature.
186
184
  """
@@ -188,8 +186,7 @@ class FeatureController(Controller):
188
186
  raise RuntimeError('can not access the absolute class based for-vdevices because they are not set yet')
189
187
  return self._abs_cls_for_vdevice
190
188
 
191
- def set_class_based_for_vdevice(
192
- self, data: Union[Dict[Type[VDevice], List[Union[Connection, Type[Connection]]]], None]):
189
+ def set_class_based_for_vdevice(self, data: Dict[Type[VDevice], Connection]):
193
190
  """
194
191
  This method allows to set the data of the class based `@for_vdevice` decorator.
195
192
  """
@@ -233,10 +230,9 @@ class FeatureController(Controller):
233
230
  all_vdevices = self.get_abs_inner_vdevice_classes()
234
231
 
235
232
  cls_based_for_vdevice = self.get_class_based_for_vdevice()
236
- cls_based_for_vdevice = {} if cls_based_for_vdevice is None else cls_based_for_vdevice
237
233
  for cur_vdevice in all_vdevices:
238
234
  # determine the class based for_vdevice value only if there is no one defined for this vDevice
239
- if cur_vdevice in cls_based_for_vdevice.keys() and len(cls_based_for_vdevice[cur_vdevice]) > 0:
235
+ if cur_vdevice in cls_based_for_vdevice.keys():
240
236
  # there already exists a definition for this vDevice -> IGNORE
241
237
  continue
242
238
 
@@ -263,13 +259,11 @@ class FeatureController(Controller):
263
259
  if vdevice_of_interest is not None and next_parent_feat is not None:
264
260
  next_parent_feat_cls_based_for_vdevice = \
265
261
  FeatureController.get_for(next_parent_feat).get_abs_class_based_for_vdevice()
266
- next_parent_feat_cls_based_for_vdevice = {} if next_parent_feat_cls_based_for_vdevice is None else \
267
- next_parent_feat_cls_based_for_vdevice
268
262
  if vdevice_of_interest in next_parent_feat_cls_based_for_vdevice.keys():
269
- for cur_cnn in next_parent_feat_cls_based_for_vdevice[vdevice_of_interest]:
270
- # clean metadata here because this is no connection between real devices
271
- cur_cnn.set_metadata_for_all_subitems(None)
272
- parent_values.append(cur_cnn)
263
+ cnn = next_parent_feat_cls_based_for_vdevice[vdevice_of_interest]
264
+ # clean metadata here because this is no connection between real devices
265
+ cnn.set_metadata_for_all_subitems(None)
266
+ parent_values.append(cnn)
273
267
 
274
268
  this_vdevice_intersection = parent_values
275
269
 
@@ -289,12 +283,12 @@ class FeatureController(Controller):
289
283
  f'{", ".join([cur_cnn.get_tree_str() for cur_cnn in this_vdevice_intersection])})\n\n')
290
284
 
291
285
  # set the determined data into the class based `@for_vdevice` class property
292
- cls_based_for_vdevice[cur_vdevice] = this_vdevice_intersection
286
+ cls_based_for_vdevice[cur_vdevice] = Connection.based_on(OrConnectionRelation(*this_vdevice_intersection))
293
287
 
294
288
  self._abs_cls_for_vdevice = cls_based_for_vdevice
295
289
 
296
290
  def get_method_based_for_vdevice(self) -> \
297
- Union[Dict[str, Dict[Callable, Dict[Type[VDevice], List[Connection]]]], None]:
291
+ Union[Dict[str, Dict[Callable, Dict[Type[VDevice], Connection]]], None]:
298
292
  """
299
293
  This method returns the method based data for the `@for_vdevice` decorator or None, if there is no decorator
300
294
  given
@@ -302,16 +296,19 @@ class FeatureController(Controller):
302
296
  return self._for_vdevice
303
297
 
304
298
  def set_method_based_for_vdevice(
305
- self, data: Union[Dict[str, Dict[Callable, Dict[Type[VDevice], List[Connection]]]], None]):
299
+ self, data: Union[Dict[str, Dict[Callable, Dict[Type[VDevice], Connection]]], None]):
306
300
  """
307
301
  This method allows to set the data for the method based `@for_vdevice` decorator.
308
302
  """
309
303
  self._for_vdevice = data
310
304
 
311
305
  def get_method_variation(
312
- self, of_method_name: str, for_vdevice: Type[VDevice],
313
- with_connection: Union[Connection, Tuple[Connection]], ignore_no_findings: bool = False) \
314
- -> Union[Callable, None]:
306
+ self,
307
+ of_method_name: str,
308
+ for_vdevice: Type[VDevice],
309
+ with_connection: Connection,
310
+ ignore_no_findings: bool = False
311
+ ) -> Union[Callable, None]:
315
312
  """
316
313
  This method searches for the unique possible method variation and returns it. In its search, the method also
317
314
  includes the parent classes of the related feature element of this controller.
@@ -342,17 +339,6 @@ class FeatureController(Controller):
342
339
  :return: the method variation callable for the given data (or none, if the method does not exist in this object
343
340
  or in a parent class of it)
344
341
  """
345
-
346
- all_vdevice_method_variations = self.get_method_based_for_vdevice()
347
-
348
- if isinstance(with_connection, tuple):
349
- with_connection = Connection.based_on(with_connection)
350
-
351
- if all_vdevice_method_variations is None:
352
- raise ValueError("the current feature has no method variations")
353
- if of_method_name not in all_vdevice_method_variations.keys():
354
- raise ValueError(f"can not find the method `{of_method_name}` in method variation data dictionary")
355
-
356
342
  # first determine all possible method-variations
357
343
  all_possible_method_variations = self._determine_all_theoretically_unordered_method_variations(
358
344
  of_method_name=of_method_name, for_vdevice=for_vdevice, with_connection=with_connection)
@@ -378,31 +364,21 @@ class FeatureController(Controller):
378
364
  if len(all_possible_method_variations) == 1:
379
365
  return list(all_possible_method_variations.keys())[0]
380
366
 
381
- # if there are more than one possible method variation, try to sort them hierarchical
382
- # we have to determine the outer one
383
- length_before = None
384
- while length_before is None or length_before != len(all_possible_method_variations):
385
- length_before = len(all_possible_method_variations)
386
- for cur_meth, cur_cnn in all_possible_method_variations.items():
387
- can_be_deleted = True
388
- for _, cur_other_cnn in all_possible_method_variations.items():
389
- if cur_cnn == cur_other_cnn:
390
- continue
391
- if not cur_cnn.contained_in(cur_other_cnn):
392
- can_be_deleted = False
393
- if can_be_deleted:
394
- del all_possible_method_variations[cur_meth]
395
- break
396
- if len(all_possible_method_variations) == 1:
397
- # done
398
- break
399
-
400
- if len(all_possible_method_variations) > 1:
367
+ # if there are more than one possible method variation, try to determine the outer one
368
+ remaining_possible_method_variations = list(
369
+ filter(
370
+ lambda meth: not max(all_possible_method_variations[meth].contained_in(cur_other_cnn)
371
+ for cur_other_cnn in all_possible_method_variations.values()
372
+ if cur_other_cnn != all_possible_method_variations[meth]),
373
+ all_possible_method_variations.keys())
374
+ )
375
+
376
+ if len(remaining_possible_method_variations) > 1:
401
377
  raise UnclearMethodVariationError(
402
378
  f"found more than one possible method variation for method "
403
379
  f"`{self.related_cls.__name__}.{of_method_name}` with vDevice `{for_vdevice.__name__}` "
404
380
  f"and usable connection `{with_connection.get_tree_str()}´")
405
- return list(all_possible_method_variations.keys())[0]
381
+ return remaining_possible_method_variations[0]
406
382
 
407
383
  def get_inner_vdevice_classes(self) -> List[Type[VDevice]]:
408
384
  """
@@ -592,26 +568,14 @@ class FeatureController(Controller):
592
568
  FeatureController.get_for(
593
569
  parent_vdevice_feature).get_abs_class_based_for_vdevice()[relevant_parent_class]
594
570
  # check if VDevice connection elements are all contained in the parent connection
595
- for cur_element in cur_vdevice_cls_cnn:
596
- if isinstance(cur_element, tuple):
597
- if not Connection.check_if_tuple_contained_in_connection(
598
- cur_element, Connection.based_on(*parent_vdevice_cnn)):
599
- raise VDeviceResolvingError(
600
- f"the VDevice `{cur_vdevice.__name__}` is a child of the VDevice "
601
- f"`{relevant_parent_class.__name__}`, which doesn't implements the connection of "
602
- f"the child - the connection tuple `("
603
- f"{', '.join([cur_tuple_item.get_tree_str() for cur_tuple_item in cur_element])})´"
604
- f" is not contained in the connection-tree of the parent VDevice")
605
- else:
606
- if not cur_element.contained_in(
607
- Connection.based_on(*parent_vdevice_cnn), ignore_metadata=True):
608
- raise VDeviceResolvingError(
609
- f"the VDevice `{cur_vdevice.__name__}` is a child of the VDevice "
610
- f"`{relevant_parent_class.__name__}`, which doesn't implements the connection of "
611
- f"the child - the connection element `{cur_element.get_tree_str()})´ is not "
612
- f"contained in the connection-tree of the parent VDevice")
613
-
614
- # check all features where we have found parent VDevices as inner-classes to check next inheritance levels
571
+ if not cur_vdevice_cls_cnn.contained_in(parent_vdevice_cnn, ignore_metadata=True):
572
+ raise VDeviceResolvingError(
573
+ f"the VDevice `{cur_vdevice.__name__}` is a child of the VDevice "
574
+ f"`{relevant_parent_class.__name__}`, which doesn't implements the connection of "
575
+ f"the child - the connection element `{cur_vdevice_cls_cnn.get_tree_str()})´ is not "
576
+ f"contained in the connection-tree of the parent VDevice")
577
+
578
+ # validate inheritance levels for all features with parent VDevices as inner-classes
615
579
  for cur_feature in to_checking_parent_features:
616
580
  FeatureController.get_for(cur_feature).validate_inherited_class_based_vdevice_cnn_subset()
617
581