From 2f63f2f1fc107b1d42b43a0467990bca21f01d3d Mon Sep 17 00:00:00 2001 From: Matyas Date: Mon, 12 Aug 2024 12:54:44 +0300 Subject: [PATCH 1/3] First implementation, fixes eclipse-esmf/esmf-sdk-py-aspect-model-loader#43 --- .../loader/instantiator/state_instantiator.py | 103 ++++++++++++++++++ .../pyproject.toml | 2 +- .../2.0.0/AspectWithState.ttl | 34 ++++++ .../tests/integration/test_characteristics.py | 22 ++++ 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py create mode 100644 core/esmf-aspect-meta-model-python/tests/integration/resources/org.eclipse.esmf.test.characteristics/2.0.0/AspectWithState.ttl diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py new file mode 100644 index 0000000..2d9d836 --- /dev/null +++ b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py @@ -0,0 +1,103 @@ +# Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH +# +# See the AUTHORS file(s) distributed with this work for additional +# information regarding authorship. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 + +import typing + +import rdflib # type: ignore + +from rdflib.term import Node + +from esmf_aspect_meta_model_python.base.characteristics.state import State +from esmf_aspect_meta_model_python.impl.characteristics.default_state import DefaultState +from esmf_aspect_meta_model_python.loader.instantiator.constants import DATA_TYPE_ERROR_MSG +from esmf_aspect_meta_model_python.loader.instantiator_base import InstantiatorBase +from esmf_aspect_meta_model_python.loader.rdf_helper import RdfHelper +from esmf_aspect_meta_model_python.vocabulary.SAMM import SAMM +from esmf_aspect_meta_model_python.vocabulary.SAMMC import SAMMC + + +class StateInstantiator(InstantiatorBase[State]): + def _create_instance(self, element_node: Node) -> State: + data_type = self._get_data_type(element_node) + if data_type is None: + raise TypeError(DATA_TYPE_ERROR_MSG) + + meta_model_base_attributes = self._get_base_attributes(element_node) + value_collection_node = self._aspect_graph.value( + subject=element_node, + predicate=self._sammc.get_urn(SAMMC.values), + ) + value_nodes = RdfHelper.get_rdf_list_values(value_collection_node, self._aspect_graph) + values = [self.__to_state_node_value(value_node) for value_node in value_nodes] + + defaultValue = self._aspect_graph.value( + subject=element_node, + predicate=self._sammc.get_urn(SAMMC.default_value), + ) + default = self.__to_state_node_value(defaultValue) + + return DefaultState(meta_model_base_attributes, data_type, values, default) + + def __to_state_node_value(self, value_node: Node) -> typing.Dict: + """ + This method instantiates one possible value of an state. + :param value_node: Node of the Graph that represents one state value. + The Argument can either be a Literal or a URIRef. + - If value_node is a Literal it will represent e.g. a string or an integer value + - If value_node is a URIRef it will represent a value of a ComplexType + :return: the one generated value of the state + """ + if isinstance(value_node, rdflib.Literal): + # value represents a simple data type + return value_node.toPython() + + elif isinstance(value_node, rdflib.URIRef): + # value represents a complex data type + value = {} + value_node_properties = self._aspect_graph.predicate_objects(value_node) + for property_urn, property_value in value_node_properties: + if property_urn != rdflib.RDF.type and isinstance(property_urn, str): + property_name = property_urn.split("#")[1] + actual_value: typing.Optional[typing.Any] + if self.__is_collection_value(property_urn): + actual_value = self.__instantiate_state_collection(property_value) + else: + actual_value = self.__to_state_node_value(property_value) + value[property_name] = actual_value + + value_node_name = value_node.split("#")[1] + value_key = self._samm.get_urn(SAMM.name).toPython() + value[value_key] = value_node_name # type: ignore + return value + + else: + # illegal node type for state value (e.g., Blank Node) + raise TypeError( + f"Every value of an state must either be a Literal (string, int, etc.) or " + f"a URI reference to a ComplexType. Values of type {type(value_node).__name__} are not allowed" + ) + + def __is_collection_value(self, property_subject: str) -> bool: + characteristic = self._aspect_graph.value( # type: ignore + subject=property_subject, + predicate=self._samm.get_urn(SAMM.characteristic), + ) + characteristic_type = self._aspect_graph.value(subject=characteristic, predicate=rdflib.RDF.type) + return characteristic_type in self._sammc.collections_urns() + + def __instantiate_state_collection(self, value_list) -> typing.List[typing.Dict]: + """creates a collection as a child for state characteristics""" + value_node_list = RdfHelper.get_rdf_list_values(value_list, self._aspect_graph) + values = [] + for value_node in value_node_list: + value = self.__to_state_node_value(value_node) + values.append(value) + return values diff --git a/core/esmf-aspect-meta-model-python/pyproject.toml b/core/esmf-aspect-meta-model-python/pyproject.toml index 6ada2f6..e56bdf8 100644 --- a/core/esmf-aspect-meta-model-python/pyproject.toml +++ b/core/esmf-aspect-meta-model-python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "esmf-aspect-model-loader" -version = "2.1.1" +version = "2.1.5" description = "Load Aspect Models based on the Semantic Aspect Meta Model" authors = [ "Eclipse Semantic Modeling Framework", diff --git a/core/esmf-aspect-meta-model-python/tests/integration/resources/org.eclipse.esmf.test.characteristics/2.0.0/AspectWithState.ttl b/core/esmf-aspect-meta-model-python/tests/integration/resources/org.eclipse.esmf.test.characteristics/2.0.0/AspectWithState.ttl new file mode 100644 index 0000000..9e7d969 --- /dev/null +++ b/core/esmf-aspect-meta-model-python/tests/integration/resources/org.eclipse.esmf.test.characteristics/2.0.0/AspectWithState.ttl @@ -0,0 +1,34 @@ +# Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH +# +# See the AUTHORS file(s) distributed with this work for +# additional information regarding authorship. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 + +@prefix : . +@prefix samm: . +@prefix samm-c: . +@prefix xsd: . +@prefix unit: . + +:TestAspect a samm:Aspect ; + samm:preferredName "Test Aspect"@en ; + samm:preferredName "Test Aspekt"@de ; + samm:description "This is a test description"@en ; + samm:see ; + samm:properties ( :testPropertyOne ) ; + samm:operations ( ) . + +:testPropertyOne a samm:Property ; + samm:characteristic [ + a samm-c:State ; + samm:preferredName "Test State"@en ; + samm:description "This is a test state."@en ; + samm:dataType xsd:string ; + samm-c:values ( "default" "good" "bad" ) ; + samm-c:defaultValue "default" + ] . diff --git a/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py b/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py index 5edc22b..54714f4 100644 --- a/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py +++ b/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py @@ -22,6 +22,7 @@ Measurement, Quantifiable, StructuredValue, + State ) RESOURCE_PATH = getcwd() / Path("tests/integration/resources/org.eclipse.esmf.test.characteristics/2.0.0") @@ -116,6 +117,27 @@ def test_loading_aspect_with_simple_enum(): assert "baz" in values +def test_loading_aspect_with_simple_state(): + print(RESOURCE_PATH) + file_path = RESOURCE_PATH / "AspectWithState.ttl" + aspect_loader = AspectLoader() + model_elements = aspect_loader.load_aspect_model(file_path) + aspect = model_elements[0] + + first_property = aspect.properties[0] + state_characteristic = first_property.characteristic + assert isinstance(state_characteristic, State) + assert state_characteristic.name == "testPropertyOne_characteristic" + + values = state_characteristic.values + assert len(values) == 3 + assert "default" in values + assert "good" in values + assert "bad" in values + default = state_characteristic.default_value + assert default == "default" + + def test_loading_aspect_with_quantifiable(): file_path = RESOURCE_PATH / "AspectWithQuantifiableAndUnit.ttl" aspect_loader = AspectLoader() From e372dd8aeb02c988326408ae1eff2461c49b49de Mon Sep 17 00:00:00 2001 From: thec0dewriter <47274810+thec0dewriter@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:17:09 +0300 Subject: [PATCH 2/3] Update core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py Fix stylecheck Co-authored-by: Andreas Textor --- .../tests/integration/test_characteristics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py b/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py index 54714f4..bad8471 100644 --- a/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py +++ b/core/esmf-aspect-meta-model-python/tests/integration/test_characteristics.py @@ -21,8 +21,8 @@ Enumeration, Measurement, Quantifiable, - StructuredValue, - State + State, + StructuredValue ) RESOURCE_PATH = getcwd() / Path("tests/integration/resources/org.eclipse.esmf.test.characteristics/2.0.0") From a85adafb88a5f6a23e7b8efcc11a937fce5404cf Mon Sep 17 00:00:00 2001 From: thec0dewriter <47274810+thec0dewriter@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:38:51 +0300 Subject: [PATCH 3/3] #43 state initiator implementation v2 (#1) * StateInstantiator modified for EnumerationInstantiator inheritance --- .../loader/instantiator/state_instantiator.py | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py index 2d9d836..ba067d5 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py +++ b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/instantiator/state_instantiator.py @@ -9,7 +9,7 @@ # # SPDX-License-Identifier: MPL-2.0 -import typing +from typing import Any, Dict, Optional import rdflib # type: ignore @@ -18,13 +18,13 @@ from esmf_aspect_meta_model_python.base.characteristics.state import State from esmf_aspect_meta_model_python.impl.characteristics.default_state import DefaultState from esmf_aspect_meta_model_python.loader.instantiator.constants import DATA_TYPE_ERROR_MSG -from esmf_aspect_meta_model_python.loader.instantiator_base import InstantiatorBase +from esmf_aspect_meta_model_python.loader.instantiator.enumeration_instantiator import EnumerationInstantiator from esmf_aspect_meta_model_python.loader.rdf_helper import RdfHelper from esmf_aspect_meta_model_python.vocabulary.SAMM import SAMM from esmf_aspect_meta_model_python.vocabulary.SAMMC import SAMMC -class StateInstantiator(InstantiatorBase[State]): +class StateInstantiator(EnumerationInstantiator): def _create_instance(self, element_node: Node) -> State: data_type = self._get_data_type(element_node) if data_type is None: @@ -46,9 +46,9 @@ def _create_instance(self, element_node: Node) -> State: return DefaultState(meta_model_base_attributes, data_type, values, default) - def __to_state_node_value(self, value_node: Node) -> typing.Dict: + def __to_state_node_value(self, value_node: Optional[Node]) -> Dict: """ - This method instantiates one possible value of an state. + This method instantiates one possible value of a state. :param value_node: Node of the Graph that represents one state value. The Argument can either be a Literal or a URIRef. - If value_node is a Literal it will represent e.g. a string or an integer value @@ -66,9 +66,9 @@ def __to_state_node_value(self, value_node: Node) -> typing.Dict: for property_urn, property_value in value_node_properties: if property_urn != rdflib.RDF.type and isinstance(property_urn, str): property_name = property_urn.split("#")[1] - actual_value: typing.Optional[typing.Any] + actual_value: Optional[Any] if self.__is_collection_value(property_urn): - actual_value = self.__instantiate_state_collection(property_value) + actual_value = self.__instantiate_enum_collection(property_value) else: actual_value = self.__to_state_node_value(property_value) value[property_name] = actual_value @@ -84,20 +84,3 @@ def __to_state_node_value(self, value_node: Node) -> typing.Dict: f"Every value of an state must either be a Literal (string, int, etc.) or " f"a URI reference to a ComplexType. Values of type {type(value_node).__name__} are not allowed" ) - - def __is_collection_value(self, property_subject: str) -> bool: - characteristic = self._aspect_graph.value( # type: ignore - subject=property_subject, - predicate=self._samm.get_urn(SAMM.characteristic), - ) - characteristic_type = self._aspect_graph.value(subject=characteristic, predicate=rdflib.RDF.type) - return characteristic_type in self._sammc.collections_urns() - - def __instantiate_state_collection(self, value_list) -> typing.List[typing.Dict]: - """creates a collection as a child for state characteristics""" - value_node_list = RdfHelper.get_rdf_list_values(value_list, self._aspect_graph) - values = [] - for value_node in value_node_list: - value = self.__to_state_node_value(value_node) - values.append(value) - return values