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.
- _balder/_version.py +1 -1
- _balder/cnnrelations/__init__.py +7 -0
- _balder/cnnrelations/and_connection_relation.py +149 -0
- _balder/cnnrelations/base_connection_relation.py +270 -0
- _balder/cnnrelations/or_connection_relation.py +65 -0
- _balder/collector.py +10 -16
- _balder/connection.py +400 -881
- _balder/connection_metadata.py +255 -0
- _balder/controllers/device_controller.py +37 -16
- _balder/controllers/feature_controller.py +63 -99
- _balder/controllers/normal_scenario_setup_controller.py +5 -5
- _balder/controllers/scenario_controller.py +6 -6
- _balder/controllers/setup_controller.py +2 -3
- _balder/decorator_connect.py +12 -10
- _balder/decorator_for_vdevice.py +17 -25
- _balder/decorator_gateway.py +3 -3
- _balder/executor/testcase_executor.py +0 -1
- _balder/executor/variation_executor.py +212 -199
- _balder/feature.py +1 -1
- _balder/feature_replacement_mapping.py +69 -0
- _balder/feature_vdevice_mapping.py +88 -0
- _balder/fixture_manager.py +10 -9
- _balder/objects/connections/osi_3_network.py +2 -2
- _balder/objects/connections/osi_4_transport.py +2 -2
- _balder/routing_path.py +27 -31
- _balder/solver.py +1 -1
- _balder/testresult.py +1 -1
- _balder/utils.py +27 -1
- {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/METADATA +2 -2
- {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/RECORD +34 -27
- {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/WHEEL +1 -1
- {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b10.dist-info → baldertest-0.1.0b12.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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
|
|
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
|
-
|
|
346
|
-
if
|
|
347
|
-
|
|
348
|
-
meta
|
|
349
|
-
|
|
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
|
-
|
|
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
|
|
433
|
-
|
|
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
|
|
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"`{
|
|
444
|
-
|
|
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],
|
|
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],
|
|
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],
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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],
|
|
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) ->
|
|
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
|
|
170
|
-
given
|
|
177
|
+
This method returns the class based data for the `@for_vdevice` decorator.
|
|
171
178
|
"""
|
|
172
|
-
|
|
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],
|
|
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()
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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],
|
|
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],
|
|
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,
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
|