Skip to content

Commit 648efed

Browse files
committed
Update Registry after test
1 parent 8da14be commit 648efed

9 files changed

Lines changed: 283 additions & 82 deletions

File tree

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/interfaces/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .base import *
2+
from .registry import *
3+
from .discovery import *
4+
from .repository import *

server/app/interfaces/base.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@
2020
from werkzeug.exceptions import NotFound, BadRequest
2121
from werkzeug.routing import MapAdapter
2222

23+
import app.model
2324
from basyx.aas import model
2425
from basyx.aas.adapter._generic import XML_NS_MAP
25-
from basyx.aas.adapter.json import StrictStrippedAASFromJsonDecoder, StrictAASFromJsonDecoder, AASToJsonEncoder
26+
from app.adapter import ServerStrictStrippedAASFromJsonDecoder, ServerStrictAASFromJsonDecoder, ServerAASToJsonEncoder
2627
from basyx.aas.adapter.xml import xml_serialization, XMLConstructables, read_aas_xml_element
2728
from basyx.aas.model import AbstractObjectStore
2829
from app.util.converters import base64url_decode
30+
from app.model import AssetLink, AssetAdministrationShellDescriptor, SubmodelDescriptor
31+
from app.model.provider import _DESCRIPTOR_TYPE
2932

3033

31-
T = TypeVar("T")
3234

35+
T = TypeVar("T")
36+
_STORABLE = TypeVar("_STORABLE", model.provider._IDENTIFIABLE, _DESCRIPTOR_TYPE)
3337

3438
@enum.unique
3539
class MessageType(enum.Enum):
@@ -159,7 +163,7 @@ def __init__(self, *args, content_type="text/xml", **kwargs):
159163
super().__init__(*args, **kwargs, content_type=content_type)
160164

161165

