baldertest 0.1.0__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 (89) hide show
  1. _balder/__init__.py +12 -0
  2. _balder/_version.py +34 -0
  3. _balder/balder_plugin.py +73 -0
  4. _balder/balder_session.py +341 -0
  5. _balder/balder_settings.py +15 -0
  6. _balder/cnnrelations/__init__.py +7 -0
  7. _balder/cnnrelations/and_connection_relation.py +176 -0
  8. _balder/cnnrelations/base_connection_relation.py +270 -0
  9. _balder/cnnrelations/or_connection_relation.py +65 -0
  10. _balder/collector.py +874 -0
  11. _balder/connection.py +863 -0
  12. _balder/connection_metadata.py +255 -0
  13. _balder/console/__init__.py +0 -0
  14. _balder/console/balder.py +58 -0
  15. _balder/controllers/__init__.py +12 -0
  16. _balder/controllers/base_device_controller.py +72 -0
  17. _balder/controllers/controller.py +29 -0
  18. _balder/controllers/device_controller.py +446 -0
  19. _balder/controllers/feature_controller.py +715 -0
  20. _balder/controllers/normal_scenario_setup_controller.py +402 -0
  21. _balder/controllers/scenario_controller.py +524 -0
  22. _balder/controllers/setup_controller.py +134 -0
  23. _balder/controllers/vdevice_controller.py +95 -0
  24. _balder/decorator_connect.py +104 -0
  25. _balder/decorator_covered_by.py +74 -0
  26. _balder/decorator_fixture.py +29 -0
  27. _balder/decorator_for_vdevice.py +118 -0
  28. _balder/decorator_gateway.py +34 -0
  29. _balder/decorator_insert_into_tree.py +52 -0
  30. _balder/decorator_parametrize.py +31 -0
  31. _balder/decorator_parametrize_by_feature.py +36 -0
  32. _balder/device.py +18 -0
  33. _balder/exceptions.py +182 -0
  34. _balder/executor/__init__.py +0 -0
  35. _balder/executor/basic_executable_executor.py +133 -0
  36. _balder/executor/basic_executor.py +205 -0
  37. _balder/executor/executor_tree.py +217 -0
  38. _balder/executor/parametrized_testcase_executor.py +52 -0
  39. _balder/executor/scenario_executor.py +169 -0
  40. _balder/executor/setup_executor.py +163 -0
  41. _balder/executor/testcase_executor.py +203 -0
  42. _balder/executor/unresolved_parametrized_testcase_executor.py +184 -0
  43. _balder/executor/variation_executor.py +882 -0
  44. _balder/exit_code.py +19 -0
  45. _balder/feature.py +74 -0
  46. _balder/feature_replacement_mapping.py +107 -0
  47. _balder/feature_vdevice_mapping.py +88 -0
  48. _balder/fixture_definition_scope.py +19 -0
  49. _balder/fixture_execution_level.py +22 -0
  50. _balder/fixture_manager.py +483 -0
  51. _balder/fixture_metadata.py +26 -0
  52. _balder/node_gateway.py +103 -0
  53. _balder/objects/__init__.py +0 -0
  54. _balder/objects/connections/__init__.py +0 -0
  55. _balder/objects/connections/osi_1_physical.py +116 -0
  56. _balder/objects/connections/osi_2_datalink.py +35 -0
  57. _balder/objects/connections/osi_3_network.py +47 -0
  58. _balder/objects/connections/osi_4_transport.py +40 -0
  59. _balder/objects/connections/osi_5_session.py +13 -0
  60. _balder/objects/connections/osi_6_presentation.py +13 -0
  61. _balder/objects/connections/osi_7_application.py +83 -0
  62. _balder/objects/devices/__init__.py +0 -0
  63. _balder/objects/devices/this_device.py +12 -0
  64. _balder/parametrization.py +75 -0
  65. _balder/plugin_manager.py +138 -0
  66. _balder/previous_executor_mark.py +23 -0
  67. _balder/routing_path.py +335 -0
  68. _balder/scenario.py +20 -0
  69. _balder/setup.py +18 -0
  70. _balder/solver.py +246 -0
  71. _balder/testresult.py +163 -0
  72. _balder/unmapped_vdevice.py +13 -0
  73. _balder/utils/__init__.py +0 -0
  74. _balder/utils/functions.py +103 -0
  75. _balder/utils/inner_device_managing_metaclass.py +14 -0
  76. _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
  77. _balder/utils/typings.py +4 -0
  78. _balder/vdevice.py +9 -0
  79. balder/__init__.py +56 -0
  80. balder/connections.py +43 -0
  81. balder/devices.py +9 -0
  82. balder/exceptions.py +44 -0
  83. balder/parametrization.py +8 -0
  84. baldertest-0.1.0.dist-info/METADATA +356 -0
  85. baldertest-0.1.0.dist-info/RECORD +89 -0
  86. baldertest-0.1.0.dist-info/WHEEL +5 -0
  87. baldertest-0.1.0.dist-info/entry_points.txt +2 -0
  88. baldertest-0.1.0.dist-info/licenses/LICENSE +21 -0
  89. baldertest-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,402 @@
