Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 21 additions & 39 deletions server/app/interfaces/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,18 @@ def get_all_specific_asset_ids_by_aas_id(self, aas_id: model.Identifier) -> List
return list(self.aas_id_to_asset_ids.get(aas_id, set()))

def add_specific_asset_ids_to_aas(self, aas_id: model.Identifier, asset_ids: List[model.SpecificAssetId]) -> None:

if aas_id not in self.aas_id_to_asset_ids:
self.aas_id_to_asset_ids[aas_id] = set()

for asset in asset_ids:
self.aas_id_to_asset_ids[aas_id].add(asset)
self.aas_id_to_asset_ids.setdefault(aas_id, set()).update(asset_ids)
for asset_id in asset_ids:
self.asset_id_to_aas_ids.setdefault(asset_id, set()).add(aas_id)

def delete_specific_asset_ids_by_aas_id(self, aas_id: model.Identifier) -> None:
key = aas_id
if key in self.aas_id_to_asset_ids:
del self.aas_id_to_asset_ids[key]
for asset_id in self.aas_id_to_asset_ids.pop(aas_id, set()):
aas_ids = self.asset_id_to_aas_ids.get(asset_id)
if aas_ids is None:
continue
aas_ids.discard(aas_id)
if not aas_ids:
del self.asset_id_to_aas_ids[asset_id]

def search_aas_ids_by_asset_link(self, asset_link: server_model.AssetLink) -> List[model.Identifier]:
result = []
Expand All @@ -63,16 +64,6 @@ def search_aas_ids_by_asset_link(self, asset_link: server_model.AssetLink) -> Li
result.extend(list(aas_ids))
return result

def _add_aas_id_to_specific_asset_id(self, asset_id: model.SpecificAssetId, aas_id: model.Identifier) -> None:
if asset_id in self.asset_id_to_aas_ids:
self.asset_id_to_aas_ids[asset_id].add(aas_id)
else:
self.asset_id_to_aas_ids[asset_id] = {aas_id}

def _delete_aas_id_from_specific_asset_ids(self, asset_id: model.SpecificAssetId, aas_id: model.Identifier) -> None:
if asset_id in self.asset_id_to_aas_ids:
self.asset_id_to_aas_ids[asset_id].discard(aas_id)