162-
class ResultToJsonEncoder(AASToJsonEncoder):
166+
class ResultToJsonEncoder(ServerAASToJsonEncoder):
163167
@classmethod
164168
def _result_to_json(cls, result: Result) -> Dict[str, object]:
165169
return {
@@ -264,13 +268,13 @@ def http_exception_to_response(exception: werkzeug.exceptions.HTTPException, res
264268
class ObjectStoreWSGIApp(BaseWSGIApp):
265269
object_store: AbstractObjectStore
266270

267-
def _get_all_obj_of_type(self, type_: Type[T]) -> Iterator[T]:
271+
def _get_all_obj_of_type(self, type_: Type[_STORABLE]) -> Iterator[_STORABLE]:
268272
for obj in self.object_store:
269273
if isinstance(obj, type_):
270274
yield obj
271275

272-
def _get_obj_ts(self, identifier: model.Identifier, type_: Type[T]) \
273-
-> T:
276+
def _get_obj_ts(self, identifier: model.Identifier, type_: Type[_STORABLE]) \
277+
-> _STORABLE:
274278
identifiable = self.object_store.get(identifier)
275279
if not isinstance(identifiable, type_):
276280
raise NotFound(f"No {type_.__name__} with {identifier} found!")
@@ -292,7 +296,12 @@ class HTTPApiDecoder:
292296

293297
@classmethod
294298
def check_type_support(cls, type_: type):
295-
if type_ not in cls.type_constructables_map:
299+
tolerated_types = (
300+
AssetAdministrationShellDescriptor,
301+
SubmodelDescriptor,
302+
AssetLink,
303+
)
304+
if type_ not in cls.type_constructables_map and type_ not in tolerated_types:
296305
raise TypeError(f"Parsing {type_} is not supported!")
297306

298307
@classmethod
@@ -304,8 +313,8 @@ def assert_type(cls, obj: object, type_: Type[T]) -> T:
304313
@classmethod
305314
def json_list(cls, data: Union[str, bytes], expect_type: Type[T], stripped: bool, expect_single: bool) -> List[T]:
306315
cls.check_type_support(expect_type)
307-
decoder: Type[StrictAASFromJsonDecoder] = StrictStrippedAASFromJsonDecoder if stripped \
308-
else StrictAASFromJsonDecoder
316+
decoder: Type[ServerStrictAASFromJsonDecoder] = ServerStrictStrippedAASFromJsonDecoder if stripped \
317+
else ServerStrictAASFromJsonDecoder
309318
try:
310319
parsed = json.loads(data, cls=decoder)
311320
if isinstance(parsed, list) and expect_single:
@@ -324,6 +333,9 @@ def json_list(cls, data: Union[str, bytes], expect_type: Type[T], stripped: bool
324333
model.SpecificAssetId: decoder._construct_specific_asset_id,
325334
model.Reference: decoder._construct_reference,
326335
model.Qualifier: decoder._construct_qualifier,
336+
app.model.AssetAdministrationShellDescriptor: decoder._construct_asset_administration_shell_descriptor,
337+
app.model.SubmodelDescriptor: decoder._construct_submodel_descriptor,
338+
app.model.AssetLink: decoder._construct_asset_link,
327339
}
328340

329341
constructor: Optional[Callable[..., T]] = mapping.get(expect_type) # type: ignore[assignment]

server/app/interfaces/registry.py

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,21 @@ def get_aas_descriptor_by_id(self, request: Request, url_args: Dict, response_t:
139139
return response_t(descriptor)
140140

141141
def put_aas_descriptor_by_id(self, request: Request, url_args: Dict, response_t: Type[APIResponse],
142-
**_kwargs) -> Response:
143-
descriptor = self._get_aas_descriptor(url_args)
144-
descriptor.update_from(HTTPApiDecoder.request_body(request, server_model.AssetAdministrationShellDescriptor,
145-
is_stripped_request(request)))
146-
descriptor.commit()
147-
return response_t()
142+
map_adapter: MapAdapter, **_kwargs) -> Response:
143+
try:
144+
descriptor = self._get_aas_descriptor(url_args)
145+
descriptor.update_from(HTTPApiDecoder.request_body(request, server_model.AssetAdministrationShellDescriptor,
146+
is_stripped_request(request)))
147+
descriptor.commit()
148+
return response_t()
149+
except NotFound:
150+
descriptor = HTTPApiDecoder.request_body(request, server_model.AssetAdministrationShellDescriptor, False)
151+
self.object_store.add(descriptor)
152+
descriptor.commit()
153+
created_resource_url = map_adapter.build(self.get_aas_descriptor_by_id, {
154+
"aas_id": descriptor.id
155+
}, force_external=True)
156+
return response_t(descriptor, status=201, headers={"Location": created_resource_url})
148157

149158
def delete_aas_descriptor_by_id(self, request: Request, url_args: Dict, response_t: Type[APIResponse],
150159
**_kwargs) -> Response:
@@ -211,21 +220,36 @@ def put_submodel_descriptor_by_id_through_superpath(self,
211220
response_t:
212221
Type[
213222
APIResponse],
214-
**_kwargs) -> Response:
223+
map_adapter: MapAdapter, **_kwargs) -> Response:
215224
aas_descriptor = self._get_aas_descriptor(url_args)
216-
submodel_id = url_args["submodel_id"]
217-
submodel_descriptor = next(
218-
(sd for sd in aas_descriptor.submodel_descriptors if
219-
sd.id == submodel_id), None)
220-
if submodel_descriptor is None:
221-
raise NotFound(
222-
f"Submodel Descriptor with Identifier {submodel_id} not found in AssetAdministrationShell!")
223-
submodel_descriptor.update_from(
224-
HTTPApiDecoder.request_body(request,
225-
server_model.SubmodelDescriptor,
226-
is_stripped_request(request)))
227-
aas_descriptor.commit()
228-
return response_t()
225+
try:
226+
submodel_id = url_args["submodel_id"]
227+
submodel_descriptor = next(
228+
(sd for sd in aas_descriptor.submodel_descriptors if
229+
sd.id == submodel_id), None)
230+
if submodel_descriptor is None:
231+
raise NotFound(
232+
f"Submodel Descriptor with Identifier {submodel_id} not found in AssetAdministrationShell!")
233+
submodel_descriptor.update_from(
234+
HTTPApiDecoder.request_body(request,
235+
server_model.SubmodelDescriptor,
236+
is_stripped_request(request)))
237+
aas_descriptor.commit()
238+
return response_t()
239+
except NotFound:
240+
submodel_descriptor = HTTPApiDecoder.request_body(request,
241+
server_model.SubmodelDescriptor,
242+
is_stripped_request(
243+
request))
244+
aas_descriptor.submodel_descriptors.append(submodel_descriptor)
245+
aas_descriptor.commit()
246+
created_resource_url = map_adapter.build(
247+
self.get_submodel_descriptor_by_id_through_superpath, {
248+
"aas_id": aas_descriptor.id,
249+
"submodel_id": submodel_descriptor.id
250+
}, force_external=True)
251+
return response_t(submodel_descriptor, status=201,
252+
headers={"Location": created_resource_url})
229253

230254
def delete_submodel_descriptor_by_id_through_superpath(self,
231255
request: Request,
@@ -270,12 +294,22 @@ def post_submodel_descriptor(self, request: Request, url_args: Dict, response_t:
270294
return response_t(submodel_descriptor, status=201, headers={"Location": created_resource_url})
271295

272296
def put_submodel_descriptor_by_id(self, request: Request, url_args: Dict, response_t: Type[APIResponse],
273-
**_kwargs) -> Response:
274-
submodel_descriptor = self._get_submodel_descriptor(url_args)
275-
submodel_descriptor.update_from(
276-
HTTPApiDecoder.request_body(request, server_model.SubmodelDescriptor, is_stripped_request(request)))
277-
submodel_descriptor.commit()
278-
return response_t()
297+
map_adapter: MapAdapter, **_kwargs) -> Response:
298+
try:
299+
submodel_descriptor = self._get_submodel_descriptor(url_args)
300+
submodel_descriptor.update_from(
301+
HTTPApiDecoder.request_body(request, server_model.SubmodelDescriptor, is_stripped_request(request)))
302+
submodel_descriptor.commit()
303+
return response_t()
304+
except NotFound:
305+
submodel_descriptor = HTTPApiDecoder.request_body(request, server_model.SubmodelDescriptor,
306+
is_stripped_request(request))
307+
self.object_store.add(submodel_descriptor)
308+
submodel_descriptor.commit()
309+
created_resource_url = map_adapter.build(self.get_submodel_descriptor_by_id, {
310+
"submodel_id": submodel_descriptor.id
311+
}, force_external=True)
312+
return response_t(submodel_descriptor, status=201, headers={"Location": created_resource_url})
279313

280314
def delete_submodel_descriptor_by_id(self, request: Request, url_args: Dict, response_t: Type[APIResponse],
281315
**_kwargs) -> Response:

server/app/model/provider.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
from basyx.aas import model
44
from basyx.aas.model import provider as sdk_provider
5+
from pathlib import Path
6+
from typing import Dict, IO, Iterable
57

68
from app.model import descriptor
9+
from app.adapter import read_server_aas_json_file_into
10+
11+
PathOrIO = Union[Path, IO]
712

813

914
_DESCRIPTOR_TYPE = Union[descriptor.AssetAdministrationShellDescriptor, descriptor.SubmodelDescriptor]
@@ -46,3 +51,30 @@ def __len__(self) -> int:
4651

4752
def __iter__(self) -> Iterator[_DESCRIPTOR_TYPE]:
4853
return iter(self._backend.values())
54+
55+
def load_directory(directory: Union[Path, str]) -> DictDescriptorStore:
56+
"""
57+
Create a new :class:`~basyx.aas.model.provider.DictIdentifiableStore` and use it to load Asset Administration Shell
58+
and Submodel files in ``AASX``, ``JSON`` and ``XML`` format from a given directory into memory. Additionally, load
59+
all embedded supplementary files into a new :class:`~basyx.aas.adapter.aasx.DictSupplementaryFileContainer`.
60+
61+
:param directory: :class:`~pathlib.Path` or ``str`` pointing to the directory containing all Asset Administration
62+
Shell and Submodel files to load
63+
:return: Tuple consisting of a :class:`~basyx.aas.model.provider.DictIdentifiableStore` and a
64+
:class:`~basyx.aas.adapter.aasx.DictSupplementaryFileContainer` containing all loaded data
65+
"""
66+
67+
dict_descriptor_store: DictDescriptorStore = DictDescriptorStore()
68+
69+
directory = Path(directory)
70+
71+
for file in directory.iterdir():
72+
if not file.is_file():
73+
continue
74+
75+
suffix = file.suffix.lower()
76+
if suffix == ".json":
77+
with open(file) as f:
78+
read_server_aas_json_file_into(dict_descriptor_store, f)
79+
80+
return dict_descriptor_store

server/app/model/service_specification.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from enum import Enum
33

44
class ServiceSpecificationProfileEnum(str, Enum):
5-
AAS_REGISTRY_FULL = "https://adminshell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-001"
6-
AAS_REGISTRY_READ = "https://adminshell.io/aas/API/3/0/AssetAdministrationShellRegistryServiceSpecification/SSP-002"
7-
SUBMODEL_REGISTRY_FULL = "https://adminshell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-001"
8-
SUBMODEL_REGISTRY_READ = "https://adminshell.io/aas/API/3/0/SubmodelRegistryServiceSpecification/SSP-002"
5+
AAS_REGISTRY_FULL = "https://adminshell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001"
6+
AAS_REGISTRY_READ = "https://adminshell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002"
7+
SUBMODEL_REGISTRY_FULL = "https://adminshell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001"
8+
SUBMODEL_REGISTRY_READ = "https://adminshell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-002"
99
#TODO add other profiles
1010

1111

0 commit comments

Comments
 (0)