1
+ from __future__ import annotations
2
+ from typing import Type, List, Union, Optional, Dict, TYPE_CHECKING
3
+
4
+ import logging
5
+ import inspect
6
+ from abc import ABC, abstractmethod
7
+ from _balder.setup import Setup
8
+ from _balder.device import Device
9
+ from _balder.scenario import Scenario
10
+ from _balder.connection_metadata import ConnectionMetadata
11
+ from _balder.controllers.controller import Controller
12
+ from _balder.controllers.device_controller import DeviceController
13
+ from _balder.controllers.vdevice_controller import VDeviceController
14
+ from _balder.exceptions import MultiInheritanceError, DeviceOverwritingError, MissingFeaturesOfVDeviceError
15
+
16
+ if TYPE_CHECKING:
17
+ from _balder.connection import Connection
18
+ from _balder.controllers import ScenarioController
19
+ from _balder.controllers import SetupController
20
+
21
+ logger = logging.getLogger(__file__)
22
+
23
+
24
+ class NormalScenarioSetupController(Controller, ABC):
25
+ """
26
+ This is the abstract base controller class for the Scenario- and Setup-Controller.
27
+ """
28
+ # describes if the current controller is for setups or for scenarios (has to be set in child controller)
29
+ _related_type: Optional[Type[Scenario, Setup]] = None
30
+
31
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
32
+
33
+ # ---------------------------------- CLASS METHODS -----------------------------------------------------------------
34
+
35
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
36
+
37
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
38
+
39
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
40
+
41
+ @staticmethod
42
+ def get_for(related_cls) -> Union[ScenarioController, SetupController]:
43
+ """
44
+ This class returns the current existing controller instance for the given item. If the instance does not exist
45
+ yet, it will automatically create it and saves the instance in an internal dictionary.
46
+
47
+ .. note::
48
+ This method automatically returns the correct controller type, depending on the class you provide with
49
+ `related_cls`.
50
+ """
51
+ # pylint: disable-next=import-outside-toplevel
52
+ from _balder.controllers.setup_controller import SetupController
53
+ # pylint: disable-next=import-outside-toplevel
54
+ from _balder.controllers.scenario_controller import ScenarioController
55
+
56
+ if issubclass(related_cls, Scenario):
57
+ return ScenarioController.get_for(related_cls)
58
+ if issubclass(related_cls, Setup):
59
+ return SetupController.get_for(related_cls)
60
+
61
+ raise TypeError(f"illegal non supported type `{related_cls.__name__}` given for `related_cls`")
62
+
63
+ def get_all_inner_device_classes(self) -> List[Type[Device]]:
64
+ """
65
+ This method provides a list of all :meth:`Device` classes that have been defined as inner classes in the related
66
+ scenario or setup.
67
+ """
68
+
69
+ all_classes = inspect.getmembers(self.related_cls, inspect.isclass)
70
+ filtered_classes = []
71
+ for _, cur_class in all_classes:
72
+ if not issubclass(cur_class, Device):
73
+ # filter all classes and make sure that only the child classes of :meth:`Device` remain
74
+ continue
75
+ if DeviceController.get_for(cur_class).get_outer_class() != self.related_cls:
76
+ # filter all classes that do not match the setup name in __qualname__
77
+ continue
78
+ # otherwise, add this candidate
79
+ filtered_classes.append(cur_class)
80
+ return filtered_classes
81
+
82
+ def get_inner_device_class_by_string(self, device_str: str) -> Union[Type[Device], None]:
83
+ """
84
+ This method returns the inner Device class for the given string.
85
+
86
+ :param device_str: the name string of the Device that should be returned
87
+
88
+ :return: the Device class or None, if the method has not found any class with this name
89
+ """
90
+ possible_devs = [cur_vdevice for cur_vdevice in self.get_all_inner_device_classes()
91
+ if cur_vdevice.__name__ == device_str]
92
+ if len(possible_devs) == 0:
93
+ return None
94
+ if len(possible_devs) > 1:
95
+ raise RuntimeError("found more than one possible vDevices - something unexpected happened")
96
+
97
+ return possible_devs[0]
98
+
99
+ def get_all_abs_inner_device_classes(self) -> List[Type[Device]]:
100
+ """
101
+ This method provides a list of all :meth:`Device` classes that are valid for the related scenario or setup
102
+ class. If the class itself does not implement some inner devices by its own, it returns the absolute inner
103
+ devices of the next higher setup/scenario parent class.
104
+ """
105
+ cls_devices = self.get_all_inner_device_classes()
106
+ if len(cls_devices) == 0:
107
+ # search for parent class
108
+ base_class = self.get_next_parent_class()
109
+
110
+ if base_class is None:
111
+ # if the class type is the original `Setup` or `Scenario` type -> no inner devices exists
112
+ return []
113
+ return self.__class__.get_for(base_class).get_all_abs_inner_device_classes()
114
+
115
+ return cls_devices
116
+
117
+ def get_abs_inner_device_class_by_string(self, device_str: str) -> Union[Type[Device], None]:
118
+ """
119
+ This method returns the absolute inner Device class for the given string.
120
+
121
+ :param device_str: the name string of the Device that should be returned
122
+
123
+ :return: the Device class or None, if the method has not found any class with this name
124
+ """
125
+ possible_devs = [cur_vdevice for cur_vdevice in self.get_all_abs_inner_device_classes()
126
+ if cur_vdevice.__name__ == device_str]
127
+ if len(possible_devs) == 0:
128
+ return None
129
+ if len(possible_devs) > 1:
130
+ raise RuntimeError("found more than one possible vDevices - something unexpected happened")
131
+
132
+ return possible_devs[0]
133
+
134
+ @abstractmethod
135
+ def get_next_parent_class(self) -> Union[Type[Scenario], Type[Setup], None]:
136
+ """
137
+ This method returns the next parent class which is a subclass of the :class:`Scenario`/:class:`Setup` itself.
138
+
139
+ :return: returns the next parent class or None if the next parent class is :class:`Scenario`/:class:`Setup`
140
+ itself
141
+ """
142
+
143
+ def get_all_connections(self) -> List[Connection]:
144
+ """
145
+ This method returns all connection objects which have been defined between devices of the related class.
146
+ """
147
+ all_device_classes = self.get_all_inner_device_classes()
148
+ all_connections = []
149
+ for cur_device in all_device_classes:
150
+ cur_device_controller = DeviceController.get_for(cur_device)
151
+ for cur_node_name in cur_device_controller.connections.keys():
152
+ all_connections += cur_device_controller.connections[cur_node_name]
153
+ return all_connections
154
+
155
+ def get_all_abs_connections(self) -> List[Connection]:
156
+ """
157
+ This method returns all absolute connection objects which have been defined between absolute devices of the
158
+ related scenario or setup class.
159
+ """
160
+ all_device_classes = self.get_all_abs_inner_device_classes()
161
+ all_connections = []
162
+ for cur_device in all_device_classes:
163
+ cur_device_controller = DeviceController.get_for(cur_device)
164
+ for _, cur_connections in cur_device_controller.absolute_connections.items():
165
+ for cur_cnn in cur_connections:
166
+ if cur_cnn not in all_connections:
167
+ all_connections.append(cur_cnn)
168
+ return all_connections
169
+
170
+ def validate_inheritance(self):
171
+ """
172
+ This method validates that the inheritance of the related :class:`Setup`/:class:`Scenario` class was done
173
+ correctly. It checks that all inner devices that are inherited has the same naming as their parents and also
174
+ that every reused name (that is already be used for a device in the parent class) does also inherit from this
175
+ parent scenario/setup device.
176
+
177
+ In addition to that, it secures that either all devices are overwritten in the current class or no devices are
178
+ overwritten in the related class.
179
+ """
180
+
181
+ # get parent scenario / setup class and check no multi inheritance
182
+ parent_scenario_or_setup = self.get_next_parent_class()
183
+ if parent_scenario_or_setup is None:
184
+ # done, because the parent class is direct Scenario/Setup class
185
+ return
186
+
187
+ parent_scenario_or_setup_controller = self.__class__.get_for(parent_scenario_or_setup)
188
+
189
+ devices = self.get_all_inner_device_classes()
190
+ abs_parent_devices = parent_scenario_or_setup_controller.get_all_abs_inner_device_classes()
191
+ abs_parent_devices_by_name = {cur_parent.__name__: cur_parent for cur_parent in abs_parent_devices}
192
+
193
+ if len(devices) == 0:
194
+ # ignore it because cur item has no own device definitions
195
+ return
196
+
197
+ # check that a device is newly defined or has the same name as the parent device
198
+ for cur_item_device in devices:
199
+ # check if name exists in parent
200
+ relevant_parent_according_naming = abs_parent_devices_by_name.get(cur_item_device.__name__, None)
201
+
202
+ # check if device is inherited from a parent
203
+ relevant_parent_device_according_inheritance = None
204
+ for cur_parent in abs_parent_devices:
205
+ if issubclass(cur_item_device, cur_parent):
206
+ if relevant_parent_device_according_inheritance is not None:
207
+ # multi inheritance is not allowed
208
+ raise MultiInheritanceError(
209
+ f"found more than one {self._related_type.__name__}-Device parent classes for the "
210
+ f"class `{cur_item_device.__name__}` - multi inheritance is not allowed for device "
211
+ f"classes")
212
+ relevant_parent_device_according_inheritance = cur_parent
213
+
214
+ # now check if both is fulfilled
215
+ if relevant_parent_according_naming == relevant_parent_device_according_inheritance and \
216
+ relevant_parent_device_according_inheritance is not None:
217
+ # device is inherited AND has the same name as used in parent -> ALLOWED
218
+ pass
219
+ elif relevant_parent_according_naming is None and relevant_parent_device_according_inheritance is None:
220
+ # both are none -> it is a new device -> ALLOWED
221
+ pass
222
+ elif relevant_parent_according_naming is None:
223
+ # reused a naming but does not inherit from it -> NOT ALLOWED
224
+ raise DeviceOverwritingError(
225
+ f"the inner device class `{cur_item_device.__qualname__}` which inherits from another "
226
+ f"device `{relevant_parent_device_according_inheritance.__qualname__}` - it should also have "
227
+ f"the same name")
228
+ elif relevant_parent_device_according_inheritance is None:
229
+ # inherit from a parent device, but it doesn't have the same naming -> NOT ALLOWED
230
+ raise DeviceOverwritingError(
231
+ f"the inner device class `{cur_item_device.__qualname__}` has the same name than the "
232
+ f"device `{relevant_parent_according_naming.__qualname__}` - it should also inherit from it")
233
+
234
+ # secure that all parent devices are implemented here too
235
+ for cur_parent in abs_parent_devices:
236
+ found_parent = len([dev for dev in devices if issubclass(dev, cur_parent)]) > 0
237
+ if not found_parent:
238
+ raise DeviceOverwritingError(
239
+ f"found a device `{cur_parent.__qualname__}` which is part of a parent class, but it is "
240
+ f"not implemented in child class `{self.related_cls.__name__}`")
241
+
242
+ # also check the parent class here
243
+ self.__class__.get_for(parent_scenario_or_setup).validate_inheritance()
244
+
245
+ def check_vdevice_feature_existence(self):
246
+ """
247
+ This method validates that the :class:`Feature` property set of a :class:`Device` holds all required
248
+ :class:`Feature` objects of the related :class:`VDevice`. For this the method checks that every feature (that
249
+ is used in a mapped :class:`VDevice`) also exists as a child :class:`Feature` property in the related
250
+ :class:`Device` class.
251
+
252
+ .. note::
253
+ Variations are not related to this and will not be checked here.
254
+
255
+ """
256
+
257
+ for cur_device in self.get_all_abs_inner_device_classes():
258
+ cur_device_instantiated_features = \
259
+ DeviceController.get_for(cur_device).get_all_instantiated_feature_objects()
260
+ for _, cur_feature in cur_device_instantiated_features.items():
261
+ active_vdevice, related_device = cur_feature.active_vdevice_device_mapping
262
+ if active_vdevice is None:
263
+ # ignore this, because no active vdevice exists
264
+ continue
265
+
266
+ # secure that all the defined features in the VDevice also exist in the related device ->
267
+ # otherwise error
268
+ orig_device_features = [
269
+ feat for _, feat in
270
+ DeviceController.get_for(related_device).get_all_instantiated_feature_objects().items()]
271
+ active_vdevice_instantiated_features = \
272
+ VDeviceController.get_for(active_vdevice).get_all_instantiated_feature_objects()
273
+ for _, cur_vdevice_feature in active_vdevice_instantiated_features.items():
274
+ # search for it
275
+ found_it = False
276
+ for cur_orig_feature in orig_device_features:
277
+ if isinstance(cur_orig_feature, cur_vdevice_feature.__class__):
278
+ found_it = True
279
+ break
280
+ if not found_it:
281
+ raise MissingFeaturesOfVDeviceError(
282
+ f"the device `{related_device.__name__}` which is mapped to the VDevice "
283
+ f"`{active_vdevice.__name__}` doesn't have an implementation for the feature "
284
+ f"`{cur_vdevice_feature.__class__.__name__}` required by the VDevice class "
285
+ f"`{active_vdevice.__name__}`")
286
+
287
+ def get_absolute_single_connections(self) -> \
288
+ Dict[Type[Device], Dict[str, Dict[Type[Device], Dict[str, List[Connection]]]]]:
289
+ """
290
+ This method determines the synchronized (both devices of a connection were updated) absolute connections
291
+ between all devices of this scenario/setup.
292
+
293
+ :return: returns a dictionary which provides all single connections between two devices that can be accessed
294
+ with `result[dev1][node-of-dev1][dev2][node-of-dev2]`
295
+ """
296
+ # start to generate the singles for every connection between the devices of every scenario
297
+ all_abs_single_connections = {}
298
+
299
+ all_devices = self.get_all_abs_inner_device_classes()
300
+ for cur_from_device in all_devices:
301
+ cur_from_device_controller = DeviceController.get_for(cur_from_device)
302
+
303
+ # generate the whole `all_abs_single_connections` and convert the connections to singles
304
+ for cur_to_device, cur_connections in cur_from_device_controller.absolute_connections.items():
305
+ for cur_cnn in cur_connections:
306
+ if cur_from_device == cur_cnn.from_device:
307
+ cur_from_node = cur_cnn.from_node_name
308
+ cur_to_node = cur_cnn.to_node_name
309
+ else:
310
+ cur_from_node = cur_cnn.to_node_name
311
+ cur_to_node = cur_cnn.from_node_name
312
+ if cur_from_device not in all_abs_single_connections.keys():
313
+ all_abs_single_connections[cur_from_device] = {}
314
+ if cur_from_node not in all_abs_single_connections[cur_from_device].keys():
315
+ all_abs_single_connections[cur_from_device][cur_from_node] = {}
316
+ if cur_to_device not in \
317
+ all_abs_single_connections[cur_from_device][cur_from_node].keys():
318
+ all_abs_single_connections[cur_from_device][
319
+ cur_from_node][cur_to_device] = {}
320
+ if cur_to_node not in \
321
+ all_abs_single_connections[cur_from_device][cur_from_node][cur_to_device].keys():
322
+ all_abs_single_connections[cur_from_device][cur_from_node][cur_to_device][
323
+ cur_to_node] = cur_cnn.get_singles()
324
+ # we do not have to set the connection in communication device, because the absolute
325
+ # connections are always synchronized
326
+ else:
327
+ raise ValueError(
328
+ f'found multiple definitions for connection from device '
329
+ f'{cur_from_device.__qualname__} (node: `{cur_from_node}`) to device '
330
+ f'{cur_to_device.__qualname__} (node: `{cur_to_node}`) in scenario '
331
+ f'`{self.related_cls.__name__}`')
332
+ return all_abs_single_connections
333
+
334
+ def determine_raw_absolute_device_connections(self):
335
+ """
336
+ This method determines and creates the basic `_absolute_connections` for the related scenario/setup. Note,
337
+ that this method only creates the class attribute and adds the synchronized connections (same on both sides if
338
+ they are bidirectional). It does not analyse or take :class:`Feature` classes into consideration.
339
+ """
340
+ # determine next relevant base class
341
+ next_base_class = self.get_next_parent_class()
342
+
343
+ # executed this method for all parents too
344
+ if next_base_class:
345
+ NormalScenarioSetupController.get_for(next_base_class).determine_raw_absolute_device_connections()
346
+
347
+ all_relevant_cnns = []
348
+
349
+ all_devices = self.get_all_inner_device_classes()
350
+ all_devices_as_strings = [search_device.__name__ for search_device in all_devices]
351
+
352
+ # check if the devices of the current item has minimum one own connect() decorator
353
+ has_connect_decorator = False
354
+ for cur_device in all_devices:
355
+ if len(DeviceController.get_for(cur_device).connections) > 0:
356
+ has_connect_decorator = True
357
+
358
+ if len(all_devices) == 0 and len(self.get_all_abs_inner_device_classes()) > 0:
359
+ # only the parent class has defined scenarios -> use absolute data from next parent
360
+ # NOTHING TO DO, because we also use these devices in child setup/scenario
361
+ return
362
+
363
+ if len(all_devices) > 0 and not has_connect_decorator:
364
+ # the current item has defined devices, but no own `@connect()` decorator -> use absolute data from
365
+ # next parent
366
+ if next_base_class is not None:
367
+ # only if there is a next base class
368
+ next_base_class_controller = NormalScenarioSetupController.get_for(next_base_class)
369
+
370
+ for cur_parent_cnn in next_base_class_controller.get_all_abs_connections():
371
+
372
+ # find all related devices (for this connection)
373
+ related_from_device = \
374
+ all_devices[all_devices_as_strings.index(cur_parent_cnn.from_device.__name__)]
375
+ related_to_device = all_devices[all_devices_as_strings.index(cur_parent_cnn.to_device.__name__)]
376
+ new_cnn = cur_parent_cnn.clone()
377
+ new_cnn.set_metadata_for_all_subitems(ConnectionMetadata(
378
+ from_device=related_from_device, from_device_node_name=cur_parent_cnn.from_node_name,
379
+ to_device=related_to_device, to_device_node_name=cur_parent_cnn.to_node_name)
380
+ )
381
+ all_relevant_cnns.append(new_cnn)
382
+
383
+ # throw warning (but only if this scenario/setup has minimum one of the parent classes has inner
384
+ # devices (and connection between them) by its own)
385
+ if len(next_base_class_controller.get_all_abs_inner_device_classes()) > 0 and \
386
+ len(next_base_class_controller.get_all_abs_connections()) > 0:
387
+ logger.warning(
388
+ f"the collected `{self.related_cls.__name__}` class overwrites devices, but does "
389
+ f"not define connections between them by its own - please provide them in case you "
390
+ f"overwrite devices")
391
+ else:
392
+ # otherwise, use data from current layer, because there is no parent, no devices or this item overwrites
393
+ # the connections from higher classes
394
+ for cur_device in all_devices:
395
+ for _, cur_cnn_list in DeviceController.get_for(cur_device).connections.items():
396
+ # now add every single connection correctly into the dictionary
397
+ all_relevant_cnns += [cur_cnn for cur_cnn in cur_cnn_list if cur_cnn not in all_relevant_cnns]
398
+
399
+ # now set the absolute connections correctly
400
+ for cur_cnn in all_relevant_cnns:
401
+ DeviceController.get_for(cur_cnn.from_device).add_new_absolute_connection(cur_cnn)
402
+ DeviceController.get_for(cur_cnn.to_device).add_new_absolute_connection(cur_cnn)