baldertest 0.1.0b10__py3-none-any.whl → 0.1.0b11__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 (32) 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 +25 -11
  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 +122 -115
  19. _balder/feature.py +1 -1
  20. _balder/fixture_manager.py +10 -9
  21. _balder/objects/connections/osi_3_network.py +2 -2
  22. _balder/objects/connections/osi_4_transport.py +2 -2
  23. _balder/routing_path.py +18 -25
  24. _balder/solver.py +1 -1
  25. _balder/testresult.py +1 -1
  26. _balder/utils.py +27 -1
  27. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b11.dist-info}/METADATA +2 -2
  28. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b11.dist-info}/RECORD +32 -27
  29. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b11.dist-info}/WHEEL +1 -1
  30. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b11.dist-info}/LICENSE +0 -0
  31. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b11.dist-info}/entry_points.txt +0 -0
  32. {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b11.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ from typing import Type, Dict, List, Tuple, Union, Callable, Iterable, Any
4
4
  import logging
5
5
  import inspect
6
6
  from collections import OrderedDict
7
+ from _balder.cnnrelations import OrConnectionRelation
7
8
  from _balder.device import Device
8
9
  from _balder.scenario import Scenario
9
10
  from _balder.connection import Connection
@@ -215,15 +216,15 @@ class ScenarioController(NormalScenarioSetupController):
215
216
 
216
217
  # now check if one or more single of the classbased connection are CONTAINED IN the possible
217
218
  # parallel connection (only if there exists more than one parallel)
218
- feature_cnn = Connection.based_on(*FeatureController.get_for(
219
- cur_feature.__class__).get_abs_class_based_for_vdevice()[mapped_vdevice])
219
+ feature_cnn = FeatureController.get_for(
220
+ cur_feature.__class__).get_abs_class_based_for_vdevice()[mapped_vdevice]
220
221
 
221
222
  # search node names that is the relevant connection
222
223
  relevant_cnns: List[Connection] = []
223
224
  mapped_device_abs_cnns = DeviceController.get_for(mapped_device).get_all_absolute_connections()
224
225
  for _, all_connections in mapped_device_abs_cnns.items():
225
226
  relevant_cnns += [cur_cnn for cur_cnn in all_connections
226
- if cur_cnn.has_connection_from_to(cur_from_device, mapped_device)]
227
+ if cur_cnn.has_connection_from_to(cur_from_device, end_device=mapped_device)]
227
228
 
228
229
  if len(relevant_cnns) <= 1:
229
230
  # ignore if there are not more than one relevant connection
@@ -338,10 +339,9 @@ class ScenarioController(NormalScenarioSetupController):
338
339
  continue
339
340
 
340
341
  # now try to reduce the scenario connections according to the requirements of the feature class
341
- cur_feature_class_based_for_vdevice = \
342
+ cur_feature_cnn = \
342
343
  FeatureController.get_for(
343
344
  cur_feature.__class__).get_abs_class_based_for_vdevice()[mapped_vdevice]
344
- cur_feature_cnn = Connection.based_on(*cur_feature_class_based_for_vdevice)
345
345
 
346
346
  device_cnn_singles = get_single_cnns_between_device_for_feature(
347
347
  from_device=cur_from_device, to_device=mapped_device, relevant_feature_cnn=cur_feature_cnn)
@@ -395,7 +395,7 @@ class ScenarioController(NormalScenarioSetupController):
395
395
  cur_from_device_controller = DeviceController.get_for(cur_from_device)
396
396
  cur_to_device_controller = DeviceController.get_for(cur_to_device)
397
397
 
398
- new_cnn = Connection.based_on(*cur_single_cnns)
398
+ new_cnn = Connection.based_on(OrConnectionRelation(*cur_single_cnns))
399
399
  new_cnn.set_metadata_for_all_subitems(cur_single_cnns[0].metadata)
400
400
  if cur_from_device == cur_single_cnns[0].from_device:
401
401
  cur_from_device_controller.add_new_absolute_connection(new_cnn)
@@ -3,7 +3,6 @@ from typing import Type, Dict, Union, TYPE_CHECKING
3
3
 
4
4
  import logging
5
5
  from _balder.setup import Setup
6
- from _balder.connection import Connection
7
6
  from _balder.exceptions import IllegalVDeviceMappingError, MultiInheritanceError
8
7
  from _balder.controllers.feature_controller import FeatureController
9
8
  from _balder.controllers.device_controller import DeviceController
@@ -113,12 +112,12 @@ class SetupController(NormalScenarioSetupController):
113
112
  continue
114
113
 
115
114
  # there exists a class based requirement for this vDevice
116
- class_based_cnn = Connection.based_on(*feature_class_based_for_vdevice[mapped_vdevice])
115
+ class_based_cnn = feature_class_based_for_vdevice[mapped_vdevice]
117
116
  # search relevant connection
