Skip to content

Commit a534a86

Browse files
authored
Merge branch 'develop' into fix/submodel-refs-concept-desc-paging-metadata
2 parents ece6a85 + 8a9b39a commit a534a86

5 files changed

Lines changed: 91 additions & 42 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ server/example_configurations/repository_standalone/input/
3636
server/example_configurations/repository_standalone/storage/
3737
server/example_configurations/registry_standalone/input/
3838
server/example_configurations/registry_standalone/storage/
39+
server/example_configurations/discovery_standalone/storage/

server/app/interfaces/discovery.py

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

77
import json
8+
import os
89
from typing import Dict, List, Set, Type
910

1011
import werkzeug.exceptions
@@ -65,28 +66,58 @@ def _delete_aas_id_from_specific_asset_ids(self, asset_id: model.SpecificAssetId
6566
@classmethod
6667
def from_file(cls, filename: str) -> "DiscoveryStore":
6768
"""
68-
Load the state of the `DiscoveryStore` from a local file.
69-
Safely handles files that are missing expected keys.
69+
Load a persisted discovery store from JSON.
7070
71+
The file stores the AAS-to-asset-id mapping as the source of truth.
72+
While loading, the reverse asset-id-to-AAS index is rebuilt in memory so
73+
lookup by asset ID works without persisting duplicate state.
7174
"""
7275
with open(filename, "r") as file:
7376
data = json.load(file, cls=jsonization.ServerAASFromJsonDecoder)
74-
discovery_store = DiscoveryStore()
75-
discovery_store.aas_id_to_asset_ids = data.get("aas_id_to_asset_ids", {})
76-
discovery_store.asset_id_to_aas_ids = data.get("asset_id_to_aas_ids", {})
77-
return discovery_store
77+
78+
discovery_store = DiscoveryStore()
79+
80+
for aas_id, asset_ids in data.get("aas_id_to_asset_ids", {}).items():
81+
parsed_asset_ids = set()
82+
83+
for asset_id in asset_ids:
84+
if isinstance(asset_id, model.SpecificAssetId):
85+
parsed_asset_id = asset_id
86+
else:
87+
parsed_asset_id = model.SpecificAssetId(
88+
name=asset_id["name"],
89+
value=asset_id["value"],
90+
)
91+
92+
parsed_asset_ids.add(parsed_asset_id)
93+
discovery_store._add_aas_id_to_specific_asset_id(parsed_asset_id, aas_id)
94+
95+
discovery_store.aas_id_to_asset_ids[aas_id] = parsed_asset_ids
96+
97+
return discovery_store
7898

7999
def to_file(self, filename: str) -> None:
80100
"""
81-
Write the current state of the `DiscoveryStore` to a local JSON file for persistence.
101+
Persist the discovery store as JSON.
102+
103+
Only the AAS-to-asset-id mapping is written because the reverse lookup
104+
index can be rebuilt when the store is loaded. The data is written to a
105+
temporary file first and then atomically moved into place to avoid
106+
corrupting the existing store if serialization fails.
82107
"""
83-
with open(filename, "w") as file:
84-
data = {
85-
"aas_id_to_asset_ids": self.aas_id_to_asset_ids,
86-
"asset_id_to_aas_ids": self.asset_id_to_aas_ids,
108+
data = {
109+
"aas_id_to_asset_ids": {
110+
aas_id: list(asset_ids)
111+
for aas_id, asset_ids in self.aas_id_to_asset_ids.items()
87112
}
113+
}
114+
115+
temp_filename = f"{filename}.tmp"
116+
with open(temp_filename, "w") as file:
88117
json.dump(data, file, cls=jsonization.ServerAASToJsonEncoder, indent=4)
89118

119+
os.replace(temp_filename, filename)
120+
90121

91122
class DiscoveryAPI(BaseWSGIApp):
92123
def __init__(self, persistent_store: DiscoveryStore, base_path: str = "/api/v3.1"):
@@ -160,8 +191,13 @@ def get_all_aas_ids_by_asset_link(
160191
aas_keys = self.persistent_store.search_aas_ids_by_asset_link(asset_link)
161192
matching_aas_keys.update(aas_keys)
162193

194+
<<<<<<< fix/submodel-refs-concept-desc-paging-metadata
163195
paginated_slice, cursor = self._get_slice(request, list(matching_aas_keys))
164196
return response_t(list(paginated_slice), paging_metadata=cursor)
197+
=======
198+
paginated_slice, paging_metadata = self._get_slice(request, list(matching_aas_keys))
199+
return response_t(list(paginated_slice), paging_metadata=paging_metadata)
200+
>>>>>>> develop
165201

166202
def search_all_aas_ids_by_asset_link(
167203
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
@@ -171,8 +207,13 @@ def search_all_aas_ids_by_asset_link(
171207
for asset_link in asset_links:
172208
aas_keys = self.persistent_store.search_aas_ids_by_asset_link(asset_link)
173209
matching_aas_keys.update(aas_keys)
210+
<<<<<<< fix/submodel-refs-concept-desc-paging-metadata
174211
paginated_slice, cursor = self._get_slice(request, list(matching_aas_keys))
175212
return response_t(list(paginated_slice), paging_metadata=cursor)
213+
=======
214+
paginated_slice, paging_metadata = self._get_slice(request, list(matching_aas_keys))
215+
return response_t(list(paginated_slice), paging_metadata=paging_metadata)
216+
>>>>>>> develop
176217

177218
def get_all_specific_asset_ids_by_aas_id(
178219
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs

server/app/services/run_discovery.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,22 @@
88

99
wsgi_optparams = {}
1010

11+
1112
if base_path is not None:
1213
wsgi_optparams["base_path"] = base_path
1314

1415

1516
# Load DiscoveryStore from disk, if `storage_path` is set
1617
if storage_path:
17-
discovery_store: DiscoveryStore = DiscoveryStore.from_file(storage_path)
18+
# If the storage file exists, we load it
19+
if os.path.exists(storage_path) and os.path.getsize(storage_path) > 0:
20+
discovery_store = DiscoveryStore.from_file(storage_path)
21+
22+
# If the file doesn't exist at the given path, we initiate a new file
23+
else:
24+
os.makedirs(os.path.dirname(storage_path), exist_ok=True)
25+
discovery_store = DiscoveryStore()
26+
discovery_store.to_file(storage_path)
1827
else:
1928
discovery_store = DiscoveryStore()
2029

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Eclipse BaSyx Python SDK - Discovery Service
22

33
This is a Python-based implementation of the **BaSyx Asset Administration Shell (AAS) Discovery Service**.
4-
It provides basic discovery functionality for AAS IDs and their corresponding assets, as specified in the official [Discovery Service Specification v3.1.0_SSP-001](https://app.swaggerhub.com/apis/Plattform_i40/DiscoveryServiceSpecification/V3.1.0_SSP-001).
4+
It provides basic discovery functionality for AAS IDs and their corresponding assets, as specified in the official [Discovery Service Specification v3.1.1_SSP-001](https://app.swaggerhub.com/apis/Plattform_i40/DiscoveryServiceSpecification/V3.1.1_SSP-001).
55

66
## Overview
77

@@ -11,38 +11,36 @@ The Discovery Service stores and retrieves relations between AAS identifiers and
1111

1212
| Function | Description | Example URL |
1313
|------------------------------------------|----------------------------------------------------------|-----------------------------------------------------------------------|
14-
| **search_all_aas_ids_by_asset_link** | Find AAS identifiers by providing asset link values | `POST http://localhost:8084/api/v3.0/lookup/shellsByAssetLink` |
15-
| **get_all_specific_asset_ids_by_aas_id** | Return specific asset ids associated with an AAS ID | `GET http://localhost:8084/api/v3.0/lookup/shells/{aasIdentifier}` |
16-
| **post_all_asset_links_by_id** | Register specific asset ids linked to an AAS | `POST http://localhost:8084/api/v3.0/lookup/shells/{aasIdentifier}` |
17-
| **delete_all_asset_links_by_id** | Delete all asset links associated with a specific AAS ID | `DELETE http://localhost:8084/api/v3.0/lookup/shells/{aasIdentifier}` |
18-
|
14+
| **get_description** | Return the supported Discovery Service profiles | `GET http://localhost:8084/api/v3.1/description` |
15+
| **get_all_aas_ids_by_asset_link** | Find AAS identifiers by asset link query parameter | `GET http://localhost:8084/api/v3.1/lookup/shells?assetIds={assetIds}` |
16+
| **search_all_aas_ids_by_asset_link** | Find AAS identifiers by providing asset link values | `POST http://localhost:8084/api/v3.1/lookup/shellsByAssetLink` |
17+
| **get_all_specific_asset_ids_by_aas_id** | Return specific asset ids associated with an AAS ID | `GET http://localhost:8084/api/v3.1/lookup/shells/{aasIdentifier}` |
18+
| **post_all_asset_links_by_id** | Register specific asset ids linked to an AAS | `POST http://localhost:8084/api/v3.1/lookup/shells/{aasIdentifier}` |
19+
| **delete_all_asset_links_by_id** | Delete all asset links associated with a specific AAS ID | `DELETE http://localhost:8084/api/v3.1/lookup/shells/{aasIdentifier}` |
20+
1921

2022
## Configuration
21-
Add discovery_store as directory
22-
The service can be configured to use either:
23+
This example Docker compose configuration starts a discovery server.
24+
25+
The container image can also be built and run via:
26+
```
27+
$ docker compose up
28+
```
2329

24-
- **In-memory storage** (default): Temporary data storage that resets on service restart.
25-
- **MongoDB storage**: Persistent backend storage using MongoDB.
30+
## Persistence
2631

27-
### Configuration via Environment Variables
32+
The discovery service can run in persistent or non-persistent mode.
2833

29-
| Variable | Description | Default |
30-
|------------------|--------------------------------------------|-----------------------------|
31-
| `STORAGE_TYPE` | `inmemory` or `mongodb` | `inmemory` |
32-
| `MONGODB_URI` | MongoDB connection URI | `mongodb://localhost:27017` |
33-
| `MONGODB_DBNAME` | Name of the MongoDB database | `basyx_registry` |
34+
### Persistent Mode
3435

35-
## Deployment via Docker
36+
Persistent mode configuration is provided in the `compose.yaml`.
3637

37-
A `Dockerfile` and `docker-compose.yml` are provided for simple deployment.
38-
The container image can be built and run via:
39-
```bash
40-
docker compose up --build
41-
```
42-
## Test
38+
Only the AAS-to-asset-ID mapping is persisted. The reverse lookup index is rebuilt in memory when the service starts.
39+
40+
### Non-Persistent Mode
4341

44-
Examples of asset links and specific asset IDs for testing purposes are provided as JSON files in the [storage](./storage) folder.
42+
If `storage_path` is not set, the discovery service runs in memory only.
4543

46-
## Acknowledgments
44+
## Notes
45+
- Stop the service before manually editing `discovery_store.json`.
4746

48-
This Dockerfile is inspired by the [tiangolo/uwsgi-nginx-docker](https://github.com/tiangolo/uwsgi-nginx-docker) repository.

server/example_configurations/discovery_standalone/compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
dockerfile: server/docker/discovery/Dockerfile
77
ports:
88
- "8084:80"
9-
#environment:
10-
#- storage_path=/discovery_store.json
11-
#volumes:
12-
# - ./discovery_store.json:/discovery_store.json
9+
environment:
10+
storage_path: /storage/discovery_store.json
11+
volumes:
12+
- ./storage:/storage

0 commit comments

Comments
 (0)