diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 1c27640f..88c6969a 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -402,17 +402,19 @@ 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. 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 455a309e..c1ce35fe 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -340,10 +340,16 @@ 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. + # 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) @classmethod 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",