118
117
  cur_device_controller = DeviceController.get_for(cur_device)
119
118
  for _, cur_cnn_list in cur_device_controller.get_all_absolute_connections().items():
120
119
  for cur_cnn in cur_cnn_list:
121
- if not cur_cnn.has_connection_from_to(cur_device, mapped_device):
120
+ if not cur_cnn.has_connection_from_to(cur_device, end_device=mapped_device):
122
121
  # this connection can be ignored, because it is no connection between the current device
123
122
  # and the mapped device
124
123
  continue
@@ -5,10 +5,15 @@ import re
5
5
  from _balder.device import Device
6
6
  from _balder.connection import Connection
7
7
  from _balder.controllers import DeviceController
8
+ from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
8
9
 
9
10
 
10
- def connect(with_device: Union[Type[Device], str], over_connection: Union[Type[Connection], Connection],
11
- self_node_name: str = None, dest_node_name: str = None):
11
+ def connect(
12
+ with_device: Union[Type[Device], str],
13
+ over_connection: Union[Connection, Type[Connection], AndConnectionRelation, OrConnectionRelation],
14
+ self_node_name: str = None,
15
+ dest_node_name: str = None
16
+ ):
12
17
  """
13
18
  This decorator connects two devices with each other. It can be used for scenarios as well as setup devices.
14
19
 
@@ -27,11 +32,6 @@ def connect(with_device: Union[Type[Device], str], over_connection: Union[Type[C
27
32
  if not isinstance(with_device, str) and not issubclass(with_device, Device):
28
33
  raise ValueError("the value of `with_device` must be a `Device` (or a subclass thereof) or the device name "
29
34
  "as a string")
30
- if isinstance(over_connection, tuple):
31
- # doesn't make sense, because we can describe what's needed in one scenario with several `@connect` decorations
32
- # anyway. We are describing a setup, therefore an AND link makes no sense here either
33
- raise TypeError("an AND link (signaled via the tuple) of the connection is not possible here - for further "
34
- "separate connections use the `@connect` decorator again")
35
35
  if isinstance(over_connection, type):
36
36
  if not issubclass(over_connection, Connection):
37
37
  raise TypeError("the type of `over_connection` must be a `Connection` (or a subclass of it)")
@@ -91,11 +91,13 @@ def connect(with_device: Union[Type[Device], str], over_connection: Union[Type[C
91
91
  # in another `@connect()` decorator (and also remove possible metadata from the new clone)
92
92
  cur_cnn_instance = over_connection.clone()
93
93
  cur_cnn_instance.set_metadata_for_all_subitems(None)
94
- elif issubclass(over_connection, Connection):
94
+ elif isinstance(over_connection, type) and issubclass(over_connection, Connection):
95
95
  # not instantiated -> instantiate it
96
96
  cur_cnn_instance = over_connection()
97
- cur_cnn_instance.set_devices(from_device=decorated_cls, to_device=with_device)
98
- cur_cnn_instance.update_node_names(from_device_node_name=self_node_name, to_device_node_name=dest_node_name)
97
+ elif isinstance(over_connection, (AndConnectionRelation, OrConnectionRelation)):
98
+ over_connection = Connection.based_on(over_connection)
99
+ cur_cnn_instance.metadata.set_from(from_device=decorated_cls, from_device_node_name=self_node_name)
100
+ cur_cnn_instance.metadata.set_to(to_device=with_device, to_device_node_name=dest_node_name)
99
101
 
100
102
  decorated_cls_device_controller.add_new_raw_connection(cur_cnn_instance)
101
103
  return decorated_cls
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
- from typing import List, Union, Type, Tuple
2
+ from typing import Union, Type
3
3
 
4
4
  import inspect
5
+ from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
5
6
  from _balder.collector import Collector
6
7
  from _balder.feature import Feature
7
8
  from _balder.vdevice import VDevice
@@ -13,9 +14,9 @@ from _balder.exceptions import DuplicateForVDeviceError, UnknownVDeviceException
13
14
  def for_vdevice(
14
15
  vdevice: Union[str, Type[VDevice]],
15
16
  with_connections: Union[
16
- Type[Connection], Connection, Tuple[Union[Type[Connection], Connection]],
17
- List[Union[Type[Connection], Connection, Tuple[Union[Type[Connection], Connection]]]]] = Connection(),
18
- ):
17
+ Type[Connection], Connection, AndConnectionRelation, OrConnectionRelation
18
+ ] = Connection(),
19
+ ):
19
20
  """
20
21
  With the `@for_vdevice` you can limit the decorated object for a special allowed connection tree for every existing
21
22
  vDevice. This decorator can be used to decorate whole :class:`Feature` classes just like single methods of a
