Skip to content

Commit dd6dd5b

Browse files
committed
Consolidate duplicated implementation of service specification
Perviously 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
1 parent c8b4d99 commit dd6dd5b

5 files changed

Lines changed: 88 additions & 96 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: 17 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"],
@@ -120,8 +127,11 @@ def __init__(self, persistent_store: DiscoveryStore, base_path: str = "/api/v3.1
120127
strict_slashes=False,
121128
)
122129

130+
def get_description(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
131+
return response_t(SUPPORTED_PROFILES.to_dict())
132+
123133
def get_all_aas_ids_by_asset_link(
124-
self, request: Request, url_args: dict, response_t: type, **_kwargs
134+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
125135
) -> Response:
126136
asset_ids_param = request.args.get("assetIds", "")
127137
if not asset_ids_param:
@@ -152,7 +162,7 @@ def get_all_aas_ids_by_asset_link(
152162
return response_t(list(paginated_slice), cursor=cursor)
153163

154164
def search_all_aas_ids_by_asset_link(
155-
self, request: Request, url_args: dict, response_t: type, **_kwargs
165+
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
156166
) -> Response:
157167
asset_links = HTTPApiDecoder.request_body_list(request, server_model.AssetLink, False)
158168
matching_aas_keys = set()
@@ -163,13 +173,13 @@ def search_all_aas_ids_by_asset_link(
163173
return response_t(list(paginated_slice), cursor=cursor)
164174

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

172-
def post_all_asset_links_by_id(self, request: Request, url_args: dict, response_t: type, **_kwargs) -> Response:
182+
def post_all_asset_links_by_id(self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs) -> Response:
173183
aas_identifier = str(url_args["aas_id"])
174184
specific_asset_ids = HTTPApiDecoder.request_body_list(request, model.SpecificAssetId, False)
175185
self.persistent_store.add_specific_asset_ids_to_aas(aas_identifier, specific_asset_ids)
@@ -178,7 +188,7 @@ def post_all_asset_links_by_id(self, request: Request, url_args: dict, response_
178188
updated = {aas_identifier: self.persistent_store.get_all_specific_asset_ids_by_aas_id(aas_identifier)}
179189
return response_t(updated)
180190

181-
def delete_all_asset_links_by_id(self, request: Request, url_args: dict, response_t: type, **_kwargs) -> Response:
191+
def delete_all_asset_links_by_id(self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs) -> Response:
182192
aas_identifier = str(url_args["aas_id"])
183193
self.persistent_store.delete_specific_asset_ids_by_aas_id(aas_identifier)
184194
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 & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,70 @@
1-
from enum import Enum
21
from typing import List
2+
import enum
33

4+
class ServiceSpecificationProfileEnum(str, enum.Enum):
5+
"""
6+
Enumeration of all standardized Service Specification Profiles
7+
from the AAS Part 2 API Specification (IDTA-01002-3-1).
8+
Each profile is uniquely identified by its semantic URI.
9+
"""
410

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
11+
# --- Asset Administration Shell (AAS) ---
12+
AAS_FULL = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellServiceSpecification/SSP-001"
13+
AAS_READ = "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellServiceSpecification/SSP-002"
14+
15+
# --- Submodel ---
16+
SUBMODEL_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-001"
17+
SUBMODEL_VALUE = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-002"
18+
SUBMODEL_READ = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-003"
19+
20+
# --- AASX File Server ---
21+
AASX_FILESERVER_FULL = "https://admin-shell.io/aas/API/3/1/AasxFileServerServiceSpecification/SSP-001"
22+
23+
# --- AAS Registry ---
24+
AAS_REGISTRY_FULL = \
25+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001"
26+
AAS_REGISTRY_READ = \
27+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002"
28+
AAS_REGISTRY_BULK = \
29+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-003"
30+
31+
# --- Submodel Registry ---
832
SUBMODEL_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001"
933
SUBMODEL_REGISTRY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-002"
10-
# TODO add other profiles
34+
SUBMODEL_REGISTRY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-003"
35+
36+
# --- AAS Repository ---
37+
AAS_REPOSITORY_FULL = \
38+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-001"
39+
AAS_REPOSITORY_READ = \
40+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-002"
41+
AAS_REPOSITORY_BULK = \
42+
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-003"
43+
44+
# --- Submodel Repository ---
45+
SUBMODEL_REPOSITORY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-001"
46+
SUBMODEL_REPOSITORY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-002"
47+
SUBMODEL_REPOSITORY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003"
48+
49+
# --- Concept Description Repository ---
50+
CONCEPT_DESCRIPTION_REPOSITORY_FULL = \
51+
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-001"
52+
CONCEPT_DESCRIPTION_REPOSITORY_READ = \
53+
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-002"
54+
CONCEPT_DESCRIPTION_REPOSITORY_BULK = \
55+
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-003"
56+
57+
# --- Discovery ---
58+
DISCOVERY_FULL = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-001"
59+
DISCOVERY_READ = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-002"
1160

1261

62+
# TODO: Maybe remove this in spite of spec? Too complicated structure
1363
class ServiceDescription:
1464
def __init__(self, profiles: List[ServiceSpecificationProfileEnum]):
1565
if not profiles:
1666
raise ValueError("At least one profile must be specified")
17-
self.profiles = profiles
67+
self.profiles: List[ServiceSpecificationProfileEnum] = profiles
1868

1969
def to_dict(self):
20-
return {"profiles": [p.value for p in self.profiles]}
70+
return {"profiles": [p.value for p in self.profiles]}

0 commit comments

Comments
 (0)