Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse JAVA SDK test suite #39

Merged
merged 2 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

The following people have contributed to this repository:

* [Georg Schmidt-Dumont]([email protected])
* [Nico Makowe](mailto:[email protected])
* [Aghyad Farrouh](mailto:[email protected])
* [Hanna Shalamitskaya](mailto:[email protected])
* [Andreas Textor](mailto:[email protected])
* [Georg Schmidt-Dumont](mailto:[email protected])
* [Michele Santoro](mailto:[email protected])
* Nico Makowe
* Aghyad Farrouh

Please add yourself to this list, if you contribute to the content.
3 changes: 2 additions & 1 deletion CONVENTIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ESMF SDK PY Aspect Model Loader Code Conventions

The following document contains a compilation of conventions and guidelines to format, structure and write code for the ESMF SDK PY Aspect Model Loader.
The following document contains a compilation of conventions and guidelines to format,
structure and write code for the ESMF SDK PY Aspect Model Loader.

## General Conventions
Our code conventions are based on the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) but
Expand Down
196 changes: 150 additions & 46 deletions core/esmf-aspect-meta-model-python/README.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,113 @@
The Aspect Model Loader as part of the Python SDK provided by the [*Eclipse Semantic Modeling Framework*](
https://projects.eclipse.org/projects/dt.esmf).

<!-- TOC -->
* [An Aspect of the Meta Model](#an-aspect-of-the-meta-model)
* [Set Up SAMM Aspect Meta Model](#set-up-samm-aspect-meta-model)
* [Install poetry](#install-poetry)
* [Install project dependencies](#install-project-dependencies)
* [Download SAMM files](#download-samm-files)
* [Download SAMM release](#download-samm-release)
* [Download SAMM branch](#download-samm-branch)
* [Aspect Meta Model Loader usage](#aspect-meta-model-loader-usage)
* [Samm Units](#samm-units)
* [SAMM CLI wrapper class](#samm-cli-wrapper-class)
* [Scripts](#scripts)
* [Automation Tasks](#automation-tasks)
* [tox](#tox)
* [GitHub actions](#github-actions)
* [Check New Pull Request](#check-new-pull-request)
* [Build release](#build-release)
<!-- TOC -->

# An Aspect of the Meta Model

The `esmf-aspect-model-loader` package provides the Python implementation for the SAMM Aspect Meta Model, or SAMM.
Each Meta Model element and each Characteristic class is represented by an interface with a corresponding
implementation.

## Usage
## Set Up SAMM Aspect Meta Model

An Aspect of the Meta Model can be created as follows using the provided `AspectInstantiator`.
Before getting started to use the `esmf-aspect-model-loader` library you need to apply some set up actions:

```
aspect_loader = AspectLoader()
model_elements = aspect_loader.load_aspect_model("absolute/path/to/turtle.ttl");
aspect = model_elements[0]
```
Required
- [Install poetry](#install-poetry)
- [Install project dependencies](#install-project-dependencies)
- [Download SAMM files](#download-samm-files)
Optional
- [Download SAMM CLI](#download-samm-cli) (needed to use SAMM CLI functions)
- [Download SAMM test models](#download-test-models) (for running integration tests)

or
### Install poetry

```
aspect_loader = AspectLoader()
aspect_urn = "urn:samm:org.eclipse.esmf.samm:aspect.model:0.0.1#AspectName"
aspect = aspect_loader.load_aspect_model("absolute/path/to/turtle.ttl", aspect_urn);
`Poetry` used as a dependency management for the `esmf-aspect-model-loader`. Follow the next [instruction](https://python-poetry.org/docs/#installation)
to install it.

To check the poetry version run:
```console
poetry --version
```

## Automatic Deployment
### Install project dependencies

A [GitHub action called 'Release'](https://github.com/eclipse-esmf/esmf-sdk-py-aspect-model-loader/actions/workflows/tagged_release.yml)
has been set up for the `esmf-aspect-model-loader`. This action checks the code quality by running tests, the [static type checker MyPy](https://github.com/python/mypy) and
the [code formatter 'Black'](https://github.com/psf/black).
Poetry provides convenient functionality for working with dependencies in the project.
To automatically download and install all the necessary libraries, just run one command:
```console
poetry install
```
It is required to run `poetry install` once in the esmf-aspect-model-loader module.

## Set Up SAMM Aspect Meta Model for development
### Download SAMM files

In order to download the SAMM sources, it is required to run `poetry install` once in the `esmf-aspect-model-loader`
module. There are two possibilities to download the SAMM files and extract the Turtle sources for the Meta Model.
There are two possibilities to download the SAMM files and extract the Turtle sources for the Meta Model:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this section should also explain for whom are the SAMM files interesting, or in other words, why would you want to download them. IMO users of the aspect-meta-model-python should not have to deal with this at all (if they don't want to), including that they should not need to know about this.

SAMM release or SAMM branch

### Possibility 1 (downloading a release)
#### Download SAMM release

The `download_samm_release` script may be executed with
This script downloads a release JAR-file from GitHub, extracts them for further usage in the Aspect Model Loader:

```
To run script, execute the next command.
```console
poetry run download-samm-release
```

It downloads a release JAR-file from GitHub and extracts the SAMM Files.
The version is specified in the python script.

Link to all Releases: https://github.com/eclipse-esmf/esmf-semantic-aspect-meta-model/releases
```
The version of the SAMM release is specified in the python script.

### Possibility 2 (downloading from the repository)
Link to all Releases: [SAMM Releases](https://github.com/eclipse-esmf/esmf-semantic-aspect-meta-model/releases)

It may happen that there is no .jar file that is up to date with the changes of the SAMM.
This script is an alternative to the `download_samm_release.py` and extracts the files from the repository
directly instead of using the newest release.
#### Download SAMM branch

The script uses the GitHub API and downloads the files from the `main` branch. If the script is run in a
pipeline, it uses a GitHub token to authorize. If the script is run locally, the API is called without a token.
This may cause problems because unauthorized API calls are limited.
The script uses the GitHub API and downloads the files from the `main` GitHub branch.

This script can be executed with
If the script is run in a pipeline, it uses a GitHub token to authorize. If the script is run locally,
the API is called without a token. This may cause problems because unauthorized API calls are limited.

```
Run the next command to download and start working with the Aspect Model Loader.
```console
poetry run download-samm-branch
```
to download and start working with the Aspect Model Loader.
Link to all branches: [SAMM Releases](https://github.com/eclipse-esmf/esmf-semantic-aspect-meta-model/branches)

## Semantic Aspect Meta Model
## Aspect Meta Model Loader usage

To be able to use SAMM meta model classes you need to download the corresponding files.
Details are described in [Set up SAMM Aspect Meta Model for development](#set-up-samm-aspect-meta-model-for-development).
An Aspect of the Meta Model can be loaded as follows:
```python
from esmf_aspect_meta_model_python import AspectLoader

loader = AspectLoader()
model_elements = loader.load_aspect_model("absolute/path/to/turtle.ttl")
aspect = model_elements[0]

This module contains classes for working with Aspect data.
# or you can provide an Aspect URN

SAMM meta model contains:
- SammUnitsGraph: provide a functionality for working with units([units.ttl](./esmf_aspect_meta_model_python/samm_aspect_meta_model/samm/unit/2.1.0/units.ttl)) data
loader = AspectLoader()
aspect_urn = "urn:samm:org.eclipse.esmf.samm:aspect.model:0.0.1#AspectName"
model_elements = loader.load_aspect_model("absolute/path/to/turtle.ttl", aspect_urn)
aspect = model_elements[0]
```

### SammUnitsGraph
## Samm Units

The class contains functions for accessing units of measurement.
SAMMUnitsGraph is a class contains functions for accessing units of measurement.
```python
from esmf_aspect_meta_model_python.samm_meta_model import SammUnitsGraph

Expand All @@ -92,3 +121,78 @@ units_graph.print_description(unit_data)
# ...
# symbol: V
```

## SAMM CLI wrapper class

The SAMM CLI is a command line tool provided number of functions for working with Aspect Models.

More detailed information about SAMM CLI functionality can be found in the
[SAMM CLI documentation](https://eclipse-esmf.github.io/esmf-developer-guide/tooling-guide/samm-cli.html).

Python Aspect Model Loader provide a wrapper class to be able to call SAMM CLI functions from the Python code.
For instance, validation of a model can be done with the following code snippet:

```python
from esmf_aspect_meta_model_python.samm_cli_functions import SammCli

samm_cli = SammCli()
model_path = "Paht_to_the_model/Model.ttl"
samm_cli.validate(model_path)
# Input model is valid
```

List of SAMMCLI functions:
- validate
- to_openapi
- to_schema
- to_json
- to_html
- to_png
- to_svg

# Scripts

The Aspect Model Loader provide scripts for downloading some additional code and data.
Provided scripts:
- download-samm-release
- download-samm-branch
- download-samm-cli
- download-test-models

All scripts run like a poetry command. The poetry is available from the folder where [pyproject.toml](pyproject.toml)
is located.

# Automation Tasks
## tox

`tox` is used for the tests automation purpose. There are two environments with different purposes and tests can
be running with the tox:
- pep8: static code checks (PEP8 style) with MyPy and Black
- py310: unit and integration tests

```console
# run all checks use the next command
poetry run tox

# run only pep8 checks
poetry run tox -e pep8

# run tests
poetry run tox -e py310
```

## GitHub actions

There are two actions on the GitHub repo:
- [Check New Pull Request](../../.github/workflows/push_request_check.yml)
- [Build release](../../.github/workflows/tagged_release.yml)

### Check New Pull Request
This action run after creation or updating a pull request and run all automation tests with tox command.

### Build release
Prepare and publish a new release for the `esmf-aspect-model-loader` to the PyPi:
[esmf-aspect-model-loader](https://pypi.org/project/esmf-aspect-model-loader/.)

A list of the available releases on the GitHub:
[Releases](https://github.com/eclipse-esmf/esmf-sdk-py-aspect-model-loader/releases).
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
# SPDX-License-Identifier: MPL-2.0

from pathlib import Path
from typing import Optional, Union
from typing import Union

from esmf_aspect_meta_model_python.base.base import Base
from esmf_aspect_meta_model_python.base.property import Property
from esmf_aspect_meta_model_python.loader.default_element_cache import DefaultElementCache
from esmf_aspect_meta_model_python.loader.samm_graph import SAMMGraph

Expand All @@ -26,9 +24,9 @@ class AspectLoader:
The default cache strategy ignores inline defined elements.
"""

def __init__(self, graph: Optional[SAMMGraph] = None, cache: Optional[DefaultElementCache] = None) -> None:
self._cache = cache if cache else DefaultElementCache()
self._graph = graph if graph else SAMMGraph(cache=self._cache)
def __init__(self) -> None:
self._cache = DefaultElementCache()
self._graph = SAMMGraph()

def get_graph(self) -> SAMMGraph:
"""Get SAMM graph.
Expand All @@ -50,8 +48,20 @@ def convert_file_path(file_path: Union[str, Path]) -> str:
if isinstance(file_path, Path):
file_path = str(file_path)

tmp_path = Path(file_path)
if not tmp_path.exists():
raise FileNotFoundError(f"Could not found the file {tmp_path}")

return file_path

def _reset_graph(self):
"""Reset graph and cache data."""
if self._graph:
self._graph = SAMMGraph()

if self._cache:
self._cache = DefaultElementCache()

def load_aspect_model(self, file_path: Union[Path, str]):
"""Load aspect model to RDF GRAPH.

Expand All @@ -61,80 +71,8 @@ def load_aspect_model(self, file_path: Union[Path, str]):
:return: instance of the aspect
"""
file_path = self.convert_file_path(file_path)
self._reset_graph()
_ = self._graph.parse(file_path)
loaded_aspect_model = self._graph.to_python()

return loaded_aspect_model

def find_by_name(self, element_name: str) -> list[Base]:
"""Find a specific model element by name, and returns the found elements

:param element_name: name or pyload of element
:return: list of found elements
"""
return self._cache.get_by_name(element_name)

def find_by_urn(self, urn: str) -> Optional[Base]:
"""Find a specific model element, and returns it or undefined.

:param urn: urn of the model element
:return: found element or None
"""
return self._cache.get_by_urn(urn)

def determine_access_path(self, base_element_name: str) -> list[list[str]]:
"""Determine the access path.

Search for the element in cache first then call "determine_element_access_path" for every found element

:param base_element_name: name of element
:return: list of paths found to access the respective value.
"""
paths: list[list[str]] = []
base_element_list = self.find_by_name(base_element_name)
for element in base_element_list:
paths.extend(self.determine_element_access_path(element))

return paths

def determine_element_access_path(self, base_element: Base) -> list[list[str]]:
"""Determine the path to access the respective value in the Aspect JSON object.

:param base_element: element for determine the path
:return: list of paths found to access the respective value.
"""
path: list[list[str]] = []
if isinstance(base_element, Property):
if hasattr(base_element, "payload_name") and base_element.payload_name is not None: # type: ignore
path.insert(0, [base_element.payload_name]) # type: ignore
else:
path.insert(0, [base_element.name])

return self.__determine_access_path(base_element, path)

def __determine_access_path(self, base_element: Base, path: list[list[str]]) -> list[list[str]]:
"""Determine access path.

:param base_element: element for determine the path
:return: list of paths found to access the respective value.
"""
if base_element is None or base_element.parent_elements is None or len(base_element.parent_elements) == 0:
return path

# in case of multiple parent get the number of additional parents and
# clone the existing paths
path.extend(path[0] for _ in range(len(base_element.parent_elements) - 1))

for index, parent in enumerate(base_element.parent_elements):
if isinstance(parent, Property):
if hasattr(parent, "payload_name") and parent.payload_name is not None: # type: ignore
path_segment = parent.payload_name # type: ignore
else:
path_segment = parent.name

if (len(path[index]) > 0 and path[index][0] != path_segment) or len(path[0]) == 0:
path[index].insert(0, path_segment)

self.__determine_access_path(parent, path) # type: ignore

return path
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def create_all_graph_elements(self, create_nodes: list[Node]):
instance = self.create_element(node)
except Exception as error:
print(f"Could nod translate the node {node} to a Python object. Error: {error}")
raise error
else:
all_nodes.append(instance)

Expand Down
Loading
Loading