@@ -37,26 +38,18 @@ def for_vdevice(
37
38
 
38
39
  :param with_connections: the assigned connection trees for this class/method (default: a universal connection)
39
40
  """
40
- idx = 0
41
- if not isinstance(with_connections, list):
42
- with_connections = [with_connections]
43
- for cur_conn in with_connections:
44
- if isinstance(cur_conn, type):
45
- if not issubclass(cur_conn, Connection):
46
- raise ValueError(f"the given element type for the element on position `{idx}` has to be a subclass of "
47
- f"`{Connection.__name__}` or a element of it")
48
- elif isinstance(cur_conn, tuple):
49
- tuple_idx = 0
50
- for cur_tuple_elem in cur_conn:
51
- if not isinstance(cur_tuple_elem, Connection) and not issubclass(cur_tuple_elem, Connection):
52
- raise ValueError(f"the given tuple element `{tuple_idx}` that is given on position `{idx}` "
53
- f"has to be a subclass of `{Connection.__name__}`")
54
- tuple_idx += 1
55
- elif not isinstance(cur_conn, Connection):
56
- raise ValueError(f"the given type on position `{idx}` has to be a subclass of `{Connection.__name__}` or "
57
- f"a element of it")
58
-
59
- idx += 1
41
+ if isinstance(with_connections, Connection):
42
+ # do nothing
43
+ pass
44
+ elif isinstance(with_connections, (AndConnectionRelation, OrConnectionRelation)):
45
+ # use container connection
46
+ with_connections = Connection.based_on(with_connections)
47
+ elif isinstance(with_connections, type) and issubclass(with_connections, Connection):
48
+ # instantiate it
49
+ with_connections = with_connections()
50
+ else:
51
+ raise TypeError(f"the given element ``with_connection`` needs to be from type `AndConnectionRelation`, "
52
+ f"`OrConnectionRelation` or `Connection` - `{type(with_connections)}` is not allowed")
60
53
 
61
54
  # note: if `args` is an empty list - no special sub-connection-tree bindings
62
55
 
@@ -105,7 +98,6 @@ def for_vdevice(
105
98
 
106
99
  vdevice = relevant_vdevices[0]
107
100
  cls_for_vdevice = fn_feature_controller.get_class_based_for_vdevice()
108
- cls_for_vdevice = {} if cls_for_vdevice is None else cls_for_vdevice
109
101
  if vdevice in cls_for_vdevice.keys():
110
102
  raise DuplicateForVDeviceError(
111
103
  f'there already exists a decorator for the vDevice `{vdevice}` in the Feature class '
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from _balder.controllers.device_controller import DeviceController
3
4
  from _balder.device import Device
4
5
  from _balder.node_gateway import NodeGateway
5
6
 
@@ -25,10 +26,9 @@ def gateway(from_node: str, to_node: str, bidirectional: bool = True):
25
26
  raise TypeError(
26
27
  f"The decorator `gateway` may only be used for `Device` objects. This is not possible for the applied "
27
28
  f"class `{cls.__name__}`.")
29
+ decorated_cls_device_controller = DeviceController.get_for(cls)
28
30
 
29
- if not hasattr(cls, '_gateways'):
30
- cls._gateways = []
31
31
  new_gateway = NodeGateway(cls, from_node, to_node, bidirectional)
32
- cls._gateways.append(new_gateway)
32
+ decorated_cls_device_controller.add_new_raw_gateway(new_gateway)
33
33
  return cls
34
34
  return decorator
@@ -12,7 +12,6 @@ from _balder.testresult import ResultState, TestcaseResult
12
12
  from _balder.utils import inspect_method
13
13
 
14
14
  if TYPE_CHECKING:
15
- from _balder.executor.unresolved_parametrized_testcase_executor import UnresolvedParametrizedTestcaseExecutor
16
15
  from _balder.executor.variation_executor import VariationExecutor
17
16
  from _balder.fixture_manager import FixtureManager
18
17
  from _balder.scenario import Scenario
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
+
3
+ import itertools
2
4
  from typing import Type, Union, List, Dict, Tuple, TYPE_CHECKING
3
5
 
4
6
  import inspect
5
7
  import logging
8
+ from _balder.cnnrelations import OrConnectionRelation
6
9
  from _balder.device import Device
7
10
  from _balder.connection import Connection
8
11
  from _balder.fixture_execution_level import FixtureExecutionLevel
@@ -21,6 +24,8 @@ if TYPE_CHECKING:
21
24
  from _balder.feature import Feature
22
25
  from _balder.scenario import Scenario
23
26
  from _balder.vdevice import VDevice
27
+ from _balder.controllers.scenario_controller import ScenarioController
28
+ from _balder.controllers.setup_controller import SetupController
24
29
  from _balder.executor.scenario_executor import ScenarioExecutor
25
30
  from _balder.fixture_manager import FixtureManager
26
31
 
@@ -96,11 +101,25 @@ class VariationExecutor(BasicExecutableExecutor):
96
101
  """property returns the current :class:`Scenario` for this variation"""
97
102
  return self._parent_executor.base_scenario_class
98
103
 
104
+ @property
105
+ def cur_scenario_controller(self) -> ScenarioController:
106
+ """
107
+ returns the current :class:`ScenarioController` for this variation
108
+ """
109
+ return self._parent_executor.base_scenario_controller
110
+
99
111
  @property
100
112
  def cur_setup_class(self) -> Setup:
101
113
  """property returns the current :class:`Setup` for this variation"""
102
114
  return self._parent_executor.parent_executor.base_setup_class
103
115
 
116
+ @property
117
+ def cur_setup_controller(self) -> SetupController:
118
+ """
119
+ returns the current :class:`SetupController` for this variation
120
+ """
121
+ return self._parent_executor.parent_executor.base_setup_controller
122
+
104
123
  @property
105
124
  def base_device_mapping(self) -> Dict[Type[Device], Type[Device]]:
106
125
  """
@@ -284,6 +303,39 @@ class VariationExecutor(BasicExecutableExecutor):
284
303
  f'can not find a valid routing on setup level for the connection `{scenario_cnn.get_tree_str()}` '
285
304
  f'between scenario devices `{scenario_cnn.from_device}` and `{scenario_cnn.to_device}`')
