aas-standard-parser 0.1.6__py3-none-any.whl → 0.1.7__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.
@@ -5,17 +5,32 @@ from typing import Dict, List
5
5
  from basyx.aas.model import (
6
6
  Property,
7
7
  SubmodelElement,
8
- SubmodelElementCollection, SubmodelElementList, Submodel,
8
+ SubmodelElementCollection, SubmodelElementList,
9
9
  )
10
10
 
11
11
  from aas_standard_parser.collection_helpers import find_by_semantic_id, find_all_by_semantic_id, find_by_id_short
12
12
 
13
13
 
14
+ class IProtocolBinding:
15
+
16
+ def __init__(self):
17
+ pass
18
+
19
+
20
+ class HttpProtocolBinding(IProtocolBinding):
21
+
22
+ def __init__(self, method_name: str, headers: Dict[str, str]):
23
+ super().__init__()
24
+ self.method_name = method_name
25
+ self.headers = headers
26
+
27
+
14
28
  class PropertyDetails:
15
29
 
16
- def __init__(self, href: str, keys: List[str]):
30
+ def __init__(self, href: str, keys: List[str], protocol_binding: IProtocolBinding = None):
17
31
  self.href = href
18
32
  self.keys = keys
33
+ self.protocol_binding = protocol_binding
19
34
 
20
35
 
21
36
  class IAuthenticationDetails:
@@ -44,7 +59,7 @@ class AIDParser():
44
59
  def __init__(self):
45
60
  pass
46
61
 
47
- def get_base_url_from_interface(self, aid_interface: SubmodelElementCollection) -> str:
62
+ def parse_base(self, aid_interface: SubmodelElementCollection) -> str:
48
63
  """Get the base address (EndpointMetadata.base) from a SMC describing an interface in the AID."""
49
64
 
