Skip to content

Commit

Permalink
aasx.adapter: Fix semantic_id type deserialization of `ModelReferen…
Browse files Browse the repository at this point in the history
…ce` (#337)

Previously, if the `semantic_id` was a 
`ModelReference`, it very often had the generic 
`type` "`model.Referable`" instead of the actual
type of the object it points to. This lead to a 
bug in the AASX writer, where 
`ConceptDescription`s were not written to the 
AASX, even though they existed in the 
`ObjectStore`.

This fixes this problem by tackling three separate
points: 

1. In `json_deserialization.py`, we now infer the
`type` of the `ModelReference` via the Reference's
last `Key`. This is more of a hotfix and issue 
#367 tracks the creation of a better solution. 

2. In `aasx.py`, an unneccessary `logger.info` 
call is removed, that previously cluttered the 
output of the writer if a `semantic_id` was not 
pointing to a `ConceptDescription`, which is not
something worth noting. Furthermore, we improve
other log messages. 

3. In `examples.data._helper`, we improve the test
for `semantic_id` equality, namely by comparing
the `ModelReference`'s attributes explicitly, when
it happens to be one, instead of relying on simple
string comparision.
  • Loading branch information
JGrothoff authored Jan 15, 2025
1 parent 1ca495f commit 933b9fa
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 5 deletions.
8 changes: 5 additions & 3 deletions sdk/basyx/aas/adapter/aasx.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 8 additions & 2 deletions sdk/basyx/aas/adapter/json/json_deserialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions sdk/basyx/aas/examples/data/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 933b9fa

Please sign in to comment.