286
305
 
306
+ def _get_matching_setup_features_for(
307
+ self,
308
+ scenario_feature_obj: Feature,
309
+ in_setup_device: Type[Device]
310
+ ) -> List[Feature]:
311
+ """
312
+ Helper method that returns all matching setup features for the provided scenario feature in the provided setup
313
+ device.
314
+ """
315
+ cur_setup_features = DeviceController.get_for(in_setup_device).get_all_instantiated_feature_objects()
316
+
317
+ replacing_feature_candidates = [
318
+ cur_setup_feature for cur_setup_feature in cur_setup_features.values()
319
+ if isinstance(cur_setup_feature, scenario_feature_obj.__class__)
320
+ ]
321
+ active_scenario_vdev, mapped_scenario_dev = scenario_feature_obj.active_vdevice_device_mapping
322
+
323
+ replacing_features = replacing_feature_candidates.copy()
324
+ if mapped_scenario_dev is not None:
325
+ # get the related setup device for the mapped scenario device (on scenario level)
326
+ setup_dev_of_mapped_scenario_dev = self.get_setup_device_for(mapped_scenario_dev)
327
+
328
+ # now check if there is a mapping on setup level too
329
+ for cur_replacing_feature in replacing_feature_candidates:
330
+ mapped_setup_vdev, mapped_setup_dev = cur_replacing_feature.active_vdevice_device_mapping
331
+ if mapped_setup_vdev is not None and not issubclass(mapped_setup_vdev, active_scenario_vdev):
332
+ # drop this feature matching, because we have different vdevice mapped
333
+ replacing_features.remove(cur_replacing_feature)
334
+ elif mapped_setup_dev is not None and mapped_setup_dev != setup_dev_of_mapped_scenario_dev:
335
+ # drop this feature matching, because it is not applicable here
336
+ replacing_features.remove(cur_replacing_feature)
337
+ return replacing_features
338
+
287
339
  # ---------------------------------- METHODS -----------------------------------------------------------------------
288
340
 
289
341
  def testsummary(self) -> ResultSummary:
@@ -336,75 +388,53 @@ class VariationExecutor(BasicExecutableExecutor):
336
388
  :raises NotApplicableVariationError: will be thrown if this variation cannot be applied, because the setup-/
337
389
  scenario-device-features can not be resolved