@classmethod
def from_file(cls, filename: str) -> "DiscoveryStore":
"""
Expand All @@ -88,21 +79,13 @@ def from_file(cls, filename: str) -> "DiscoveryStore":
discovery_store = DiscoveryStore()

for aas_id, asset_ids in data.get("aas_id_to_asset_ids", {}).items():
parsed_asset_ids = set()

for asset_id in asset_ids:
if isinstance(asset_id, model.SpecificAssetId):
parsed_asset_id = asset_id
else:
parsed_asset_id = model.SpecificAssetId(
name=asset_id["name"],
value=asset_id["value"],
)

parsed_asset_ids.add(parsed_asset_id)
discovery_store._add_aas_id_to_specific_asset_id(parsed_asset_id, aas_id)

discovery_store.aas_id_to_asset_ids[aas_id] = parsed_asset_ids
parsed_asset_ids = [
asset_id
if isinstance(asset_id, model.SpecificAssetId)
else model.SpecificAssetId(name=asset_id["name"], value=asset_id["value"])
for asset_id in asset_ids
]
discovery_store.add_specific_asset_ids_to_aas(aas_id, parsed_asset_ids)

return discovery_store

Expand Down Expand Up @@ -224,19 +207,18 @@ def post_all_asset_links_by_id(
) -> Response:
aas_identifier = str(url_args["aas_id"])
specific_asset_ids = HTTPApiDecoder.request_body_list(request, model.SpecificAssetId, False)
self.persistent_store.delete_specific_asset_ids_by_aas_id(aas_identifier)
self.persistent_store.add_specific_asset_ids_to_aas(aas_identifier, specific_asset_ids)
for asset_id in specific_asset_ids:
self.persistent_store._add_aas_id_to_specific_asset_id(asset_id, aas_identifier)
updated = {aas_identifier: self.persistent_store.get_all_specific_asset_ids_by_aas_id(aas_identifier)}
return response_t(updated)
return response_t(updated, status=201)

def delete_all_asset_links_by_id(
self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs
) -> Response:
aas_identifier = str(url_args["aas_id"])
if aas_identifier not in self.persistent_store.aas_id_to_asset_ids:
raise werkzeug.exceptions.NotFound(f"No AssetAdministrationShell with {aas_identifier} found!")
self.persistent_store.delete_specific_asset_ids_by_aas_id(aas_identifier)
for key in list(self.persistent_store.asset_id_to_aas_ids.keys()):
self.persistent_store.asset_id_to_aas_ids[key].discard(aas_identifier)
return response_t()


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ def tearDown(self) -> None:
delete_request = urllib.request.Request(
f"{SERVER_BASE_URL}/lookup/shells/{base64url_encode(self.AAS_ID)}", method="DELETE"
)
urllib.request.urlopen(delete_request).close()
try:
urllib.request.urlopen(delete_request).close()
except urllib.error.HTTPError as exc:
if exc.code != 404:
raise

# ------------------------------------------------------------------ GET /description

Expand All @@ -68,7 +72,7 @@ def test_asset_link_roundtrip(self):
aas_asset_links_path, data=body, headers={"Content-Type": "application/json"}, method="POST"
)
with urllib.request.urlopen(post_request) as response:
self.assertEqual(200, response.status)
self.assertEqual(201, response.status)

with urllib.request.urlopen(aas_asset_links_path) as response:
self.assertEqual(200, response.status)
Expand Down
28 changes: 15 additions & 13 deletions server/test/interfaces/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ def setUp(self) -> None:
def register(self, aas_id: str, asset_ids: List[Tuple[str, str]]) -> None:
assets: list[SpecificAssetId] = [SpecificAssetId(name, value) for name, value in asset_ids]
self.store.add_specific_asset_ids_to_aas(aas_id, assets)
for asset in assets:
self.store._add_aas_id_to_specific_asset_id(asset, aas_id)


# ====================================================================== /description
Expand Down Expand Up @@ -233,8 +231,7 @@ def test_post_creates_asset_links(self) -> None:
obj=[{"name": "serial", "value": "123"}],
)

# Spec documents 201 for creation; this server responds 200 with the updated mapping.
self.assert_ok(response)
self.assertEqual(201, response.status_code, msg=response.get_data(as_text=True))
body = self.format_client.parse_object(response)
self.assertEqual([("serial", "123")], [(a["name"], a["value"]) for a in body[self.AAS_ID]])
self.assertEqual(
Expand All @@ -249,22 +246,30 @@ def test_post_accepts_single_object_body(self) -> None:
f"/lookup/shells/{base64url_encode(self.AAS_ID)}", obj={"name": "serial", "value": "123"}
)

self.assert_ok(response)
self.assertEqual(201, response.status_code, msg=response.get_data(as_text=True))
self.assertEqual(
[("serial", "123")],
[(a["name"], a["value"]) for a in self.format_client.parse_object(response)[self.AAS_ID]],
)

def test_post_is_additive(self) -> None:
def test_post_replaces_existing_asset_links(self) -> None:
# Spec: PostAllAssetLinksById "Creates or replaces all asset links associated to the
# Asset Administration Shell". Prior links must be dropped, not merged.
self.register(self.AAS_ID, [("serial", "123")])

response = self.format_client.post(
f"/lookup/shells/{base64url_encode(self.AAS_ID)}", obj=[{"name": "batch", "value": "xyz"}]
)

self.assert_ok(response)
self.assertEqual(201, response.status_code, msg=response.get_data(as_text=True))
stored = {(a["name"], a["value"]) for a in self.format_client.parse_object(response)[self.AAS_ID]}
self.assertEqual({("serial", "123"), ("batch", "xyz")}, stored)
self.assertEqual({("batch", "xyz")}, stored)
self.assertEqual(
[],
self.format_client.parse_collection(
self.format_client.post("/lookup/shellsByAssetLink", obj=[{"name": "serial", "value": "123"}])
),
)

def test_post_malformed_json_returns_400(self) -> None:
response = self.format_client.post(
Expand Down Expand Up @@ -292,11 +297,8 @@ def test_delete_removes_asset_links(self) -> None:
),
)

def test_delete_unknown_aas_returns_204(self) -> None:
# Spec allows 404 here; this server always returns 204.
response = self.format_client.delete(f"/lookup/shells/{base64url_encode(self.UNKNOWN_ID)}")

self.assertEqual(204, response.status_code, msg=response.get_data(as_text=True))
def test_delete_unknown_aas_returns_404(self) -> None:
self.assert_error(self.format_client.delete(f"/lookup/shells/{base64url_encode(self.UNKNOWN_ID)}"), 404)


# ====================================================================== DiscoveryStore.from_file()/to_file()
Expand Down
Loading