Skip to content

Commit bb8bfa8

Browse files
Add resolver for parsing graph from string
1 parent 5c053aa commit bb8bfa8

File tree

89 files changed

+1445
-945
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+1445
-945
lines changed

core/esmf-aspect-meta-model-python/README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ https://projects.eclipse.org/projects/dt.esmf).
99
* [Download SAMM files](#download-samm-files)
1010
* [Download SAMM release](#download-samm-release)
1111
* [Download SAMM branch](#download-samm-branch)
12+
* [Input data handler usage](#input-data-handler-usage)
1213
* [Aspect Meta Model Loader usage](#aspect-meta-model-loader-usage)
1314
* [Samm Units](#samm-units)
1415
* [SAMM CLI wrapper class](#samm-cli-wrapper-class)
@@ -84,14 +85,47 @@ poetry run download-samm-branch
8485
```
8586
Link to all branches: [SAMM Releases](https://github.com/eclipse-esmf/esmf-semantic-aspect-meta-model/branches)
8687

88+
## Input data handler usage
89+
90+
The InputHandler is a general-purpose class designed for loading input data into an RDF graph.
91+
It easily accommodates different input sources such as local files (.ttl) or direct data strings containing
92+
RDF formatted data.
93+
94+
```python
95+
from esmf_aspect_meta_model_python.resolver.handler import InputHandler
96+
97+
# Instantiating the Handler
98+
# The InputHandler requires a path or data string upon instantiation, which defines the source of RDF data
99+
# local file
100+
model_path = "path/to/local/file/AspectName.ttl"
101+
handler = InputHandler(model_path)
102+
graph, aspect_urn = handler.get_rdf_graph()
103+
104+
# returns a tuple containing the RDF graph and the aspect URN derived from the provided data source
105+
```
106+
107+
```python
108+
from esmf_aspect_meta_model_python.resolver.handler import InputHandler
109+
110+
# Alternatively, if you have RDF data in a string format, you can directly pass it as follows:
111+
rdf_data_string = "your RDF data string here"
112+
handler = InputHandler(rdf_data_string)
113+
graph, aspect_urn = handler.get_rdf_graph()
114+
```
115+
87116
## Aspect Meta Model Loader usage
88117

89118
An Aspect of the Meta Model can be loaded as follows:
90119
```python
91120
from esmf_aspect_meta_model_python import AspectLoader
121+
from esmf_aspect_meta_model_python.resolver.handler import InputHandler
122+
123+
model_path = "absolute/path/to/turtle.ttl"
124+
handler = InputHandler(model_path)
125+
graph, aspect_urn = handler.get_rdf_graph()
92126

93127
loader = AspectLoader()
94-
model_elements = loader.load_aspect_model("absolute/path/to/turtle.ttl")
128+
model_elements = loader.load_aspect_model(graph, aspect_urn)
95129
aspect = model_elements[0]
96130

97131
# or you can provide an Aspect URN

core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,4 @@
8787
)
8888
from .loader.aspect_loader import AspectLoader
8989
from .loader.samm_graph import SAMMGraph
90+
from .resolver.handler import InputHandler

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

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
#
1010
# SPDX-License-Identifier: MPL-2.0
1111

12-
from pathlib import Path
13-
from typing import Union
12+
from typing import List
1413

15-
from esmf_aspect_meta_model_python.loader.default_element_cache import DefaultElementCache
14+
from rdflib import Graph, URIRef
15+
16+
from esmf_aspect_meta_model_python.base.aspect import Aspect
1617
from esmf_aspect_meta_model_python.loader.samm_graph import SAMMGraph
1718

1819

@@ -24,55 +25,37 @@ class AspectLoader:
2425
The default cache strategy ignores inline defined elements.
2526
"""
2627

27-
def __init__(self) -> None:
28-
self._cache = DefaultElementCache()
29-
self._graph = SAMMGraph()
30-
31-
def get_graph(self) -> SAMMGraph:
32-
"""Get SAMM graph.
28+
def __init__(self):
29+
self.graph = None
3330

34-
:return: parsed SAMM Aspect model Graph.
31+
def load_aspect_model(self, rdf_graph: Graph, aspect_urn: URIRef | str = "") -> List[Aspect]:
3532
"""
36-
return self._graph
37-
38-
def get_samm_version(self) -> str:
39-
"""Get SAMM version of the graph."""
40-
return self._graph.get_samm_version()
33+
Creates a python object(s) to represent the Aspect model graph.
4134
42-
@staticmethod
43-
def convert_file_path(file_path: Union[str, Path]) -> str:
44-
"""Convert file_path to the string.
45-
46-
:param file_path: path to model file
47-
"""
48-
if isinstance(file_path, Path):
49-
file_path = str(file_path)
35+
This function takes an RDF graph and a URN for an Aspect node and converts it into
36+
a set of structured and connected Python objects that represents the Aspect model graph. The output is a
37+
list of Python objects derived from the RDF graph centered around the specified Aspect node.
5038
51-
tmp_path = Path(file_path)
52-
if not tmp_path.exists():
53-
raise FileNotFoundError(f"Could not found the file {tmp_path}")
39+
Args:
40+
rdf_graph (RDFGraph): The RDF graph from which to create the model.
41+
aspect_urn (str): The URN identifier for the main Aspect node in the RDF graph.
5442
55-
return file_path
43+
Returns:
44+
list: A list of Python objects that represent the Aspect elements of the Aspect model graph.
5645
57-
def _reset_graph(self):
58-
"""Reset graph and cache data."""
59-
if self._graph:
60-
self._graph = SAMMGraph()
46+
Examples:
47+
# Assuming 'graph' is a predefined RDFGraph object and 'aspect_urn' is defined:
48+
aspect_model = create_aspect_model_graph(graph, "urn:example:aspectNode")
49+
print(aspect_model) # This prints the list of Python objects.
6150
62-
if self._cache:
63-
self._cache = DefaultElementCache()
64-
65-
def load_aspect_model(self, file_path: Union[Path, str]):
66-
"""Load aspect model to RDF GRAPH.
67-
68-
Create an aspect object with all the including properties and operations with the turtle file
69-
70-
:param file_path: path to the turtle file. Can be either a string or a Path object
71-
:return: instance of the aspect
51+
Notes:
52+
It's crucial that the aspect_urn corresponds to a valid Aspect node within the RDF graph;
53+
otherwise, the function may not perform as expected.
7254
"""
73-
file_path = self.convert_file_path(file_path)
74-
self._reset_graph()
75-
_ = self._graph.parse(file_path)
76-
loaded_aspect_model = self._graph.to_python()
55+
self.graph = SAMMGraph(graph=rdf_graph)
56+
loaded_aspect_model = self.graph.to_python(aspect_urn)
57+
58+
# Add check that loaded_aspect_model is not empty
59+
# Add check that aspect_urn is ref to an Aspect node
7760

7861
return loaded_aspect_model

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

Lines changed: 30 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH
1+
# Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH
22
#
33
# See the AUTHORS file(s) distributed with this work for additional
44
# information regarding authorship.
@@ -9,17 +9,17 @@
99
#
1010
# SPDX-License-Identifier: MPL-2.0
1111

12-
from pathlib import Path
1312
from typing import List, Optional, Union
1413

1514
from rdflib import RDF, Graph, URIRef
1615
from rdflib.graph import Node
1716

17+
from esmf_aspect_meta_model_python.base.aspect import Aspect
1818
from esmf_aspect_meta_model_python.base.base import Base
1919
from esmf_aspect_meta_model_python.base.property import Property
2020
from esmf_aspect_meta_model_python.loader.default_element_cache import DefaultElementCache
2121
from esmf_aspect_meta_model_python.loader.model_element_factory import ModelElementFactory
22-
from esmf_aspect_meta_model_python.resolver.base import AspectModelResolver, BaseResolver
22+
from esmf_aspect_meta_model_python.resolver.meta_model import AspectMetaModelResolver
2323
from esmf_aspect_meta_model_python.vocabulary.SAMM import SAMM
2424

2525

@@ -31,16 +31,13 @@ class SAMMGraph:
3131
def __init__(
3232
self,
3333
graph: Graph | None = None,
34-
resolver: BaseResolver | None = None,
35-
cache: DefaultElementCache | None = None,
34+
cache: Union[DefaultElementCache, None] = None,
3635
):
37-
super().__init__()
38-
3936
self._graph = graph if graph else Graph()
40-
self._resolver = resolver if resolver else AspectModelResolver()
4137
self._cache = cache if cache else DefaultElementCache()
4238
self._samm_version = ""
43-
self._file_path: str = ""
39+
40+
self.populate_with_meta_data()
4441

4542
def __repr__(self) -> str:
4643
return repr(self._graph)
@@ -52,7 +49,7 @@ def get_rdf_graph(self) -> Graph:
5249
"""Get RDF graph."""
5350
return self._graph
5451

55-
def get_samm_version(self) -> str:
52+
def _get_samm_version_from_graph(self):
5653
"""Get SAMM version from the graph."""
5754
version = ""
5855

@@ -63,78 +60,55 @@ def get_samm_version(self) -> str:
6360

6461
return version
6562

66-
@staticmethod
67-
def convert_file_path(file_path: Union[Path, str]) -> str:
68-
"""Convert file_path to the string.
69-
70-
:param file_path: path to model file
71-
"""
72-
if isinstance(file_path, Path):
73-
file_path = str(file_path)
74-
75-
return file_path
76-
77-
def parse(self, file_path: Union[Path, str]) -> Graph:
78-
"""Parse a file to the SAMM graph.
79-
80-
:param file_path: Path to the *ttl file.
81-
"""
82-
self._file_path = self.convert_file_path(file_path)
83-
self._graph.parse(self._file_path)
84-
self._samm_version = self.get_samm_version()
85-
self._resolver.resolve(self._graph, self._file_path, self._samm_version)
86-
87-
return self._graph
63+
def get_samm_version(self):
64+
"""Get SAMM version from the graph."""
65+
version = self._get_samm_version_from_graph()
8866

89-
def _get_model_file_path(self, model_file_path: str = "") -> str:
90-
"""Get a model file path.
67+
if not version:
68+
raise ValueError("SAMM version not found in the Graph.")
69+
else:
70+
self._samm_version = version
9171

92-
:param model_file_path: str with path to the model
93-
:return: validated path rto the model fiel
94-
"""
95-
model_file_path = model_file_path if model_file_path else self._file_path
96-
if not model_file_path:
97-
raise ValueError("Path to the model is empty")
72+
def populate_with_meta_data(self):
73+
"""Populate RDF graph with SAMM data."""
74+
if not self._samm_version:
75+
self.get_samm_version()
9876

99-
return model_file_path
77+
meta_model_reader = AspectMetaModelResolver()
78+
meta_model_reader.parse(self._graph, self._samm_version)
10079

101-
def get_nodes_from_graph(self, model_file_path: str = "") -> List[Node]:
102-
"""Get a list of URIRef to nodes from the base model file."""
80+
def get_aspect_nodes_from_graph(self) -> List[Node]:
81+
"""Get a list of Aspect nodes from the graph."""
10382
nodes = []
104-
model_file_path = self._get_model_file_path(model_file_path)
105-
base_graph = Graph().parse(model_file_path, format="turtle")
83+
samm = SAMM(self._samm_version)
10684

10785
# Search for Aspect elements
108-
samm = SAMM(self._samm_version)
109-
for subject in base_graph.subjects(predicate=RDF.type, object=samm.get_urn(SAMM.aspect)): # type: ignore
86+
for subject in self._graph.subjects(predicate=RDF.type, object=samm.get_urn(SAMM.aspect)): # type: ignore
11087
nodes.append(subject)
11188

112-
if not nodes:
113-
for subject, object in base_graph.subject_objects(predicate=RDF.type, unique=True):
114-
prefix_data = str(object).replace("<", "").split(":")
115-
if ":".join(prefix_data[:3]) == self.samm_prefix:
116-
nodes.append(subject)
117-
11889
return nodes
11990

12091
def get_base_nodes(self, aspect_urn: URIRef | str = "") -> List[Node]:
12192
"""Get a list of base graph elements.
12293
123-
:param aspect_urn: URN of the Aspect node.
94+
:param model_pointer: pointer to the model
12495
:return: List of base graph elements.
12596
"""
12697
base_elements: list[Node] = []
12798

12899
if aspect_urn:
129100
base_elements += [aspect_urn if isinstance(aspect_urn, URIRef) else URIRef(aspect_urn)]
130101
else:
131-
base_elements += self.get_nodes_from_graph()
102+
base_elements += self.get_aspect_nodes_from_graph()
132103

133104
return base_elements
134105

135-
def to_python(self, aspect_urn: URIRef | str = "") -> List[URIRef | None]:
106+
def to_python(self, aspect_urn: URIRef | str = "") -> List[Aspect]:
136107
"""Convert SAMM graph to Python objects."""
137108
base_nodes = self.get_base_nodes(aspect_urn)
109+
if not base_nodes:
110+
raise ValueError(f"Could not found Aspect node by the URN {aspect_urn}.")
111+
138112
model_element_factory = ModelElementFactory(self._samm_version, self._graph, self._cache)
139113
aspect_elements = model_element_factory.create_all_graph_elements(base_nodes)
140114

0 commit comments

Comments
 (0)