338
390
  """
339
- feature_replacement = {}
340
- abs_setup_vdevice_mappings = {}
391
+ feature_replacement = {scenario_dev: {} for scenario_dev in self.base_device_mapping.keys()}
392
+ abs_setup_vdevice_mappings = {setup_dev: {} for setup_dev in self.base_device_mapping.values()}
341
393
  for cur_scenario_device, cur_setup_device in self.base_device_mapping.items():
342
394
  cur_setup_features = DeviceController.get_for(cur_setup_device).get_all_instantiated_feature_objects()
343
395
 
344
- if cur_scenario_device not in feature_replacement.keys():
345
- feature_replacement[cur_scenario_device] = {}
346
-
347
- if cur_setup_device not in abs_setup_vdevice_mappings.keys():
348
- abs_setup_vdevice_mappings[cur_setup_device] = {}
349
-
350
396
  all_assigned_setup_features = []
351
397
  cur_scenario_device_orig_features = \
352
398
  DeviceController.get_for(cur_scenario_device).get_original_instanced_feature_objects()
353
- for cur_attr_name, cur_abstract_scenario_feature_obj in cur_scenario_device_orig_features.items():
354
- replacing_features = [cur_setup_feature for _, cur_setup_feature in cur_setup_features.items()
355
- if isinstance(cur_setup_feature, cur_abstract_scenario_feature_obj.__class__)]
356
- used_scenario_vdevice, mapped_scenario_device = \
357
- cur_abstract_scenario_feature_obj.active_vdevice_device_mapping
358
- # check if there is a mapped device in scenario level
359
- cleanup_replacing_features = replacing_features.copy()
360
- if mapped_scenario_device is not None:
361
- # get the related setup device for the mapped scenario device (on scenario level)
362
- to_scenarios_vdevice_mapped_setup_device = self.get_setup_device_for(mapped_scenario_device)
363
-
364
- # now check if there is a mapping on setup level too
365
- for cur_replacing_feature in replacing_features:
366
- mapped_setup_vdevice, mapped_setup_device = cur_replacing_feature.active_vdevice_device_mapping
367
- if mapped_setup_vdevice is not None and \
368
- not issubclass(mapped_setup_vdevice, used_scenario_vdevice):
369
- # drop this feature matching, because we have different vdevice mapped
370
- cleanup_replacing_features.remove(cur_replacing_feature)
371
- elif mapped_setup_device is not None and \
372
- mapped_setup_device != to_scenarios_vdevice_mapped_setup_device:
373
- # drop this feature matching, because it is not applicable here
374
- cleanup_replacing_features.remove(cur_replacing_feature)
375
-
376
- if len(cleanup_replacing_features) != 1:
399
+ for cur_attr_name, cur_scenario_feature_obj in cur_scenario_device_orig_features.items():
400
+ active_scenario_vdevice, mapped_scenario_device = cur_scenario_feature_obj.active_vdevice_device_mapping
401
+
402
+ cur_setup_feature_objs = self._get_matching_setup_features_for(
403
+ scenario_feature_obj=cur_scenario_feature_obj, in_setup_device=cur_setup_device
404
+ )
405
+
406
+ if len(cur_setup_feature_objs) != 1:
377
407
  raise NotApplicableVariationException(
378
408
  f'this variation can not be applicable because there was no setup feature implementation of '
379
- f'`{cur_abstract_scenario_feature_obj.__class__.__name__}` (used by scenario device '
409
+ f'`{cur_scenario_feature_obj.__class__.__name__}` (used by scenario device '
380
410
  f'`{cur_scenario_device.__name__}`) in setup device `{cur_setup_device.__name__}`')
411
+ cur_setup_feature_obj = cur_setup_feature_objs[0]
381
412
 
382
413
  if mapped_scenario_device is None:
383
414
  # we have exactly one matching candidate, but also no vDevice mapping
384
415
  # check if the matching candidate has a vDevice mapping
385
- _, mapped_setup_device = cleanup_replacing_features[0].active_vdevice_device_mapping
386
- cleanup_feature_controller = FeatureController.get_for(cleanup_replacing_features[0].__class__)
416
+ _, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
417
+ cleanup_feature_controller = FeatureController.get_for(cur_setup_feature_obj.__class__)
387
418
  if mapped_setup_device is None \
388
419
  and len(cleanup_feature_controller.get_abs_inner_vdevice_classes()) > 0:
389
420
  # there is no vDevice mapping on scenario and no vDevice mapping on setup level, but the
390
421
  # feature defined vDevices -> NOT APPLICABLE
391
422
  logger.warning(
392
423
  f"missing vDevice mapping for feature "
393
- f"`{cur_abstract_scenario_feature_obj.__class__.__name__}` (used in scenario device "
424
+ f"`{cur_scenario_feature_obj.__class__.__name__}` (used in scenario device "
394
425
  f"`{cur_scenario_device.__name__}` and in setup device `{cur_setup_device.__name__}`) - "
395
426
  f"VARIATION CAN NOT BE APPLIED")
396
427
  raise NotApplicableVariationException(
397
- f'this variation can not be applicable because there was no vDevice mapping given on '
428
+ f'this variation can not be applied because there was no vDevice mapping given on '
398
429
  f'scenario or on setup level for the feature '
399
- f'`{cur_abstract_scenario_feature_obj.__class__.__name__}` (used by scenario device '
430
+ f'`{cur_scenario_feature_obj.__class__.__name__}` (used by scenario device '
400
431
  f'`{cur_scenario_device.__name__}`) in setup device `{cur_setup_device.__name__}`')
401
432
 
402
- all_assigned_setup_features.append(cleanup_replacing_features[0])
433
+ all_assigned_setup_features.append(cur_setup_feature_obj)
403
434
  if cur_attr_name not in feature_replacement[cur_scenario_device].keys():
404
- cleanup_feature = cleanup_replacing_features[0]
405
- cleanup_feature_controller = FeatureController.get_for(cleanup_feature.__class__)
435
+ cleanup_feature_controller = FeatureController.get_for(cur_setup_feature_obj.__class__)
406
436
 
407
- used_setup_vdevice, mapped_setup_device = cleanup_feature.active_vdevice_device_mapping
437
+ used_setup_vdevice, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
408
438
 
409
439
  # if there is a vDevice mapping on scenario level, but not on setup level, so update the
410
440
  # VDevice-Device-Mapping there
@@ -413,21 +443,21 @@ class VariationExecutor(BasicExecutableExecutor):
413
443
  # because check was already done in collector-stage)
414
444
  setup_vdevices = [cur_vdevice for cur_vdevice
415
445
  in cleanup_feature_controller.get_abs_inner_vdevice_classes()
416
- if cur_vdevice.__name__ == used_scenario_vdevice.__name__]
446
+ if cur_vdevice.__name__ == active_scenario_vdevice.__name__]
417
447
  used_setup_vdevice = setup_vdevices[0]
418
448
  # set the mapping
419
- abs_setup_vdevice_mappings[cur_setup_device][cleanup_feature] = {
449
+ abs_setup_vdevice_mappings[cur_setup_device][cur_setup_feature_obj] = {
420
450
  used_setup_vdevice: self.get_setup_device_for(mapped_scenario_device)}
421
451
  # if there is a vDevice mapping on setup level, but not on scenario level, so directly update the
422
452
  # VDevice-Device-Mapping there
423
453
  elif mapped_scenario_device is None and mapped_setup_device is not None:
424
- abs_setup_vdevice_mappings[cur_setup_device][cleanup_feature] = {
454
+ abs_setup_vdevice_mappings[cur_setup_device][cur_setup_feature_obj] = {
425
455
  used_setup_vdevice: mapped_setup_device}
426
456
 
427
457
  feature_replacement[cur_scenario_device][cur_attr_name] = \
428
- (cur_abstract_scenario_feature_obj, cleanup_feature)
458
+ (cur_scenario_feature_obj, cur_setup_feature_obj)
429
459
  # also add all setup features that are not assigned as autonomous features
430
- for _, cur_setup_feature in cur_setup_features.items():
460
+ for cur_setup_feature in cur_setup_features.values():
431
461
  if cur_setup_feature not in all_assigned_setup_features:
432
462
  # determine free name
433
463
  idx = 0
@@ -679,31 +709,16 @@ class VariationExecutor(BasicExecutableExecutor):
679
709
 
680
710
  def determine_absolute_scenario_device_connections(self):
681
711
  """
