aas-standard-parser 0.1.5__tar.gz → 0.1.6__tar.gz
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.
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/PKG-INFO +1 -1
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/__init__.py +3 -4
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/aimc_parser.py +138 -38
- aas_standard_parser-0.1.6/aas_standard_parser/submodel_parser.py +76 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/PKG-INFO +1 -1
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/SOURCES.txt +1 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/pyproject.toml +2 -5
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/LICENSE +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/README.md +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/aid_parser.py +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/collection_helpers.py +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/reference_helpers.py +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/dependency_links.txt +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/requires.txt +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/top_level.txt +0 -0
- {aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/setup.cfg +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
1
|
import importlib.metadata
|
|
2
|
+
from datetime import datetime
|
|
3
3
|
|
|
4
4
|
# TODO: introduce MIT license
|
|
5
5
|
__copyright__ = f"Copyright (C) {datetime.now().year} :em engineering methods AG. All rights reserved."
|
|
@@ -9,12 +9,11 @@ try:
|
|
|
9
9
|
__version__ = importlib.metadata.version(__name__)
|
|
10
10
|
except importlib.metadata.PackageNotFoundError:
|
|
11
11
|
__version__ = "0.0.0-dev"
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
__project__ = "aas-standard-parser"
|
|
14
14
|
__package__ = "aas-standard-parser"
|
|
15
15
|
|
|
16
|
-
from aas_standard_parser.aimc_parser import AIMCParser
|
|
17
16
|
from aas_standard_parser.aid_parser import AIDParser
|
|
18
|
-
|
|
17
|
+
from aas_standard_parser.aimc_parser import AIMCParser
|
|
19
18
|
|
|
20
19
|
__all__ = ["AIMCParser", "AIDParser"]
|
|
@@ -20,8 +20,12 @@ class SourceSinkRelation:
|
|
|
20
20
|
|
|
21
21
|
:return: The source reference as a dictionary.
|
|
22
22
|
"""
|
|
23
|
-
dict_string = json.dumps(
|
|
24
|
-
|
|
23
|
+
dict_string = json.dumps(
|
|
24
|
+
self.source, cls=basyx.aas.adapter.json.AASToJsonEncoder
|
|
25
|
+
)
|
|
26
|
+
dict_string = dict_string.replace("GlobalReference", "Submodel").replace(
|
|
27
|
+
"FragmentReference", "SubmodelElementCollection"
|
|
28
|
+
)
|
|
25
29
|
return json.loads(dict_string)
|
|
26
30
|
|
|
27
31
|
def sink_as_dict(self) -> dict:
|
|
@@ -29,7 +33,9 @@ class SourceSinkRelation:
|
|
|
29
33
|
|
|
30
34
|
:return: The sink reference as a dictionary.
|
|
31
35
|
"""
|
|
32
|
-
return json.loads(
|
|
36
|
+
return json.loads(
|
|
37
|
+
json.dumps(self.sink, cls=basyx.aas.adapter.json.AASToJsonEncoder)
|
|
38
|
+
)
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
class MappingConfiguration:
|
|
@@ -66,37 +72,54 @@ class AIMCParser:
|
|
|
66
72
|
|
|
67
73
|
self.aimc_submodel = aimc_submodel
|
|
68
74
|
|
|
69
|
-
def get_mapping_configuration_root_element(
|
|
75
|
+
def get_mapping_configuration_root_element(
|
|
76
|
+
self,
|
|
77
|
+
) -> model.SubmodelElementCollection | None:
|
|
70
78
|
"""Get the mapping configuration root submodel element collection from the AIMC submodel.
|
|
71
79
|
|
|
72
80
|
:return: The mapping configuration root submodel element collection or None if not found.
|
|
73
81
|
"""
|
|
74
82
|
self.mapping_configuration_element = next(
|
|
75
|
-
(
|
|
83
|
+
(
|
|
84
|
+
elem
|
|
85
|
+
for elem in self.aimc_submodel.submodel_element
|
|
86
|
+
if elem.id_short == "MappingConfigurations"
|
|
87
|
+
),
|
|
88
|
+
None,
|
|
76
89
|
)
|
|
77
90
|
|
|
78
91
|
if self.mapping_configuration_element is None:
|
|
79
|
-
logger.error(
|
|
92
|
+
logger.error(
|
|
93
|
+
"'MappingConfigurations' element list not found in AIMC submodel."
|
|
94
|
+
)
|
|
80
95
|
return None
|
|
81
96
|
|
|
82
97
|
return self.mapping_configuration_element
|
|
83
98
|
|
|
84
|
-
def get_mapping_configuration_elements(
|
|
99
|
+
def get_mapping_configuration_elements(
|
|
100
|
+
self,
|
|
101
|
+
) -> list[model.SubmodelElementCollection] | None:
|
|
85
102
|
"""Get all mapping configurations elements from the AIMC submodel.
|
|
86
103
|
|
|
87
104
|
:return: A dictionary containing all mapping configurations elements.
|
|
88
105
|
"""
|
|
89
106
|
if self.mapping_configuration_element is None:
|
|
90
|
-
self.mapping_configuration_element =
|
|
107
|
+
self.mapping_configuration_element = (
|
|
108
|
+
self.get_mapping_configuration_root_element()
|
|
109
|
+
)
|
|
91
110
|
|
|
92
111
|
if self.mapping_configuration_element is None:
|
|
93
112
|
return None
|
|
94
113
|
|
|
95
114
|
mapping_configurations: list[model.SubmodelElementCollection] = [
|
|
96
|
-
element
|
|
115
|
+
element
|
|
116
|
+
for element in self.mapping_configuration_element.value
|
|
117
|
+
if isinstance(element, model.SubmodelElementCollection)
|
|
97
118
|
]
|
|
98
119
|
|
|
99
|
-
logger.debug(
|
|
120
|
+
logger.debug(
|
|
121
|
+
f"Found {len(mapping_configurations)} mapping configuration elements in AIMC submodel."
|
|
122
|
+
)
|
|
100
123
|
|
|
101
124
|
return mapping_configurations
|
|
102
125
|
|
|
@@ -125,14 +148,22 @@ class AIMCParser:
|
|
|
125
148
|
mcs = MappingConfigurations()
|
|
126
149
|
mcs.configurations = mapping_configurations
|
|
127
150
|
# add all unique AID submodel IDs from all mapping configurations
|
|
128
|
-
mcs.aid_submodel_ids = list(
|
|
151
|
+
mcs.aid_submodel_ids = list(
|
|
152
|
+
{mc.aid_submodel_id for mc in mapping_configurations}
|
|
153
|
+
)
|
|
129
154
|
|
|
130
|
-
logger.debug(
|
|
131
|
-
|
|
155
|
+
logger.debug(
|
|
156
|
+
f"Found {len(mcs.aid_submodel_ids)} unique AID submodel IDs in mapping configurations."
|
|
157
|
+
)
|
|
158
|
+
logger.debug(
|
|
159
|
+
f"Found {len(mcs.configurations)} mapping configurations in AIMC submodel."
|
|
160
|
+
)
|
|
132
161
|
|
|
133
162
|
return mcs
|
|
134
163
|
|
|
135
|
-
def parse_mapping_configuration(
|
|
164
|
+
def parse_mapping_configuration(
|
|
165
|
+
self, mapping_configuration_element: model.SubmodelElementCollection
|
|
166
|
+
) -> MappingConfiguration | None:
|
|
136
167
|
"""Parse a mapping configuration element.
|
|
137
168
|
|
|
138
169
|
:param mapping_configuration_element: The mapping configuration element to parse.
|
|
@@ -144,19 +175,30 @@ class AIMCParser:
|
|
|
144
175
|
|
|
145
176
|
logger.debug(f"Parse mapping configuration '{mapping_configuration_element}'")
|
|
146
177
|
|
|
147
|
-
interface_reference = self._get_interface_reference(
|
|
178
|
+
interface_reference = self._get_interface_reference(
|
|
179
|
+
mapping_configuration_element
|
|
180
|
+
)
|
|
148
181
|
|
|
149
182
|
if interface_reference is None:
|
|
150
183
|
return None
|
|
151
184
|
|
|
152
|
-
source_sink_relations = self._generate_source_sink_relations(
|
|
185
|
+
source_sink_relations = self._generate_source_sink_relations(
|
|
186
|
+
mapping_configuration_element
|
|
187
|
+
)
|
|
153
188
|
|
|
154
189
|
if len(source_sink_relations) == 0:
|
|
155
|
-
logger.error(
|
|
190
|
+
logger.error(
|
|
191
|
+
f"No source-sink relations found in mapping configuration '{mapping_configuration_element.id_short}'."
|
|
192
|
+
)
|
|
156
193
|
return None
|
|
157
194
|
|
|
158
195
|
# check if all relations have the same AID submodel
|
|
159
|
-
aid_submodel_ids = list(
|
|
196
|
+
aid_submodel_ids = list(
|
|
197
|
+
{
|
|
198
|
+
source_sink_relation.aid_submodel_id
|
|
199
|
+
for source_sink_relation in source_sink_relations
|
|
200
|
+
}
|
|
201
|
+
)
|
|
160
202
|
|
|
161
203
|
if len(aid_submodel_ids) != 1:
|
|
162
204
|
logger.error(
|
|
@@ -171,66 +213,124 @@ class AIMCParser:
|
|
|
171
213
|
mc.aid_submodel_id = aid_submodel_ids[0]
|
|
172
214
|
return mc
|
|
173
215
|
|
|
174
|
-
def _get_interface_reference(
|
|
216
|
+
def _get_interface_reference(
|
|
217
|
+
self, mapping_configuration_element: model.SubmodelElementCollection
|
|
218
|
+
) -> model.ReferenceElement | None:
|
|
175
219
|
"""Get the interface reference ID from the mapping configuration element.
|
|
176
220
|
|
|
177
221
|
:param mapping_configuration_element: The mapping configuration element to extract the interface reference ID from.
|
|
178
222
|
:return: The interface reference ID or None if not found.
|
|
179
223
|
"""
|
|
180
|
-
logger.debug(
|
|
224
|
+
logger.debug(
|
|
225
|
+
f"Get 'InterfaceReference' from mapping configuration '{mapping_configuration_element}'."
|
|
226
|
+
)
|
|
181
227
|
|
|
182
228
|
interface_ref: model.ReferenceElement = next(
|
|
183
|
-
(
|
|
229
|
+
(
|
|
230
|
+
elem
|
|
231
|
+
for elem in mapping_configuration_element.value
|
|
232
|
+
if elem.id_short == "InterfaceReference"
|
|
233
|
+
),
|
|
234
|
+
None,
|
|
184
235
|
)
|
|
185
236
|
|
|
186
|
-
if interface_ref is None or not isinstance(
|
|
187
|
-
|
|
237
|
+
if interface_ref is None or not isinstance(
|
|
238
|
+
interface_ref, model.ReferenceElement
|
|
239
|
+
):
|
|
240
|
+
logger.error(
|
|
241
|
+
f"'InterfaceReference' not found in mapping configuration '{mapping_configuration_element.id_short}'."
|
|
242
|
+
)
|
|
188
243
|
return None
|
|
189
244
|
|
|
190
245
|
if interface_ref.value is None or len(interface_ref.value.key) == 0:
|
|
191
|
-
logger.error(
|
|
246
|
+
logger.error(
|
|
247
|
+
f"'InterfaceReference' has no value in mapping configuration '{mapping_configuration_element.id_short}'."
|
|
248
|
+
)
|
|
192
249
|
return None
|
|
193
250
|
|
|
194
251
|
return interface_ref
|
|
195
252
|
|
|
196
|
-
def _generate_source_sink_relations(
|
|
253
|
+
def _generate_source_sink_relations(
|
|
254
|
+
self, mapping_configuration_element: model.SubmodelElementCollection
|
|
255
|
+
) -> list[SourceSinkRelation]:
|
|
197
256
|
source_sink_relations: list[SourceSinkRelation] = []
|
|
198
257
|
|
|
199
|
-
logger.debug(
|
|
258
|
+
logger.debug(
|
|
259
|
+
f"Get 'MappingSourceSinkRelations' from mapping configuration '{mapping_configuration_element}'."
|
|
260
|
+
)
|
|
200
261
|
|
|
201
262
|
relations_list: model.SubmodelElementList = next(
|
|
202
|
-
(
|
|
263
|
+
(
|
|
264
|
+
elem
|
|
265
|
+
for elem in mapping_configuration_element.value
|
|
266
|
+
if elem.id_short == "MappingSourceSinkRelations"
|
|
267
|
+
),
|
|
268
|
+
None,
|
|
203
269
|
)
|
|
204
270
|
|
|
205
|
-
if relations_list is None or not isinstance(
|
|
206
|
-
|
|
271
|
+
if relations_list is None or not isinstance(
|
|
272
|
+
relations_list, model.SubmodelElementList
|
|
273
|
+
):
|
|
274
|
+
logger.error(
|
|
275
|
+
f"'MappingSourceSinkRelations' not found in mapping configuration '{mapping_configuration_element.id_short}'."
|
|
276
|
+
)
|
|
207
277
|
return source_sink_relations
|
|
208
278
|
|
|
209
279
|
for source_sink_relation in relations_list.value:
|
|
210
280
|
logger.debug(f"Parse source-sink relation '{source_sink_relation}'.")
|
|
211
281
|
|
|
212
282
|
if not isinstance(source_sink_relation, model.RelationshipElement):
|
|
213
|
-
logger.warning(
|
|
283
|
+
logger.warning(
|
|
284
|
+
f"'{source_sink_relation.id_short}' is not a RelationshipElement"
|
|
285
|
+
)
|
|
214
286
|
continue
|
|
215
287
|
|
|
216
|
-
if
|
|
217
|
-
|
|
288
|
+
if (
|
|
289
|
+
source_sink_relation.first is None
|
|
290
|
+
or len(source_sink_relation.first.key) == 0
|
|
291
|
+
):
|
|
292
|
+
logger.warning(
|
|
293
|
+
f"'first' reference is missing in RelationshipElement '{source_sink_relation.id_short}'"
|
|
294
|
+
)
|
|
218
295
|
continue
|
|
219
296
|
|
|
220
|
-
if
|
|
221
|
-
|
|
297
|
+
if (
|
|
298
|
+
source_sink_relation.second is None
|
|
299
|
+
or len(source_sink_relation.second.key) == 0
|
|
300
|
+
):
|
|
301
|
+
logger.warning(
|
|
302
|
+
f"'second' reference is missing in RelationshipElement '{source_sink_relation.id_short}'"
|
|
303
|
+
)
|
|
222
304
|
continue
|
|
223
305
|
|
|
224
|
-
global_ref = next(
|
|
306
|
+
global_ref = next(
|
|
307
|
+
(
|
|
308
|
+
key
|
|
309
|
+
for key in source_sink_relation.first.key
|
|
310
|
+
if key.type == model.KeyTypes.GLOBAL_REFERENCE
|
|
311
|
+
),
|
|
312
|
+
None,
|
|
313
|
+
)
|
|
225
314
|
|
|
226
315
|
if global_ref is None:
|
|
227
|
-
logger.warning(
|
|
316
|
+
logger.warning(
|
|
317
|
+
f"No GLOBAL_REFERENCE key found in 'first' reference of RelationshipElement '{source_sink_relation.id_short}'"
|
|
318
|
+
)
|
|
228
319
|
continue
|
|
229
320
|
|
|
230
|
-
last_fragment_ref = next(
|
|
321
|
+
last_fragment_ref = next(
|
|
322
|
+
(
|
|
323
|
+
key
|
|
324
|
+
for key in reversed(source_sink_relation.first.key)
|
|
325
|
+
if key.type == model.KeyTypes.FRAGMENT_REFERENCE
|
|
326
|
+
),
|
|
327
|
+
None,
|
|
328
|
+
)
|
|
231
329
|
|
|
232
330
|
if last_fragment_ref is None:
|
|
233
|
-
logger.warning(
|
|
331
|
+
logger.warning(
|
|
332
|
+
f"No FRAGMENT_REFERENCE key found in 'first' reference of RelationshipElement '{source_sink_relation.id_short}'"
|
|
333
|
+
)
|
|
234
334
|
continue
|
|
235
335
|
|
|
236
336
|
relation = SourceSinkRelation()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from basyx.aas import model
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_submodel_element_by_path(
|
|
9
|
+
submodel: model.Submodel, path: str
|
|
10
|
+
) -> model.SubmodelElement:
|
|
11
|
+
"""Returns a specific submodel element from the submodel at a specific path.
|
|
12
|
+
|
|
13
|
+
:param submodel: The submodel to search within.
|
|
14
|
+
:param path: IdShort path to the submodel element (dot-separated), e.g., "Element1.Element2[0].Element3".
|
|
15
|
+
:return: The found submodel element or None if not found.
|
|
16
|
+
"""
|
|
17
|
+
# Split the path by '.' and traverse the structure
|
|
18
|
+
parts = path.split(".")
|
|
19
|
+
current_elements = submodel.submodel_element
|
|
20
|
+
part_index = 0
|
|
21
|
+
for part in parts:
|
|
22
|
+
part_index += 1
|
|
23
|
+
# Handle indexed access like "Element[0]" for SubmodelElementLists
|
|
24
|
+
if "[" in part and "]" in part:
|
|
25
|
+
# Split SubmodelElementList name and index
|
|
26
|
+
base, idx = part[:-1].split("[")
|
|
27
|
+
idx = int(idx)
|
|
28
|
+
# Find the SubmodelElementList in the current elements
|
|
29
|
+
submodel_element = next(
|
|
30
|
+
(el for el in current_elements if el.id_short == base), None
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if not submodel_element or not (
|
|
34
|
+
isinstance(submodel_element, model.SubmodelElementList)
|
|
35
|
+
or isinstance(submodel_element, model.SubmodelElementCollection)
|
|
36
|
+
):
|
|
37
|
+
logger.debug(
|
|
38
|
+
f"Submodel element '{base}' not found or is not a collection/list in current {current_elements}."
|
|
39
|
+
)
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
# Check if index is within range
|
|
43
|
+
if idx >= len(submodel_element.value):
|
|
44
|
+
logger.debug(
|
|
45
|
+
f"Index '{idx}' out of range for element '{base}' with length {len(submodel_element.value)}."
|
|
46
|
+
)
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
# get the element by its index from SubmodelElementList
|
|
50
|
+
submodel_element = submodel_element.value[idx]
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
# Find the SubmodelElement in the current SubmodelElementCollection
|
|
54
|
+
submodel_element = next(
|
|
55
|
+
(el for el in current_elements if el.id_short == part), None
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if not submodel_element:
|
|
59
|
+
logger.debug(
|
|
60
|
+
f"Submodel element '{part}' not found in current {current_elements}."
|
|
61
|
+
)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
# If we've reached the last part, return the found element
|
|
65
|
+
if part_index == len(parts):
|
|
66
|
+
return submodel_element
|
|
67
|
+
|
|
68
|
+
# If the found element is a collection or list, continue traversing
|
|
69
|
+
if isinstance(submodel_element, model.SubmodelElementCollection) or isinstance(
|
|
70
|
+
submodel_element, model.SubmodelElementList
|
|
71
|
+
):
|
|
72
|
+
current_elements = submodel_element.value
|
|
73
|
+
else:
|
|
74
|
+
return submodel_element
|
|
75
|
+
|
|
76
|
+
return submodel_element
|
{aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/SOURCES.txt
RENAMED
|
@@ -6,6 +6,7 @@ aas_standard_parser/aid_parser.py
|
|
|
6
6
|
aas_standard_parser/aimc_parser.py
|
|
7
7
|
aas_standard_parser/collection_helpers.py
|
|
8
8
|
aas_standard_parser/reference_helpers.py
|
|
9
|
+
aas_standard_parser/submodel_parser.py
|
|
9
10
|
aas_standard_parser.egg-info/PKG-INFO
|
|
10
11
|
aas_standard_parser.egg-info/SOURCES.txt
|
|
11
12
|
aas_standard_parser.egg-info/dependency_links.txt
|
|
@@ -4,15 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aas-standard-parser"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.6"
|
|
8
8
|
description = "Some auxiliary functions for parsing standard submodels"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
11
11
|
authors = [{ name = "Daniel Klein", email = "daniel.klein@em.ag" }]
|
|
12
|
-
dependencies = [
|
|
13
|
-
"typing>=3.7.4.3",
|
|
14
|
-
"basyx-python-sdk>=1.2.1",
|
|
15
|
-
]
|
|
12
|
+
dependencies = ["typing>=3.7.4.3", "basyx-python-sdk>=1.2.1"]
|
|
16
13
|
[project.urls]
|
|
17
14
|
Homepage = "https://github.com/fluid40/aas-http-client"
|
|
18
15
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/collection_helpers.py
RENAMED
|
File without changes
|
{aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser/reference_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/requires.txt
RENAMED
|
File without changes
|
{aas_standard_parser-0.1.5 → aas_standard_parser-0.1.6}/aas_standard_parser.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|