Skip to content

Commit 20ae930

Browse files
committed
Merge branch 'develop' into update/api_V3.1
2 parents 3093a45 + 5824891 commit 20ae930

36 files changed

Lines changed: 2658 additions & 374 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/

sdk/basyx/aas/model/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
BlobType = bytes
2929

3030
# The following string aliases are constrained by the decorator functions defined in the string_constraints module,
31-
# wherever they are used for an instance attributes.
31+
# wherever they are used for an instance's attributes.
3232
ContentType = str # any mimetype as in RFC2046
3333
Identifier = str
3434
LabelType = str

server/README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,22 @@ The files are only read, changes won't persist.
1212
Alternatively, the container can also be told to use the [Local-File Backend][2] instead, which stores Asset Administration Shells (AAS) and Submodels as individual *JSON* files and allows for persistent changes (except supplementary files, i.e. files referenced by `File` SubmodelElements).
1313
See [below](#options) on how to configure this.
1414

15+
## Docker Hub
16+
17+
Pre-built images are published to [Docker Hub][11] on every release.
18+
Pull the latest version via:
19+
```
20+
$ docker pull eclipsebasyx/basyx-python-server:latest
21+
```
22+
23+
Or pin to a specific release:
24+
```
25+
$ docker pull eclipsebasyx/basyx-python-server:2.0.1
26+
```
27+
1528
## Building
1629

17-
The container image can be built via:
30+
If you need to build the image locally (e.g. for development), run:
1831
```
1932
$ docker build -t basyx-python-server -f Dockerfile ..
2033
```
@@ -130,3 +143,4 @@ This Dockerfile is inspired by the [tiangolo/uwsgi-nginx-docker][10] repository.
130143
[8]: https://basyx-python-sdk.readthedocs.io/en/latest/adapter/json.html
131144
[9]: https://basyx-python-sdk.readthedocs.io/en/latest/adapter/xml.html
132145
[10]: https://github.com/tiangolo/uwsgi-nginx-docker
146+
[11]: https://hub.docker.com/r/eclipsebasyx/basyx-python-server

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)