682
- This method determines the variation absolute connections for this variation and sets them to the internal
683
- properties `_abs_variation_*_device_connections`. This will be used to determine the real connection-subtree
712
+ This method determines the absolute connections for this variation and sets the internal properties
713
+ `_abs_variation_*_device_connections` with them. This will be used to determine the real connection-subtree
684
714
  (that can be used for this variation) by the method `create_all_valid_routings()`.
685
715
 
686
716
  The method re-executes the algorithm to determine the absolute connections for a scenario/setup (see the method
687
717
  :meth:`Collector.determine_absolute_device_connections_for`), but it considers the real applied vDevice and
688
718
  their feature restrictions too.
689
719
  """
690
- abs_var_scenario_device_cnns = {}
691
-
692
720
  # first determine all relevant absolute connection depending on the current scenario
693
- for cur_scenario_device, _ in self.base_device_mapping.items():
694
- cur_scenario_device_abs_cnns = \
695
- DeviceController.get_for(cur_scenario_device).get_all_absolute_connections()
696
- for _, cur_cnn_list in cur_scenario_device_abs_cnns.items():
697
- for cur_cnn in cur_cnn_list:
698
- if cur_scenario_device not in abs_var_scenario_device_cnns.keys():
699
- abs_var_scenario_device_cnns[cur_scenario_device] = {}
700
-
701
- cur_to_device, _ = cur_cnn.get_conn_partner_of(cur_scenario_device)
702
-
703
- if cur_to_device not in abs_var_scenario_device_cnns[cur_scenario_device].keys():
704
- abs_var_scenario_device_cnns[cur_scenario_device][cur_to_device] = []
705
-
706
- abs_var_scenario_device_cnns[cur_scenario_device][cur_to_device].append(cur_cnn)
721
+ abs_var_scenario_device_cnns = self.cur_scenario_controller.get_all_abs_connections()
707
722
 
708
723
  # now iterate over every feature, that is used by the scenario and determine the class-based feature connections
709
724
  # of the mapped scenario feature (and its vDevice)
@@ -726,31 +741,34 @@ class VariationExecutor(BasicExecutableExecutor):
726
741
 
727
742
  # get relevant class based connections for the current feature on setup level (this is really be used
728
743
  # here)
729
- feature_cnns = \
744
+ feature_cnn = \
730
745
  FeatureController.get_for(
731
746
  cur_setup_feature.__class__).get_abs_class_based_for_vdevice()[cur_setup_feature_vdevice]
732
747
  # connection that are relevant for this feature
733
- relevant_cnns = abs_var_scenario_device_cnns[cur_scenario_device][cur_mapped_scenario_device]
748
+ relevant_cnns = [
749
+ cnn for cnn in abs_var_scenario_device_cnns
750
+ if cnn.has_connection_from_to(cur_scenario_device, end_device=cur_mapped_scenario_device)
751
+ ]
734
752
 
735
753
  relevant_device_cnn = None
736
754
 
737
755
  if len(relevant_cnns) > 1:
738
756
  # we have parallel possibilities -> determine the selected one (only one is allowed to fit)
739
757
  for cur_relevant_cnn in relevant_cnns:
740
- for cur_relevant_single_cnn in cur_relevant_cnn.get_singles():
741
- for cur_feature_cnn in feature_cnns:
742
- for cur_feature_single_cnn in cur_feature_cnn.get_singles():
743
- if cur_feature_single_cnn.contained_in(cur_relevant_single_cnn):
744
- if relevant_device_cnn is not None:
745
- raise UnclearAssignableFeatureConnectionError(
746
- f"the devices {cur_scenario_device.__name__} and "
747
- f"{cur_mapped_scenario_device.__name__} have multiple parallel "
748
- f"connections - the device `{cur_scenario_device.__name__}` uses a "
749
- f"feature `{cur_scenario_feature.__class__.__name__}` that matches "
750
- f"with the device `{cur_mapped_scenario_device.__name__}`, but it is "
751
- f"not clear which of the parallel connection could be used"
752
- )
753
- relevant_device_cnn = cur_relevant_cnn
758
+ for cur_relevant_single_cnn, cur_feature_single_cnn in (
759
+ itertools.product(cur_relevant_cnn.get_singles(), feature_cnn.get_singles())):
760
+ if not cur_feature_single_cnn.contained_in(cur_relevant_single_cnn, ignore_metadata=True):
761
+ continue
762
+ if relevant_device_cnn is not None:
763
+ raise UnclearAssignableFeatureConnectionError(
764
+ f"the devices {cur_scenario_device.__name__} and "
765
+ f"{cur_mapped_scenario_device.__name__} have multiple parallel connections - the "
766
+ f"device `{cur_scenario_device.__name__}` uses a feature "
767
+ f"`{cur_scenario_feature.__class__.__name__}` that matches with the device "
768
+ f"`{cur_mapped_scenario_device.__name__}`, but it is not clear which of the "
769
+ f"parallel connection could be used"
770
+ )
771
+ relevant_device_cnn = cur_relevant_cnn
754
772
  elif len(relevant_cnns) == 1:
755
773
  relevant_device_cnn = relevant_cnns[0]
756
774
  if relevant_device_cnn is None:
@@ -759,36 +777,19 @@ class VariationExecutor(BasicExecutableExecutor):
759
777
 
760
778
  # now cleanup the scenario-device connection `relevant_device_cnn` according to the class-based feature
761
779
  # connection
762
- new_cleaned_singles = []
763
- for cur_old_cnn_single in relevant_device_cnn.get_singles():
764
- for cur_feature_cnn in feature_cnns:
765
- if cur_feature_cnn.contained_in(cur_old_cnn_single, ignore_metadata=True):
766
- new_cleaned_singles.append(cur_old_cnn_single)
767
-
768
- new_cnn_to_replace = Connection.based_on(*new_cleaned_singles)
769
- new_cnn_to_replace.set_metadata_for_all_subitems(new_cleaned_singles[0].metadata)
770
-
771
- abs_var_scenario_device_cnns[cur_scenario_device][cur_mapped_scenario_device].remove(
772
- relevant_device_cnn)
773
- abs_var_scenario_device_cnns[cur_scenario_device][cur_mapped_scenario_device].append(new_cnn_to_replace)
774
-
775
- # also search the connection in the other direction
776
- other_dir_relevant_device_cnn = None
777
- for cur_cnn in abs_var_scenario_device_cnns[cur_mapped_scenario_device][cur_scenario_device]:
778
- if cur_cnn.equal_with(relevant_device_cnn):
779
- other_dir_relevant_device_cnn = cur_cnn
780
- break
781
- # and also replace it
782
- abs_var_scenario_device_cnns[cur_mapped_scenario_device][cur_scenario_device].remove(
783
- other_dir_relevant_device_cnn)
784
- abs_var_scenario_device_cnns[cur_mapped_scenario_device][cur_scenario_device].append(new_cnn_to_replace)
780
+ new_cnn_to_replace = Connection.based_on(OrConnectionRelation(*[
781
+ cur_old_cnn_single for cur_old_cnn_single in relevant_device_cnn.get_singles()
782
+ if feature_cnn.contained_in(cur_old_cnn_single, ignore_metadata=True)
783
+ ]))
784
+ new_cnn_to_replace.set_metadata_for_all_subitems(relevant_device_cnn.metadata)
785
+
786
+ abs_var_scenario_device_cnns.remove(relevant_device_cnn)
787
+ abs_var_scenario_device_cnns.append(new_cnn_to_replace)
788
+
789
+ # we do not need to check other direction because `has_connection_from_to()` returns both possibilities
785
790
 
786
791
  # set the determined values in variation object
787
- self._abs_variation_scenario_device_connections = []
788
- for _, from_device_dict in abs_var_scenario_device_cnns.items():
789
- for _, cur_cnn_list in from_device_dict.items():
790
- for cur_cnn in cur_cnn_list:
791
- self._abs_variation_scenario_device_connections.append(cur_cnn)
792
+ self._abs_variation_scenario_device_connections = abs_var_scenario_device_cnns
792
793
 
793
794
  def create_all_valid_routings(self):
794
795
  """
