Skip to content

Commit 9dbb62f

Browse files
Ornella33s-heppner
andauthored
Consolidate duplicated implementation of service specification (#527)
Previously we had two implementations of the service specification: one in interfaces.base.py and the other one in model.service_specification.py. This removes the implementation in interfaces.base.py and adapt the correponding usages to the one in model.service_specification.py. Fixes #523 --------- Co-authored-by: s-heppner <iat@s-heppner.com>
1 parent 6b55752 commit 9dbb62f

5 files changed

Lines changed: 92 additions & 95 deletions

File tree

server/app/interfaces/base.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -42,70 +42,6 @@
4242
T = TypeVar("T")
4343

4444

45-
class ServiceSpecificationProfileEnum(str, enum.Enum):
46-
"""
47-
Enumeration of all standardized Service Specification Profiles
48-
from the AAS Part 2 API Specification (IDTA-01002-3-1).
49-
Each profile is uniquely identified by its semantic URI.
50-
"""
51-
52-
# --- Asset Administration Shell (AAS) ---
53-
AAS_FULL = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellServiceSpecification/SSP-001"
54-
AAS_READ = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellServiceSpecification/SSP-002"
55-
56-
# --- Submodel ---
57-
SUBMODEL_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-001"
58-
SUBMODEL_VALUE = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-002"
59-
SUBMODEL_READ = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-003"
60-
61-
# --- AASX File Server ---
62-
AASX_FILESERVER_FULL = "https://admin-shell.io/aas/API/3/1/AasxFileServerServiceSpecification/SSP-001"
63-
64-
# --- AAS Registry ---
65-
AAS_REGISTRY_FULL = \
66-
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001"
67-
AAS_REGISTRY_READ = \
68-
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002"
69-
AAS_REGISTRY_BULK = \
70-
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-003"
71-
72-
# --- Submodel Registry ---
73-
SUBMODEL_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001"
74-
SUBMODEL_REGISTRY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-002"
75-
SUBMODEL_REGISTRY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-003"
76-
77-
# --- AAS Repository ---
78-
AAS_REPOSITORY_FULL = \
79-
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-001"
80-
AAS_REPOSITORY_READ = \
81-
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-002"
82-
AAS_REPOSITORY_BULK = \
83-
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-003"
84-
85-
# --- Submodel Repository ---
86-
SUBMODEL_REPOSITORY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-001"
87-
SUBMODEL_REPOSITORY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-002"
88-
SUBMODEL_REPOSITORY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003"
89-
90-
# --- Concept Description Repository ---
91-
CONCEPT_DESCRIPTION_REPOSITORY_FULL = \
92-
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-001"
93-
CONCEPT_DESCRIPTION_REPOSITORY_READ = \
94-
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-002"
95-
CONCEPT_DESCRIPTION_REPOSITORY_BULK = \
96-
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-003"
97-
98-
# --- Discovery ---
99-
DISCOVERY_FULL = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-001"
100-
DISCOVERY_READ = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-002"
101-
102-
103-
# TODO: Maybe remove this in spite of spec? Too complicated structure
104-
class ServiceDescription:
105-
def __init__(self, profiles: List[ServiceSpecificationProfileEnum]):
106-
self.profiles: List[ServiceSpecificationProfileEnum] = profiles
107-
108-
10945
@enum.unique
11046
class MessageType(enum.Enum):
11147
UNDEFINED = enum.auto()

server/app/interfaces/discovery.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
import json
8-
from typing import Dict, List, Set
8+
from typing import Dict, List, Set, Type
99

1010
import werkzeug.exceptions
1111
from basyx.aas import model
@@ -14,8 +14,14 @@
1414

1515
from app import model as server_model
1616
from app.adapter import jsonization
17-
from app.interfaces.base import BaseWSGIApp, HTTPApiDecoder
17+
from app.interfaces.base import BaseWSGIApp, HTTPApiDecoder, APIResponse
1818
from app.util.converters import IdentifierToBase64URLConverter, base64url_decode
19+
from app.model import ServiceSpecificationProfileEnum, ServiceDescription
20+
21+
SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([
22+
ServiceSpecificationProfileEnum.DISCOVERY_FULL,
23+
ServiceSpecificationProfileEnum.DISCOVERY_READ,
24+
])
1925

2026

2127
class DiscoveryStore:
@@ -90,6 +96,7 @@ def __init__(self, persistent_store: DiscoveryStore, base_path: str = "/api/v3.1
9096
Submount(
9197
base_path,
9298
[
99+
Rule("/description", methods=["GET"], endpoint=self.get_description),
93100
Rule(
94101
"/lookup/shellsByAssetLink",
95102
methods=["POST"],
@@ -122,8 +129,11 @@ def __init__(self, persistent_store: DiscoveryStore, base_path: str = "/api/v3.1
122129
strict_slashes=False,
123130
)
124131

132+
def get_description(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
133+
return response_t(SUPPORTED_PROFILES.to_dict())
134+
125135
def get_all_aas_ids_by_asset_link(
126-
self, request: Request, url_args: dict, response_t: type, **_kwargs
136+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
127137
) -> Response:
128138
asset_ids_param = request.args.get("assetIds", "")
129139
if not asset_ids_param:
@@ -154,7 +164,7 @@ def get_all_aas_ids_by_asset_link(
154164
return response_t(list(paginated_slice), cursor=cursor)
155165

156166
def search_all_aas_ids_by_asset_link(
157-
self, request: Request, url_args: dict, response_t: type, **_kwargs
167+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
158168
) -> Response:
159169
asset_links = HTTPApiDecoder.request_body_list(request, server_model.AssetLink, False)
160170
matching_aas_keys = set()
@@ -165,13 +175,15 @@ def search_all_aas_ids_by_asset_link(
165175
return response_t(list(paginated_slice), cursor=cursor)
166176

167177
def get_all_specific_asset_ids_by_aas_id(
168-
self, request: Request, url_args: dict, response_t: type, **_kwargs
178+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
169179
) -> Response:
170180
aas_identifier = str(url_args["aas_id"])
171181
asset_ids = self.persistent_store.get_all_specific_asset_ids_by_aas_id(aas_identifier)
172182
return response_t(asset_ids)
173183

174-
def post_all_asset_links_by_id(self, request: Request, url_args: dict, response_t: type, **_kwargs) -> Response:
184+
def post_all_asset_links_by_id(
185+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
186+
) -> Response:
175187
aas_identifier = str(url_args["aas_id"])
176188
specific_asset_ids = HTTPApiDecoder.request_body_list(request, model.SpecificAssetId, False)
177189
self.persistent_store.add_specific_asset_ids_to_aas(aas_identifier, specific_asset_ids)
@@ -180,7 +192,9 @@ def post_all_asset_links_by_id(self, request: Request, url_args: dict, response_
180192
updated = {aas_identifier: self.persistent_store.get_all_specific_asset_ids_by_aas_id(aas_identifier)}
181193
return response_t(updated)
182194

183-
def delete_all_asset_links_by_id(self, request: Request, url_args: dict, response_t: type, **_kwargs) -> Response:
195+
def delete_all_asset_links_by_id(
196+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
197+
) -> Response:
184198
aas_identifier = str(url_args["aas_id"])
185199
self.persistent_store.delete_specific_asset_ids_by_aas_id(aas_identifier)
186200
for key in list(self.persistent_store.asset_id_to_aas_ids.keys()):

server/app/interfaces/registry.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@
1717

1818
import app.model as server_model
1919
from app.interfaces.base import APIResponse, HTTPApiDecoder, ObjectStoreWSGIApp, is_stripped_request
20-
from app.model import DictDescriptorStore
20+
from app.model import DictDescriptorStore, ServiceSpecificationProfileEnum, ServiceDescription
2121
from app.util.converters import IdentifierToBase64URLConverter, base64url_decode
2222

23+
SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([
24+
ServiceSpecificationProfileEnum.AAS_REGISTRY_FULL,
25+
ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_FULL,
26+
ServiceSpecificationProfileEnum.AAS_REGISTRY_READ,
27+
ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_READ,
28+
])
29+
2330

2431
class RegistryAPI(ObjectStoreWSGIApp):
2532
def __init__(self, object_store: model.AbstractObjectStore, base_path: str = "/api/v3.1.1"):
@@ -156,15 +163,7 @@ def _get_submodel_descriptor(self, url_args: Dict) -> server_model.SubmodelDescr
156163
def get_self_description(
157164
self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
158165
) -> Response:
159-
service_description = server_model.ServiceDescription(
160-
profiles=[
161-
server_model.ServiceSpecificationProfileEnum.AAS_REGISTRY_FULL,
162-
server_model.ServiceSpecificationProfileEnum.AAS_REGISTRY_READ,
163-
server_model.ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_FULL,
164-
server_model.ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_READ,
165-
]
166-
)
167-
return response_t(service_description.to_dict())
166+
return response_t(SUPPORTED_PROFILES.to_dict())
168167

169168
# ------ AAS REGISTRY ROUTES -------
170169
def get_all_aas_descriptors(

server/app/interfaces/repository.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
from werkzeug.exceptions import BadRequest, Conflict, NotFound
2323
from werkzeug.routing import MapAdapter, Rule, Submount
2424

25-
from app.interfaces.base import APIResponse, HTTPApiDecoder, ObjectStoreWSGIApp, T, is_stripped_request
2625
from app.util.converters import IdentifierToBase64URLConverter, IdShortPathConverter, base64url_decode
27-
from .base import (ObjectStoreWSGIApp, APIResponse, is_stripped_request, HTTPApiDecoder, T,
28-
ServiceSpecificationProfileEnum, ServiceDescription)
26+
from .base import ObjectStoreWSGIApp, APIResponse, is_stripped_request, HTTPApiDecoder, T
27+
from app.model import ServiceSpecificationProfileEnum, ServiceDescription
2928

3029
SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([
3130
ServiceSpecificationProfileEnum.AAS_REPOSITORY_FULL,
3231
ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_FULL,
32+
ServiceSpecificationProfileEnum.AAS_REPOSITORY_READ,
33+
ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_READ,
3334
])
3435

3536

@@ -515,11 +516,7 @@ def not_implemented(self, request: Request, url_args: Dict, **_kwargs) -> Respon
515516
raise werkzeug.exceptions.NotImplemented("This route is not implemented!")
516517

517518
def get_description(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
518-
profiles = []
519-
for profile in SUPPORTED_PROFILES.profiles:
520-
profiles.append(profile.value)
521-
description = {"profiles": profiles}
522-
return response_t(description)
519+
return response_t(SUPPORTED_PROFILES.to_dict())
523520

524521
# ------ AAS REPO ROUTES -------
525522
def get_aas_all(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,71 @@
1-
from enum import Enum
21
from typing import List
2+
import enum
33

44

5-
class ServiceSpecificationProfileEnum(str, Enum):
6-
AAS_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001" # noqa: E501
7-
AAS_REGISTRY_READ = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002" # noqa: E501
5+
class ServiceSpecificationProfileEnum(str, enum.Enum):
6+
"""
7+
Enumeration of all standardized Service Specification Profiles
8+
from the AAS Part 2 API Specification (IDTA-01002-3-1).
9+
Each profile is uniquely identified by its semantic URI.
10+
"""
11+
12+
# --- Asset Administration Shell (AAS) ---
13+
AAS_FULL = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellServiceSpecification/SSP-001"
14+
AAS_READ = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellServiceSpecification/SSP-002"
15+
16+
# --- Submodel ---
17+
SUBMODEL_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-001"
18+
SUBMODEL_VALUE = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-002"
19+
SUBMODEL_READ = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-003"
20+
21+
# --- AASX File Server ---
22+
AASX_FILESERVER_FULL = "https://admin-shell.io/aas/API/3/1/AasxFileServerServiceSpecification/SSP-001"
23+
24+
# --- AAS Registry ---
25+
AAS_REGISTRY_FULL = \
26+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001"
27+
AAS_REGISTRY_READ = \
28+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002"
29+
AAS_REGISTRY_BULK = \
30+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-003"
31+
32+
# --- Submodel Registry ---
833
SUBMODEL_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001"
934
SUBMODEL_REGISTRY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-002"
10-
# TODO add other profiles
35+
SUBMODEL_REGISTRY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-003"
36+
37+
# --- AAS Repository ---
38+
AAS_REPOSITORY_FULL = \
39+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-001"
40+
AAS_REPOSITORY_READ = \
41+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-002"
42+
AAS_REPOSITORY_BULK = \
43+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-003"
44+
45+
# --- Submodel Repository ---
46+
SUBMODEL_REPOSITORY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-001"
47+
SUBMODEL_REPOSITORY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-002"
48+
SUBMODEL_REPOSITORY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003"
49+
50+
# --- Concept Description Repository ---
51+
CONCEPT_DESCRIPTION_REPOSITORY_FULL = \
52+
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-001"
53+
CONCEPT_DESCRIPTION_REPOSITORY_READ = \
54+
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-002"
55+
CONCEPT_DESCRIPTION_REPOSITORY_BULK = \
56+
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-003"
57+
58+
# --- Discovery ---
59+
DISCOVERY_FULL = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-001"
60+
DISCOVERY_READ = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-002"
1161

1262

63+
# TODO: Maybe remove this in spite of spec? Too complicated structure
1364
class ServiceDescription:
1465
def __init__(self, profiles: List[ServiceSpecificationProfileEnum]):
1566
if not profiles:
1667
raise ValueError("At least one profile must be specified")
17-
self.profiles = profiles
68+
self.profiles: List[ServiceSpecificationProfileEnum] = profiles
1869

1970
def to_dict(self):
2071
return {"profiles": [p.value for p in self.profiles]}

0 commit comments

Comments
 (0)