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
_balder/connection.py
CHANGED
|
@@ -3,11 +3,27 @@ from typing import List, Tuple, Union, Type, Dict
|
|
|
3
3
|
|
|
4
4
|
import copy
|
|
5
5
|
import itertools
|
|
6
|
+
|
|
7
|
+
from _balder.connection_metadata import ConnectionMetadata
|
|
6
8
|
from _balder.device import Device
|
|
7
9
|
from _balder.exceptions import IllegalConnectionTypeError
|
|
10
|
+
from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
|
|
11
|
+
from _balder.utils import cnn_type_check_and_convert
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConnectionType(type):
|
|
15
|
+
"""
|
|
16
|
+
Metaclass for :class:`Connection` objects
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __and__(cls, other) -> AndConnectionRelation:
|
|
20
|
+
return cls().__and__(other)
|
|
21
|
+
|
|
22
|
+
def __or__(cls, other) -> OrConnectionRelation:
|
|
23
|
+
return cls().__or__(other)
|
|
8
24
|
|
|
9
25
|
|
|
10
|
-
class Connection:
|
|
26
|
+
class Connection(metaclass=ConnectionType):
|
|
11
27
|
"""
|
|
12
28
|
This is the basic connection class. On one side it is the common base class for all connection objects. On the other
|
|
13
29
|
side it can also be used to describe a container connection, that contains a list of different connection items.
|
|
@@ -23,7 +39,7 @@ class Connection:
|
|
|
23
39
|
.. note::
|
|
24
40
|
With a direct instance of a :class:`Connection` object you can create an own Connection-Tree. You can use
|
|
25
41
|
this container object, if you need a container for a list of :class:`Connection`-Trees that are combined
|
|
26
|
-
with an AND (
|
|
42
|
+
with an AND (``&``) or/and an OR (``|``).
|
|
27
43
|
|
|
28
44
|
:param from_device: the device this connection starts from (default: None)
|
|
29
45
|
|
|
@@ -33,47 +49,35 @@ class Connection:
|
|
|
33
49
|
|
|
34
50
|
:param to_device_node_name: the node name of the device the connection ends
|
|
35
51
|
"""
|
|
52
|
+
# note: currently every connection is bidirectional (we want to add support for this later)
|
|
36
53
|
|
|
37
54
|
# contains all metadata of this connection object
|
|
38
|
-
self._metadata =
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
self._metadata = ConnectionMetadata(
|
|
56
|
+
from_device=from_device,
|
|
57
|
+
to_device=to_device,
|
|
58
|
+
from_device_node_name=from_device_node_name,
|
|
59
|
+
to_device_node_name=to_device_node_name,
|
|
60
|
+
bidirectional=True
|
|
61
|
+
)
|
|
41
62
|
|
|
42
|
-
self._bidirectional = None
|
|
43
63
|
# contains all sub connection objects, this connection tree is based on
|
|
44
|
-
self._based_on_connections =
|
|
64
|
+
self._based_on_connections = OrConnectionRelation()
|
|
45
65
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
f"`from_device` - should be a subclasses of `balder.Device`")
|
|
49
|
-
self._metadata["from_device"] = from_device
|
|
66
|
+
def __and__(self, other: Union[Connection, AndConnectionRelation, OrConnectionRelation]) -> AndConnectionRelation:
|
|
67
|
+
new_list = AndConnectionRelation(self)
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
f"`from_device_node_name` - should be a string value")
|
|
54
|
-
self._metadata["from_device_node_name"] = from_device_node_name
|
|
69
|
+
other = cnn_type_check_and_convert(other)
|
|
70
|
+
new_list.append(other)
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
raise TypeError(f"detect illegal argument element {str(to_device)} for given attribute "
|
|
58
|
-
f"`to_device` - should be a subclasses of `balder.Device`")
|
|
59
|
-
self._metadata["to_device"] = to_device
|
|
72
|
+
return new_list
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
f"`to_device_node_name` - should be a string value")
|
|
64
|
-
self._metadata["to_device_node_name"] = to_device_node_name
|
|
74
|
+
def __or__(self, other: Union[Connection, AndConnectionRelation, OrConnectionRelation]) -> OrConnectionRelation:
|
|
75
|
+
new_list = OrConnectionRelation(self)
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self._bidirectional = True
|
|
77
|
+
other = cnn_type_check_and_convert(other)
|
|
78
|
+
new_list.append(other)
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
and to_device_node_name is None) or (
|
|
72
|
-
from_device is not None and to_device is not None and from_device_node_name is not None and
|
|
73
|
-
to_device_node_name is not None)):
|
|
74
|
-
raise ValueError(
|
|
75
|
-
"you have to provide all or none of the following items: `from_device`, `from_device_node_name`, "
|
|
76
|
-
"`to_device` or `to_device_node_name`")
|
|
80
|
+
return new_list
|
|
77
81
|
|
|
78
82
|
def __eq__(self, other):
|
|
79
83
|
if isinstance(other, Connection):
|
|
@@ -82,202 +86,12 @@ class Connection:
|
|
|
82
86
|
return False
|
|
83
87
|
|
|
84
88
|
def __hash__(self):
|
|
85
|
-
all_hashes = hash(self.
|
|
86
|
-
hash(self.to_node_name) + hash(str(self))
|
|
87
|
-
for cur_child in self.based_on_elements:
|
|
88
|
-
all_hashes += hash(cur_child)
|
|
89
|
+
all_hashes = hash(self.metadata) + hash(self.based_on_elements)
|
|
89
90
|
return hash(all_hashes)
|
|
90
91
|
|
|
91
|
-
def clone_without_based_on_elements(self) -> Connection:
|
|
92
|
-
"""
|
|
93
|
-
This method returns a copied version of this element, while all `_based_on_connections` are removed (the copied
|
|
94
|
-
element has an empty list here).
|
|
95
|
-
|
|
96
|
-
:return: a python copied object of this item
|
|
97
|
-
"""
|
|
98
|
-
self_copy = copy.copy(self)
|
|
99
|
-
self_copy._based_on_connections = [] # pylint: disable=protected-access
|
|
100
|
-
return self_copy
|
|
101
|
-
|
|
102
|
-
def clone(self) -> Connection:
|
|
103
|
-
"""
|
|
104
|
-
This method returns an exact clone of this connection. For this clone every inner connection object will be
|
|
105
|
-
newly instantiated, but all internal references (like the devices and so on) will not be copied (objects are the
|
|
106
|
-
same for this object and the clone). The method will make a normal copy for every connection object in the
|
|
107
|
-
`_based_on_elements` list.
|
|
108
|
-
"""
|
|
109
|
-
self_copy = self.clone_without_based_on_elements()
|
|
110
|
-
|
|
111
|
-
for cur_based_on in self._based_on_connections:
|
|
112
|
-
if isinstance(cur_based_on, tuple):
|
|
113
|
-
cloned_tuple = tuple([cur_tuple_element.clone() for cur_tuple_element in cur_based_on])
|
|
114
|
-
self_copy.append_to_based_on(cloned_tuple)
|
|
115
|
-
elif isinstance(cur_based_on, Connection):
|
|
116
|
-
cloned_cur_based_on = cur_based_on.clone()
|
|
117
|
-
self_copy.append_to_based_on(cloned_cur_based_on)
|
|
118
|
-
else:
|
|
119
|
-
raise TypeError('based on element is not from valid type')
|
|
120
|
-
return self_copy
|
|
121
|
-
|
|
122
|
-
@staticmethod
|
|
123
|
-
def __cut_conn_from_only_parent_to_child(elem: Connection) -> List[Connection]:
|
|
124
|
-
"""
|
|
125
|
-
This helper method returns all possible pieces while the base element will remain intact - so every returned
|
|
126
|
-
element is from the same type as `elem` (only the related `based_on_elements` are changed).
|
|
127
|
-
|
|
128
|
-
.. note::
|
|
129
|
-
The given element itself will also be added here!
|
|
130
|
-
|
|
131
|
-
.. note::
|
|
132
|
-
The given element has to be single!
|
|
133
|
-
"""
|
|
134
|
-
all_pieces = [elem.clone()]
|
|
135
|
-
if len(elem.based_on_elements) == 0:
|
|
136
|
-
return all_pieces
|
|
137
|
-
# if the next element is a tuple -> call procedure for tuples and add a copy of this object as child
|
|
138
|
-
if isinstance(elem.based_on_elements[0], tuple):
|
|
139
|
-
# return all possibilities of the tuple while adding the current object as child of the tuple
|
|
140
|
-
for cur_tuple in Connection.__cut_tuple_from_only_parent_to_child(elem.based_on_elements[0]):
|
|
141
|
-
# for this we do not have to use `clone`, because we copy the base object with `copy.copy` and
|
|
142
|
-
# completely replace the `_based_on_connections` by our own
|
|
143
|
-
copied_conn = elem.clone_without_based_on_elements()
|
|
144
|
-
copied_conn.append_to_based_on(cur_tuple)
|
|
145
|
-
all_pieces.append(copied_conn)
|
|
146
|
-
return all_pieces
|
|
147
|
-
# if this element is the last element with a parent -> copy it and remove the parent, return it
|
|
148
|
-
if len(elem.based_on_elements[0].based_on_elements) == 0:
|
|
149
|
-
new_elem = elem.clone_without_based_on_elements()
|
|
150
|
-
all_pieces.append(new_elem)
|
|
151
|
-
return all_pieces
|
|
152
|
-
# otherwise, the current item has grandparents, so call the method recursively for parents and add a copy of
|
|
153
|
-
# this object as child
|
|
154
|
-
all_possible_parents = Connection.__cut_conn_from_only_parent_to_child(elem.based_on_elements[0])
|
|
155
|
-
for cur_parent in all_possible_parents:
|
|
156
|
-
copied_conn = elem.clone_without_based_on_elements()
|
|
157
|
-
copied_conn.append_to_based_on(cur_parent)
|
|
158
|
-
all_pieces.append(copied_conn)
|
|
159
|
-
return all_pieces
|
|
160
|
-
|
|
161
|
-
@staticmethod
|
|
162
|
-
def __cut_tuple_from_only_parent_to_child(elem: Tuple[Connection]) -> List[Tuple[Connection]]:
|
|
163
|
-
"""
|
|
164
|
-
This helper method returns all possible pieces while the base elements of the tuple will remain intact - so
|
|
165
|
-
every returned tuple element is from same type like the related elements in the tuple `elem` (only the related
|
|
166
|
-
`based_on_elements` are changed).
|
|
167
|
-
|
|
168
|
-
.. note::
|
|
169
|
-
The given element has to be single!
|
|
170
|
-
|
|
171
|
-
.. note::
|
|
172
|
-
Note that this method also returns every possible ordering
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
tuple_with_all_possibilities = (
|
|
176
|
-
tuple([Connection.__cut_conn_from_only_parent_to_child(cur_tuple_item) for cur_tuple_item in elem]))
|
|
177
|
-
|
|
178
|
-
cloned_tuple_list = []
|
|
179
|
-
for cur_tuple in list(itertools.product(*tuple_with_all_possibilities)):
|
|
180
|
-
cloned_tuple = tuple([cur_tuple_item.clone() for cur_tuple_item in cur_tuple])
|
|
181
|
-
cloned_tuple_list.append(cloned_tuple)
|
|
182
|
-
return cloned_tuple_list
|
|
183
|
-
|
|
184
|
-
@staticmethod
|
|
185
|
-
def check_if_tuple_contained_in_connection(tuple_elem: Tuple[Connection], other_elem: Connection) -> bool:
|
|
186
|
-
"""
|
|
187
|
-
This method checks if the tuple given by `tuple_elem` is contained in the `other_elem`. To ensure that a tuple
|
|
188
|
-
element is contained in a connection tree, there has to be another tuple into the `other_elem`, that has the
|
|
189
|
-
same length or is bigger. In addition, there has to exist an order combination where every element of the
|
|
190
|
-
`tuple_elem` is contained in the found tuple in `other_elem`. In this case it doesn't matter where the tuple is
|
|
191
|
-
in `other_elem` (will be converted to single, and tuple will be searched in all BASED_ON elements). If the tuple
|
|
192
|
-
element of `other_elem` has fewer items than our `tuple_elem`, it will be ignored. The method only search for
|
|
193
|
-
a valid existing item in the `other_elem` tuple for every item of the `tuple_elem`.
|
|
194
|
-
|
|
195
|
-
:param tuple_elem: the tuple element that should be contained in the `other_elem`
|
|
196
|
-
|
|
197
|
-
:param other_elem: the connection object, the given tuple should be contained in
|
|
198
|
-
"""
|
|
199
|
-
def tuple_is_contained_in_other(inner_tuple, contained_in_tuple):
|
|
200
|
-
# check if every tuple elem fits in one of `contained_in_tuple` (allow to use a position in
|
|
201
|
-
# `contained_in_tuple` multiple times)
|
|
202
|
-
for cur_tuple_element in inner_tuple:
|
|
203
|
-
found_match_for_this_elem = False
|
|
204
|
-
for cur_contained_in_elem in contained_in_tuple:
|
|
205
|
-
if cur_tuple_element.contained_in(cur_contained_in_elem, ignore_metadata=True):
|
|
206
|
-
found_match_for_this_elem = True
|
|
207
|
-
break
|
|
208
|
-
if not found_match_for_this_elem:
|
|
209
|
-
return False
|
|
210
|
-
return True
|
|
211
|
-
|
|
212
|
-
other_singles = other_elem.get_singles()
|
|
213
|
-
tuple_singles = Connection.convert_tuple_to_singles(tuple_elem)
|
|
214
|
-
for cur_tuple_single in tuple_singles:
|
|
215
|
-
for cur_other_single in other_singles:
|
|
216
|
-
# check if we found a tuple element in the other object -> go the single connection upwards and search
|
|
217
|
-
# for a tuple
|
|
218
|
-
cur_sub_other_single: List[Union[Connection, Tuple[Connection]]] = [cur_other_single]
|
|
219
|
-
while len(cur_sub_other_single) > 0:
|
|
220
|
-
if isinstance(cur_sub_other_single[0], tuple):
|
|
221
|
-
# found a tuple -> check if length does match
|
|
222
|
-
if len(cur_sub_other_single[0]) >= len(cur_tuple_single) and tuple_is_contained_in_other(
|
|
223
|
-
cur_tuple_single, contained_in_tuple=cur_sub_other_single[0]):
|
|
224
|
-
return True
|
|
225
|
-
# otherwise, this complete element is not possible - skip this single!
|
|
226
|
-
break
|
|
227
|
-
cur_sub_other_single = cur_sub_other_single[0].based_on_elements
|
|
228
|
-
return False
|
|
229
|
-
|
|
230
|
-
@staticmethod
|
|
231
|
-
def convert_tuple_to_singles(tuple_elem: Tuple[Connection]) -> List[Union[Connection, Tuple[Connection]]]:
|
|
232
|
-
"""
|
|
233
|
-
This method converts the given `tuple_elem` to single items and return these.
|
|
234
|
-
|
|
235
|
-
:param tuple_elem: the tuple element out of which the single items are being created
|
|
236
|
-
|
|
237
|
-
:returns: a list of new connection objects that are single items
|
|
238
|
-
"""
|
|
239
|
-
singles_tuple = ()
|
|
240
|
-
tuple_idx = 0
|
|
241
|
-
for cur_tuple_elem in tuple_elem:
|
|
242
|
-
if not isinstance(cur_tuple_elem, Connection):
|
|
243
|
-
raise TypeError(f"the tuple element at index {tuple_idx} of element from `other_conn` is not "
|
|
244
|
-
"from type `Connection`")
|
|
245
|
-
# get all singles of this tuple element
|
|
246
|
-
singles_tuple += (cur_tuple_elem.get_singles(),)
|
|
247
|
-
tuple_idx += 1
|
|
248
|
-
# now get the variations and add them to our results
|
|
249
|
-
return list(itertools.product(*singles_tuple))
|
|
250
|
-
|
|
251
|
-
@staticmethod
|
|
252
|
-
def cleanup_connection_list(full_list: List[Union[Connection, Tuple[Connection]]]) \
|
|
253
|
-
-> List[Union[Connection, Tuple[Connection]]]:
|
|
254
|
-
"""
|
|
255
|
-
This method cleanup a connection list while removing items that are direct duplicates and by removing duplicates
|
|
256
|
-
that are fully contained_in other items.
|
|
257
|
-
|
|
258
|
-
:param full_list: the full list of connections and tuples of connections that should be cleaned-up
|
|
259
|
-
|
|
260
|
-
:returns: returns the cleaned up list
|
|
261
|
-
"""
|
|
262
|
-
result = full_list.copy()
|
|
263
|
-
next_loop = True
|
|
264
|
-
while not next_loop:
|
|
265
|
-
next_loop = False
|
|
266
|
-
for cur_elem in result:
|
|
267
|
-
all_other_elems = [cur_item for cur_item in result if cur_item != cur_elem]
|
|
268
|
-
if isinstance(cur_elem, Connection):
|
|
269
|
-
for cur_other_elem in all_other_elems:
|
|
270
|
-
# check if it contained in or the same
|
|
271
|
-
if cur_elem.contained_in(cur_other_elem, ignore_metadata=True):
|
|
272
|
-
# we can remove it from result list
|
|
273
|
-
result.remove(cur_elem)
|
|
274
|
-
next_loop = True
|
|
275
|
-
break
|
|
276
|
-
return result
|
|
277
|
-
|
|
278
92
|
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
279
93
|
|
|
280
|
-
# ---------------------------------- CLASS METHODS
|
|
94
|
+
# ---------------------------------- CLASS METHODS -----------------------------------------------------------------
|
|
281
95
|
|
|
282
96
|
@classmethod
|
|
283
97
|
def get_parents(cls, tree_name: Union[str, None] = None) -> List[Type[Connection]]:
|
|
@@ -337,342 +151,206 @@ class Connection:
|
|
|
337
151
|
return False
|
|
338
152
|
|
|
339
153
|
@classmethod
|
|
340
|
-
def based_on(
|
|
341
|
-
|
|
154
|
+
def based_on(
|
|
155
|
+
cls,
|
|
156
|
+
connection: Union[Connection, Type[Connection], AndConnectionRelation, OrConnectionRelation]
|
|
157
|
+
) -> Connection:
|
|
342
158
|
"""
|
|
343
159
|
With this method it is possible to define several sublayers of the connection. You can pass various other
|
|
344
160
|
connections in this method as arguments.
|
|
345
161
|
|
|
346
|
-
Note that
|
|
347
|
-
|
|
348
|
-
`BaseConnType` is based on a `ConnType1` or a `ConnType2`.
|
|
162
|
+
Note that you can define logical statements with the `|` and the `&` operator.
|
|
163
|
+
For example: ``Connection1() or Connection2() and Connection3()``.
|
|
349
164
|
|
|
350
|
-
|
|
351
|
-
you have to pass them as a tuple. This would look like this: `BaseConnType.based_on((ConnType1, ConnType2))`
|
|
352
|
-
|
|
353
|
-
:param args: all connection items for which this connection is based on
|
|
165
|
+
:param connection: all connection items for which this connection is based on
|
|
354
166
|
|
|
355
167
|
"""
|
|
356
168
|
this_instance = cls()
|
|
357
169
|
|
|
170
|
+
if isinstance(connection, type) and issubclass(connection, Connection):
|
|
171
|
+
connection = connection()
|
|
358
172
|
new_items = []
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
new_items.append(cur_item)
|
|
173
|
+
if isinstance(connection, Connection):
|
|
174
|
+
new_items.append(connection)
|
|
175
|
+
elif isinstance(connection, AndConnectionRelation):
|
|
176
|
+
new_items.append(connection)
|
|
177
|
+
elif isinstance(connection, OrConnectionRelation):
|
|
178
|
+
for cur_inner_elem in connection.connections:
|
|
179
|
+
new_items.append(cur_inner_elem)
|
|
180
|
+
else:
|
|
181
|
+
raise TypeError(f'can not use object from type {connection}')
|
|
182
|
+
|
|
370
183
|
# do not create a container connection if no container is required here
|
|
371
|
-
if cls == Connection and len(new_items) == 1 and
|
|
184
|
+
if cls == Connection and len(new_items) == 1 and isinstance(new_items[0], Connection):
|
|
372
185
|
return new_items[0]
|
|
373
186
|
this_instance.append_to_based_on(*new_items)
|
|
374
187
|
return this_instance
|
|
375
188
|
|
|
376
189
|
@classmethod
|
|
377
|
-
def
|
|
378
|
-
cls,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
:param
|
|
389
|
-
:param
|
|
390
|
-
:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
for cur_other_cnn in are_in_cnns_from:
|
|
397
|
-
if cur_cnn.equal_with(cur_other_cnn, ignore_metadata=ignore_metadata):
|
|
398
|
-
found_equal = True
|
|
399
|
-
break
|
|
400
|
-
if not found_equal:
|
|
401
|
-
return False
|
|
402
|
-
return True
|
|
403
|
-
|
|
404
|
-
@classmethod
|
|
405
|
-
def check_equal_connection_tuples_are_in(
|
|
406
|
-
cls, tuples_from: List[Tuple[Connection]], are_in_tuples_from: List[Tuple[Connection]],
|
|
407
|
-
ignore_metadata: bool=False) -> bool:
|
|
408
|
-
"""
|
|
409
|
-
This method validates that the connection tuples from the first list are contained (are equal) within the tuple
|
|
410
|
-
elements of the second list.
|
|
411
|
-
|
|
412
|
-
.. note::
|
|
413
|
-
This method only checks that every single tuple from the first element is contained in the second list.
|
|
414
|
-
It does not check the other direction. If you want to validate this, you need to call this method with both
|
|
415
|
-
possibilities.
|
|
416
|
-
|
|
417
|
-
:param tuples_from: the first list of connection-tuples
|
|
418
|
-
:param are_in_tuples_from: the second list of connection-tuples
|
|
419
|
-
:param ignore_metadata: True, if the metadata of the single connections should be ignored
|
|
420
|
-
:return: True in case that every tuple connections of the first list is equal with one tuple in the second,
|
|
421
|
-
otherwise False
|
|
422
|
-
"""
|
|
423
|
-
for cur_search_tuple in tuples_from:
|
|
424
|
-
found_match_for_cur_search_tuple = False
|
|
425
|
-
# go through each unmatched other tuple
|
|
426
|
-
for cur_other_tuple in are_in_tuples_from:
|
|
427
|
-
cur_search_tuple_is_completely_in_other_tuple = True
|
|
428
|
-
for cur_search_tuple_elem in cur_search_tuple:
|
|
429
|
-
fount_it = False
|
|
430
|
-
for cur_other_tuple_elem in cur_other_tuple:
|
|
431
|
-
if cur_search_tuple_elem.equal_with(cur_other_tuple_elem, ignore_metadata=ignore_metadata):
|
|
432
|
-
fount_it = True
|
|
433
|
-
break
|
|
434
|
-
if not fount_it:
|
|
435
|
-
cur_search_tuple_is_completely_in_other_tuple = False
|
|
436
|
-
break
|
|
437
|
-
if cur_search_tuple_is_completely_in_other_tuple:
|
|
438
|
-
# here we have no match
|
|
439
|
-
found_match_for_cur_search_tuple = True
|
|
440
|
-
break
|
|
441
|
-
if not found_match_for_cur_search_tuple:
|
|
442
|
-
return False
|
|
443
|
-
return True
|
|
190
|
+
def filter_connections_that_are_contained_in(
|
|
191
|
+
cls,
|
|
192
|
+
cnns_from: List[Union[Connection, AndConnectionRelation, OrConnectionRelation]],
|
|
193
|
+
are_contained_in: Connection,
|
|
194
|
+
ignore_metadata: bool = False
|
|
195
|
+
) -> List[Connection]:
|
|
196
|
+
"""
|
|
197
|
+
This method filters the connection elements from the first list to include only those connections that are
|
|
198
|
+
contained within the provided connection ``are_contained_in``.
|
|
199
|
+
|
|
200
|
+
:param cnns_from: a list of connections
|
|
201
|
+
:param are_contained_in: the connection, the connection elements should be contained in
|
|
202
|
+
:param ignore_metadata: True, if the metadata should be ignored
|
|
203
|
+
:return: a list with the filtered connections
|
|
204
|
+
"""
|
|
205
|
+
return [
|
|
206
|
+
cnn for cnn in cnns_from
|
|
207
|
+
if cnn.contained_in(other_conn=are_contained_in, ignore_metadata=ignore_metadata)
|
|
208
|
+
]
|
|
444
209
|
|
|
445
210
|
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
446
211
|
|
|
447
212
|
@property
|
|
448
|
-
def metadata(self) ->
|
|
213
|
+
def metadata(self) -> ConnectionMetadata:
|
|
449
214
|
"""returns the connection metadata dictionary"""
|
|
450
215
|
return self._metadata
|
|
451
216
|
|
|
452
|
-
@metadata.setter
|
|
453
|
-
def metadata(self, data):
|
|
454
|
-
empty_metadata = {
|
|
455
|
-
"from_device": None, "to_device": None, "from_device_node_name": None, "to_device_node_name": None}
|
|
456
|
-
|
|
457
|
-
if not isinstance(data, dict):
|
|
458
|
-
raise ValueError("the given metadata value has to be a dictionary")
|
|
459
|
-
if data != {}:
|
|
460
|
-
if sorted(["from_device", "to_device", "from_device_node_name", "to_device_node_name"]) != \
|
|
461
|
-
sorted(list(data.keys())):
|
|
462
|
-
raise ValueError("if you provide a metadata dictionary you have to provide all elements of it")
|
|
463
|
-
else:
|
|
464
|
-
data = empty_metadata.copy()
|
|
465
|
-
|
|
466
|
-
# only allow to set the metadata dictionary if the old one has the same values or was empty before (no values)
|
|
467
|
-
if data != empty_metadata:
|
|
468
|
-
if self._metadata == empty_metadata:
|
|
469
|
-
# it is ok, because the dictionary was empty before
|
|
470
|
-
pass
|
|
471
|
-
elif self._metadata == data:
|
|
472
|
-
# it is ok too, because the new set data is the same as the data was before
|
|
473
|
-
pass
|
|
474
|
-
else:
|
|
475
|
-
raise ValueError("you can not set another metadata than the data set before - please reset it first")
|
|
476
|
-
|
|
477
|
-
self._metadata = data
|
|
478
|
-
|
|
479
217
|
@property
|
|
480
218
|
def from_device(self):
|
|
481
219
|
"""device from which the connection starts"""
|
|
482
|
-
return self._metadata
|
|
220
|
+
return self._metadata.from_device if self._metadata else None
|
|
483
221
|
|
|
484
222
|
@property
|
|
485
223
|
def to_device(self):
|
|
486
224
|
"""device at which the connection ends"""
|
|
487
|
-
return self._metadata
|
|
225
|
+
return self._metadata.to_device if self._metadata else None
|
|
488
226
|
|
|
489
227
|
@property
|
|
490
228
|
def from_node_name(self):
|
|
491
229
|
"""the name of the node in the `Device` from which the connection starts"""
|
|
492
|
-
return self._metadata
|
|
230
|
+
return self._metadata.from_node_name if self._metadata else None
|
|
493
231
|
|
|
494
232
|
@property
|
|
495
233
|
def to_node_name(self):
|
|
496
234
|
"""the name of the node in the `Device` at which the connection ends"""
|
|
497
|
-
return self._metadata
|
|
235
|
+
return self._metadata.to_node_name if self._metadata else None
|
|
498
236
|
|
|
499
237
|
@property
|
|
500
|
-
def based_on_elements(self) ->
|
|
238
|
+
def based_on_elements(self) -> OrConnectionRelation:
|
|
501
239
|
"""returns a copy of the internal based_on_connection"""
|
|
502
|
-
return self._based_on_connections.
|
|
240
|
+
return self._based_on_connections.clone()
|
|
503
241
|
|
|
504
242
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
505
243
|
|
|
506
|
-
|
|
507
|
-
"""
|
|
508
|
-
This method returns true if the metadata of the current connection is contained in the given one.
|
|
509
|
-
|
|
510
|
-
The method returns true in the following situations:
|
|
511
|
-
* both connections are bidirectional and the from and to elements (device and node name) are the same
|
|
512
|
-
* both connections are unidirectional and have the same from and to elements
|
|
513
|
-
* both connections are bidirectional and the from is the to and the to is the from
|
|
514
|
-
* one connection is unidirectional and the other is bidirectional and the from and to elements are the same
|
|
515
|
-
* one connection is unidirectional and the other is bidirectional and the from is the to and the to is the from
|
|
516
|
-
|
|
517
|
-
:return: true if the metadata of the current connection is contained in the metadata of the given one
|
|
518
|
-
"""
|
|
519
|
-
check_same = \
|
|
520
|
-
self.from_device == of_connection.from_device and self.from_node_name == of_connection.from_node_name and \
|
|
521
|
-
self.to_device == of_connection.to_device and self.to_node_name == of_connection.to_node_name
|
|
522
|
-
check_opposite = \
|
|
523
|
-
self.from_device == of_connection.to_device and self.from_node_name == of_connection.to_node_name and \
|
|
524
|
-
self.to_device == of_connection.from_device and self.to_node_name == of_connection.from_node_name
|
|
525
|
-
if self.is_bidirectional() and of_connection.is_bidirectional():
|
|
526
|
-
return check_same or check_opposite
|
|
527
|
-
|
|
528
|
-
if self.is_bidirectional() and not of_connection.is_bidirectional() or \
|
|
529
|
-
not self.is_bidirectional() and of_connection.is_bidirectional():
|
|
530
|
-
return check_same or check_opposite
|
|
531
|
-
|
|
532
|
-
return check_same
|
|
533
|
-
|
|
534
|
-
def _metadata_equal_with(self, of_connection: Connection) -> bool:
|
|
535
|
-
"""
|
|
536
|
-
This method returns true if the metadata of the current connection is equal with the metadata of the given
|
|
537
|
-
connection.
|
|
538
|
-
|
|
539
|
-
The method returns true in the following situations:
|
|
540
|
-
* both connections are bidirectional and the from and to elements (device and node name) are the same
|
|
541
|
-
* both connections are unidirectional and have the same from and to elements
|
|
542
|
-
* both connections are bidirectional and the from is the to and the to is the from
|
|
543
|
-
|
|
544
|
-
:return: true if the metadata of the current connection is contained in the metadata of the given one
|
|
545
|
-
"""
|
|
546
|
-
check_same = \
|
|
547
|
-
self.from_device == of_connection.from_device and self.from_node_name == of_connection.from_node_name and \
|
|
548
|
-
self.to_device == of_connection.to_device and self.to_node_name == of_connection.to_node_name
|
|
549
|
-
check_opposite = \
|
|
550
|
-
self.from_device == of_connection.to_device and self.from_node_name == of_connection.to_node_name and \
|
|
551
|
-
self.to_device == of_connection.from_device and self.to_node_name == of_connection.from_node_name
|
|
552
|
-
if self.is_bidirectional() and of_connection.is_bidirectional() or \
|
|
553
|
-
not self.is_bidirectional() and not of_connection.is_bidirectional():
|
|
554
|
-
return check_same or check_opposite
|
|
555
|
-
|
|
556
|
-
return False
|
|
244
|
+
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
557
245
|
|
|
558
|
-
def get_intersection_with_other_single(
|
|
559
|
-
|
|
246
|
+
def get_intersection_with_other_single(
|
|
247
|
+
self,
|
|
248
|
+
other_conn: Union[Connection, AndConnectionRelation, OrConnectionRelation]
|
|
249
|
+
) -> List[Connection]:
|
|
560
250
|
"""
|
|
561
251
|
A helper method that returns an intersection between the two connections (self and the given one).
|
|
562
252
|
|
|
563
|
-
:param other_conn: the other **single** connection object (could be a
|
|
564
|
-
|
|
253
|
+
:param other_conn: the other **single** connection object (could be a relation too, but note that them needs to
|
|
254
|
+
be **single** elements!)
|
|
565
255
|
"""
|
|
566
256
|
if not self.is_single():
|
|
567
257
|
raise ValueError(
|
|
568
258
|
"the current connection object is not single -> method only possible for single connections")
|
|
569
|
-
if
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
"provide a single connection or a single tuple")
|
|
573
|
-
if not other_conn.is_single():
|
|
574
|
-
raise ValueError(
|
|
575
|
-
"the connection object given by `other_conn` is not single -> method only possible for single "
|
|
576
|
-
"connections")
|
|
577
|
-
elif isinstance(other_conn, tuple):
|
|
578
|
-
# has to be a tuple
|
|
579
|
-
cur_idx = 0
|
|
580
|
-
for cur_tuple_element in other_conn:
|
|
581
|
-
if not cur_tuple_element.is_single():
|
|
582
|
-
raise ValueError(
|
|
583
|
-
f"the connection object given by tuple element at index {cur_idx} in `other_conn` is not "
|
|
584
|
-
f"single -> method only possible for single connections")
|
|
585
|
-
cur_idx += 1
|
|
586
|
-
other_conn = Connection.based_on(other_conn)
|
|
587
|
-
else:
|
|
588
|
-
raise TypeError("the object given by `other_conn` has to be from type `Connection` or has to be a tuple "
|
|
589
|
-
"of `Connection` objects")
|
|
259
|
+
if not other_conn.is_single():
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"the other connection object is not single -> method only possible for single connections")
|
|
590
262
|
|
|
591
263
|
intersection = []
|
|
592
264
|
|
|
593
|
-
def determine_for(pieces: List[Union[Connection, Tuple[Connection]]], in_other_cnn: Connection):
|
|
594
|
-
for cur_piece in pieces:
|
|
595
|
-
if isinstance(cur_piece, Connection):
|
|
596
|
-
if cur_piece.contained_in(in_other_cnn, ignore_metadata=True):
|
|
597
|
-
intersection.append(cur_piece)
|
|
598
|
-
else:
|
|
599
|
-
# isinstance of tuple
|
|
600
|
-
if Connection.check_if_tuple_contained_in_connection(cur_piece, in_other_cnn):
|
|
601
|
-
intersection.append(cur_piece)
|
|
602
|
-
|
|
603
265
|
#: check if some sub elements of self connection are contained in `other_conn`
|
|
604
|
-
self_pieces = self.
|
|
605
|
-
|
|
266
|
+
self_pieces = self.cut_into_all_possible_pieces()
|
|
267
|
+
intersection.extend(self.__class__.filter_connections_that_are_contained_in(
|
|
268
|
+
cnns_from=self_pieces,
|
|
269
|
+
are_contained_in=other_conn,
|
|
270
|
+
ignore_metadata=True
|
|
271
|
+
))
|
|
606
272
|
|
|
607
273
|
#: check if some sub elements of `other_conn` are contained in self connection
|
|
608
|
-
other_pieces = other_conn.
|
|
609
|
-
|
|
274
|
+
other_pieces = other_conn.cut_into_all_possible_pieces()
|
|
275
|
+
intersection.extend(self.__class__.filter_connections_that_are_contained_in(
|
|
276
|
+
cnns_from=other_pieces,
|
|
277
|
+
are_contained_in=self,
|
|
278
|
+
ignore_metadata=True
|
|
279
|
+
))
|
|
610
280
|
|
|
611
281
|
#: filter all duplicated (and contained in each other) connections
|
|
612
282
|
intersection_without_duplicates = []
|
|
613
283
|
for cur_conn in intersection:
|
|
614
|
-
checkable_cur_conn = Connection.based_on(cur_conn) if isinstance(cur_conn, tuple) else cur_conn
|
|
615
284
|
found_it = False
|
|
616
285
|
for cur_existing_conn in intersection_without_duplicates:
|
|
617
|
-
|
|
618
|
-
if isinstance(cur_existing_conn, tuple) else cur_existing_conn
|
|
619
|
-
if checkable_cur_conn.equal_with(checkable_cur_existing_conn, ignore_metadata=True):
|
|
286
|
+
if cur_conn.equal_with(cur_existing_conn, ignore_metadata=True):
|
|
620
287
|
found_it = True
|
|
621
288
|
break
|
|
622
289
|
if not found_it:
|
|
623
290
|
intersection_without_duplicates.append(cur_conn)
|
|
291
|
+
|
|
624
292
|
intersection_filtered = []
|
|
625
293
|
#: filter all *contained in each other* connections
|
|
626
294
|
for cur_conn in intersection_without_duplicates:
|
|
627
|
-
usable_cur_conn = Connection.based_on(cur_conn) if isinstance(cur_conn, tuple) else cur_conn
|
|
628
295
|
is_contained_in_another = False
|
|
629
296
|
for cur_validate_cnn in intersection_without_duplicates:
|
|
630
|
-
cur_usable_validate_cnn = \
|
|
631
|
-
Connection.based_on(cur_validate_cnn) if isinstance(cur_validate_cnn, tuple) else cur_validate_cnn
|
|
632
297
|
if cur_validate_cnn == cur_conn:
|
|
633
298
|
# skip the same element
|
|
634
299
|
continue
|
|
635
|
-
if
|
|
300
|
+
if cur_conn.contained_in(cur_validate_cnn, ignore_metadata=True):
|
|
636
301
|
is_contained_in_another = True
|
|
637
302
|
break
|
|
638
303
|
if not is_contained_in_another:
|
|
304
|
+
if isinstance(cur_conn, (AndConnectionRelation,OrConnectionRelation)):
|
|
305
|
+
cur_conn = Connection.based_on(cur_conn)
|
|
639
306
|
intersection_filtered.append(cur_conn)
|
|
640
307
|
|
|
641
308
|
# only return unique objects
|
|
642
309
|
return intersection_filtered
|
|
643
310
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
def set_devices(self, from_device: Type[Device], to_device: Type[Device]):
|
|
311
|
+
def clone_without_based_on_elements(self) -> Connection:
|
|
647
312
|
"""
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
.. note::
|
|
651
|
-
Note that you can not change the devices after they were set (only possible internally)
|
|
313
|
+
This method returns a copied version of this element, while all `_based_on_connections` are removed (the copied
|
|
314
|
+
element has an empty list here).
|
|
652
315
|
|
|
653
|
-
:
|
|
316
|
+
:return: a python copied object of this item
|
|
317
|
+
"""
|
|
318
|
+
self_copy = copy.copy(self)
|
|
319
|
+
self_copy._based_on_connections = OrConnectionRelation() # pylint: disable=protected-access
|
|
320
|
+
return self_copy
|
|
654
321
|
|
|
655
|
-
|
|
322
|
+
def clone(self) -> Connection:
|
|
323
|
+
"""
|
|
324
|
+
This method returns an exact clone of this connection. For this clone every inner connection object will be
|
|
325
|
+
newly instantiated, but all internal references (like the devices and so on) will not be copied (objects are the
|
|
326
|
+
same for this object and the clone). The method will make a normal copy for every connection object in the
|
|
327
|
+
`_based_on_elements` list.
|
|
656
328
|
"""
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
self._metadata["to_device"] = to_device
|
|
329
|
+
self_copy = self.clone_without_based_on_elements()
|
|
330
|
+
self_copy.append_to_based_on(self._based_on_connections.clone()) # pylint: disable=protected-access
|
|
331
|
+
return self_copy
|
|
661
332
|
|
|
662
|
-
def
|
|
333
|
+
def cut_into_all_possible_subtree_branches(self) -> List[Connection]:
|
|
334
|
+
"""
|
|
335
|
+
This method returns a list of all possible connection tree branches. A branch is a single connection, while
|
|
336
|
+
this method returns a list of all possible singles where every single connection has this connection as head.
|
|
663
337
|
"""
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
338
|
+
all_pieces = [self.clone()]
|
|
339
|
+
if len(self.based_on_elements) == 0:
|
|
340
|
+
return all_pieces
|
|
667
341
|
|
|
668
|
-
|
|
342
|
+
if not self.is_single():
|
|
343
|
+
raise ValueError("the current connection object is not single -> method only possible for single "
|
|
344
|
+
"connections")
|
|
669
345
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
346
|
+
# return all possibilities of the relation while remain the current object as head
|
|
347
|
+
for sub_branch in self.based_on_elements.cut_into_all_possible_subtree_branches():
|
|
348
|
+
copied_conn = self.clone_without_based_on_elements()
|
|
349
|
+
copied_conn.append_to_based_on(sub_branch)
|
|
350
|
+
all_pieces.append(copied_conn)
|
|
351
|
+
return all_pieces
|
|
674
352
|
|
|
675
|
-
def
|
|
353
|
+
def cut_into_all_possible_pieces(self) -> List[Connection]:
|
|
676
354
|
"""
|
|
677
355
|
This method cuts the resolved connection tree in all possible pieces by removing elements that change
|
|
678
356
|
the existing tree - thereby the method returns a list with all possibilities (a copy of this object with all
|
|
@@ -684,7 +362,7 @@ class Connection:
|
|
|
684
362
|
"""
|
|
685
363
|
if self.__class__ == Connection:
|
|
686
364
|
# this is only a container, execute process for every item of this one
|
|
687
|
-
child_elems = self.based_on_elements
|
|
365
|
+
child_elems = self.based_on_elements.connections
|
|
688
366
|
else:
|
|
689
367
|
child_elems = [self]
|
|
690
368
|
|
|
@@ -692,32 +370,31 @@ class Connection:
|
|
|
692
370
|
|
|
693
371
|
for cur_item in child_elems:
|
|
694
372
|
|
|
695
|
-
if
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
raise ValueError(
|
|
703
|
-
f"one of the given tuple element has a item at index {tuple_idx}, that is not single -> "
|
|
704
|
-
f"method only works with single items or tuple of single items")
|
|
705
|
-
tuple_idx += 1
|
|
706
|
-
|
|
707
|
-
cur_element = [cur_item]
|
|
708
|
-
while len(cur_element) > 0:
|
|
709
|
-
if isinstance(cur_element[0], Connection):
|
|
710
|
-
all_pieces += Connection.__cut_conn_from_only_parent_to_child(cur_element[0])
|
|
711
|
-
elif isinstance(cur_element[0], tuple):
|
|
712
|
-
all_pieces += Connection.__cut_tuple_from_only_parent_to_child(cur_element[0])
|
|
373
|
+
if not cur_item.is_single():
|
|
374
|
+
raise ValueError("one of the given element is not single -> method only works with single items")
|
|
375
|
+
|
|
376
|
+
cur_element = cur_item
|
|
377
|
+
while cur_element:
|
|
378
|
+
|
|
379
|
+
if isinstance(cur_element, AndConnectionRelation):
|
|
713
380
|
# now we can not go deeper, because we need this current AND connection
|
|
381
|
+
all_pieces += [Connection.based_on(and_relation)
|
|
382
|
+
for and_relation in cur_element.cut_into_all_possible_subtree_branches()]
|
|
714
383
|
break
|
|
715
|
-
|
|
384
|
+
|
|
385
|
+
all_pieces += cur_element.cut_into_all_possible_subtree_branches()
|
|
386
|
+
|
|
387
|
+
if len(cur_element.based_on_elements.connections) > 1:
|
|
388
|
+
# should never be fulfilled because we have a single connection
|
|
389
|
+
raise ValueError('unexpected size of inner connections')
|
|
390
|
+
|
|
391
|
+
cur_element = cur_element.based_on_elements.connections[0] \
|
|
392
|
+
if cur_element.based_on_elements.connections else None
|
|
716
393
|
|
|
717
394
|
# filter all duplicates
|
|
718
395
|
return list(set(all_pieces))
|
|
719
396
|
|
|
720
|
-
def set_metadata_for_all_subitems(self, metadata: Union[
|
|
397
|
+
def set_metadata_for_all_subitems(self, metadata: Union[ConnectionMetadata, None]):
|
|
721
398
|
"""
|
|
722
399
|
This method sets the metadata for all existing Connection items in this element.
|
|
723
400
|
|
|
@@ -725,19 +402,14 @@ class Connection:
|
|
|
725
402
|
metadata from every item)
|
|
726
403
|
"""
|
|
727
404
|
if metadata is None:
|
|
728
|
-
metadata =
|
|
729
|
-
|
|
405
|
+
metadata = ConnectionMetadata()
|
|
406
|
+
|
|
407
|
+
if not isinstance(metadata, ConnectionMetadata):
|
|
408
|
+
raise TypeError('metadata must be an instance of `ConnectionMetadata`')
|
|
730
409
|
|
|
731
410
|
for cur_base_elem in self._based_on_connections:
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
elif isinstance(cur_base_elem, tuple):
|
|
735
|
-
for cur_tuple_elem in cur_base_elem:
|
|
736
|
-
cur_tuple_elem.set_metadata_for_all_subitems(metadata=metadata)
|
|
737
|
-
else:
|
|
738
|
-
raise TypeError(
|
|
739
|
-
f"found illegal type {cur_base_elem.__class__} for based_on item at index {cur_base_elem}")
|
|
740
|
-
self.metadata = metadata
|
|
411
|
+
cur_base_elem.set_metadata_for_all_subitems(metadata=metadata)
|
|
412
|
+
self._metadata = metadata
|
|
741
413
|
|
|
742
414
|
def get_tree_str(self) -> str:
|
|
743
415
|
"""
|
|
@@ -756,15 +428,15 @@ class Connection:
|
|
|
756
428
|
else:
|
|
757
429
|
based_on_strings.append(cur_elem.get_tree_str())
|
|
758
430
|
|
|
759
|
-
return f"{self.__class__.__name__}.based_on({'
|
|
431
|
+
return f"{self.__class__.__name__}.based_on({'| '.join(based_on_strings)})"
|
|
760
432
|
|
|
761
|
-
def is_bidirectional(self):
|
|
433
|
+
def is_bidirectional(self) -> Union[bool, None]:
|
|
762
434
|
"""
|
|
763
435
|
Provides the information in which direction the connection is supported (if the property returns true, the
|
|
764
436
|
connection must work in both directions, otherwise it is a unidirectional connection from the `from_device` to
|
|
765
437
|
the` to_device`
|
|
766
438
|
"""
|
|
767
|
-
return self.
|
|
439
|
+
return self._metadata.bidirectional if self._metadata else None
|
|
768
440
|
|
|
769
441
|
def is_universal(self):
|
|
770
442
|
"""
|
|
@@ -779,36 +451,27 @@ class Connection:
|
|
|
779
451
|
Connection object of the tree - there are no undefined connection layers between an object and the given
|
|
780
452
|
parent).
|
|
781
453
|
"""
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
for cur_tuple_elem in cur_based_on:
|
|
785
|
-
# check the next parent class type only if the main self type is not a container
|
|
786
|
-
# (base `balder.Connection` object)
|
|
787
|
-
if self.__class__ != Connection:
|
|
788
|
-
if cur_tuple_elem not in self.__class__.get_parents():
|
|
789
|
-
# only one element is not directly parent -> return false
|
|
790
|
-
return False
|
|
791
|
-
if not cur_tuple_elem.is_resolved():
|
|
792
|
-
# only one subelement is not resolved -> return false
|
|
793
|
-
return False
|
|
794
|
-
else:
|
|
795
|
-
# no tuple, single element
|
|
454
|
+
if self.__class__ == Connection:
|
|
455
|
+
return self.based_on_elements.is_resolved()
|
|
796
456
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
return False
|
|
803
|
-
if not cur_based_on.is_resolved():
|
|
804
|
-
# only one subelement is not resolved -> return false
|
|
457
|
+
self_parents = self.__class__.get_parents()
|
|
458
|
+
|
|
459
|
+
for cur_based_on in self.based_on_elements.connections:
|
|
460
|
+
if isinstance(cur_based_on, Connection):
|
|
461
|
+
if cur_based_on.__class__ not in self_parents:
|
|
805
462
|
return False
|
|
463
|
+
elif isinstance(cur_based_on, (AndConnectionRelation, OrConnectionRelation)):
|
|
464
|
+
for cur_type in cur_based_on.get_all_used_connection_types():
|
|
465
|
+
if cur_type not in self_parents:
|
|
466
|
+
return False
|
|
467
|
+
if not cur_based_on.is_resolved():
|
|
468
|
+
return False
|
|
806
469
|
return True
|
|
807
470
|
|
|
808
471
|
def is_single(self):
|
|
809
472
|
"""
|
|
810
|
-
This method returns true if there exists no logical **OR** in the based on connection(s). One **AND**
|
|
811
|
-
allowed.
|
|
473
|
+
This method returns true if there exists no logical **OR** in the based on connection(s). One **AND** relation
|
|
474
|
+
is allowed.
|
|
812
475
|
|
|
813
476
|
.. note::
|
|
814
477
|
Note that this method also returns False, if the connection is not completely resolved!
|
|
@@ -824,14 +487,6 @@ class Connection:
|
|
|
824
487
|
if not self.is_resolved():
|
|
825
488
|
return False
|
|
826
489
|
|
|
827
|
-
if isinstance(self.based_on_elements[0], tuple):
|
|
828
|
-
for cur_tuple_elem in self.based_on_elements[0]:
|
|
829
|
-
# if only one element of tuple is not single -> return False
|
|
830
|
-
if not cur_tuple_elem.is_single():
|
|
831
|
-
return False
|
|
832
|
-
# all elements are single
|
|
833
|
-
return True
|
|
834
|
-
|
|
835
490
|
return self.based_on_elements[0].is_single()
|
|
836
491
|
|
|
837
492
|
def get_resolved(self) -> Connection:
|
|
@@ -842,60 +497,62 @@ class Connection:
|
|
|
842
497
|
|
|
843
498
|
.. note::
|
|
844
499
|
This method returns the connection without a container :class:`Connection`, if the container
|
|
845
|
-
:class:`Connection` would only consist of one single connection (which is no
|
|
846
|
-
method returns this child connection directly without any :class:`Connection` container otherwise the
|
|
500
|
+
:class:`Connection` would only consist of one single connection (which is no AND relation!). In that case
|
|
501
|
+
the method returns this child connection directly without any :class:`Connection` container otherwise the
|
|
847
502
|
:class:`Connection` container class with all resolved child classes will be returned.
|
|
848
503
|
"""
|
|
504
|
+
copied_base = self.clone_without_based_on_elements()
|
|
505
|
+
|
|
849
506
|
if self.is_resolved():
|
|
850
|
-
copied_base
|
|
507
|
+
copied_base.append_to_based_on(self.based_on_elements.get_simplified_relation())
|
|
508
|
+
elif self.__class__ == Connection:
|
|
509
|
+
# the base object is a container Connection - iterate over the items and determine the values for them
|
|
510
|
+
copied_base.append_to_based_on(self.based_on_elements.get_simplified_relation().get_resolved())
|
|
851
511
|
else:
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
direct_ancestors_tuple = ()
|
|
867
|
-
for cur_tuple_elem in next_higher_parent:
|
|
868
|
-
if cur_tuple_elem.__class__ in self.__class__.get_parents():
|
|
869
|
-
# element already is a direct ancestor
|
|
870
|
-
direct_ancestors_tuple += (cur_tuple_elem, )
|
|
871
|
-
else:
|
|
872
|
-
all_pos_possibilities = []
|
|
873
|
-
# add all possible direct parents to the possibilities list
|
|
874
|
-
for cur_direct_parent in self.__class__.get_parents():
|
|
875
|
-
if cur_direct_parent.is_parent_of(cur_tuple_elem.__class__):
|
|
876
|
-
all_pos_possibilities.append(cur_direct_parent.based_on(cur_tuple_elem))
|
|
877
|
-
direct_ancestors_tuple += (all_pos_possibilities, )
|
|
878
|
-
# resolve the opportunities and create multiple possible tuples where all elements are direct
|
|
879
|
-
# parents
|
|
880
|
-
for cur_possibility in itertools.product(*direct_ancestors_tuple):
|
|
881
|
-
new_child_tuple = (
|
|
882
|
-
tuple([cur_tuple_element.get_resolved() for cur_tuple_element in cur_possibility]))
|
|
883
|
-
copied_base.append_to_based_on(new_child_tuple)
|
|
884
|
-
else:
|
|
885
|
-
if next_higher_parent.__class__ in self.__class__.get_parents():
|
|
886
|
-
# is already a direct parent
|
|
887
|
-
copied_base.append_to_based_on(next_higher_parent.get_resolved())
|
|
512
|
+
# independent which based-on elements we have, we need to determine all elements between this connection
|
|
513
|
+
# and the elements of the relation
|
|
514
|
+
simplified_based_on = self.based_on_elements.get_simplified_relation()
|
|
515
|
+
|
|
516
|
+
for next_higher_parent in simplified_based_on:
|
|
517
|
+
if isinstance(next_higher_parent, AndConnectionRelation):
|
|
518
|
+
# determine all possibilities
|
|
519
|
+
direct_ancestors_relations = ()
|
|
520
|
+
for cur_and_elem in next_higher_parent:
|
|
521
|
+
# `cur_and_elem` needs to be a connection, because we are using simplified which has only
|
|
522
|
+
# `OR[AND[Cnn, ...], Cnn, ..]`
|
|
523
|
+
if cur_and_elem.__class__ in self.__class__.get_parents():
|
|
524
|
+
# element already is a direct ancestor
|
|
525
|
+
direct_ancestors_relations += (cur_and_elem, )
|
|
888
526
|
else:
|
|
889
|
-
|
|
890
|
-
#
|
|
891
|
-
for
|
|
892
|
-
if
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
527
|
+
all_pos_possibilities = []
|
|
528
|
+
# add all possible direct parents to the possibilities list
|
|
529
|
+
for cur_direct_parent in self.__class__.get_parents():
|
|
530
|
+
if cur_direct_parent.is_parent_of(cur_and_elem.__class__):
|
|
531
|
+
all_pos_possibilities.append(cur_direct_parent.based_on(cur_and_elem))
|
|
532
|
+
direct_ancestors_relations += (all_pos_possibilities, )
|
|
533
|
+
# resolve the opportunities and create multiple possible AND relations where all elements are
|
|
534
|
+
# direct parents
|
|
535
|
+
for cur_possibility in itertools.product(*direct_ancestors_relations):
|
|
536
|
+
new_child_relation = AndConnectionRelation(*cur_possibility).get_resolved()
|
|
537
|
+
copied_base.append_to_based_on(new_child_relation)
|
|
538
|
+
else:
|
|
539
|
+
# `next_higher_parent` needs to be a connection, because we are using simplified which has only
|
|
540
|
+
# `OR[AND[Cnn, ...], Cnn, ..]`
|
|
541
|
+
if next_higher_parent.__class__ in self.__class__.get_parents():
|
|
542
|
+
# is already a direct parent
|
|
543
|
+
copied_base.append_to_based_on(next_higher_parent.get_resolved())
|
|
544
|
+
continue
|
|
545
|
+
# only add the first level of direct parents - deeper will be added by recursively call of
|
|
546
|
+
# `get_resolved`
|
|
547
|
+
for cur_self_direct_parent in self.__class__.get_parents():
|
|
548
|
+
if next_higher_parent.__class__.is_parent_of(cur_self_direct_parent):
|
|
549
|
+
new_child = cur_self_direct_parent.based_on(next_higher_parent)
|
|
550
|
+
copied_base.append_to_based_on(new_child.get_resolved())
|
|
551
|
+
|
|
552
|
+
# if it is a connection container, where only one element exists that is no AND relation -> return this directly
|
|
896
553
|
# instead of the container
|
|
897
554
|
if copied_base.__class__ == Connection and len(copied_base.based_on_elements) == 1 and not \
|
|
898
|
-
isinstance(copied_base.based_on_elements[0],
|
|
555
|
+
isinstance(copied_base.based_on_elements[0], AndConnectionRelation):
|
|
899
556
|
return copied_base.based_on_elements[0]
|
|
900
557
|
|
|
901
558
|
return copied_base
|
|
@@ -910,42 +567,29 @@ class Connection:
|
|
|
910
567
|
If the current object is a container :class:`Connection` object (direct instance of :class:`Connection`),
|
|
911
568
|
this method returns the single elements without a container class!
|
|
912
569
|
"""
|
|
913
|
-
|
|
570
|
+
# note: This method always work with the resolved and simplified version of the object because it is using
|
|
571
|
+
# `get_resolved()`.
|
|
572
|
+
if self.is_universal():
|
|
573
|
+
return [self]
|
|
914
574
|
|
|
915
|
-
|
|
916
|
-
if self.__class__ == Connection and len(self.based_on_elements) == 0:
|
|
575
|
+
if self.is_single():
|
|
917
576
|
return [self]
|
|
918
|
-
if self.__class__ == Connection:
|
|
919
|
-
all_self_items = self.based_on_elements
|
|
920
|
-
for cur_item in all_self_items:
|
|
921
|
-
if isinstance(cur_item, tuple):
|
|
922
|
-
all_singles += Connection.convert_tuple_to_singles(cur_item)
|
|
923
|
-
elif cur_item.is_single():
|
|
924
|
-
all_singles.append(copy.copy(cur_item))
|
|
925
|
-
else:
|
|
926
|
-
# do this for resolved version of this object
|
|
927
|
-
resolved_obj = cur_item.get_resolved()
|
|
928
|
-
|
|
929
|
-
if len(resolved_obj.based_on_elements) == 0:
|
|
930
|
-
# element has no children -> return only this
|
|
931
|
-
return [resolved_obj]
|
|
932
|
-
for cur_child in resolved_obj.based_on_elements:
|
|
933
|
-
for cur_single_child in cur_child.get_singles():
|
|
934
|
-
copied_base = resolved_obj.clone_without_based_on_elements()
|
|
935
|
-
copied_base.append_to_based_on(cur_single_child)
|
|
936
|
-
all_singles.append(copied_base)
|
|
937
|
-
# convert all tuple objects in a connection object and set metadata of self object
|
|
938
|
-
cleaned_singles = []
|
|
939
|
-
|
|
940
|
-
for cur_single in all_singles:
|
|
941
|
-
if isinstance(cur_single, Connection):
|
|
942
|
-
new_cnn = cur_single
|
|
943
|
-
else:
|
|
944
|
-
new_cnn = Connection.based_on(cur_single)
|
|
945
577
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
578
|
+
all_singles = []
|
|
579
|
+
|
|
580
|
+
resolved_self = self.get_resolved()
|
|
581
|
+
|
|
582
|
+
for cur_child in resolved_self.based_on_elements:
|
|
583
|
+
for cur_single_child in cur_child.get_singles():
|
|
584
|
+
cur_single_child = cur_single_child.clone()
|
|
585
|
+
if self.__class__ == Connection and isinstance(cur_single_child, Connection):
|
|
586
|
+
all_singles.append(cur_single_child)
|
|
587
|
+
else:
|
|
588
|
+
copied_base = resolved_self.clone_without_based_on_elements()
|
|
589
|
+
copied_base.append_to_based_on(cur_single_child)
|
|
590
|
+
all_singles.append(copied_base)
|
|
591
|
+
|
|
592
|
+
return all_singles
|
|
949
593
|
|
|
950
594
|
def get_conn_partner_of(self, device: Type[Device], node: Union[str, None] = None) -> Tuple[Type[Device], str]:
|
|
951
595
|
"""
|
|
@@ -956,30 +600,15 @@ class Connection:
|
|
|
956
600
|
:param node: the node name of the device itself (only required if the connection starts and ends with the same
|
|
957
601
|
device)
|
|
958
602
|
"""
|
|
959
|
-
|
|
960
|
-
raise ValueError(f"the given device `{device.__qualname__}` is no component of this connection")
|
|
961
|
-
if node is None:
|
|
962
|
-
# check that the from_device and to_device are not the same
|
|
963
|
-
if self.from_device == self.to_device:
|
|
964
|
-
raise ValueError("the connection is a inner-device connection (start and end is the same device) - you "
|
|
965
|
-
"have to provide the `node` string too")
|
|
966
|
-
if device == self.from_device:
|
|
967
|
-
return self.to_device, self.to_node_name
|
|
968
|
-
|
|
969
|
-
return self.from_device, self.from_node_name
|
|
970
|
-
|
|
971
|
-
if node not in (self.from_node_name, self.to_node_name):
|
|
972
|
-
raise ValueError(f"the given node `{node}` is no component of this connection")
|
|
973
|
-
|
|
974
|
-
if device == self.from_device and node == self.from_node_name:
|
|
975
|
-
return self.to_device, self.to_node_name
|
|
976
|
-
|
|
977
|
-
if device == self.to_device and node == self.to_node_name:
|
|
978
|
-
return self.from_device, self.from_node_name
|
|
979
|
-
|
|
980
|
-
raise ValueError(f"the given node `{node}` is no component of the given device `{device.__qualname__}`")
|
|
603
|
+
return self.metadata.get_conn_partner_of(device, node)
|
|
981
604
|
|
|
982
|
-
def has_connection_from_to(
|
|
605
|
+
def has_connection_from_to(
|
|
606
|
+
self,
|
|
607
|
+
start_device: Type[Device],
|
|
608
|
+
start_device_node_name: Union[str, None] = None,
|
|
609
|
+
end_device: Union[Type[Device], None] = None,
|
|
610
|
+
end_device_node_name: Union[str, None] = None,
|
|
611
|
+
) -> bool:
|
|
983
612
|
"""
|
|
984
613
|
This method checks if there is a connection from ``start_device`` to ``end_device``. This will return
|
|
985
614
|
true if the ``start_device`` and ``end_device`` given in this method are also the ``start_device`` and
|
|
@@ -989,25 +618,20 @@ class Connection:
|
|
|
989
618
|
|
|
990
619
|
:param start_device: the device for which the method should check whether it is a communication partner (for
|
|
991
620
|
non-bidirectional connection, this has to be the start device)
|
|
992
|
-
|
|
621
|
+
:param start_device_node_name: the node name that start device should have or None if it should be ignored
|
|
993
622
|
:param end_device: the other device for which the method should check whether it is a communication partner (for
|
|
994
623
|
non-bidirectional connection, this has to be the end device - this is optional if only the
|
|
995
624
|
start device should be checked)
|
|
625
|
+
:param end_device_node_name: the node name that start device should have or None if it should be ignored
|
|
996
626
|
|
|
997
627
|
:return: returns true if the given direction is possible
|
|
998
628
|
"""
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
if self.is_bidirectional():
|
|
1007
|
-
return start_device == self.from_device and end_device == self.to_device or \
|
|
1008
|
-
start_device == self.to_device and end_device == self.from_device
|
|
1009
|
-
|
|
1010
|
-
return start_device == self.from_device and end_device == self.to_device
|
|
629
|
+
return self.metadata.has_connection_from_to(
|
|
630
|
+
start_device=start_device,
|
|
631
|
+
start_device_node_name=start_device_node_name,
|
|
632
|
+
end_device=end_device,
|
|
633
|
+
end_device_node_name=end_device_node_name
|
|
634
|
+
)
|
|
1011
635
|
|
|
1012
636
|
def equal_with(self, other_conn: Connection, ignore_metadata=False) -> bool:
|
|
1013
637
|
"""
|
|
@@ -1020,11 +644,7 @@ class Connection:
|
|
|
1020
644
|
|
|
1021
645
|
.. note::
|
|
1022
646
|
Note that it doesn't matter if the connection is embedded in a container-Connection element (direct
|
|
1023
|
-
instance of :class`Connection`) or not. It only checks, that the logical data of them are the same.
|
|
1024
|
-
elements are a container for a list of child connections, the method secures that both has the same
|
|
1025
|
-
children. If only one (same for both) has one child connection which is embedded in a container
|
|
1026
|
-
:class:`Connection` class, it returns also true if the other connection is like the one child element of
|
|
1027
|
-
the other container :class:`Connection`.
|
|
647
|
+
instance of :class`Connection`) or not. It only checks, that the logical data of them are the same.
|
|
1028
648
|
|
|
1029
649
|
:param other_conn: the other connection, this connection will be compared with
|
|
1030
650
|
|
|
@@ -1032,11 +652,8 @@ class Connection:
|
|
|
1032
652
|
|
|
1033
653
|
:return: returns True if both elements are same
|
|
1034
654
|
"""
|
|
1035
|
-
if self.__class__ not in (Connection, other_conn.__class__):
|
|
1036
|
-
return False
|
|
1037
|
-
|
|
1038
655
|
if not ignore_metadata:
|
|
1039
|
-
metadata_check_result = self.
|
|
656
|
+
metadata_check_result = self.metadata.equal_with(other_conn.metadata)
|
|
1040
657
|
if metadata_check_result is False:
|
|
1041
658
|
return False
|
|
1042
659
|
|
|
@@ -1045,37 +662,18 @@ class Connection:
|
|
|
1045
662
|
resolved_self = self.get_resolved()
|
|
1046
663
|
resolved_other = other_conn.get_resolved()
|
|
1047
664
|
|
|
1048
|
-
|
|
1049
|
-
cur_elem for cur_elem in resolved_self.based_on_elements if isinstance(cur_elem, Connection)]
|
|
1050
|
-
self_based_on_tuples = [
|
|
1051
|
-
cur_elem for cur_elem in resolved_self.based_on_elements if isinstance(cur_elem, tuple)]
|
|
1052
|
-
other_based_on_elems = [
|
|
1053
|
-
cur_elem for cur_elem in resolved_other.based_on_elements if isinstance(cur_elem, Connection)]
|
|
1054
|
-
other_based_on_tuples = [
|
|
1055
|
-
cur_elem for cur_elem in resolved_other.based_on_elements if isinstance(cur_elem, tuple)]
|
|
1056
|
-
|
|
1057
|
-
# check single connection elements (if they match all in both directions)
|
|
1058
|
-
if (not self.__class__.check_equal_connections_are_in(
|
|
1059
|
-
cnns_from=self_based_on_elems, are_in_cnns_from=other_based_on_elems, ignore_metadata=ignore_metadata)
|
|
1060
|
-
or not self.__class__.check_equal_connections_are_in(
|
|
1061
|
-
cnns_from=other_based_on_elems, are_in_cnns_from=self_based_on_elems,
|
|
1062
|
-
ignore_metadata=ignore_metadata)):
|
|
1063
|
-
return False
|
|
1064
|
-
|
|
1065
|
-
# check tuple connection elements (if they match all in both directions)
|
|
1066
|
-
if (not self.__class__.check_equal_connection_tuples_are_in(
|
|
1067
|
-
tuples_from=self_based_on_tuples, are_in_tuples_from=other_based_on_tuples,
|
|
1068
|
-
ignore_metadata=ignore_metadata)
|
|
1069
|
-
or not self.__class__.check_equal_connection_tuples_are_in(
|
|
1070
|
-
tuples_from=other_based_on_tuples, are_in_tuples_from=self_based_on_tuples,
|
|
1071
|
-
ignore_metadata=ignore_metadata)):
|
|
665
|
+
if self.__class__ != other_conn.__class__:
|
|
1072
666
|
return False
|
|
1073
667
|
|
|
1074
|
-
return
|
|
668
|
+
return resolved_self.based_on_elements.equal_with(resolved_other.based_on_elements)
|
|
1075
669
|
|
|
1076
|
-
def contained_in(
|
|
670
|
+
def contained_in(
|
|
671
|
+
self,
|
|
672
|
+
other_conn: Connection | AndConnectionRelation | OrConnectionRelation,
|
|
673
|
+
ignore_metadata=False
|
|
674
|
+
) -> bool:
|
|
1077
675
|
"""
|
|
1078
|
-
This method helps to find out whether this connection-tree fits within
|
|
676
|
+
This method helps to find out whether this connection-tree fits within another connection tree. A connection
|
|
1079
677
|
object is a certain part of the large connection tree that Balder has at its disposal. This method checks
|
|
1080
678
|
whether a possibility of this connection tree fits in one possibility of the given connection tree.
|
|
1081
679
|
|
|
@@ -1089,8 +687,11 @@ class Connection:
|
|
|
1089
687
|
|
|
1090
688
|
:return: true if the self object is contained in the `other_conn`, otherwise false
|
|
1091
689
|
"""
|
|
690
|
+
# note: This method always work with the resolved and simplified version of the object because it is using
|
|
691
|
+
# `get_resolved()`.
|
|
692
|
+
|
|
1092
693
|
if not ignore_metadata:
|
|
1093
|
-
metadata_check_result = self.
|
|
694
|
+
metadata_check_result = self.metadata.contained_in(other_conn.metadata)
|
|
1094
695
|
if metadata_check_result is False:
|
|
1095
696
|
return False
|
|
1096
697
|
|
|
@@ -1102,156 +703,103 @@ class Connection:
|
|
|
1102
703
|
# one of the resolved object is a raw `Connection` object without based-on-elements -> always true
|
|
1103
704
|
return True
|
|
1104
705
|
|
|
1105
|
-
self_possibilities = [resolved_self]
|
|
1106
706
|
if resolved_self.__class__ == Connection:
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
707
|
+
return resolved_self.based_on_elements.contained_in(resolved_other, ignore_metadata=ignore_metadata)
|
|
708
|
+
|
|
709
|
+
if resolved_self.__class__ == resolved_other.__class__:
|
|
710
|
+
# The element itself has already matched, now we still have to check whether at least one inner element
|
|
711
|
+
# of this type is contained in the minimum one element of the other
|
|
712
|
+
singles_self = resolved_self.get_singles()
|
|
713
|
+
singles_other = resolved_other.get_singles()
|
|
714
|
+
|
|
715
|
+
# check that for one single_self element all hierarchical based_on elements are in one of the single
|
|
716
|
+
# other element
|
|
717
|
+
for cur_single_self, cur_single_other in itertools.product(singles_self, singles_other):
|
|
718
|
+
# check if both consists of only one element
|
|
719
|
+
if len(cur_single_self.based_on_elements) == 0:
|
|
720
|
+
# the cur self single is only one element -> this is contained in the other
|
|
1111
721
|
return True
|
|
1112
|
-
elif not cur_self.__class__ == resolved_other.__class__:
|
|
1113
|
-
for cur_based_on_elem in resolved_other.based_on_elements:
|
|
1114
|
-
if isinstance(cur_based_on_elem, tuple):
|
|
1115
|
-
# check if the current connection fits in one of the tuple items -> allowed too (like a smaller
|
|
1116
|
-
# AND contained in a bigger AND)
|
|
1117
|
-
for cur_other_tuple_element in cur_based_on_elem:
|
|
1118
|
-
if cur_self.contained_in(cur_other_tuple_element, ignore_metadata=ignore_metadata):
|
|
1119
|
-
return True
|
|
1120
|
-
else:
|
|
1121
|
-
if cur_self.contained_in(cur_based_on_elem, ignore_metadata=ignore_metadata):
|
|
1122
|
-
# element was found in this branch
|
|
1123
|
-
return True
|
|
1124
|
-
else:
|
|
1125
|
-
# The element itself has already matched, now we still have to check whether at least one tuple or a
|
|
1126
|
-
# connection (i.e. one element of the list) is matched with one of the other
|
|
1127
|
-
singles_self = cur_self.get_singles()
|
|
1128
|
-
singles_other = resolved_other.get_singles()
|
|
1129
|
-
|
|
1130
|
-
# check that for one single_self element all hierarchical based_on elements are in one of the single
|
|
1131
|
-
# other element
|
|
1132
|
-
for cur_single_self in singles_self:
|
|
1133
|
-
for cur_single_other in singles_other:
|
|
1134
|
-
# check if both consists of only one element
|
|
1135
|
-
if len(cur_single_self.based_on_elements) == 0:
|
|
1136
|
-
# the cur self single is only one element -> this is contained in the other
|
|
1137
|
-
return True
|
|
1138
722
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
# check that every tuple element of self is contained in the other connection
|
|
1177
|
-
for cur_self_tuple_element in cur_single_self.based_on_elements[0]:
|
|
1178
|
-
if not cur_self_tuple_element.contained_in(
|
|
1179
|
-
cur_single_other, ignore_metadata=ignore_metadata):
|
|
1180
|
-
# find a match, where the current tuple element is not contained in the other
|
|
1181
|
-
# connection -> tuple can not be contained in
|
|
1182
|
-
return False
|
|
723
|
+
if len(cur_single_other.based_on_elements) == 0:
|
|
724
|
+
# the other element is only one element, but the self element not -> contained_in
|
|
725
|
+
# for this single definitely false
|
|
726
|
+
continue
|
|
727
|
+
|
|
728
|
+
# note: for both only one `based_on_elements` is possible, because they are singles
|
|
729
|
+
self_first_based_on = cur_single_self.based_on_elements[0]
|
|
730
|
+
other_first_based_on = cur_single_other.based_on_elements[0]
|
|
731
|
+
|
|
732
|
+
self_is_and = isinstance(self_first_based_on, AndConnectionRelation)
|
|
733
|
+
self_is_cnn = isinstance(self_first_based_on, Connection)
|
|
734
|
+
other_is_and = isinstance(other_first_based_on, AndConnectionRelation)
|
|
735
|
+
other_is_cnn = isinstance(other_first_based_on, Connection)
|
|
736
|
+
|
|
737
|
+
if self_is_and and (other_is_and or other_is_cnn) or self_is_cnn and other_is_cnn:
|
|
738
|
+
# find a complete valid match
|
|
739
|
+
if self_first_based_on.contained_in(other_first_based_on, ignore_metadata=ignore_metadata):
|
|
740
|
+
return True
|
|
741
|
+
# skip all others possibilities
|
|
742
|
+
else:
|
|
743
|
+
# the elements itself do not match -> go deeper within the other connection
|
|
744
|
+
if isinstance(resolved_other, AndConnectionRelation):
|
|
745
|
+
# check if the current connection fits in one of the AND relation items -> allowed too (f.e. a
|
|
746
|
+
# smaller AND contained in a bigger AND)
|
|
747
|
+
for cur_other_and_element in resolved_other.connections:
|
|
748
|
+
if resolved_self.contained_in(cur_other_and_element, ignore_metadata=ignore_metadata):
|
|
749
|
+
return True
|
|
750
|
+
|
|
751
|
+
resolved_other_relation = resolved_other.based_on_elements \
|
|
752
|
+
if isinstance(resolved_other, Connection) else resolved_other
|
|
753
|
+
|
|
754
|
+
for cur_other_based_on in resolved_other_relation.connections:
|
|
755
|
+
if isinstance(cur_other_based_on, AndConnectionRelation):
|
|
756
|
+
# check if the current connection fits in one of the AND relation items -> allowed too (f.e. a
|
|
757
|
+
# smaller AND contained in a bigger AND)
|
|
758
|
+
for cur_other_and_element in cur_other_based_on.connections:
|
|
759
|
+
if resolved_self.contained_in(cur_other_and_element, ignore_metadata=ignore_metadata):
|
|
1183
760
|
return True
|
|
1184
|
-
|
|
761
|
+
else:
|
|
762
|
+
if resolved_self.contained_in(cur_other_based_on, ignore_metadata=ignore_metadata):
|
|
763
|
+
# element was found in this branch
|
|
764
|
+
return True
|
|
1185
765
|
return False
|
|
1186
766
|
|
|
1187
767
|
def intersection_with(
|
|
1188
|
-
self, other_conn: Union[Connection, Type[Connection],
|
|
1189
|
-
List[Connection, Type[Connection], Tuple[Connection]]]) \
|
|
768
|
+
self, other_conn: Union[Connection, Type[Connection], AndConnectionRelation, OrConnectionRelation]) \
|
|
1190
769
|
-> Union[Connection, None]:
|
|
1191
770
|
"""
|
|
1192
771
|
This method returns a list of subtrees that describe the intersection of this connection subtree and the
|
|
1193
772
|
given ones. Note that this method converts all connections in **single** **resolved** connections first.
|
|
1194
|
-
|
|
1195
|
-
|
|
773
|
+
The method checks if there are common intersections between the elements of this object and the given connection
|
|
774
|
+
elements within the single connections.
|
|
1196
775
|
The method cleans up this list and only return unique sub-connection trees!
|
|
1197
776
|
|
|
1198
777
|
:param other_conn: the other sub connection tree list
|
|
1199
778
|
|
|
1200
779
|
:return: the intersection connection or none, if the method has no intersection
|
|
1201
780
|
"""
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
other_conn
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
if other_conn.__class__ == Connection:
|
|
1209
|
-
if len(other_conn.based_on_elements) == 0:
|
|
1210
|
-
return self.clone()
|
|
1211
|
-
other_conn = other_conn.based_on_elements
|
|
1212
|
-
else:
|
|
1213
|
-
other_conn = [other_conn]
|
|
781
|
+
other_conn = cnn_type_check_and_convert(other_conn)
|
|
782
|
+
|
|
783
|
+
if isinstance(other_conn, Connection) and other_conn.__class__ == Connection:
|
|
784
|
+
if len(other_conn.based_on_elements) == 0:
|
|
785
|
+
return self.clone()
|
|
786
|
+
other_conn = other_conn.based_on_elements
|
|
1214
787
|
|
|
1215
|
-
if self.
|
|
1216
|
-
return Connection.based_on(
|
|
788
|
+
if self.is_universal():
|
|
789
|
+
return other_conn.clone() if isinstance(other_conn, Connection) else Connection.based_on(other_conn.clone())
|
|
1217
790
|
|
|
1218
|
-
# determine all single connection of the two sides (could contain
|
|
791
|
+
# determine all single connection of the two sides (could contain AND relations, where every element is a single
|
|
1219
792
|
# connection too)
|
|
1220
|
-
self_conn_singles =
|
|
1221
|
-
other_conn_singles =
|
|
1222
|
-
|
|
1223
|
-
idx = 0
|
|
1224
|
-
for cur_self_elem in self.get_singles():
|
|
1225
|
-
if isinstance(cur_self_elem, Connection):
|
|
1226
|
-
self_conn_singles.append(cur_self_elem)
|
|
1227
|
-
elif isinstance(cur_self_elem, type) and issubclass(cur_self_elem, Connection):
|
|
1228
|
-
self_conn_singles.append(cur_self_elem())
|
|
1229
|
-
else:
|
|
1230
|
-
raise TypeError(f"the element at index {idx} of `self_conn` is not from type `Connection` or is a "
|
|
1231
|
-
f"tuple of that")
|
|
1232
|
-
|
|
1233
|
-
idx = 0
|
|
1234
|
-
for cur_other_elem in other_conn:
|
|
1235
|
-
if isinstance(cur_other_elem, tuple):
|
|
1236
|
-
other_conn_singles += Connection.convert_tuple_to_singles(cur_other_elem)
|
|
1237
|
-
elif isinstance(cur_other_elem, Connection):
|
|
1238
|
-
other_conn_singles += cur_other_elem.get_singles()
|
|
1239
|
-
elif isinstance(cur_other_elem, type) and issubclass(cur_other_elem, Connection):
|
|
1240
|
-
other_conn_singles.append(cur_other_elem())
|
|
1241
|
-
else:
|
|
1242
|
-
raise TypeError(f"the element at index {idx} of `other_conn` is not from type `Connection` or is a "
|
|
1243
|
-
f"tuple of that")
|
|
1244
|
-
idx += 1
|
|
793
|
+
self_conn_singles = self.get_singles()
|
|
794
|
+
other_conn_singles = other_conn.get_singles()
|
|
1245
795
|
|
|
1246
796
|
intersections = []
|
|
1247
797
|
# determine intersections between all of these single components
|
|
1248
|
-
for cur_self_conn in self_conn_singles:
|
|
1249
|
-
for
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if cur_intersection not in intersections:
|
|
1254
|
-
intersections.append(cur_intersection)
|
|
798
|
+
for cur_self_conn, cur_other_conn in itertools.product(self_conn_singles, other_conn_singles):
|
|
799
|
+
for cur_intersection in cur_self_conn.get_intersection_with_other_single(cur_other_conn):
|
|
800
|
+
intersections.append(cur_intersection)
|
|
801
|
+
|
|
802
|
+
intersections = set(intersections)
|
|
1255
803
|
|
|
1256
804
|
#: filter all *contained in each other* connections
|
|
1257
805
|
intersection_filtered = []
|
|
@@ -1270,79 +818,50 @@ class Connection:
|
|
|
1270
818
|
if len(intersection_filtered) == 0:
|
|
1271
819
|
# there is no intersection
|
|
1272
820
|
return None
|
|
1273
|
-
if len(intersection_filtered) > 1 or isinstance(intersection_filtered[0], tuple):
|
|
1274
|
-
return Connection.based_on(*intersection_filtered).clone()
|
|
1275
821
|
|
|
1276
|
-
return
|
|
822
|
+
return Connection.based_on(OrConnectionRelation(*[cnn.clone() for cnn in intersection_filtered]))
|
|
1277
823
|
|
|
1278
824
|
def append_to_based_on(
|
|
1279
|
-
self, *args: Union[
|
|
825
|
+
self, *args: Union[Type[Connection], Connection, OrConnectionRelation, AndConnectionRelation]) -> None:
|
|
1280
826
|
"""
|
|
1281
827
|
with this method you can extend the internal based_on list with the transferred elements. Any number of
|
|
1282
|
-
:meth:`Connection` objects or
|
|
828
|
+
:meth:`Connection` objects or relations with :meth:`Connection` objects can be given to this method. They will
|
|
829
|
+
all be added to the internal OR relation.
|
|
1283
830
|
|
|
1284
831
|
:param args: all connection items that should be added here
|
|
1285
832
|
"""
|
|
1286
833
|
|
|
1287
|
-
|
|
1288
|
-
if
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
if self.__class__ != Connection:
|
|
1320
|
-
if not cur_tuple_elem.is_parent_of(self.__class__):
|
|
1321
|
-
raise IllegalConnectionTypeError(
|
|
1322
|
-
f"the given connection `{cur_tuple_elem.__name__}` (tuple element {cur_tuple_idx} "
|
|
1323
|
-
f"for parameter at pos {cur_idx}) is no parent class of the "
|
|
1324
|
-
f"`{self.__class__.__name__}`")
|
|
1325
|
-
# this is a simple Connection type object -> simply add the instance of connection to result
|
|
1326
|
-
# tuple
|
|
1327
|
-
result_tuple += (cur_tuple_elem(), )
|
|
1328
|
-
elif isinstance(cur_tuple_elem, Connection):
|
|
1329
|
-
# `based_on` call for this sub-connection because we get an `Connection` object
|
|
1330
|
-
if self.__class__ != Connection:
|
|
1331
|
-
if not cur_tuple_elem.__class__.is_parent_of(self.__class__):
|
|
1332
|
-
raise IllegalConnectionTypeError(
|
|
1333
|
-
f"the given connection `{cur_tuple_elem.__class__.__name__}` (tuple element "
|
|
1334
|
-
f"{cur_tuple_idx} for parameter at pos {cur_idx}) is no parent class of the "
|
|
1335
|
-
f"`{self.__class__.__name__}`")
|
|
1336
|
-
result_tuple += (cur_tuple_elem, )
|
|
1337
|
-
elif isinstance(cur_tuple_elem, tuple):
|
|
1338
|
-
raise TypeError(f"nested tuples (tuple element {cur_tuple_idx} for parameter at pos {cur_idx}) "
|
|
1339
|
-
f"and thus nested AND operations are not possible")
|
|
1340
|
-
elif isinstance(cur_tuple_elem, list):
|
|
1341
|
-
raise TypeError(f"nested lists (tuple element {cur_tuple_idx} for parameter at pos {cur_idx}) "
|
|
1342
|
-
f"and thus nested OR/AND operations are not possible")
|
|
1343
|
-
else:
|
|
1344
|
-
raise TypeError(f"illegal type `{cur_tuple_elem.__name__}` for tuple element {cur_tuple_idx} "
|
|
1345
|
-
f"for parameter at pos {cur_idx}")
|
|
1346
|
-
self._based_on_connections.append(result_tuple)
|
|
834
|
+
def validate_that_subconnection_is_parent(idx, connection_type: Type[Connection]):
|
|
835
|
+
if connection_type == Connection:
|
|
836
|
+
raise ValueError(f"it is not allowed to provide a container Connection object in based_on items - "
|
|
837
|
+
f"found at index {idx}")
|
|
838
|
+
# `based_on` call for this sub-connection because we get an `Connection` object
|
|
839
|
+
if self.__class__ != Connection:
|
|
840
|
+
if not connection_type.is_parent_of(self.__class__):
|
|
841
|
+
raise IllegalConnectionTypeError(
|
|
842
|
+
f"the connection `{cur_elem.__class__.__name__}` (at parameter pos {idx}) is "
|
|
843
|
+
f"no parent class of the `{self.__class__.__name__}`")
|
|
844
|
+
|
|
845
|
+
for cur_idx, cur_elem in enumerate(args):
|
|
846
|
+
if isinstance(cur_elem, type) and issubclass(cur_elem, Connection):
|
|
847
|
+
cur_elem = cur_elem()
|
|
848
|
+
|
|
849
|
+
if isinstance(cur_elem, Connection):
|
|
850
|
+
if cur_elem.is_universal():
|
|
851
|
+
# ignore it, because it is irrelevant for this connection
|
|
852
|
+
continue
|
|
853
|
+
validate_that_subconnection_is_parent(cur_idx, cur_elem.__class__)
|
|
854
|
+
self._based_on_connections.append(cur_elem)
|
|
855
|
+
elif isinstance(cur_elem, OrConnectionRelation):
|
|
856
|
+
for cur_inner_elem in cur_elem.connections:
|
|
857
|
+
if isinstance(cur_inner_elem, Connection) and cur_inner_elem.is_universal():
|
|
858
|
+
# ignore it, because it is irrelevant for this connection
|
|
859
|
+
continue
|
|
860
|
+
validate_that_subconnection_is_parent(cur_idx, cur_inner_elem.__class__)
|
|
861
|
+
self._based_on_connections.append(cur_inner_elem)
|
|
862
|
+
elif isinstance(cur_elem, AndConnectionRelation):
|
|
863
|
+
for cur_inner_type in cur_elem.get_all_used_connection_types():
|
|
864
|
+
validate_that_subconnection_is_parent(cur_idx, cur_inner_type)
|
|
865
|
+
self._based_on_connections.append(cur_elem)
|
|
1347
866
|
else:
|
|
1348
|
-
raise TypeError(f"illegal type `{
|
|
867
|
+
raise TypeError(f"illegal type `{cur_elem.__name__}` for parameter at pos {cur_idx}")
|