Skip to content

Commit 37de4de

Browse files
Oleksandr Muzyka (EPAM)Oleksandr Muzyka (EPAM)
authored andcommitted
Add SAMM version validation to SAMMGraph load_aspect_model
1 parent 6f4fcac commit 37de4de

File tree

4 files changed

+115
-5
lines changed

4 files changed

+115
-5
lines changed

core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/samm_graph.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,41 @@ def load_aspect_model(self) -> Aspect:
213213

214214
graph = self.rdf_graph + self.samm_graph
215215
self._reader.prepare_aspect_model(graph)
216+
self._validate_samm_namespace_version(graph)
216217

217218
model_element_factory = ModelElementFactory(self.samm_version, graph, self._cache)
218219
self.aspect = model_element_factory.create_element(aspect_urn)
219220

220221
return self.aspect
221222

223+
def _validate_samm_namespace_version(self, graph: Graph) -> None:
224+
"""
225+
Validates that the SAMM version in the given RDF graph matches the detected SAMM version.
226+
227+
This method iterates through the namespaces in the RDF graph and checks if any namespace
228+
corresponds to the SAMM namespace prefix. If a SAMM namespace is found, it extracts the version
229+
from the namespace and compares it to the `samm_version` attribute of the class. If the versions
230+
do not match, a `ValueError` is raised.
231+
232+
Args:
233+
graph (Graph): The RDF graph whose namespaces are to be validated.
234+
235+
Raises:
236+
ValueError: If the SAMM version in the graph's namespace does not match the detected SAMM version.
237+
"""
238+
for prefix, namespace in graph.namespace_manager.namespaces():
239+
if prefix.startswith(self.samm_namespace_prefix):
240+
namespace_info = namespace.split(":") # [urn, namespace_id, namespace_specific_str, entity, version#]
241+
242+
if len(namespace_info) == 5 and namespace_info[2] == "org.eclipse.esmf.samm":
243+
version = namespace_info[-1].replace("#", "")
244+
245+
if version != self.samm_version:
246+
raise ValueError(
247+
f"SAMM version mismatch. Found '{version}', but expected '{self.samm_version}'. "
248+
"Ensure all RDF files use a single, consistent SAMM version"
249+
)
250+
222251
def _get_aspect_from_elements(self):
223252
"""Geta and save the Aspect element from the model elements."""
224253
if self.model_elements:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#
2+
# Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
3+
#
4+
5+
@prefix : <urn:samm:org.eclipse.esmf.test.general_with_references:2.2.0#>.
6+
@prefix type: <urn:samm:org.eclipse.esmf.test.types:2.1.0#> .
7+
@prefix samm: <urn:samm:org.eclipse.esmf.samm:meta-model:2.2.0#> .
8+
9+
:AspectWithEarlierSubmodelVersionReferences a samm:Aspect ;
10+
samm:preferredName "Aspect with references"@en ;
11+
samm:description "Test aspect with references from different files."@en ;
12+
samm:properties ( :testProperty ) ;
13+
samm:operations ( ) .
14+
15+
:testProperty a samm:Entity ;
16+
samm:preferredName "Test property"@en ;
17+
samm:description "Test property description."@en ;
18+
samm:properties ( :ExternalPartId
19+
type:TypeList ) .

core/esmf-aspect-meta-model-python/tests/integration/aspect_model_loader/test_resolve_elements_referencies.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@
99
#
1010
# SPDX-License-Identifier: MPL-2.0
1111

12-
from os import getcwd
1312
from pathlib import Path
1413

14+
import pytest
15+
1516
from esmf_aspect_meta_model_python import SAMMGraph
1617