@@ -828,16 +829,18 @@ class VariationExecutor(BasicExecutableExecutor):
828
829
  if virtual_routing_cnns[cur_cnn] is None:
829
830
  virtual_routing_cnns[cur_cnn] = Connection.based_on(virtual_cnn)
830
831
  else:
831
- virtual_routing_cnns[cur_cnn] = Connection.based_on(virtual_routing_cnns[cur_cnn], virtual_cnn)
832
+ virtual_routing_cnns[cur_cnn] = Connection.based_on(
833
+ OrConnectionRelation(virtual_routing_cnns[cur_cnn], virtual_cnn))
832
834
  virtual_routing_cnns[cur_cnn].set_metadata_for_all_subitems(virtual_cnn.metadata)
833
835
 
834
836
  self._abs_variation_connections = []
835
837
  for cur_cnn in self._abs_variation_scenario_device_connections:
836
838
  cur_virtual_cnn = virtual_routing_cnns[cur_cnn]
837
839
  new_intersection = cur_cnn.intersection_with(cur_virtual_cnn)
838
- new_intersection.set_metadata_for_all_subitems(None)
839
- # always set the metadata for setup devices
840
- new_intersection.set_metadata_for_all_subitems(cur_virtual_cnn.metadata)
840
+ if new_intersection:
841
+ new_intersection.set_metadata_for_all_subitems(None)
842
+ # always set the metadata for setup devices
843
+ new_intersection.set_metadata_for_all_subitems(cur_virtual_cnn.metadata)
841
844
  self._abs_variation_connections.append(new_intersection)
