Skip to content

Commit b9ef3a4

Browse files
authored
Expand server with Registry and Discovery API (#407)
Previously, the server only contained the AAS Repository API, more precisely the AAS Repository, Submodel Repository and Concept Description Repository APIs. This also adds the Registry and Discovery APIs. Because of this, we had to refactor the way we start the server, as now effectively, we have multiple different servers that could be executed in the `server` package.
1 parent a53e228 commit b9ef3a4

33 files changed

Lines changed: 2578 additions & 381 deletions

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@ compliance_tool/aas_compliance_tool/version.py
3232
server/app/version.py
3333

3434
# Ignore the content of the server storage
35-
server/input/
36-
server/storage/
35+
server/example_configurations/repository_standalone/input/
36+
server/example_configurations/repository_standalone/storage/
37+
server/example_configurations/registry_standalone/input/
38+
server/example_configurations/registry_standalone/storage/

server/app/adapter/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .jsonization import *

server/app/adapter/jsonization.py

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
import logging
2+
from typing import Callable, Dict, Optional, Set, Type
3+
4+
from basyx.aas import model
5+
from basyx.aas.adapter._generic import ASSET_KIND, ASSET_KIND_INVERSE, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, PathOrIO
6+
from basyx.aas.adapter.json import AASToJsonEncoder
7+
from basyx.aas.adapter.json.json_deserialization import AASFromJsonDecoder, _get_ts, read_aas_json_file_into
8+
9+
import app.model as server_model
10+
11+
logger = logging.getLogger(__name__)
12+
13+
JSON_SERVER_AAS_TOP_LEVEL_KEYS_TO_TYPES = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES + (
14+
("assetAdministrationShellDescriptors", server_model.AssetAdministrationShellDescriptor),
15+
("submodelDescriptors", server_model.SubmodelDescriptor),
16+
)
17+
18+
19+
class ServerAASFromJsonDecoder(AASFromJsonDecoder):
20+
@classmethod
21+
def _get_aas_class_parsers(cls) -> Dict[str, Callable[[Dict[str, object]], object]]:
22+
aas_class_parsers = super()._get_aas_class_parsers()
23+
aas_class_parsers.update(
24+
{
25+
"AssetAdministrationShellDescriptor": cls._construct_asset_administration_shell_descriptor,
26+
"SubmodelDescriptor": cls._construct_submodel_descriptor,
27+
"AssetLink": cls._construct_asset_link,
28+
"ProtocolInformation": cls._construct_protocol_information,
29+
"Endpoint": cls._construct_endpoint,
30+
}
31+
)
32+
return aas_class_parsers
33+
34+
# ##################################################################################################
35+
# Utility Methods used in constructor methods to add general attributes (from abstract base classes)
36+
# ##################################################################################################
37+
38+
@classmethod
39+
def _amend_abstract_attributes(cls, obj: object, dct: Dict[str, object]) -> None:
40+
super()._amend_abstract_attributes(obj, dct)
41+
42+
if isinstance(obj, server_model.Descriptor):
43+
if "description" in dct:
44+
obj.description = cls._construct_lang_string_set(
45+
_get_ts(dct, "description", list), model.MultiLanguageTextType
46+
)
47+
if "displayName" in dct:
48+
obj.display_name = cls._construct_lang_string_set(
49+
_get_ts(dct, "displayName", list), model.MultiLanguageNameType
50+
)
51+
if "extensions" in dct:
52+
for extension in _get_ts(dct, "extensions", list):
53+
obj.extension.add(cls._construct_extension(extension))
54+
55+
@classmethod
56+
def _construct_asset_administration_shell_descriptor(
57+
cls, dct: Dict[str, object], object_class=server_model.AssetAdministrationShellDescriptor
58+
) -> server_model.AssetAdministrationShellDescriptor:
59+
ret = object_class(id_=_get_ts(dct, "id", str))
60+
cls._amend_abstract_attributes(ret, dct)
61+
if "administration" in dct:
62+
ret.administration = cls._construct_administrative_information(_get_ts(dct, "administration", dict))
63+
if "assetKind" in dct:
64+
ret.asset_kind = ASSET_KIND_INVERSE[_get_ts(dct, "assetKind", str)]
65+
if "assetType" in dct:
66+
ret.asset_type = _get_ts(dct, "assetType", str)
67+
global_asset_id = None
68+
if "globalAssetId" in dct:
69+
ret.global_asset_id = _get_ts(dct, "globalAssetId", str)
70+
specific_asset_id = set()
71+
if "specificAssetIds" in dct:
72+
for desc_data in _get_ts(dct, "specificAssetIds", list):
73+
specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId))
74+
if "endpoints" in dct:
75+
for endpoint_dct in _get_ts(dct, "endpoints", list):
76+
if "protocolInformation" in endpoint_dct:
77+
ret.endpoints.append(cls._construct_endpoint(endpoint_dct, server_model.Endpoint))
78+
elif "href" in endpoint_dct:
79+
protocol_info = server_model.ProtocolInformation(
80+
href=_get_ts(endpoint_dct["href"], "href", str),
81+
endpoint_protocol=(
82+
_get_ts(endpoint_dct["href"], "endpointProtocol", str)
83+
if "endpointProtocol" in endpoint_dct["href"]
84+
else None
85+
),
86+
endpoint_protocol_version=(
87+
_get_ts(endpoint_dct["href"], "endpointProtocolVersion", list)
88+
if "endpointProtocolVersion" in endpoint_dct["href"]
89+
else None
90+
),
91+
)
92+
ret.endpoints.append(
93+
server_model.Endpoint(
94+
protocol_information=protocol_info, interface=_get_ts(endpoint_dct, "interface", str)
95+
)
96+
)
97+
if "idShort" in dct:
98+
ret.id_short = _get_ts(dct, "idShort", str)
99+
if "submodelDescriptors" in dct:
100+
for sm_dct in _get_ts(dct, "submodelDescriptors", list):
101+
ret.submodel_descriptors.append(
102+
cls._construct_submodel_descriptor(sm_dct, server_model.SubmodelDescriptor)
103+
)
104+
return ret
105+
106+
@classmethod
107+
def _construct_protocol_information(
108+
cls, dct: Dict[str, object], object_class=server_model.ProtocolInformation
109+
) -> server_model.ProtocolInformation:
110+
ret = object_class(
111+
href=_get_ts(dct, "href", str),
112+
endpoint_protocol=_get_ts(dct, "endpointProtocol", str) if "endpointProtocol" in dct else None,
113+
endpoint_protocol_version=(
114+
_get_ts(dct, "endpointProtocolVersion", list) if "endpointProtocolVersion" in dct else None
115+
),
116+
subprotocol=_get_ts(dct, "subprotocol", str) if "subprotocol" in dct else None,
117+
subprotocol_body=_get_ts(dct, "subprotocolBody", str) if "subprotocolBody" in dct else None,
118+
subprotocol_body_encoding=(
119+
_get_ts(dct, "subprotocolBodyEncoding", str) if "subprotocolBodyEncoding" in dct else None
120+
),
121+
)
122+
return ret
123+
124+
@classmethod
125+
def _construct_endpoint(cls, dct: Dict[str, object], object_class=server_model.Endpoint) -> server_model.Endpoint:
126+
ret = object_class(
127+
protocol_information=cls._construct_protocol_information(
128+
_get_ts(dct, "protocolInformation", dict), server_model.ProtocolInformation
129+
),
130+
interface=_get_ts(dct, "interface", str),
131+
)
132+
cls._amend_abstract_attributes(ret, dct)
133+
return ret
134+
135+
@classmethod
136+
def _construct_submodel_descriptor(
137+
cls, dct: Dict[str, object], object_class=server_model.SubmodelDescriptor
138+
) -> server_model.SubmodelDescriptor:
139+
ret = object_class(id_=_get_ts(dct, "id", str), endpoints=[])
140+
cls._amend_abstract_attributes(ret, dct)
141+
for endpoint_dct in _get_ts(dct, "endpoints", list):
142+
if "protocolInformation" in endpoint_dct:
143+
ret.endpoints.append(cls._construct_endpoint(endpoint_dct, server_model.Endpoint))
144+
elif "href" in endpoint_dct:
145+
protocol_info = server_model.ProtocolInformation(
146+
href=_get_ts(endpoint_dct["href"], "href", str),
147+
endpoint_protocol=(
148+
_get_ts(endpoint_dct["href"], "endpointProtocol", str)
149+
if "endpointProtocol" in endpoint_dct["href"]
150+
else None
151+
),
152+
endpoint_protocol_version=(
153+
_get_ts(endpoint_dct["href"], "endpointProtocolVersion", list)
154+
if "endpointProtocolVersion" in endpoint_dct["href"]
155+
else None
156+
),
157+
)
158+
ret.endpoints.append(
159+
server_model.Endpoint(
160+
protocol_information=protocol_info, interface=_get_ts(endpoint_dct, "interface", str)
161+
)
162+
)
163+
if "administration" in dct:
164+
ret.administration = cls._construct_administrative_information(_get_ts(dct, "administration", dict))
165+
if "idShort" in dct:
166+
ret.id_short = _get_ts(dct, "idShort", str)
167+
if "semanticId" in dct:
168+
ret.semantic_id = cls._construct_reference(_get_ts(dct, "semanticId", dict))
169+
if "supplementalSemanticIds" in dct:
170+
for ref in _get_ts(dct, "supplementalSemanticIds", list):
171+
ret.supplemental_semantic_id.append(cls._construct_reference(ref))
172+
return ret
173+
174+
@classmethod
175+
def _construct_asset_link(
176+
cls, dct: Dict[str, object], object_class=server_model.AssetLink
177+
) -> server_model.AssetLink:
178+
ret = object_class(name=_get_ts(dct, "name", str), value=_get_ts(dct, "value", str))
179+
return ret
180+
181+
182+
class ServerStrictAASFromJsonDecoder(ServerAASFromJsonDecoder):
183+
"""
184+
A strict version of the AASFromJsonDecoder class for deserializing Asset Administration Shell data from the
185+
official JSON format
186+
187+
This version has set ``failsafe = False``, which will lead to Exceptions raised for every missing attribute or wrong
188+
object type.
189+
"""
190+
191+
failsafe = False
192+
193+
194+
class ServerStrippedAASFromJsonDecoder(ServerAASFromJsonDecoder):
195+
"""
196+
Decoder for stripped JSON objects. Used in the HTTP adapter.
197+
"""
198+
199+
stripped = True
200+
201+
202+
class ServerStrictStrippedAASFromJsonDecoder(ServerStrictAASFromJsonDecoder, ServerStrippedAASFromJsonDecoder):
203+
"""
204+
Non-failsafe decoder for stripped JSON objects.
205+
"""
206+
207+
pass
208+
209+
210+
def read_server_aas_json_file_into(
211+
object_store: model.AbstractObjectStore,
212+
file: PathOrIO,
213+
replace_existing: bool = False,
214+
ignore_existing: bool = False,
215+
failsafe: bool = True,
216+
stripped: bool = False,
217+
decoder: Optional[Type[AASFromJsonDecoder]] = None,
218+
) -> Set[model.Identifier]:
219+
return read_aas_json_file_into(
220+
object_store=object_store,
221+
file=file,
222+
replace_existing=replace_existing,
223+
ignore_existing=ignore_existing,
224+
failsafe=failsafe,
225+
stripped=stripped,
226+
decoder=decoder,
227+
keys_to_types=JSON_SERVER_AAS_TOP_LEVEL_KEYS_TO_TYPES,
228+
)
229+
230+
231+
class ServerAASToJsonEncoder(AASToJsonEncoder):
232+
233+
@classmethod
234+
def _get_aas_class_serializers(cls) -> Dict[Type, Callable]:
235+
serializers = super()._get_aas_class_serializers()
236+
serializers.update(
237+
{
238+
server_model.AssetAdministrationShellDescriptor: cls._asset_administration_shell_descriptor_to_json,
239+
server_model.SubmodelDescriptor: cls._submodel_descriptor_to_json,
240+
server_model.Endpoint: cls._endpoint_to_json,
241+
server_model.ProtocolInformation: cls._protocol_information_to_json,
242+
server_model.AssetLink: cls._asset_link_to_json,
243+
}
244+
)
245+
return serializers
246+
247+
@classmethod
248+
def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]:
249+
data: Dict[str, object] = super()._abstract_classes_to_json(obj)
250+
if isinstance(obj, server_model.Descriptor):
251+
if obj.description:
252+
data["description"] = obj.description
253+
if obj.display_name:
254+
data["displayName"] = obj.display_name
255+
if obj.extension:
256+
data["extensions"] = list(obj.extension)
257+
return data
258+
259+
@classmethod
260+
def _asset_administration_shell_descriptor_to_json(
261+
cls, obj: server_model.AssetAdministrationShellDescriptor
262+
) -> Dict[str, object]:
263+
"""
264+
serialization of an object from class AssetAdministrationShell to json
265+
266+
:param obj: object of class AssetAdministrationShell
267+
:return: dict with the serialized attributes of this object
268+
"""
269+
data = cls._abstract_classes_to_json(obj)
270+
data.update(cls._namespace_to_json(obj))
271+
data["id"] = obj.id
272+
if obj.administration:
273+
data["administration"] = obj.administration
274+
if obj.asset_kind:
275+
data["assetKind"] = ASSET_KIND[obj.asset_kind]
276+
if obj.asset_type:
277+
data["assetType"] = obj.asset_type
278+
if obj.global_asset_id:
279+
data["globalAssetId"] = obj.global_asset_id
280+
if obj.specific_asset_id:
281+
data["specificAssetIds"] = list(obj.specific_asset_id)
282+
if obj.endpoints:
283+
data["endpoints"] = list(obj.endpoints)
284+
if obj.id_short:
285+
data["idShort"] = obj.id_short
286+
if obj.submodel_descriptors:
287+
data["submodelDescriptors"] = list(obj.submodel_descriptors)
288+
return data
289+
290+
@classmethod
291+
def _protocol_information_to_json(cls, obj: server_model.ProtocolInformation) -> Dict[str, object]:
292+
data = cls._abstract_classes_to_json(obj)
293+
294+
data["href"] = obj.href
295+
if obj.endpoint_protocol:
296+
data["endpointProtocol"] = obj.endpoint_protocol
297+
if obj.endpoint_protocol_version:
298+
data["endpointProtocolVersion"] = obj.endpoint_protocol_version
299+
if obj.subprotocol:
300+
data["subprotocol"] = obj.subprotocol
301+
if obj.subprotocol_body:
302+
data["subprotocolBody"] = obj.subprotocol_body
303+
if obj.subprotocol_body_encoding:
304+
data["subprotocolBodyEncoding"] = obj.subprotocol_body_encoding
305+
return data
306+
307+
@classmethod
308+
def _endpoint_to_json(cls, obj: server_model.Endpoint) -> Dict[str, object]:
309+
data = cls._abstract_classes_to_json(obj)
310+
data["protocolInformation"] = cls._protocol_information_to_json(obj.protocol_information)
311+
data["interface"] = obj.interface
312+
return data
313+
314+
@classmethod
315+
def _submodel_descriptor_to_json(cls, obj: server_model.SubmodelDescriptor) -> Dict[str, object]:
316+
"""
317+
serialization of an object from class Submodel to json
318+
319+
:param obj: object of class Submodel
320+
:return: dict with the serialized attributes of this object
321+
"""
322+
data = cls._abstract_classes_to_json(obj)
323+
data["id"] = obj.id
324+
data["endpoints"] = [cls._endpoint_to_json(ep) for ep in obj.endpoints]
325+
if obj.id_short:
326+
data["idShort"] = obj.id_short
327+
if obj.administration:
328+
data["administration"] = obj.administration
329+
if obj.semantic_id:
330+
data["semanticId"] = obj.semantic_id
331+
if obj.supplemental_semantic_id:
332+
data["supplementalSemanticIds"] = list(obj.supplemental_semantic_id)
333+
return data
334+
335+
@classmethod
336+
def _asset_link_to_json(cls, obj: server_model.AssetLink) -> Dict[str, object]:
337+
data = cls._abstract_classes_to_json(obj)
338+
data["name"] = obj.name
339+
data["value"] = obj.value
340+
return data

server/app/backend/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .local_file import *

0 commit comments

Comments
 (0)