From 557c5fdd70ca457501ce777d592a69239b02f03a Mon Sep 17 00:00:00 2001 From: "Hanna Shalamitskaya (EPAM)" Date: Tue, 21 May 2024 17:17:10 +0500 Subject: [PATCH 1/2] Reuse JAVA SDK models for testing --- .../scripts/download_test_models.py | 67 ++++++++++++++++ .../tests/integration/java_models/__init__.py | 0 .../java_models/test_loading_aspects.py | 78 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 core/esmf-aspect-meta-model-python/scripts/download_test_models.py create mode 100644 core/esmf-aspect-meta-model-python/tests/integration/java_models/__init__.py create mode 100644 core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py diff --git a/core/esmf-aspect-meta-model-python/scripts/download_test_models.py b/core/esmf-aspect-meta-model-python/scripts/download_test_models.py new file mode 100644 index 0000000..883495f --- /dev/null +++ b/core/esmf-aspect-meta-model-python/scripts/download_test_models.py @@ -0,0 +1,67 @@ +# Copyright (c) 2024 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 + +from os import mkdir, remove +from os.path import exists, join +from pathlib import Path +from zipfile import ZipFile + +import requests + + +FOLDER_TO_EXTRACT = "valid" +TEST_MODELS_PATH = join("tests", "integration", "java_models", "resources") +VERSION = "2.7.0" + + +def get_model_files_path(version: str) -> str: + """Get a path for storing test models.""" + base_path = Path(__file__).parents[1].absolute() + models_path = join(base_path, TEST_MODELS_PATH, f"esmf-test-aspect-models-{version}.jar") + + return models_path + + +def download_test_models(version: str = VERSION): + """Downloads and extract the esmf-test-aspect-models.""" + model_files_path = get_model_files_path(version) + + print(f"Start downloading esmf-test-aspect-models version {version}") + url = ( + f"https://repo1.maven.org/maven2/org/eclipse/esmf/esmf-test-aspect-models/{version}/" + f"esmf-test-aspect-models-{version}.jar" + ) + response = requests.get(url, allow_redirects=True) + + resource_folder = Path(model_files_path).parent.absolute() + if not exists(resource_folder): + mkdir(resource_folder) + + with open(model_files_path, "wb") as f: + f.write(response.content) + print("JAR-File Downloaded") + + print(f"Start extracting files from {model_files_path}") + extracted_file_path = Path(model_files_path).parents[0].absolute() + archive = ZipFile(model_files_path) + for file_name in archive.namelist(): + if file_name.startswith(FOLDER_TO_EXTRACT): + archive.extract(file_name, extracted_file_path) + + archive.close() + print("Done extracting files.") + + print("Deleting esmf-test-aspect-models JAR file.") + remove(model_files_path) + + +if __name__ == "__main__": + download_test_models() diff --git a/core/esmf-aspect-meta-model-python/tests/integration/java_models/__init__.py b/core/esmf-aspect-meta-model-python/tests/integration/java_models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py b/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py new file mode 100644 index 0000000..fbdc0fb --- /dev/null +++ b/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py @@ -0,0 +1,78 @@ +"""Collect statistics about loading test Aspect models.""" + +import csv + +from glob import glob +from os.path import join +from pathlib import Path + +from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader + +SAMM_VERSION = "2.0.0" + + +def get_test_files(): + """Get ttl models for testing.""" + base_path = Path(__file__).parent.absolute() + samm_folder_name = f"samm_{SAMM_VERSION.replace('.', '_')}" + search_pattern = join(base_path, "resources", "**", samm_folder_name, "**", "*.ttl") + test_model_files = glob(search_pattern, recursive=True) + + return test_model_files + + +def load_test_models(): + """Test for loading Aspect models.""" + test_files = get_test_files() + result = [] + all_test_files = len(test_files) + i = 0 + step = 10 + print("Loading test Aspect models...") + + for test_file in test_files: + i += 1 + if i % step == 0: + print(f"{i}/{all_test_files}") + + test_file_path = Path(test_file) + data = { + "file_name": test_file_path.name, + "folder_name": join(test_file_path.parents[1].name, test_file_path.parents[0].name), + "status": "initializing", + "error": None, + } + + try: + loader = AspectLoader() + model_elements = loader.load_aspect_model(test_file) + if not model_elements: + raise Exception("No elements loaded") + except Exception as error: + data["error"] = str(error) + data["status"] = "exception" + else: + data["status"] = "success" + + result.append(data) + + print(f"{i}/{all_test_files}") + return result + + +def run_test_loading(): + """Run loading of all test Aspect models.""" + report = load_test_models() + + base_path = Path(__file__).parent.absolute() + with open(join(base_path, "loading_models_test_report.csv"), "w", newline="") as csvfile: + fieldnames = ["folder_name", "file_name", "status", "error"] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + writer.writeheader() + for row in report: + writer.writerow(row) + + +if __name__ == "__main__": + run_test_loading() From 8deaad413ffc3e3b0a0573d1d4c92231f353c269 Mon Sep 17 00:00:00 2001 From: "Hanna Shalamitskaya (EPAM)" Date: Wed, 5 Jun 2024 12:13:58 +0500 Subject: [PATCH 2/2] WIP: Fix PR comments --- AUTHORS.md | 7 +- CONVENTIONS.md | 3 +- core/esmf-aspect-meta-model-python/README.md | 196 ++++++++++++++---- .../loader/aspect_loader.py | 96 ++------- .../loader/model_element_factory.py | 1 + .../loader/samm_graph.py | 79 ++++++- .../resolver/meta_model.py | 23 +- .../pyproject.toml | 5 +- .../scripts/constants.py | 36 ++++ .../scripts/download_samm_cli.py | 15 +- .../scripts/download_test_models.py | 75 ++++--- .../samm}/__init__.py | 0 .../samm}/download_samm_branch.py | 2 +- .../samm}/download_samm_release.py | 0 .../samm}/github_file.py | 0 .../samm}/github_folder.py | 2 +- .../java_models/test_loading_aspects.py | 32 ++- .../integration/test_aspect_functionality.py | 37 ++-- .../tests/integration/test_general.py | 24 ++- .../tests/unit/loader/test_aspect_loader.py | 181 ++++------------ .../tests/unit/loader/test_samm_graph.py | 128 ++++++++++++ 21 files changed, 601 insertions(+), 341 deletions(-) create mode 100644 core/esmf-aspect-meta-model-python/scripts/constants.py rename core/esmf-aspect-meta-model-python/{esmf_aspect_meta_model_python/samm_aspect_meta_model => scripts/samm}/__init__.py (100%) rename core/esmf-aspect-meta-model-python/{esmf_aspect_meta_model_python/samm_aspect_meta_model => scripts/samm}/download_samm_branch.py (94%) rename core/esmf-aspect-meta-model-python/{esmf_aspect_meta_model_python/samm_aspect_meta_model => scripts/samm}/download_samm_release.py (100%) rename core/esmf-aspect-meta-model-python/{esmf_aspect_meta_model_python/samm_aspect_meta_model => scripts/samm}/github_file.py (100%) rename core/esmf-aspect-meta-model-python/{esmf_aspect_meta_model_python/samm_aspect_meta_model => scripts/samm}/github_folder.py (95%) diff --git a/AUTHORS.md b/AUTHORS.md index 5df4bd1..a9fac52 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,10 +2,11 @@ The following people have contributed to this repository: -* [Georg Schmidt-Dumont](georg.schmidt-dumont@de.bosch.com) -* [Nico Makowe](mailto:nico.makowe@de.bosch.com) -* [Aghyad Farrouh](mailto:aghyad.farrouh@systecs.com) +* [Hanna Shalamitskaya](mailto:external.Hanna.Shalamitskaya@de.bosch.com) * [Andreas Textor](mailto:andreas.textor@de.bosch.com) +* [Georg Schmidt-Dumont](mailto:georg.schmidt-dumont@de.bosch.com) * [Michele Santoro](mailto:michele.santoro@de.bosch.com) +* Nico Makowe +* Aghyad Farrouh Please add yourself to this list, if you contribute to the content. diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 676bb9c..b799cfb 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -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 diff --git a/core/esmf-aspect-meta-model-python/README.md b/core/esmf-aspect-meta-model-python/README.md index 73ea992..2694bc6 100644 --- a/core/esmf-aspect-meta-model-python/README.md +++ b/core/esmf-aspect-meta-model-python/README.md @@ -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). + +* [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) + + # 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: +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 @@ -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). diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/aspect_loader.py b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/aspect_loader.py index 7d65392..a6ecfad 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/aspect_loader.py +++ b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/aspect_loader.py @@ -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 @@ -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. @@ -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. @@ -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 diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/model_element_factory.py b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/model_element_factory.py index e134ccd..d3bf139 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/model_element_factory.py +++ b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/model_element_factory.py @@ -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) diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/samm_graph.py b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/samm_graph.py index 40149bf..d97e334 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/samm_graph.py +++ b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/loader/samm_graph.py @@ -10,11 +10,13 @@ # SPDX-License-Identifier: MPL-2.0 from pathlib import Path -from typing import List, Union +from typing import List, Optional, Union from rdflib import RDF, Graph, URIRef from rdflib.graph import Node +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.model_element_factory import ModelElementFactory from esmf_aspect_meta_model_python.resolver.base import AspectModelResolver, BaseResolver @@ -109,7 +111,7 @@ def get_nodes_from_graph(self, model_file_path: str = "") -> List[Node]: if not nodes: for subject, object in base_graph.subject_objects(predicate=RDF.type, unique=True): - prefix_data = str(object)[1:-1].split(":") + prefix_data = str(object).replace("<", "").split(":") if ":".join(prefix_data[:3]) == self.samm_prefix: nodes.append(subject) @@ -137,3 +139,76 @@ def to_python(self, aspect_urn: URIRef | str = "") -> List[URIRef | None]: aspect_elements = model_element_factory.create_all_graph_elements(base_nodes) return aspect_elements + + 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 diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/resolver/meta_model.py b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/resolver/meta_model.py index 4d0a4ce..693d3fe 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/resolver/meta_model.py +++ b/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/resolver/meta_model.py @@ -17,6 +17,8 @@ from rdflib import Graph +from scripts.samm.download_samm_release import main as download_samm_release + class BaseMetaModelResolver(ABC): """Interface for meta-model resolver class.""" @@ -38,16 +40,30 @@ class AspectMetaModelResolver(BaseMetaModelResolver): def __init__(self, base_path: str = ""): self._base_path = base_path if base_path else str(Path(__file__).parents[2]) - def get_samm_files(self, meta_model_version: str) -> List[str]: + def _get_samm_files_path(self, meta_model_version: str) -> List[str]: """Collect all SAMM files. - :param meta_model_version: Meta model version + :param meta_model_version: meta-model version + :return: List of all path to SAMM files for the given meta-model version """ path_template = join(self._base_path, self.samm_folder_path, "**", meta_model_version, "*.ttl") samm_files = [samm_file for samm_file in glob(path_template, recursive=True)] return samm_files + def get_samm_files(self, meta_model_version: str) -> List[str]: + """Check and collect paths to SAMM files. + + :param meta_model_version: meta-model version + :return: List of all path to SAMM files for the given meta-model version + """ + samm_files = self._get_samm_files_path(meta_model_version) + if not samm_files: + download_samm_release() + samm_files = self._get_samm_files_path(meta_model_version) + + return samm_files + @staticmethod def validate_file(file_path: str): """Validate a SAMM file. @@ -73,7 +89,6 @@ def parse(self, aspect_graph: Graph, meta_model_version: str): :param aspect_graph: RDF Graph :param meta_model_version: version of the meta-model to extract the right SAMM turtle files """ - samm_files = self.get_samm_files(meta_model_version) - for file_path in samm_files: + for file_path in self.get_samm_files(meta_model_version): self.validate_file(file_path) aspect_graph.parse(file_path, format="turtle") diff --git a/core/esmf-aspect-meta-model-python/pyproject.toml b/core/esmf-aspect-meta-model-python/pyproject.toml index bbac1fe..6ada2f6 100644 --- a/core/esmf-aspect-meta-model-python/pyproject.toml +++ b/core/esmf-aspect-meta-model-python/pyproject.toml @@ -47,9 +47,10 @@ types-requests = "^2.30.0.0" cache_dir = ".pytest_cache" [tool.poetry.scripts] -download-samm-release = "esmf_aspect_meta_model_python.samm_aspect_meta_model.download_samm_release:main" -download-samm-branch = "esmf_aspect_meta_model_python.samm_aspect_meta_model.download_samm_branch:main" +download-samm-release = "scripts.samm.download_samm_release:main" +download-samm-branch = "scripts.samm.download_samm_branch:main" download-samm-cli = "scripts.download_samm_cli:download_samm_cli" +download-test-models = "scripts.download_test_models:download_test_models" [build-system] requires = ["poetry>=0.12"] diff --git a/core/esmf-aspect-meta-model-python/scripts/constants.py b/core/esmf-aspect-meta-model-python/scripts/constants.py new file mode 100644 index 0000000..7039582 --- /dev/null +++ b/core/esmf-aspect-meta-model-python/scripts/constants.py @@ -0,0 +1,36 @@ +# Copyright (c) 2024 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 + +from os.path import join +from string import Template + + +SAMM_VERSION = "2.1.0" +JAVA_CLI_VERSION = "2.7.0" + + +class SAMMCliConstants: + BASE_PATH = Template("https://github.com/eclipse-esmf/esmf-sdk/releases/download/v$version_number/$file_name") + JAVA_CLI_VERSION = JAVA_CLI_VERSION + LINUX_FILE_NAME = Template("samm-cli-$version_number-linux-x86_64.tar.gz") + SAMM_VERSION = SAMM_VERSION + WIN_FILE_NAME = Template("samm-cli-$version_number-windows-x86_64.zip") + + +class TestModelConstants: + FOLDER_TO_EXTRACT = "valid" + JAVA_CLI_VERSION = JAVA_CLI_VERSION + MAVEN_URL = Template( + "https://repo1.maven.org/maven2/org/eclipse/esmf/esmf-test-aspect-models/$version_number/" + "esmf-test-aspect-models-$version_number.jar" + ) + SAMM_VERSION = SAMM_VERSION + TEST_MODELS_PATH = join("tests", "integration", "java_models", "resources") diff --git a/core/esmf-aspect-meta-model-python/scripts/download_samm_cli.py b/core/esmf-aspect-meta-model-python/scripts/download_samm_cli.py index f22a1b2..033e4e5 100644 --- a/core/esmf-aspect-meta-model-python/scripts/download_samm_cli.py +++ b/core/esmf-aspect-meta-model-python/scripts/download_samm_cli.py @@ -12,24 +12,19 @@ import sys import zipfile -from string import Template - -BASE_PATH = Template("https://github.com/eclipse-esmf/esmf-sdk/releases/download/v$version_number/$file_name") -LINUX_FILE_NAME = Template("samm-cli-$version_number-linux-x86_64.tar.gz") -SAMM_CLI_VERSION = "2.6.1" -WIN_FILE_NAME = Template("samm-cli-$version_number-windows-x86_64.zip") +from scripts.constants import SAMMCliConstants as Const def get_samm_cli_file_name(): """Get a SAMM CLI file name for the current platform.""" if platform.system() == "Windows": - file_name = WIN_FILE_NAME.substitute(version_number=SAMM_CLI_VERSION) + file_name = Const.WIN_FILE_NAME.substitute(version_number=Const.VERSION) elif platform.system() == "Linux": - file_name = LINUX_FILE_NAME.substitute(version_number=SAMM_CLI_VERSION) + file_name = Const.LINUX_FILE_NAME.substitute(version_number=Const.VERSION) else: raise NotImplementedError( - f"Please download a SAMM CLI manually for your operation system from '{BASE_PATH}'" + f"Please download a SAMM CLI manually for your operation system from '{Const.BASE_PATH}'" ) return file_name @@ -67,7 +62,7 @@ def download_samm_cli(): print(error) else: print(f"Start downloading SAMM CLI {samm_cli_file_name}") - url = BASE_PATH.substitute(version_number=SAMM_CLI_VERSION, file_name=samm_cli_file_name) + url = Const.BASE_PATH.substitute(version_number=Const.VERSION, file_name=samm_cli_file_name) dir_path = Path(__file__).resolve().parents[1] archive_file = os.path.join(dir_path, samm_cli_file_name) diff --git a/core/esmf-aspect-meta-model-python/scripts/download_test_models.py b/core/esmf-aspect-meta-model-python/scripts/download_test_models.py index 883495f..6b7d55a 100644 --- a/core/esmf-aspect-meta-model-python/scripts/download_test_models.py +++ b/core/esmf-aspect-meta-model-python/scripts/download_test_models.py @@ -9,58 +9,75 @@ # # SPDX-License-Identifier: MPL-2.0 -from os import mkdir, remove +import shutil + +from os import listdir, mkdir, remove from os.path import exists, join from pathlib import Path from zipfile import ZipFile import requests - -FOLDER_TO_EXTRACT = "valid" -TEST_MODELS_PATH = join("tests", "integration", "java_models", "resources") -VERSION = "2.7.0" +from scripts.constants import TestModelConstants as Const -def get_model_files_path(version: str) -> str: +def get_resources_folder_path() -> str: """Get a path for storing test models.""" base_path = Path(__file__).parents[1].absolute() - models_path = join(base_path, TEST_MODELS_PATH, f"esmf-test-aspect-models-{version}.jar") + models_path = join(base_path, Const.TEST_MODELS_PATH) return models_path -def download_test_models(version: str = VERSION): - """Downloads and extract the esmf-test-aspect-models.""" - model_files_path = get_model_files_path(version) +def clear_folder(): + """Remove all files to clear test models directory.""" + resources_folder = get_resources_folder_path() - print(f"Start downloading esmf-test-aspect-models version {version}") - url = ( - f"https://repo1.maven.org/maven2/org/eclipse/esmf/esmf-test-aspect-models/{version}/" - f"esmf-test-aspect-models-{version}.jar" - ) - response = requests.get(url, allow_redirects=True) + if exists(resources_folder) or len(listdir(resources_folder)) != 0: + shutil.rmtree(resources_folder) - resource_folder = Path(model_files_path).parent.absolute() - if not exists(resource_folder): - mkdir(resource_folder) + mkdir(resources_folder) - with open(model_files_path, "wb") as f: + +def download_jar_file(jar_file_path: str): + """Download JAR with test models.""" + url = Const.MAVEN_URL.substitute(version_number=Const.JAVA_CLI_VERSION) + response = requests.get(url, allow_redirects=True) + + with open(jar_file_path, "wb") as f: f.write(response.content) - print("JAR-File Downloaded") - print(f"Start extracting files from {model_files_path}") - extracted_file_path = Path(model_files_path).parents[0].absolute() - archive = ZipFile(model_files_path) + +def extract_test_models(resources_folder: str, jar_file_path: str): + """Unzip and extract test models.""" + archive = ZipFile(jar_file_path) for file_name in archive.namelist(): - if file_name.startswith(FOLDER_TO_EXTRACT): - archive.extract(file_name, extracted_file_path) + if file_name.startswith(Const.FOLDER_TO_EXTRACT): + archive.extract(file_name, resources_folder) archive.close() - print("Done extracting files.") - print("Deleting esmf-test-aspect-models JAR file.") - remove(model_files_path) + +def download_test_models(version: str = Const.JAVA_CLI_VERSION): + """Downloads and extract the esmf-test-aspect-models.""" + print("Start a script to download the JAVA test Aspect Models") + resources_folder = get_resources_folder_path() + jar_file_name = f"esmf-test-aspect-models-{version}.jar" + jar_file_path = join(resources_folder, jar_file_name) + + print(f"Remove previous version of test models from the folder {resources_folder}") + clear_folder() + + print(f"Start downloading esmf-test-aspect-models version {version}") + download_jar_file(jar_file_path) + print("JAR-File Downloaded") + + print(f"Start extracting files from {jar_file_name} to the folder {resources_folder}") + extract_test_models(resources_folder, jar_file_path) + print("Done extracting files") + + print("Deleting esmf-test-aspect-models JAR file") + remove(jar_file_path) if __name__ == "__main__": diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/__init__.py b/core/esmf-aspect-meta-model-python/scripts/samm/__init__.py similarity index 100% rename from core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/__init__.py rename to core/esmf-aspect-meta-model-python/scripts/samm/__init__.py diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/download_samm_branch.py b/core/esmf-aspect-meta-model-python/scripts/samm/download_samm_branch.py similarity index 94% rename from core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/download_samm_branch.py rename to core/esmf-aspect-meta-model-python/scripts/samm/download_samm_branch.py index e8e468a..4d87240 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/download_samm_branch.py +++ b/core/esmf-aspect-meta-model-python/scripts/samm/download_samm_branch.py @@ -17,7 +17,7 @@ import requests -from esmf_aspect_meta_model_python.samm_aspect_meta_model.github_folder import GitFolder, decoding_url_response +from scripts.samm.github_folder import GitFolder, decoding_url_response def main(): diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/download_samm_release.py b/core/esmf-aspect-meta-model-python/scripts/samm/download_samm_release.py similarity index 100% rename from core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/download_samm_release.py rename to core/esmf-aspect-meta-model-python/scripts/samm/download_samm_release.py diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/github_file.py b/core/esmf-aspect-meta-model-python/scripts/samm/github_file.py similarity index 100% rename from core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/github_file.py rename to core/esmf-aspect-meta-model-python/scripts/samm/github_file.py diff --git a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/github_folder.py b/core/esmf-aspect-meta-model-python/scripts/samm/github_folder.py similarity index 95% rename from core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/github_folder.py rename to core/esmf-aspect-meta-model-python/scripts/samm/github_folder.py index 8dedda0..83f8057 100644 --- a/core/esmf-aspect-meta-model-python/esmf_aspect_meta_model_python/samm_aspect_meta_model/github_folder.py +++ b/core/esmf-aspect-meta-model-python/scripts/samm/github_folder.py @@ -16,7 +16,7 @@ import requests -from esmf_aspect_meta_model_python.samm_aspect_meta_model.github_file import GitFile +from scripts.samm.github_file import GitFile class GitFolder(GitFile): diff --git a/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py b/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py index fbdc0fb..3c55da0 100644 --- a/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py +++ b/core/esmf-aspect-meta-model-python/tests/integration/java_models/test_loading_aspects.py @@ -3,19 +3,36 @@ import csv from glob import glob -from os.path import join +from os import listdir +from os.path import exists, join from pathlib import Path from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader +from scripts.constants import TestModelConstants +from scripts.download_test_models import download_test_models -SAMM_VERSION = "2.0.0" + +def get_resources_folder_path() -> str: + """Get a path for storing test models.""" + base_path = Path(__file__).parents[3].absolute() + models_path = join(base_path, TestModelConstants.TEST_MODELS_PATH) + + return models_path + + +def check_resources_folder(test_models_exists: bool = False): + """Remove all files to clear test models directory.""" + resources_folder = get_resources_folder_path() + + if not exists(resources_folder) or len(listdir(resources_folder)) == 0 or not test_models_exists: + download_test_models() def get_test_files(): """Get ttl models for testing.""" - base_path = Path(__file__).parent.absolute() - samm_folder_name = f"samm_{SAMM_VERSION.replace('.', '_')}" - search_pattern = join(base_path, "resources", "**", samm_folder_name, "**", "*.ttl") + resources_folder = get_resources_folder_path() + samm_folder_name = f"samm_{TestModelConstants.SAMM_VERSION.replace('.', '_')}" + search_pattern = join(resources_folder, "**", samm_folder_name, "**", "*.ttl") test_model_files = glob(search_pattern, recursive=True) return test_model_files @@ -24,6 +41,10 @@ def get_test_files(): def load_test_models(): """Test for loading Aspect models.""" test_files = get_test_files() + if not test_files: + check_resources_folder() + test_files = get_test_files() + result = [] all_test_files = len(test_files) i = 0 @@ -57,6 +78,7 @@ def load_test_models(): result.append(data) print(f"{i}/{all_test_files}") + return result diff --git a/core/esmf-aspect-meta-model-python/tests/integration/test_aspect_functionality.py b/core/esmf-aspect-meta-model-python/tests/integration/test_aspect_functionality.py index 466641b..8e33b3b 100644 --- a/core/esmf-aspect-meta-model-python/tests/integration/test_aspect_functionality.py +++ b/core/esmf-aspect-meta-model-python/tests/integration/test_aspect_functionality.py @@ -22,17 +22,18 @@ def test_get_access_path(): aspect_loader = AspectLoader() model_elements = aspect_loader.load_aspect_model(file_path) aspect = model_elements[0] - path = aspect_loader.determine_element_access_path(aspect.properties[2].data_type.properties[2]) # type: ignore + graph = aspect_loader.get_graph() + path = graph.determine_element_access_path(aspect.properties[2].data_type.properties[2]) # type: ignore assert path[0][0] == "position" assert path[0][1] == "z" - path = aspect_loader.determine_access_path("y") + path = graph.determine_access_path("y") assert path[0][0] == "position" assert path[0][1] == "y" - path = aspect_loader.determine_access_path("x") + path = graph.determine_access_path("x") assert path[0][0] == "position" assert path[0][1] == "x" @@ -43,11 +44,12 @@ def test_get_access_path_input_property(): aspect_loader = AspectLoader() model_elements = aspect_loader.load_aspect_model(file_path) aspect = model_elements[0] - path = aspect_loader.determine_element_access_path(aspect.operations[0].input_properties[0]) + graph = aspect_loader.get_graph() + path = graph.determine_element_access_path(aspect.operations[0].input_properties[0]) assert path[0][0] == "input" - path = aspect_loader.determine_element_access_path(aspect.operations[1].input_properties[0]) + path = graph.determine_element_access_path(aspect.operations[1].input_properties[0]) assert path[0][0] == "input" @@ -56,8 +58,9 @@ def test_find_properties_by_name() -> None: file_path = RESOURCE_PATH / "AspectWithProperties.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) + graph = aspect_loader.get_graph() - result = aspect_loader.find_by_name("testPropertyOne") + result = graph.find_by_name("testPropertyOne") assert result is not None assert len(result) == 1 assert isinstance(result[0], BaseImpl) @@ -67,7 +70,7 @@ def test_find_properties_by_name() -> None: assert len(result[0].see) == 0 assert len(result[0].descriptions) == 0 - result = aspect_loader.find_by_name("testPropertyTwo") + result = graph.find_by_name("testPropertyTwo") assert result is not None assert len(result) == 1 assert isinstance(result[0], BaseImpl) @@ -77,7 +80,7 @@ def test_find_properties_by_name() -> None: assert len(result[0].see) == 0 assert len(result[0].descriptions) == 0 - result = aspect_loader.find_by_name("Unknown") + result = graph.find_by_name("Unknown") assert len(result) == 0 @@ -85,7 +88,9 @@ def test_find_property_chaticaristic_by_name() -> None: file_path = RESOURCE_PATH / "AspectWithPropertyWithAllBaseAttributes.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) - result = aspect_loader.find_by_name("BooleanTestCharacteristic") + graph = aspect_loader.get_graph() + result = graph.find_by_name("BooleanTestCharacteristic") + assert result is not None assert len(result) == 1 assert isinstance(result[0], BaseImpl) @@ -100,7 +105,9 @@ def test_find_properties_by_urn() -> None: file_path = RESOURCE_PATH / "AspectWithProperties.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) - element = aspect_loader.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyOne") + graph = aspect_loader.get_graph() + element = graph.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyOne") + assert element is not None assert isinstance(element, BaseImpl) assert element.name == "testPropertyOne" @@ -109,7 +116,7 @@ def test_find_properties_by_urn() -> None: assert len(element.see) == 0 assert len(element.descriptions) == 0 - element = aspect_loader.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyTwo") + element = graph.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyTwo") assert element is not None assert isinstance(element, BaseImpl) assert element.name == "testPropertyTwo" @@ -118,7 +125,7 @@ def test_find_properties_by_urn() -> None: assert len(element.see) == 0 assert len(element.descriptions) == 0 - element = aspect_loader.find_by_urn("Unknown") + element = graph.find_by_urn("Unknown") assert element is None @@ -126,7 +133,9 @@ def test_find_property_chaticaristic_by_urn() -> None: file_path = RESOURCE_PATH / "AspectWithPropertyWithAllBaseAttributes.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) - element = aspect_loader.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#BooleanTestCharacteristic") + graph = aspect_loader.get_graph() + element = graph.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#BooleanTestCharacteristic") + assert element is not None assert isinstance(element, BaseImpl) assert element.name == "BooleanTestCharacteristic" @@ -135,5 +144,5 @@ def test_find_property_chaticaristic_by_urn() -> None: assert len(element.see) == 0 assert len(element.descriptions) == 0 - element = aspect_loader.find_by_urn("Unknown") + element = graph.find_by_urn("Unknown") assert element is None diff --git a/core/esmf-aspect-meta-model-python/tests/integration/test_general.py b/core/esmf-aspect-meta-model-python/tests/integration/test_general.py index bb820a6..35a052a 100644 --- a/core/esmf-aspect-meta-model-python/tests/integration/test_general.py +++ b/core/esmf-aspect-meta-model-python/tests/integration/test_general.py @@ -282,8 +282,9 @@ def test_find_properties_by_name() -> None: file_path = RESOURCE_PATH / "AspectWithProperties.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) + graph = aspect_loader.get_graph() - result = aspect_loader.find_by_name("testPropertyOne") + result = graph.find_by_name("testPropertyOne") assert result is not None assert len(result) == 1 assert isinstance(result[0], BaseImpl) @@ -293,7 +294,7 @@ def test_find_properties_by_name() -> None: assert len(result[0].see) == 0 assert len(result[0].descriptions) == 0 - result = aspect_loader.find_by_name("testPropertyTwo") + result = graph.find_by_name("testPropertyTwo") assert result is not None assert len(result) == 1 assert isinstance(result[0], BaseImpl) @@ -303,7 +304,7 @@ def test_find_properties_by_name() -> None: assert len(result[0].see) == 0 assert len(result[0].descriptions) == 0 - result = aspect_loader.find_by_name("Unknown") + result = graph.find_by_name("Unknown") assert len(result) == 0 @@ -311,7 +312,9 @@ def test_find_property_characteristic_by_name() -> None: file_path = RESOURCE_PATH / "AspectWithPropertyWithAllBaseAttributes.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) - result = aspect_loader.find_by_name("BooleanTestCharacteristic") + graph = aspect_loader.get_graph() + + result = graph.find_by_name("BooleanTestCharacteristic") assert result is not None assert len(result) == 1 assert isinstance(result[0], BaseImpl) @@ -326,8 +329,9 @@ def test_find_properties_by_urn() -> None: file_path = RESOURCE_PATH / "AspectWithProperties.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) + graph = aspect_loader.get_graph() - result = aspect_loader.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyOne") + result = graph.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyOne") assert result is not None assert isinstance(result, BaseImpl) assert result.name == "testPropertyOne" @@ -336,7 +340,7 @@ def test_find_properties_by_urn() -> None: assert len(result.see) == 0 assert len(result.descriptions) == 0 - result = aspect_loader.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyTwo") + result = graph.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#testPropertyTwo") assert result is not None assert isinstance(result, BaseImpl) assert result.name == "testPropertyTwo" @@ -345,7 +349,7 @@ def test_find_properties_by_urn() -> None: assert len(result.see) == 0 assert len(result.descriptions) == 0 - result = aspect_loader.find_by_urn("Unknown") + result = graph.find_by_urn("Unknown") assert result is None @@ -353,7 +357,9 @@ def test_find_property_characteristic_by_urn() -> None: file_path = RESOURCE_PATH / "AspectWithPropertyWithAllBaseAttributes.ttl" aspect_loader = AspectLoader() aspect_loader.load_aspect_model(file_path) - result = aspect_loader.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#BooleanTestCharacteristic") + graph = aspect_loader.get_graph() + result = graph.find_by_urn("urn:samm:org.eclipse.esmf.test.general:2.0.0#BooleanTestCharacteristic") + assert result is not None assert isinstance(result, BaseImpl) assert result.name == "BooleanTestCharacteristic" @@ -362,7 +368,7 @@ def test_find_property_characteristic_by_urn() -> None: assert len(result.see) == 0 assert len(result.descriptions) == 0 - result = aspect_loader.find_by_urn("Unknown") + result = graph.find_by_urn("Unknown") assert result is None diff --git a/core/esmf-aspect-meta-model-python/tests/unit/loader/test_aspect_loader.py b/core/esmf-aspect-meta-model-python/tests/unit/loader/test_aspect_loader.py index da96fa5..c7091d7 100644 --- a/core/esmf-aspect-meta-model-python/tests/unit/loader/test_aspect_loader.py +++ b/core/esmf-aspect-meta-model-python/tests/unit/loader/test_aspect_loader.py @@ -1,6 +1,5 @@ """Aspect model Loader test suite.""" -from pathlib import Path from unittest import mock from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader @@ -9,8 +8,12 @@ class TestAspectLoader: """Aspect model Loader test suite.""" - def test_init(self): - result = AspectLoader("graph", "cache") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.SAMMGraph") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.DefaultElementCache") + def test_init(self, default_element_cache_mock, samm_graph_mock): + default_element_cache_mock.return_value = "cache" + samm_graph_mock.return_value = "graph" + result = AspectLoader() assert result._cache == "cache" assert result._graph == "graph" @@ -25,159 +28,67 @@ def test_init_with_defaults(self, default_element_cache_mock, samm_graph_mock): assert result._cache == "cache" assert result._graph == "graph" default_element_cache_mock.assert_called_once() - samm_graph_mock.assert_called_once_with(cache="cache") + samm_graph_mock.assert_called_once_with() - def test_get_graph(self): - loader = AspectLoader("graph", "cache") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.SAMMGraph") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.DefaultElementCache") + def test_get_graph(self, default_element_cache_mock, samm_graph_mock): + default_element_cache_mock.return_value = "cache" + samm_graph_mock.return_value = "graph" + loader = AspectLoader() result = loader.get_graph() assert result == "graph" - def test_get_samm_version(self): + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.SAMMGraph") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.DefaultElementCache") + def test_get_samm_version(self, default_element_cache_mock, samm_graph_mock): + default_element_cache_mock.return_value = "cache" graph_mock = mock.MagicMock(name="graph") graph_mock.get_samm_version.return_value = "samm_version" - loader = AspectLoader(graph_mock, "cache") + samm_graph_mock.return_value = graph_mock + loader = AspectLoader() result = loader.get_samm_version() assert result == "samm_version" graph_mock.get_samm_version.assert_called_once() + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.Path") @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.isinstance") - def test_prepare_file_path(self, isinstance_mock): + def test_prepare_file_path(self, isinstance_mock, path_mock): isinstance_mock.return_value = True - file_path = Path("file_path") - result = AspectLoader.convert_file_path(file_path) + file_path_mock = mock.MagicMock(name="file_path") + file_path_mock.exists.return_value = True + path_mock.return_value = file_path_mock + result = AspectLoader.convert_file_path("file_path") assert result == "file_path" - isinstance_mock.assert_called_once_with(file_path, Path) + isinstance_mock.assert_called_once_with("file_path", path_mock) + path_mock.assert_called_once_with("file_path") + file_path_mock.exists.assert_called_once() - def test_load_aspect_model(self): + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.AspectLoader._reset_graph") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.AspectLoader.convert_file_path") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.SAMMGraph") + @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.DefaultElementCache") + def test_load_aspect_model( + self, + default_element_cache_mock, + samm_graph_mock, + convert_file_path_mock, + reset_graph_mock, + ): + default_element_cache_mock.return_value = "cache" graph_mock = mock.MagicMock(name="graph") graph_mock.parse.return_value = "graph" graph_mock.to_python.return_value = "loaded_aspect_model" - loader = AspectLoader(graph_mock, "cache") + samm_graph_mock.return_value = graph_mock + convert_file_path_mock.return_value = "converted_file_path" + loader = AspectLoader() result = loader.load_aspect_model("file_path") assert result == "loaded_aspect_model" - graph_mock.parse.assert_called_once_with("file_path") + convert_file_path_mock.assert_called_once_with("file_path") + reset_graph_mock.assert_called_once() + graph_mock.parse.assert_called_once_with("converted_file_path") graph_mock.to_python.assert_called_once() - - def test_find_by_name(self): - cache_mock = mock.MagicMock(name="cache") - cache_mock.get_by_name.return_value = "graph_node" - loader = AspectLoader("graph", cache=cache_mock) - result = loader.find_by_name("element_name") - - assert result == "graph_node" - cache_mock.get_by_name.assert_called_once_with("element_name") - - def test_find_by_urn(self): - cache_mock = mock.MagicMock(name="cache") - cache_mock.get_by_urn.return_value = "graph_node" - loader = AspectLoader("graph", cache_mock) - result = loader.find_by_urn("urn") - - assert result == "graph_node" - cache_mock.get_by_urn.assert_called_once_with("urn") - - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.AspectLoader.determine_element_access_path") - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.AspectLoader.find_by_name") - def test_determine_access_path(self, find_by_name_mock, determine_element_access_path_mock): - find_by_name_mock.side_effect = (["base_element"], []) - determine_element_access_path_mock.return_value = ["access_path"] - loader = AspectLoader("graph", "cache") - result = loader.determine_access_path("base_element_name") - - assert result == ["access_path"] - find_by_name_mock.assert_called_once_with("base_element_name") - determine_element_access_path_mock.assert_called_once_with("base_element") - - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.AspectLoader._AspectLoader__determine_access_path") - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.Property") - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.isinstance") - def test_determine_element_access_path_with_payload_name( - self, - isinstance_mock, - property_mock, - determine_access_path_mock, - ): - isinstance_mock.return_value = True - base_element_mock = mock.MagicMock(name="base_element") - base_element_mock.payload_name = "payload_name" - determine_access_path_mock.return_value = "element_access_path" - loader = AspectLoader("graph", "cache") - result = loader.determine_element_access_path(base_element_mock) - - assert result == "element_access_path" - isinstance_mock.assert_called_once_with(base_element_mock, property_mock) - determine_access_path_mock.assert_called_once_with(base_element_mock, [["payload_name"]]) - - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.AspectLoader._AspectLoader__determine_access_path") - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.Property") - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.isinstance") - def test_determine_element_access_path_base_element_name( - self, - isinstance_mock, - property_mock, - determine_access_path_mock, - ): - isinstance_mock.return_value = True - base_element_mock = mock.MagicMock(name="base_element") - base_element_mock.name = "base_element_name" - base_element_mock.payload_name = None - determine_access_path_mock.return_value = "element_access_path" - loader = AspectLoader("graph", "cache") - result = loader.determine_element_access_path(base_element_mock) - - assert result == "element_access_path" - isinstance_mock.assert_called_once_with(base_element_mock, property_mock) - determine_access_path_mock.assert_called_once_with(base_element_mock, [["base_element_name"]]) - - def test_determine_access_path_base_element_is_none(self): - loader = AspectLoader("graph", "cache") - result = loader._AspectLoader__determine_access_path(None, "path") - - assert result == "path" - - def test_determine_access_path_parent_element_is_none(self): - base_element_mock = mock.MagicMock(name="base_element") - base_element_mock.parent_elements = None - loader = AspectLoader("graph", "cache") - result = loader._AspectLoader__determine_access_path(base_element_mock, "path") - - assert result == "path" - - def test_determine_access_path_parent_element_is_empty_list(self): - base_element_mock = mock.MagicMock(name="base_element") - base_element_mock.parent_elements = [] - loader = AspectLoader("graph", "cache") - result = loader._AspectLoader__determine_access_path(base_element_mock, "path") - - assert result == "path" - - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.isinstance") - def test_private_determine_access_path_parent_payload_name(self, isinstance_mock): - parent_element_mock = mock.MagicMock(name="parent_element") - parent_element_mock.parent_elements = [] - parent_element_mock.payload_name = "payload_name" - base_element_mock = mock.MagicMock(name="base_element") - base_element_mock.parent_elements = [parent_element_mock] - isinstance_mock.return_value = True - loader = AspectLoader("graph", "cache") - result = loader._AspectLoader__determine_access_path(base_element_mock, [["path"]]) - - assert result == [["payload_name", "path"]] - - @mock.patch("esmf_aspect_meta_model_python.loader.aspect_loader.isinstance") - def test_private_determine_access_path_parent_name(self, isinstance_mock): - parent_element_mock = mock.MagicMock(name="parent_element") - parent_element_mock.parent_elements = [] - parent_element_mock.payload_name = None - parent_element_mock.name = "payload_element_name" - base_element_mock = mock.MagicMock(name="base_element") - base_element_mock.parent_elements = [parent_element_mock] - isinstance_mock.return_value = True - loader = AspectLoader("graph", "cache") - result = loader._AspectLoader__determine_access_path(base_element_mock, [["path"]]) - - assert result == [["payload_element_name", "path"]] diff --git a/core/esmf-aspect-meta-model-python/tests/unit/loader/test_samm_graph.py b/core/esmf-aspect-meta-model-python/tests/unit/loader/test_samm_graph.py index 3b48324..41d1e37 100644 --- a/core/esmf-aspect-meta-model-python/tests/unit/loader/test_samm_graph.py +++ b/core/esmf-aspect-meta-model-python/tests/unit/loader/test_samm_graph.py @@ -161,3 +161,131 @@ def test_to_python(self, get_base_nodes_mock, model_element_factory_mock): get_base_nodes_mock.assert_called_once_with("aspect_urn") model_element_factory_mock.assert_called_once_with("samm_version", "graph", "cache") model_element_factory_mock.create_all_graph_elements.assert_called_once_with("base_nodes") + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.DefaultElementCache") + def test_find_by_name(self, default_element_cache_mock): + cache_mock = mock.MagicMock(name="cache") + cache_mock.get_by_name.return_value = "graph_node" + default_element_cache_mock.return_value = cache_mock + samm_graph = SAMMGraph("graph", "resolver") + result = samm_graph.find_by_name("element_name") + + assert result == "graph_node" + cache_mock.get_by_name.assert_called_once_with("element_name") + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.DefaultElementCache") + def test_find_by_urn(self, default_element_cache_mock): + cache_mock = mock.MagicMock(name="cache") + cache_mock.get_by_urn.return_value = "graph_node" + default_element_cache_mock.return_value = cache_mock + samm_graph = SAMMGraph("graph", "resolver") + result = samm_graph.find_by_urn("urn") + + assert result == "graph_node" + cache_mock.get_by_urn.assert_called_once_with("urn") + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.SAMMGraph.determine_element_access_path") + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.SAMMGraph.find_by_name") + def test_determine_access_path( + self, + find_by_name_mock, + determine_element_access_path_mock, + ): + find_by_name_mock.side_effect = (["base_element"], []) + determine_element_access_path_mock.return_value = ["access_path"] + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph.determine_access_path("base_element_name") + + assert result == ["access_path"] + find_by_name_mock.assert_called_once_with("base_element_name") + determine_element_access_path_mock.assert_called_once_with("base_element") + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.SAMMGraph._SAMMGraph__determine_access_path") + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.Property") + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.isinstance") + def test_determine_element_access_path_with_payload_name( + self, + isinstance_mock, + property_mock, + determine_access_path_mock, + ): + isinstance_mock.return_value = True + base_element_mock = mock.MagicMock(name="base_element") + base_element_mock.payload_name = "payload_name" + determine_access_path_mock.return_value = "element_access_path" + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph.determine_element_access_path(base_element_mock) + + assert result == "element_access_path" + isinstance_mock.assert_called_once_with(base_element_mock, property_mock) + determine_access_path_mock.assert_called_once_with(base_element_mock, [["payload_name"]]) + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.SAMMGraph._SAMMGraph__determine_access_path") + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.Property") + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.isinstance") + def test_determine_element_access_path_base_element_name( + self, + isinstance_mock, + property_mock, + determine_access_path_mock, + ): + isinstance_mock.return_value = True + base_element_mock = mock.MagicMock(name="base_element") + base_element_mock.name = "base_element_name" + base_element_mock.payload_name = None + determine_access_path_mock.return_value = "element_access_path" + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph.determine_element_access_path(base_element_mock) + + assert result == "element_access_path" + isinstance_mock.assert_called_once_with(base_element_mock, property_mock) + determine_access_path_mock.assert_called_once_with(base_element_mock, [["base_element_name"]]) + + def test_determine_access_path_base_element_is_none(self): + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph._SAMMGraph__determine_access_path(None, "path") + + assert result == "path" + + def test_determine_access_path_parent_element_is_none(self): + base_element_mock = mock.MagicMock(name="base_element") + base_element_mock.parent_elements = None + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph._SAMMGraph__determine_access_path(base_element_mock, "path") + + assert result == "path" + + def test_determine_access_path_parent_element_is_empty_list(self): + base_element_mock = mock.MagicMock(name="base_element") + base_element_mock.parent_elements = [] + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph._SAMMGraph__determine_access_path(base_element_mock, "path") + + assert result == "path" + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.isinstance") + def test_private_determine_access_path_parent_payload_name(self, isinstance_mock): + parent_element_mock = mock.MagicMock(name="parent_element") + parent_element_mock.parent_elements = [] + parent_element_mock.payload_name = "payload_name" + base_element_mock = mock.MagicMock(name="base_element") + base_element_mock.parent_elements = [parent_element_mock] + isinstance_mock.return_value = True + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph._SAMMGraph__determine_access_path(base_element_mock, [["path"]]) + + assert result == [["payload_name", "path"]] + + @mock.patch("esmf_aspect_meta_model_python.loader.samm_graph.isinstance") + def test_private_determine_access_path_parent_name(self, isinstance_mock): + parent_element_mock = mock.MagicMock(name="parent_element") + parent_element_mock.parent_elements = [] + parent_element_mock.payload_name = None + parent_element_mock.name = "payload_element_name" + base_element_mock = mock.MagicMock(name="base_element") + base_element_mock.parent_elements = [parent_element_mock] + isinstance_mock.return_value = True + samm_graph = SAMMGraph("graph", "resolver", "cache") + result = samm_graph._SAMMGraph__determine_access_path(base_element_mock, [["path"]]) + + assert result == [["payload_element_name", "path"]]