842
845
 
843
846
  def set_conn_dependent_methods(self):
@@ -873,11 +876,15 @@ class VariationExecutor(BasicExecutableExecutor):
873
876
  relevant_abs_conn = []
874
877
  for cur_cnn in self._abs_variation_connections:
875
878
  if cur_cnn.has_connection_from_to(start_device=setup_device, end_device=mapped_setup_device):
876
- relevant_abs_conn.append(cur_cnn)
879
+ # add the children
880
+ relevant_abs_conn.extend(
881
+ cur_cnn.based_on_elements.connections if cur_cnn.__class__ == Connection else [cur_cnn]
882
+ )
883
+
877
884
  if len(relevant_abs_conn) is None:
878
885
  raise RuntimeError(f"detect empty absolute connection between device `{setup_device.__name__}` "
879
886
  f"and device `{mapped_setup_device.__name__}`")
880
- absolute_feature_method_var_cnn = Connection.based_on(*relevant_abs_conn)
887
+ absolute_feature_method_var_cnn = Connection.based_on(OrConnectionRelation(*relevant_abs_conn))
881
888
  cur_method_variation = cur_setup_feature_controller.get_method_variation(
882
889
  of_method_name=cur_method_name, for_vdevice=mapped_vdevice,
883
890
  with_connection=absolute_feature_method_var_cnn)
_balder/feature.py CHANGED
@@ -16,7 +16,7 @@ class Feature:
16
16
  :param kwargs: contains a dictionary that references all vDevices of the feature and a real
17
17
  scenario :meth:`Device` as value
18
18
  """
19
- from _balder.controllers import FeatureController
19
+ from _balder.controllers import FeatureController # pylint: disable=import-outside-toplevel
20
20
 
21
21
  #: this property contains the active mapping for the devices
22
22
  self.active_vdevices: Dict[VDevice, Union[Device, str]] = {}