From 34dcb07e6ebba229a61ef8c5fbb185944c44608d Mon Sep 17 00:00:00 2001 From: Julian Vogel Date: Mon, 6 Jan 2025 12:57:40 +0100 Subject: [PATCH 1/4] aas.adapter: skip writing `ExternalReference`s semantic ids Don't log `ModelReferences` not pointing to `ConceptDescriptions`, as it is not contrained by the spec. Warn about missing references, as the `object_store` seems to be incomplete. --- sdk/basyx/aas/adapter/aasx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 1c27640f..45193a0f 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -402,14 +402,15 @@ def write_aas(self, concept_descriptions: List[model.ConceptDescription] = [] for identifiable in objects_to_be_written: for semantic_id in traversal.walk_semantic_ids_recursive(identifiable): + if isinstance(semantic_id, model.ExternalReference): + continue if not isinstance(semantic_id, model.ModelReference) \ or semantic_id.type is not model.ConceptDescription: - logger.info("semanticId %s does not reference a ConceptDescription.", str(semantic_id)) continue try: cd = semantic_id.resolve(object_store) except KeyError: - logger.info("ConceptDescription for semanticId %s not found in object store.", str(semantic_id)) + logger.warning("ConceptDescription for semanticId %s not found in object store.", str(semantic_id)) continue except model.UnexpectedTypeError as e: logger.error("semanticId %s resolves to %s, which is not a ConceptDescription", From a2875769f19287a425d09624d442bfd1b9626ca6 Mon Sep 17 00:00:00 2001 From: Julian Vogel Date: Mon, 6 Jan 2025 12:58:51 +0100 Subject: [PATCH 2/4] aas.adapter.json: deserialize ModelReferences with last key type --- sdk/basyx/aas/adapter/json/json_deserialization.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 455a309e..226e1b1a 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -340,10 +340,15 @@ def _construct_model_reference(cls, dct: Dict[str, object], type_: Type[T], obje if reference_type is not model.ModelReference: raise ValueError(f"Expected a reference of type {model.ModelReference}, got {reference_type}!") keys = [cls._construct_key(key_data) for key_data in _get_ts(dct, "keys", list)] - if keys and not issubclass(KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)), type_): + last_key_type = KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)) + if keys and not issubclass(last_key_type, type_): logger.warning("type %s of last key of reference to %s does not match reference type %s", keys[-1].type.name, " / ".join(str(k) for k in keys), type_.__name__) - return object_class(tuple(keys), type_, cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) + # Infer type the model refence points to using `last_key_type` instead of `type_`. + # `type_` is often a `model.Referable`, which is more abstract than e.g. a `model.ConceptDescription`, + # leading to information loss while deserializing. + return object_class(tuple(keys), last_key_type, + cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) if 'referredSemanticId' in dct else None) @classmethod From f0c7eb1c4c6b7408be8de1aa3a449cd9abac3254 Mon Sep 17 00:00:00 2001 From: Julian Vogel Date: Mon, 6 Jan 2025 13:04:12 +0100 Subject: [PATCH 3/4] aas.adapter: check semantic ids have same model reference type --- sdk/basyx/aas/examples/data/_helper.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index 231ba2ad..4f75e873 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -213,6 +213,18 @@ def _check_has_semantics_equal(self, object_: model.HasSemantics, expected_objec :return: The value of expression to be used in control statements """ self.check_attribute_equal(object_, "semantic_id", expected_object.semantic_id) + if isinstance(expected_object.semantic_id, model.ModelReference) and self.check( + isinstance(object_.semantic_id, model.ModelReference), + "{} must be a ModelReference".format(repr(object_)), + ): # type: ignore + self.check( + object_.semantic_id.type == expected_object.semantic_id.type, # type: ignore + "ModelReference type {} of {} must be equal to {}".format( + object_.semantic_id.type, # type: ignore + repr(object_), + expected_object.semantic_id.type, + ), + ) for suppl_semantic_id in expected_object.supplemental_semantic_id: given_semantic_id = self._find_reference(suppl_semantic_id, object_.supplemental_semantic_id) self.check(given_semantic_id is not None, f"{object_!r} must have supplementalSemanticId", From 79cb82d1da254409efb7acf7c848d3a3aba595a2 Mon Sep 17 00:00:00 2001 From: Julian Vogel Date: Wed, 15 Jan 2025 10:24:21 +0100 Subject: [PATCH 4/4] aasx: add todo and improve logs --- sdk/basyx/aas/adapter/aasx.py | 5 +++-- sdk/basyx/aas/adapter/json/json_deserialization.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 45193a0f..88c6969a 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -410,10 +410,11 @@ def write_aas(self, try: cd = semantic_id.resolve(object_store) except KeyError: - logger.warning("ConceptDescription for semanticId %s not found in object store.", str(semantic_id)) + logger.warning("ConceptDescription for semanticId %s not found in object store. Skipping it.", + str(semantic_id)) continue except model.UnexpectedTypeError as e: - logger.error("semanticId %s resolves to %s, which is not a ConceptDescription", + logger.error("semanticId %s resolves to %s, which is not a ConceptDescription. Skipping it.", str(semantic_id), e.value) continue concept_descriptions.append(cd) diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 226e1b1a..c1ce35fe 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -347,6 +347,7 @@ def _construct_model_reference(cls, dct: Dict[str, object], type_: Type[T], obje # Infer type the model refence points to using `last_key_type` instead of `type_`. # `type_` is often a `model.Referable`, which is more abstract than e.g. a `model.ConceptDescription`, # leading to information loss while deserializing. + # TODO Remove this fix, when this function is called with correct `type_` return object_class(tuple(keys), last_key_type, cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) if 'referredSemanticId' in dct else None)