aas-standard-parser 0.1.4__py3-none-any.whl → 0.1.5__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.
- aas_standard_parser/__init__.py +4 -3
- aas_standard_parser/aid_parser.py +213 -259
- aas_standard_parser/collection_helpers.py +88 -0
- aas_standard_parser/reference_helpers.py +12 -0
- {aas_standard_parser-0.1.4.dist-info → aas_standard_parser-0.1.5.dist-info}/METADATA +1 -1
- aas_standard_parser-0.1.5.dist-info/RECORD +10 -0
- aas_standard_parser-0.1.4.dist-info/RECORD +0 -8
- {aas_standard_parser-0.1.4.dist-info → aas_standard_parser-0.1.5.dist-info}/WHEEL +0 -0
- {aas_standard_parser-0.1.4.dist-info → aas_standard_parser-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {aas_standard_parser-0.1.4.dist-info → aas_standard_parser-0.1.5.dist-info}/top_level.txt +0 -0
aas_standard_parser/__init__.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
import importlib.metadata
|
|
3
3
|
|
|
4
|
+
# TODO: introduce MIT license
|
|
4
5
|
__copyright__ = f"Copyright (C) {datetime.now().year} :em engineering methods AG. All rights reserved."
|
|
5
|
-
__author__ = "Daniel Klein"
|
|
6
|
+
__author__ = "Daniel Klein, Celina Adelhardt, Tom Gneuß"
|
|
6
7
|
|
|
7
8
|
try:
|
|
8
9
|
__version__ = importlib.metadata.version(__name__)
|
|
@@ -12,8 +13,8 @@ except importlib.metadata.PackageNotFoundError:
|
|
|
12
13
|
__project__ = "aas-standard-parser"
|
|
13
14
|
__package__ = "aas-standard-parser"
|
|
14
15
|
|
|
15
|
-
from aas_standard_parser.aid_parser import AIDParser
|
|
16
16
|
from aas_standard_parser.aimc_parser import AIMCParser
|
|
17
|
+
from aas_standard_parser.aid_parser import AIDParser
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
__all__ = ["
|
|
20
|
+
__all__ = ["AIMCParser", "AIDParser"]
|
|
@@ -1,305 +1,259 @@
|
|
|
1
1
|
"""This module provides functions to parse AID Submodels and extract MQTT interface descriptions."""
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from typing import NamedTuple
|
|
2
|
+
import base64
|
|
3
|
+
from typing import Dict, List
|
|
5
4
|
|
|
6
5
|
from basyx.aas.model import (
|
|
7
|
-
ExternalReference,
|
|
8
|
-
Key,
|
|
9
|
-
KeyTypes,
|
|
10
|
-
NamespaceSet,
|
|
11
6
|
Property,
|
|
12
|
-
Reference,
|
|
13
|
-
Submodel,
|
|
14
7
|
SubmodelElement,
|
|
15
|
-
SubmodelElementCollection,
|
|
8
|
+
SubmodelElementCollection, SubmodelElementList, Submodel,
|
|
16
9
|
)
|
|
17
|
-
from basyx.aas.util import traversal
|
|
18
10
|
|
|
11
|
+
from aas_standard_parser.collection_helpers import find_by_semantic_id, find_all_by_semantic_id, find_by_id_short
|
|
19
12
|
|
|
20
|
-
class MQTTInterfaceDescription(NamedTuple):
|
|
21
|
-
"""Represents an MQTT interface configuration for a specific asset.
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
:param base_url: The base URL for the MQTT interface.
|
|
25
|
-
:param websocket_connection: Whether this interface is using a WebSocket connection or not (default).
|
|
26
|
-
"""
|
|
14
|
+
class PropertyDetails:
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
def __init__(self, href: str, keys: List[str]):
|
|
17
|
+
self.href = href
|
|
18
|
+
self.keys = keys
|
|
31
19
|
|
|
32
|
-
class AIDParser:
|
|
33
|
-
"""A class to handle parsing of AID Submodels and connecting to MQTT topics.
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
All MQTT interface configurations are stored in a list of MQTTInterfaceDescriptions.
|
|
37
|
-
"""
|
|
21
|
+
class IAuthenticationDetails:
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
_topic_map: dict[str, str] = {}
|
|
23
|
+
def __init__(self):
|
|
24
|
+
# TODO: different implementations for different AID versions
|
|
25
|
+
pass
|
|
43
26
|
|
|
44
|
-
def __init__(self, aid_sm: Submodel):
|
|
45
|
-
"""Initialize the AIDParser with a JSON representation of an AID Submodel.
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
"""
|
|
49
|
-
mqtt_interfaces: list[SubmodelElementCollection] = self._find_all_mqtt_interfaces(aid_sm)
|
|
50
|
-
if mqtt_interfaces == []:
|
|
51
|
-
print("No MQTT interface description found in AID Submodel.")
|
|
52
|
-
|
|
53
|
-
self._mqtt_interface_descriptions = [
|
|
54
|
-
MQTTInterfaceDescription(
|
|
55
|
-
interface_smc=smc,
|
|
56
|
-
base_url=self._get_base_url_from_interface(smc),
|
|
57
|
-
websocket_connection=self._uses_websocket(smc)
|
|
58
|
-
)
|
|
59
|
-
for smc in mqtt_interfaces
|
|
60
|
-
]
|
|
61
|
-
print(f"Found {len(self._mqtt_interface_descriptions)} MQTT interfaces in AID Submodel.")
|
|
62
|
-
self._default_mqtt_interface = self._get_default_mqtt_interface_description()
|
|
63
|
-
self._fallback_mqtt_interface = self._get_fallback_mqtt_interface_description()
|
|
64
|
-
self._create_topic_map(self._default_mqtt_interface.interface_smc)
|
|
28
|
+
class BasicAuthenticationDetails(IAuthenticationDetails):
|
|
65
29
|
|
|
66
|
-
def
|
|
67
|
-
|
|
30
|
+
def __init__(self, user: str, password: str):
|
|
31
|
+
self.user = user
|
|
32
|
+
self.password = password
|
|
33
|
+
super().__init__()
|
|
68
34
|
|
|
69
|
-
:return: A list of MQTT interface SubmodelElementCollections or an empty list if none are found.
|
|
70
|
-
"""
|
|
71
|
-
interfaces: list[SubmodelElement] = find_all_by_semantic_id(
|
|
72
|
-
aid_sm.submodel_element, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/Interface"
|
|
73
|
-
)
|
|
74
35
|
|
|
75
|
-
|
|
76
|
-
contains_supplemental_semantic_id(interface, "http://www.w3.org/2011/mqtt")] if interfaces else []
|
|
36
|
+
class NoAuthenticationDetails(IAuthenticationDetails):
|
|
77
37
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
endpoint_metadata: SubmodelElementCollection = find_by_semantic_id(
|
|
81
|
-
mqtt_interface.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata"
|
|
82
|
-
)
|
|
83
|
-
if endpoint_metadata is None:
|
|
84
|
-
raise ValueError("EndpointMetadata SMC not found in AID Submodel.")
|
|
85
|
-
base: Property = find_by_semantic_id(
|
|
86
|
-
endpoint_metadata.value, "https://www.w3.org/2019/wot/td#base"
|
|
87
|
-
)
|
|
88
|
-
if base is None:
|
|
89
|
-
raise ValueError("BaseUrl Property not found in EndpointMetadata SMC.")
|
|
90
|
-
return base.value
|
|
38
|
+
def __init__(self):
|
|
39
|
+
super().__init__()
|
|
91
40
|
|
|
92
|
-
def _create_topic_map(self, mqtt_interface: SubmodelElementCollection):
|
|
93
|
-
"""Find all MQTT topics by their property definitions in the MQTT interface SMC and create a new topic Map.
|
|
94
41
|
|
|
95
|
-
|
|
42
|
+
class AIDParser():
|
|
96
43
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
print(f"Creating topic map for MQTT interface {mqtt_interface.id_short}.")
|
|
100
|
-
mqtt_property_collection: SubmodelElementCollection = self._get_mqtt_properties(mqtt_interface)
|
|
101
|
-
if not mqtt_property_collection:
|
|
102
|
-
print(f"No MQTT properties found in InteractionMetadata of MQTT Interface {mqtt_interface.id_short}.")
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
property_definitions: list[SubmodelElementCollection] = [
|
|
106
|
-
prop_def for prop_def in find_all_by_semantic_id(
|
|
107
|
-
traversal.walk_submodel(mqtt_property_collection),
|
|
108
|
-
"https://admin-shell.io/idta/AssetInterfaceDescription/1/0/PropertyDefinition"
|
|
109
|
-
)
|
|
110
|
-
if isinstance(prop_def, SubmodelElementCollection) and
|
|
111
|
-
find_by_semantic_id(prop_def.value, "https://www.w3.org/2019/wot/td#hasForm") is not None
|
|
112
|
-
]
|
|
113
|
-
self._topic_map = self._get_topics_from_property_definitions(property_definitions)
|
|
44
|
+
def __init__(self):
|
|
45
|
+
pass
|
|
114
46
|
|
|
115
|
-
def
|
|
116
|
-
"""
|
|
47
|
+
def get_base_url_from_interface(self, aid_interface: SubmodelElementCollection) -> str:
|
|
48
|
+
"""Get the base address (EndpointMetadata.base) from a SMC describing an interface in the AID."""
|
|
117
49
|
|
|
118
|
-
:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
forms = find_by_semantic_id(
|
|
124
|
-
prop_def.value, "https://www.w3.org/2019/wot/td#hasForm"
|
|
125
|
-
)
|
|
126
|
-
if forms is None:
|
|
127
|
-
print(f"Form SMC not found in PropertyDefinition {prop_def.id_short}.")
|
|
128
|
-
continue
|
|
129
|
-
|
|
130
|
-
target_href = find_by_semantic_id(
|
|
131
|
-
forms.value, "https://www.w3.org/2019/wot/hypermedia#hasTarget"
|
|
132
|
-
)
|
|
133
|
-
if target_href is None:
|
|
134
|
-
print(f"Target property not found in Form SMC of PropertyDefinition {prop_def.id_short}.")
|
|
135
|
-
continue
|
|
136
|
-
|
|
137
|
-
topic_map[prop_def.id_short] = target_href.value
|
|
138
|
-
return topic_map
|
|
139
|
-
|
|
140
|
-
def _get_default_mqtt_interface_description(self) -> MQTTInterfaceDescription:
|
|
141
|
-
"""Get the default MQTT interface description from the list of MQTT interfaces.
|
|
142
|
-
|
|
143
|
-
Default MQTT interface does not use Websocket. If no such interface is found, simply return the first one.
|
|
50
|
+
endpoint_metadata: SubmodelElementCollection | None = find_by_semantic_id(
|
|
51
|
+
aid_interface.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata"
|
|
52
|
+
)
|
|
53
|
+
if endpoint_metadata is None:
|
|
54
|
+
raise ValueError(f"'EndpointMetadata' SMC not found in the provided '{aid_interface.id_short}' SMC.")
|
|
144
55
|
|
|
145
|
-
:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return interface
|
|
56
|
+
base: Property | None = find_by_semantic_id(
|
|
57
|
+
endpoint_metadata.value, "https://www.w3.org/2019/wot/td#baseURI"
|
|
58
|
+
)
|
|
59
|
+
if base is None:
|
|
60
|
+
raise ValueError("'base' Property not found in 'EndpointMetadata' SMC.")
|
|
151
61
|
|
|
152
|
-
|
|
153
|
-
print(f"Using default MQTT interface: {self._mqtt_interface_descriptions[0].interface_smc.id_short}")
|
|
154
|
-
return self._mqtt_interface_descriptions[0]
|
|
62
|
+
return base.value
|
|
155
63
|
|
|
156
|
-
return None
|
|
157
64
|
|
|
158
|
-
def
|
|
159
|
-
"""
|
|
65
|
+
def create_property_to_href_map(self, aid_interface: SubmodelElementCollection) -> Dict[str, PropertyDetails]:
|
|
66
|
+
"""Find all first-level and nested properties in a provided SMC describing one interface in the AID.
|
|
67
|
+
Map each property (either top-level or nested) to the according 'href' attribute.
|
|
68
|
+
Nested properties are further mapped to the hierarchical list of keys
|
|
69
|
+
that are necessary to extract their value from the payload of the interface.
|
|
160
70
|
|
|
161
|
-
:
|
|
71
|
+
:param aid_interface: An SMC describing an interface in the AID.
|
|
72
|
+
:return: A dictionary mapping each property (represented by its idShort-path) to PropertyDetails.
|
|
162
73
|
"""
|
|
163
|
-
|
|
164
|
-
for interface in self._mqtt_interface_descriptions:
|
|
165
|
-
if interface.websocket_connection:
|
|
166
|
-
print(f"Using fallback MQTT interface: {interface.interface_smc.id_short}")
|
|
167
|
-
return interface
|
|
168
|
-
return None
|
|
169
|
-
|
|
170
|
-
def _get_mqtt_properties(self, default_mqtt_interface: SubmodelElementCollection) -> SubmodelElementCollection | None:
|
|
171
|
-
"""Get the MQTT properties from the InteractionMetadata SMC.
|
|
74
|
+
mapping: Dict[str, PropertyDetails] = {}
|
|
172
75
|
|
|
173
|
-
:
|
|
174
|
-
|
|
175
|
-
interaction_metadata: SubmodelElementCollection = find_by_semantic_id(
|
|
176
|
-
default_mqtt_interface.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InteractionMetadata"
|
|
76
|
+
interaction_metadata: SubmodelElementCollection | None = find_by_semantic_id(
|
|
77
|
+
aid_interface.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/InteractionMetadata"
|
|
177
78
|
)
|
|
178
79
|
if interaction_metadata is None:
|
|
179
|
-
|
|
180
|
-
return None
|
|
80
|
+
raise ValueError(f"'InteractionMetadata' SMC not found in the provided '{aid_interface.id_short}' SMC.")
|
|
181
81
|
|
|
182
|
-
|
|
82
|
+
properties: SubmodelElementCollection | None = find_by_semantic_id(
|
|
183
83
|
interaction_metadata.value, "https://www.w3.org/2019/wot/td#PropertyAffordance"
|
|
184
84
|
)
|
|
185
|
-
if
|
|
186
|
-
|
|
187
|
-
return None
|
|
188
|
-
|
|
189
|
-
return mqtt_property_collection
|
|
190
|
-
|
|
191
|
-
def _uses_websocket(self, mqtt_interface: SubmodelElementCollection) -> bool:
|
|
192
|
-
"""Check if the given MQTT interface uses a WebSocket connection by searching for the appropriate semantic ID.
|
|
85
|
+
if properties is None:
|
|
86
|
+
raise ValueError("'properties' SMC not found in 'InteractionMetadata' SMC.")
|
|
193
87
|
|
|
194
|
-
:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
88
|
+
fl_properties: List[SubmodelElement] = find_all_by_semantic_id(
|
|
89
|
+
properties.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition"
|
|
90
|
+
)
|
|
91
|
+
# TODO: some AIDs have typos in that semanticId but we only support the official ones
|
|
92
|
+
#fl_properties_alternative: List[SubmodelElement] = find_all_by_semantic_id(
|
|
93
|
+
# properties.value, "https://admin-shell.io/idta/AssetInterfaceDescription/1/0/PropertyDefinition"
|
|
94
|
+
#)
|
|
95
|
+
#fl_properties.extend(fl_properties_alternative)
|
|
96
|
+
if fl_properties is None:
|
|
97
|
+
#raise ValueError(f"No first-level 'property' SMC not found in 'properties' SMC.")
|
|
98
|
+
return {}
|
|
99
|
+
|
|
100
|
+
def traverse_property(smc: SubmodelElementCollection, parent_path: str, href: str, key_path: List[str | int],
|
|
101
|
+
is_items=False, idx=None, is_top_level=False):
|
|
102
|
+
# determine local key only if not top-level
|
|
103
|
+
if not is_top_level:
|
|
104
|
+
if is_items and idx is not None:
|
|
105
|
+
local_key = idx # integer index
|
|
106
|
+
else:
|
|
107
|
+
key_prop = find_by_semantic_id(
|
|
108
|
+
smc.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key"
|
|
109
|
+
)
|
|
110
|
+
local_key = key_prop.value if key_prop else smc.id_short # string
|
|
111
|
+
new_key_path = key_path + [local_key]
|
|
112
|
+
else:
|
|
113
|
+
new_key_path = key_path # top-level: no key added
|
|
114
|
+
|
|
115
|
+
# register this property
|
|
116
|
+
full_path = f"{parent_path}.{smc.id_short}"
|
|
117
|
+
mapping[full_path] = PropertyDetails(href, new_key_path)
|
|
118
|
+
|
|
119
|
+
# traverse nested "properties" or "items"
|
|
120
|
+
# (nested properties = object members, nested items = array elements)
|
|
121
|
+
# TODO: some apparently use the wrong semanticId:
|
|
122
|
+
# "https://www.w3.org/2019/wot/td#PropertyAffordance"
|
|
123
|
+
for nested_sem_id in [
|
|
124
|
+
"https://www.w3.org/2019/wot/json-schema#properties",
|
|
125
|
+
"https://www.w3.org/2019/wot/json-schema#items",
|
|
126
|
+
]:
|
|
127
|
+
nested_group: SubmodelElementCollection | None = find_by_semantic_id(smc.value, nested_sem_id)
|
|
128
|
+
if nested_group:
|
|
129
|
+
# attach the name of that SMC ("items" or "properties" or similar) to the key_path
|
|
130
|
+
full_path += "." + nested_group.id_short
|
|
131
|
+
|
|
132
|
+
# find all nested properties/items by semantic-ID
|
|
133
|
+
nested_properties: List[SubmodelElement] = find_all_by_semantic_id(
|
|
134
|
+
nested_group.value, "https://www.w3.org/2019/wot/json-schema#propertyName"
|
|
135
|
+
)
|
|
136
|
+
# TODO: some AIDs have typos or use wrong semanticIds but we only support the official ones
|
|
137
|
+
#nested_properties_alternative1: List[SubmodelElement] = find_all_by_semantic_id(
|
|
138
|
+
# nested_group.value, "https://admin-shell.io/idta/AssetInterfaceDescription/1/0/PropertyDefinition"
|
|
139
|
+
#)
|
|
140
|
+
# nested_properties_alternative2: List[SubmodelElement] = find_all_by_semantic_id(
|
|
141
|
+
# nested_group.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition"
|
|
142
|
+
# )
|
|
143
|
+
#nested_properties.extend(nested_properties_alternative1)
|
|
144
|
+
#nested_properties.extend(nested_properties_alternative2)
|
|
145
|
+
|
|
146
|
+
# traverse all nested properties/items recursively
|
|
147
|
+
for idx, nested in enumerate(nested_properties):
|
|
148
|
+
if nested_sem_id.endswith("#items"):
|
|
149
|
+
# for arrays: append index instead of property key
|
|
150
|
+
traverse_property(nested, full_path, href, new_key_path, is_items=True, idx=idx)
|
|
151
|
+
else:
|
|
152
|
+
traverse_property(nested, full_path, href, new_key_path)
|
|
153
|
+
|
|
154
|
+
# process all first-level properties
|
|
155
|
+
for fl_property in fl_properties:
|
|
156
|
+
forms: SubmodelElementCollection | None = find_by_semantic_id(
|
|
157
|
+
fl_property.value, "https://www.w3.org/2019/wot/td#hasForm"
|
|
158
|
+
)
|
|
159
|
+
if forms is None:
|
|
160
|
+
raise ValueError(f"'forms' SMC not found in '{fl_property.id_short}' SMC.")
|
|
198
161
|
|
|
199
|
-
|
|
200
|
-
|
|
162
|
+
href: Property | None = find_by_semantic_id(
|
|
163
|
+
forms.value, "https://www.w3.org/2019/wot/hypermedia#hasTarget"
|
|
164
|
+
)
|
|
165
|
+
if href is None:
|
|
166
|
+
raise ValueError("'href' Property not found in 'forms' SMC.")
|
|
167
|
+
|
|
168
|
+
href_value = href.value
|
|
169
|
+
idshort_path_prefix = f"{aid_interface.id_short}.{interaction_metadata.id_short}.{properties.id_short}"
|
|
170
|
+
|
|
171
|
+
traverse_property(
|
|
172
|
+
fl_property,
|
|
173
|
+
idshort_path_prefix,
|
|
174
|
+
href_value,
|
|
175
|
+
[],
|
|
176
|
+
is_top_level=True
|
|
177
|
+
)
|
|
201
178
|
|
|
202
|
-
|
|
179
|
+
return mapping
|
|
203
180
|
|
|
204
|
-
:param fallback: Whether to use the fallback MQTT interface, defaults to False
|
|
205
|
-
:return: The MQTT topic map.
|
|
206
|
-
"""
|
|
207
|
-
if fallback:
|
|
208
|
-
self._create_topic_map(self._get_fallback().interface_smc)
|
|
209
|
-
return self._topic_map
|
|
210
181
|
|
|
211
|
-
def
|
|
212
|
-
"""
|
|
182
|
+
def parse_security(self, aid_interface: SubmodelElementCollection) -> IAuthenticationDetails:
|
|
183
|
+
"""Extract the authentication details (EndpointMetadata.security) from the provided interface in the AID.
|
|
213
184
|
|
|
214
|
-
:param
|
|
215
|
-
:return:
|
|
185
|
+
:param aid_interface: An SMC describing an interface in the AID.
|
|
186
|
+
:return: A subtype of IAuthenticationDetails with details depending on the specified authentication method for the interface.
|
|
216
187
|
"""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def _get_fallback(self):
|
|
223
|
-
"""Get the fallback MQTT interface description if it exists.
|
|
188
|
+
endpoint_metadata: SubmodelElementCollection | None = find_by_semantic_id(
|
|
189
|
+
aid_interface.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/EndpointMetadata"
|
|
190
|
+
)
|
|
191
|
+
if endpoint_metadata is None:
|
|
192
|
+
raise ValueError(f"'EndpointMetadata' SMC not found in the provided '{aid_interface.id_short}' SMC.")
|
|
224
193
|
|
|
225
|
-
:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if
|
|
229
|
-
raise
|
|
230
|
-
|
|
194
|
+
security: SubmodelElementList | None = find_by_semantic_id(
|
|
195
|
+
endpoint_metadata.value, "https://www.w3.org/2019/wot/td#hasSecurityConfiguration"
|
|
196
|
+
)
|
|
197
|
+
if security is None:
|
|
198
|
+
raise ValueError("'security' SML not found in 'EndpointMetadata' SMC.")
|
|
199
|
+
|
|
200
|
+
# TODO: resolve the full reference(s)
|
|
201
|
+
# for now, assume there is only one reference to the security in use
|
|
202
|
+
# -> access SML[0]
|
|
203
|
+
# assume that this ReferenceElement points to a security scheme in this very AID SM
|
|
204
|
+
# -> can just use the last key to determine the type of security
|
|
205
|
+
sc_idshort = security.value[0].value.key[-1].value
|
|
206
|
+
|
|
207
|
+
# get the securityDefinitions SMC
|
|
208
|
+
security_definitions: SubmodelElementCollection | None = find_by_semantic_id(
|
|
209
|
+
endpoint_metadata.value, "https://www.w3.org/2019/wot/td#definesSecurityScheme"
|
|
210
|
+
)
|
|
211
|
+
if security_definitions is None:
|
|
212
|
+
raise ValueError("'securityDefinitions' SMC not found in 'EndpointMetadata' SMC.")
|
|
231
213
|
|
|
232
|
-
|
|
233
|
-
|
|
214
|
+
# find the security scheme SMC with the same idShort as mentioned in the reference "sc"
|
|
215
|
+
referenced_security: SubmodelElementCollection | None = find_by_id_short(
|
|
216
|
+
security_definitions.value, sc_idshort
|
|
217
|
+
)
|
|
218
|
+
if referenced_security is None:
|
|
219
|
+
raise ValueError(f"Referenced security scheme '{sc_idshort}' SMC not found in 'securityDefinitions' SMC")
|
|
234
220
|
|
|
235
|
-
|
|
236
|
-
:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
)
|
|
275
|
-
for element in parent:
|
|
276
|
-
if element.semantic_id.__eq__(reference):
|
|
277
|
-
return element
|
|
278
|
-
return None
|
|
279
|
-
|
|
280
|
-
def find_by_supplemental_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value: str) -> SubmodelElement:
|
|
281
|
-
"""Find a SubmodelElement by its supplemental semantic ID.
|
|
282
|
-
|
|
283
|
-
:param parent: The NamespaceSet to search within.
|
|
284
|
-
:param semantic_id_value: The supplemental semantic ID value to search for.
|
|
285
|
-
:return: The first found SubmodelElement, or None if not found.
|
|
286
|
-
"""
|
|
287
|
-
for element in parent:
|
|
288
|
-
if contains_supplemental_semantic_id(element, semantic_id_value):
|
|
289
|
-
return element
|
|
290
|
-
return None
|
|
291
|
-
|
|
292
|
-
def contains_supplemental_semantic_id(element: SubmodelElement, semantic_id_value: str) -> bool:
|
|
293
|
-
"""Check if the element contains a specific supplemental semantic ID.
|
|
294
|
-
|
|
295
|
-
:param element: The SubmodelElement to check.
|
|
296
|
-
:param semantic_id_value: The supplemental semantic ID value to search for.
|
|
297
|
-
:return: True if the element contains the supplemental semantic ID, False otherwise.
|
|
298
|
-
"""
|
|
299
|
-
reference: Reference = ExternalReference(
|
|
300
|
-
[Key(
|
|
301
|
-
type_= KeyTypes.GLOBAL_REFERENCE,
|
|
302
|
-
value=semantic_id_value
|
|
303
|
-
)]
|
|
304
|
-
)
|
|
305
|
-
return element.supplemental_semantic_id.__contains__(reference)
|
|
221
|
+
# get the name of the security scheme
|
|
222
|
+
scheme: Property | None = find_by_semantic_id(
|
|
223
|
+
referenced_security.value, "https://www.w3.org/2019/wot/security#SecurityScheme"
|
|
224
|
+
)
|
|
225
|
+
if scheme is None:
|
|
226
|
+
raise ValueError(f"'scheme' Property not found in referenced security scheme '{sc_idshort}' SMC.")
|
|
227
|
+
|
|
228
|
+
auth_details: IAuthenticationDetails = None
|
|
229
|
+
|
|
230
|
+
match scheme.value:
|
|
231
|
+
case "nosec":
|
|
232
|
+
auth_details = NoAuthenticationDetails()
|
|
233
|
+
|
|
234
|
+
case "basic":
|
|
235
|
+
basic_sc_name: Property | None = find_by_semantic_id(
|
|
236
|
+
referenced_security.value, "https://www.w3.org/2019/wot/security#name"
|
|
237
|
+
)
|
|
238
|
+
if basic_sc_name is None:
|
|
239
|
+
raise ValueError("'name' Property not found in 'basic_sc' SMC")
|
|
240
|
+
|
|
241
|
+
auth_base64 = basic_sc_name.value
|
|
242
|
+
auth_plain = base64.b64decode(auth_base64).decode("utf-8")
|
|
243
|
+
auth_details = BasicAuthenticationDetails(auth_plain.split(":")[0], auth_plain.split(":")[1])
|
|
244
|
+
|
|
245
|
+
# TODO: remaining cases
|
|
246
|
+
case "digest":
|
|
247
|
+
pass
|
|
248
|
+
case "bearer":
|
|
249
|
+
pass
|
|
250
|
+
case "psk":
|
|
251
|
+
pass
|
|
252
|
+
case "oauth2":
|
|
253
|
+
pass
|
|
254
|
+
case "apikey":
|
|
255
|
+
pass
|
|
256
|
+
case "auto":
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
return auth_details
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from basyx.aas.model import NamespaceSet, SubmodelElement, ExternalReference, Reference, Key, KeyTypes
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_all_by_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value: str) -> List[SubmodelElement]:
|
|
6
|
+
"""Find all SubmodelElements having a specific Semantic ID.
|
|
7
|
+
|
|
8
|
+
:param parent: The NamespaceSet to search within.
|
|
9
|
+
:param semantic_id_value: The semantic ID value to search for.
|
|
10
|
+
:return: The found SubmodelElement(s) or an empty list if not found.
|
|
11
|
+
"""
|
|
12
|
+
reference: Reference = ExternalReference(
|
|
13
|
+
(Key(
|
|
14
|
+
type_=KeyTypes.GLOBAL_REFERENCE,
|
|
15
|
+
value=semantic_id_value
|
|
16
|
+
),)
|
|
17
|
+
)
|
|
18
|
+
found_elements: list[SubmodelElement] = [
|
|
19
|
+
element for element in parent if element.semantic_id.__eq__(reference)
|
|
20
|
+
]
|
|
21
|
+
return found_elements
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def find_by_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value: str) -> SubmodelElement | None:
|
|
25
|
+
"""Find a SubmodelElement by its semantic ID.
|
|
26
|
+
|
|
27
|
+
:param parent: The NamespaceSet to search within.
|
|
28
|
+
:param semantic_id_value: The semantic ID value to search for.
|
|
29
|
+
:return: The first found SubmodelElement, or None if not found.
|
|
30
|
+
@rtype: object
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# create a Reference that acts like the to-be-matched semanticId
|
|
34
|
+
reference: Reference = ExternalReference(
|
|
35
|
+
(Key(
|
|
36
|
+
type_=KeyTypes.GLOBAL_REFERENCE,
|
|
37
|
+
value=semantic_id_value
|
|
38
|
+
),)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# check if the constructed Reference appears as semanticId of the child elements
|
|
42
|
+
for element in parent:
|
|
43
|
+
if element.semantic_id.__eq__(reference):
|
|
44
|
+
return element
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def find_by_id_short(parent: NamespaceSet[SubmodelElement], id_short_value: str) -> SubmodelElement | None:
|
|
49
|
+
"""Find a SubmodelElement by its idShort.
|
|
50
|
+
|
|
51
|
+
:param parent: The NamespaceSet to search within.
|
|
52
|
+
:param id_short_value: The idShort value to search for.
|
|
53
|
+
:return: The first found SubmodelElement, or None if not found.
|
|
54
|
+
"""
|
|
55
|
+
for element in parent:
|
|
56
|
+
if element.id_short == id_short_value:
|
|
57
|
+
return element
|
|
58
|
+
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def find_by_supplemental_semantic_id(parent: NamespaceSet[SubmodelElement], semantic_id_value: str) -> SubmodelElement | None:
|
|
63
|
+
"""Find a SubmodelElement by its supplemental semantic ID.
|
|
64
|
+
|
|
65
|
+
:param parent: The NamespaceSet to search within.
|
|
66
|
+
:param semantic_id_value: The supplemental semantic ID value to search for.
|
|
67
|
+
:return: The first found SubmodelElement, or None if not found.
|
|
68
|
+
"""
|
|
69
|
+
for element in parent:
|
|
70
|
+
if contains_supplemental_semantic_id(element, semantic_id_value):
|
|
71
|
+
return element
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def contains_supplemental_semantic_id(element: SubmodelElement, semantic_id_value: str) -> bool:
|
|
76
|
+
"""Check if the element contains a specific supplemental semantic ID.
|
|
77
|
+
|
|
78
|
+
:param element: The SubmodelElement to check.
|
|
79
|
+
:param semantic_id_value: The supplemental semantic ID value to search for.
|
|
80
|
+
:return: True if the element contains the supplemental semantic ID, False otherwise.
|
|
81
|
+
"""
|
|
82
|
+
reference: Reference = ExternalReference(
|
|
83
|
+
(Key(
|
|
84
|
+
type_=KeyTypes.GLOBAL_REFERENCE,
|
|
85
|
+
value=semantic_id_value
|
|
86
|
+
),)
|
|
87
|
+
)
|
|
88
|
+
return element.supplemental_semantic_id.__contains__(reference)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from basyx.aas.model import ModelReference
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def construct_idshort_path_from_reference(reference: ModelReference) -> str:
|
|
5
|
+
idshort_path: str = ""
|
|
6
|
+
|
|
7
|
+
# start from the second Key and omit the Identifiable at the beginning of the list
|
|
8
|
+
for key in reference.key[1:]:
|
|
9
|
+
idshort_path += (key.value + ".")
|
|
10
|
+
|
|
11
|
+
# get rid of the trailing dot
|
|
12
|
+
return idshort_path[:-1]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
aas_standard_parser/__init__.py,sha256=051isycE2NEcFAO-mScawNbv7NsLVhS9209bGQ27dxc,614
|
|
2
|
+
aas_standard_parser/aid_parser.py,sha256=c8fERs7fxA1n5KwSdL8fGEq_w7f7NJdWNd6_QDByFwg,11876
|
|
3
|
+
aas_standard_parser/aimc_parser.py,sha256=GR70DhhkcsBYSKOlD6bJ9jm0GF5u4uYijY7S79jgJLE,10571
|
|
4
|
+
aas_standard_parser/collection_helpers.py,sha256=OYuy5lDEdy5rXw5L5-I2-KHF33efHsZUulo2rA58_zs,3287
|
|
5
|
+
aas_standard_parser/reference_helpers.py,sha256=UbqXaub5PTvt_W3VPntSSFcTS59PUwlTpoujny8rIRI,377
|
|
6
|
+
aas_standard_parser-0.1.5.dist-info/licenses/LICENSE,sha256=simqYMD2P9Ikm0Kh9n7VGNpaVcm2TMVVQmECYZ_xVZ8,1065
|
|
7
|
+
aas_standard_parser-0.1.5.dist-info/METADATA,sha256=cD5O47siY5YgmYg3ckd6tLnhMxlINlWrFtVsEOvxp8Y,1718
|
|
8
|
+
aas_standard_parser-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
aas_standard_parser-0.1.5.dist-info/top_level.txt,sha256=OQaK6cwYttR1-eKTz5u4M0jbwSfp4HqJ56chaf0nHnw,20
|
|
10
|
+
aas_standard_parser-0.1.5.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
aas_standard_parser/__init__.py,sha256=hlC6BxwJAFFkkxMF0qAZHI3ZYdgOPr_ipESAuj7Ol1E,553
|
|
2
|
-
aas_standard_parser/aid_parser.py,sha256=0U3J9WOkt5t_xh9SZZ_7h_uQ-bR6Q7NaZltbyq69Qsc,13758
|
|
3
|
-
aas_standard_parser/aimc_parser.py,sha256=GR70DhhkcsBYSKOlD6bJ9jm0GF5u4uYijY7S79jgJLE,10571
|
|
4
|
-
aas_standard_parser-0.1.4.dist-info/licenses/LICENSE,sha256=simqYMD2P9Ikm0Kh9n7VGNpaVcm2TMVVQmECYZ_xVZ8,1065
|
|
5
|
-
aas_standard_parser-0.1.4.dist-info/METADATA,sha256=7OwoC01yIucUyd0MlP1pC5WKnLXZyuXi-7g1VwIKC5U,1718
|
|
6
|
-
aas_standard_parser-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
aas_standard_parser-0.1.4.dist-info/top_level.txt,sha256=OQaK6cwYttR1-eKTz5u4M0jbwSfp4HqJ56chaf0nHnw,20
|
|
8
|
-
aas_standard_parser-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
{aas_standard_parser-0.1.4.dist-info → aas_standard_parser-0.1.5.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|