50
65
  endpoint_metadata: SubmodelElementCollection | None = find_by_semantic_id(
@@ -62,7 +77,7 @@ class AIDParser():
62
77
  return base.value
63
78
 
64
79
 
65
- def create_property_to_href_map(self, aid_interface: SubmodelElementCollection) -> Dict[str, PropertyDetails]:
80
+ def parse_properties(self, aid_interface: SubmodelElementCollection) -> Dict[str, PropertyDetails]:
66
81
  """Find all first-level and nested properties in a provided SMC describing one interface in the AID.
67
82
  Map each property (either top-level or nested) to the according 'href' attribute.
68
83
  Nested properties are further mapped to the hierarchical list of keys
@@ -88,38 +103,53 @@ class AIDParser():
88
103
  fl_properties: List[SubmodelElement] = find_all_by_semantic_id(
89
104
  properties.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/PropertyDefinition"
90
105
  )
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
106
  if fl_properties is None:
97
- #raise ValueError(f"No first-level 'property' SMC not found in 'properties' SMC.")
107
+ print(f"WARN: No first-level 'property' SMC not found in 'properties' SMC.")
98
108
  return {}
99
109
 
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):
110
+ def traverse_property(smc: SubmodelElementCollection, parent_path: str, href: str,
111
+ key_path: List[str | int], is_items=False, idx=None, is_top_level=False,
112
+ protocol_binding: IProtocolBinding = None):
102
113
  # determine local key only if not top-level
103
114
  if not is_top_level:
104
115
  if is_items and idx is not None:
105
- local_key = idx # integer index
116
+ # is a nested "items" property -> add index to the list of keys
117
+ local_key = idx
106
118
  else:
119
+ # is a nested "properties" property -> add value of "key" attribute or idShort to list of keys
107
120
  key_prop = find_by_semantic_id(
108
121
  smc.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key"
109
122
  )
110
- local_key = key_prop.value if key_prop else smc.id_short # string
123
+ local_key = key_prop.value if key_prop else smc.id_short
111
124
  new_key_path = key_path + [local_key]
112
125
  else:
113
- new_key_path = key_path # top-level: no key added
114
-
115
- # register this property
126
+ # TODO: use the key of the first-level property (or its idShort otherwise)
127
+ # is a top-level property
128
+ #key_prop: Property | None = find_by_semantic_id(
129
+ # smc.value, "https://admin-shell.io/idta/AssetInterfacesDescription/1/0/key"
130
+ #)
131
+ #local_key = key_prop.value if key_prop else smc.id_short
132
+ #new_key_path = [local_key]
133
+
134
+ new_key_path = key_path
135
+ # NOTE (Tom Gneuß, 2025-10-20)
136
+ # See GitHub Issue: https://github.com/admin-shell-io/submodel-templates/issues/197
137
+ # First-level properties are allowed to have a "key" attribute - otherwise the idShort path is used.
138
+ # However, complex first-level properties would represent, e.g., the JSON payload (object) itself.
139
+ # This JSON payload does only have keys for nested elements.
140
+ # So, using the key (or idShort) of the first-level property to get the JSON object from the payload
141
+ # is not possible.
142
+ # On the other hand: the first-level property could intentionally be something within the JSON object.
143
+ # In that case, having a "key" (or using the idSort) is entirely valid.
144
+ # How to distinguish both cases?
145
+
146
+ # create the idShort path of this property
116
147
  full_path = f"{parent_path}.{smc.id_short}"
117
- mapping[full_path] = PropertyDetails(href, new_key_path)
148
+ # add this property with all its details to the mapping -> href (from top-level parent if this is nested),
149
+ # protocol bindings (from top-level parent if this is nested), list of keys
150
+ mapping[full_path] = PropertyDetails(href, new_key_path, protocol_binding)
118
151
 
119
152
  # 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
153
  for nested_sem_id in [
124
154
  "https://www.w3.org/2019/wot/json-schema#properties",
125
155
  "https://www.w3.org/2019/wot/json-schema#items",
@@ -133,26 +163,17 @@ class AIDParser():
133
163
  nested_properties: List[SubmodelElement] = find_all_by_semantic_id(
134
164
  nested_group.value, "https://www.w3.org/2019/wot/json-schema#propertyName"
135
165
  )
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
166
 
146
167
  # traverse all nested properties/items recursively
147
168
  for idx, nested in enumerate(nested_properties):
148
169
  if nested_sem_id.endswith("#items"):
149
- # for arrays: append index instead of property key
170
+ # for arrays: append index instead of "key" attribute
150
171
  traverse_property(nested, full_path, href, new_key_path, is_items=True, idx=idx)
151
172
  else:
152
173
  traverse_property(nested, full_path, href, new_key_path)
153
174
 
154
175
  # process all first-level properties
155
- for fl_property in fl_properties:
176
+ for fl_property in fl_properties: # type: SubmodelElementCollection
156
177
  forms: SubmodelElementCollection | None = find_by_semantic_id(
157
178
  fl_property.value, "https://www.w3.org/2019/wot/td#hasForm"
158
179
  )
@@ -165,15 +186,51 @@ class AIDParser():
165
186
  if href is None:
166
187
  raise ValueError("'href' Property not found in 'forms' SMC.")
167
188
 
189
+ # get the href value of the first-level property
168
190
  href_value = href.value
191
+
192
+ # construct the idShort path up to "Interface_.InteractionMetadata.properties"
193
+ # will be used as prefix for the idShort paths of the first-level and nested properties
169
194
  idshort_path_prefix = f"{aid_interface.id_short}.{interaction_metadata.id_short}.{properties.id_short}"
170
195
 
196
+ # check which protocol-specific subtype of forms is used
197
+ # there is no clean solution for determining the subtype (e.g., a supplSemId)
198
+ # -> can only be figured out if the specific fields are present
199
+ protocol_binding: IProtocolBinding = None
200
+
201
+ # ... try HTTP ("htv_methodName" must be present)
202
+ htv_method_name: Property | None = find_by_semantic_id(
203
+ forms.value, "https://www.w3.org/2011/http#methodName"
204
+ )
205
+ if htv_method_name is not None:
206
+ protocol_binding: HttpProtocolBinding = HttpProtocolBinding(htv_method_name.value, {})
207
+ htv_headers: SubmodelElementCollection | None = find_by_semantic_id(
208
+ forms.value, "https://www.w3.org/2011/http#headers"
209
+ )
210
+ if htv_headers is not None:
211
+ for header in htv_headers.value: # type: SubmodelElementCollection
212
+ htv_field_name: Property | None = find_by_semantic_id(
213
+ header.value, "https://www.w3.org/2011/http#fieldName"
214
+ )
215
+ htv_field_value: Property | None = find_by_semantic_id(
216
+ header.value, "https://www.w3.org/2011/http#fieldValue"
217
+ )
218
+ protocol_binding.headers[htv_field_name.value] = htv_field_value.value
219
+
220
+ # TODO: the other protocols
221
+ # ... try Modbus
222
+ # ... try MQTT
223
+
224
+ # recursively parse the first-level property and its nested properties (if any)
171
225
  traverse_property(
172
- fl_property,
173
- idshort_path_prefix,
174
- href_value,
175
- [],
176
- is_top_level=True
226
+ smc=fl_property,
227
+ parent_path=idshort_path_prefix,
228
+ href=href_value,
229
+ key_path=[],
230
+ is_items=False,
231
+ idx=None,
232
+ is_top_level=True,
233
+ protocol_binding=protocol_binding
177
234
  )
178
235
 
179
236
  return mapping
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aas-standard-parser
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Some auxiliary functions for parsing standard submodels
5
5
  Author-email: Daniel Klein <daniel.klein@em.ag>
6
6
  License: MIT License
@@ -1,11 +1,11 @@
1
1
  aas_standard_parser/__init__.py,sha256=vOFawIqavj7AIeabmIOq8iBr69lKFkzZVNKcAeIlayk,609
2
- aas_standard_parser/aid_parser.py,sha256=c8fERs7fxA1n5KwSdL8fGEq_w7f7NJdWNd6_QDByFwg,11876
2
+ aas_standard_parser/aid_parser.py,sha256=ZUAcGVVbwisICoYpsHbG92wG5JEp4DthIZDsOef2wMY,14679
3
3
  aas_standard_parser/aimc_parser.py,sha256=QVTAGCdIvf9K-L97aaBbQzHA9QG3JOrgETCHEHLBw0c,11964
4
4
  aas_standard_parser/collection_helpers.py,sha256=OYuy5lDEdy5rXw5L5-I2-KHF33efHsZUulo2rA58_zs,3287
5
5
  aas_standard_parser/reference_helpers.py,sha256=UbqXaub5PTvt_W3VPntSSFcTS59PUwlTpoujny8rIRI,377
6
6
  aas_standard_parser/submodel_parser.py,sha256=91YMIQEaXohNIwI3_b-sGnCvflQhXZrhyyDCpBD2hks,2871
7
- aas_standard_parser-0.1.6.dist-info/licenses/LICENSE,sha256=simqYMD2P9Ikm0Kh9n7VGNpaVcm2TMVVQmECYZ_xVZ8,1065
8
- aas_standard_parser-0.1.6.dist-info/METADATA,sha256=kWoMVNQ9HOySA_JIwpGrZzfMMilAPdqoTNmZ-AxLQwo,1718
9
- aas_standard_parser-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- aas_standard_parser-0.1.6.dist-info/top_level.txt,sha256=OQaK6cwYttR1-eKTz5u4M0jbwSfp4HqJ56chaf0nHnw,20
11
- aas_standard_parser-0.1.6.dist-info/RECORD,,
7
+ aas_standard_parser-0.1.7.dist-info/licenses/LICENSE,sha256=simqYMD2P9Ikm0Kh9n7VGNpaVcm2TMVVQmECYZ_xVZ8,1065
8
+ aas_standard_parser-0.1.7.dist-info/METADATA,sha256=SlrH4h1FZKL6cptnpbPKhPevHUkldsvXPLqH5H7eUjk,1718
9
+ aas_standard_parser-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ aas_standard_parser-0.1.7.dist-info/top_level.txt,sha256=OQaK6cwYttR1-eKTz5u4M0jbwSfp4HqJ56chaf0nHnw,20
11
+ aas_standard_parser-0.1.7.dist-info/RECORD,,