17-
RESOURCE_PATH = getcwd() / Path(
18-
"tests/integration/aspect_model_loader/resources/org.eclipse.esmf.test.general_with_references/2.1.0"
18+
ASPECT_MODELS_DIR = (
19+
Path.cwd() / "tests/integration/aspect_model_loader/resources/org.eclipse.esmf.test.general_with_references"
1920
)
2021

22+
ASPECT_MODELS_V2_1_0 = ASPECT_MODELS_DIR / "2.1.0"
23+
ASPECT_MODELS_V2_2_0 = ASPECT_MODELS_DIR / "2.2.0"
24+
2125

2226
def test_resolve_elements_references():
23-
file_path = RESOURCE_PATH / "AspectWithReferences.ttl"
27+
file_path = ASPECT_MODELS_V2_1_0 / "AspectWithReferences.ttl"
2428
samm_graph = SAMMGraph()
2529
samm_graph.parse(file_path)
2630
aspect = samm_graph.load_aspect_model()
@@ -51,3 +55,18 @@ def test_resolve_elements_references():
5155
assert property_4.get_preferred_name("en") == "Test List"
5256
assert property_4.get_description("en") == "This is a test list."
5357
assert property_4.see == ["http://example.com/"]
58+
59+
60+
def test_aspect_with_diff_meta_model_submodel_version():
61+
file_path = ASPECT_MODELS_V2_2_0 / "AspectWithEarlierSubmodelVersionReferences.ttl"
62+
samm_graph = SAMMGraph()
63+
samm_graph.parse(file_path)
64+
65+
with pytest.raises(
66+
ValueError,
67+
match=(
68+
"SAMM version mismatch. Found '2.1.0', but expected '2.2.0'. "
69+
"Ensure all RDF files use a single, consistent SAMM version"
70+
),
71+
):
72+
samm_graph.load_aspect_model()

core/esmf-aspect-meta-model-python/tests/unit/loader/test_samm_graph.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,10 @@ def test_load_aspect_model(self):
216216

217217
@mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.ModelElementFactory")
218218
@mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.SAMMGraph.get_aspect_urn")
219-
def test_load_aspect_model_create_element(self, get_aspect_urn_mock, model_element_factory_mock):
219+
@mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.SAMMGraph._validate_samm_namespace_version")
220+
def test_load_aspect_model_create_element(
221+
self, validate_samm_namespace_version_mock, get_aspect_urn_mock, model_element_factory_mock
222+
):
220223
reader_mock = mock.MagicMock(name="reader")
221224
cache_mock = mock.MagicMock(name="cache")
222225
samm_graph = SAMMGraph()
@@ -229,13 +232,53 @@ def test_load_aspect_model_create_element(self, get_aspect_urn_mock, model_eleme
229232
get_aspect_urn_mock.return_value = "aspect_urn"
230233
model_element_factory_mock.return_value = model_element_factory_mock
231234
model_element_factory_mock.create_element.return_value = "aspect"
235+
232236
result = samm_graph.load_aspect_model()
233237

234238
assert result == "aspect"
235239
get_aspect_urn_mock.assert_called_once()
236240
reader_mock.prepare_aspect_model.assert_called_once_with("rdf_graph_samm_graph")
237241
model_element_factory_mock.assert_called_once_with("1.2.3", "rdf_graph_samm_graph", cache_mock)
238242
model_element_factory_mock.create_element.assert_called_once_with("aspect_urn")
243+
validate_samm_namespace_version_mock.assert_called_once_with("rdf_graph_samm_graph")
244+
245+
class TestValidateSammNamespaceVersion:
246+
"""Test suite for SAMMGraph._validate_samm_namespace_version method."""
247+
248+
def test_valid_version(self):
249+
version = "1.2.3"
250+
samm_graph = SAMMGraph()
251+
samm_graph.samm_version = version
252+
graph = mock.MagicMock(name="rdf_graph")
253+
graph.namespace_manager.namespaces.return_value = [
254+
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
255+
("samm", f"urn:samm:org.eclipse.esmf.samm:meta-model:{version}#"), # True
256+
("samm1", f"urn:samm:org.eclipse.esmf.samm:{version}#"),
257+
("samm-e1", f"urn:samm:org.eclipse.esmf.samm:entity:{version}#"), # True
258+
("samm-c1", "urn:samm:org.china.zes.samm:extension:1.1.1#"),
259+
]
260+
261+
# Should not raise an exception
262+
samm_graph._validate_samm_namespace_version(graph)
263+
264+
def test_version_mismatch(self):
265+
samm_version = "2.2.0"
266+
earlier_version = "2.1.0"
267+
samm_graph = SAMMGraph()
268+
samm_graph.samm_version = samm_version
269+
graph = mock.MagicMock(name="rdf_graph")
270+
graph.namespace_manager.namespaces.return_value = [
271+
("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
272+
("samm", f"urn:samm:org.eclipse.esmf.samm:meta-model:{samm_version}#"),
273+
("samm-e1", f"urn:samm:org.eclipse.esmf.samm:extension:{earlier_version}#"), # Exception
274+
]
275+
276+
with pytest.raises(
277+
ValueError,
278+
match=f"SAMM version mismatch. Found '{earlier_version}', but expected '{samm_version}'. "
279+
"Ensure all RDF files use a single, consistent SAMM version",
280+
):
281+
samm_graph._validate_samm_namespace_version(graph)
239282

240283
def test_load_model_elements(self):
241284
samm_graph = SAMMGraph()

0 commit comments

Comments
 (0)