ESMF SDK Python Aspect Model Loader

The Python SDK offers functionality which helps software developers to work with Aspect Models in their Python applications.

This guide gives an overview of the components in the Python SDK and shows how to use them.

Getting Started

Prerequisites

  • Python Version 3.10 or higher. Check your version with

    python --version
  • In order to include the packages a Python dependency manager is required; we recommend Python Poetry. The remainder of the guide assumes usage of Poetry. Please refer to the Poetry installation guide. You can check your installed Poetry version with

    poetry --version

Aspect Model Loader for Python

Introduction

Aspect Models are stored as RDF Graphs in .ttl (RDF Turtle) files. The Aspect Model Loader for Python reads one or more Turtle file and parses the Aspect Model. The result is a Python object representing the root Aspect of the Aspect Model. The Aspect has references to all of its children (e.g., Properties and Operations).

Installation

If you want to work with Aspect Models in your Python solution you should install the Aspect Model Loader for Python and include it into your project.

Installation from package Repository

PyPI

Currently not available

GitHub Releases

To use GitHub release as dependency using Poetry, you need to add it like this:

[tool.poetry.dependencies]
esmf-aspect-meta-model-python = { git = "https://github.com/eclipse-esmf/esmf-sdk-py-aspect-model-loader.git", tag = "1.0.0" }

for more information on depend on a library located in a git: git dependencies

Installation with local package

The Python package is an archive with the file ending .tar.gz. If you are working with Poetry, you can easily import that package by adding the local reference to your pyproject.toml

[tool.poetry.dependencies]
esmf-aspect-meta-model-python = { path = "path/to/esmf-aspect-meta-model-python-x.y.z.tar.gz" }

To make Poetry recognize your changes, run

poetry update

Loading an Aspect Model

Note: The examples below will be using the Movement.ttl Aspect model.

Loading an Aspect Model from one *.ttl file

Import the Aspect Model Loader in your Python module

from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader

Then create an instance of the AspectLoader and run the method load_aspect_model() from the AspectLoader with

from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader

loader = AspectLoader()
aspect = loader.load_aspect_model(PATH_TO_TURTLE_FILE)

where the input argument PATH_TO_TURTLE_FILE can either be a Path object or a string representing a path to the ttl file.

Both, relative paths and absolute paths are allowed.

The return value of load_aspect_model() is an instance of the class DefaultAspect which is declared in the project.

Loading an Aspect Model from multiple files

If the Aspect Model is separated into multiple .ttl files you can load the Aspect Model by calling the method load_aspect_model_from_multiple_files with a list of file paths.

from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader

loader = AspectLoader()
aspect = loader.load_aspect_model_from_multiple_files([
    "file1.ttl",
    "file2.ttl",
    "file3.ttl",
])

It may happen that the multiple files contain multiple aspect definitions and not only one. In this case it is possible to pass the URN of the Aspect as a hint, so the Loader knows which Aspect to load.

from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader

loader = AspectLoader()
aspect_urn = "urn:samm:org.eclipse.esmf.samm:test:1.0.0#myAspect"
aspect = loader.load_aspect_model_from_multiple_files(
    ["file1.ttl", "file2.ttl", "file3.ttl"],
    aspect_urn,
)

The urn can either be a string or an instance of rdflib.URIRef. If no urn is passed and the .ttl files contain multiple Aspects, the Aspect Loader will load the first one that is found.

Traversing the Aspect Model

The attributes of an Aspect can be accessed with like this:

name = aspect.name
urn = aspect.urn
preferred_names = aspect.preferred_names
descriptions = aspect.descriptions
meta_model_version = aspect.meta_model_version
see = aspect.see

properties = aspect.properties
operations = aspect.operations
events = aspect.events

Implementation of the OpenAPI specification

The Aspect Models Editor provides easy ways to generate an example for an interface via Export JSON functions. Based on its structure, you can prepare either a server to send data, or a client to receive via the API.

{
  "isMoving": true,
  "position": {
    "altitude": 153,
    "latitude": 9.1781,
    "longitude": 48.80835
  },
  "speed": 0,
  "speedLimitWarning": "green"
}

A simple example of the server

import random


def generate_random_float():
    """Generate a random float value."""
    return round(random.random(), random.randint(0, 5))

def send_movement_value():
    """A simple snippet to generate Movement data."""
    traffic_lights = ["green", "yellow", "red"]
    movement = {
        "isMoving": "true" if random.randint(0, 1) else "false",
        "position": {
            "altitude": generate_random_float(),
            "latitude": generate_random_float(),
            "longitude": generate_random_float()
        },
        "speed": generate_random_float(),
        "speedLimitWarning": traffic_lights[random.randint(0, len(traffic_lights) - 1)]
    }

    return movement

Consumer Example

import json
import requests

def get_movement(url, method="get"):
    """Get a movement."""
    response = requests.request(method, url)

    if response.status_code != 200:
        raise Exception(response.text)
    else:
        movement = json.loads(response.text)

        return movement

Example of the class for Movement Aspect Model

import json
import requests

from esmf_aspect_meta_model_python.loader.aspect_loader import AspectLoader

loader = AspectLoader()

class MovementAspect:
    def __init__(self, path_to_turtle_file):
        self._ttl_file_path = path_to_turtle_file
        self._aspect = loader.load_aspect_model(self._ttl_file_path)
        self._movement = None

        self.name = None
        self.urn = None
        self.preferred_names = None
        self.descriptions = None
        self.meta_model_version = None
        self.see = None
        self.properties = None
        self.operations = None
        self.events = None


        self._init_aspect()

    def _init_aspect(self):
        self.name = self._aspect.name
        self.urn = self._aspect.urn
        self.preferred_names = self._aspect.preferred_names
        self.descriptions = self._aspect.descriptions
        self.meta_model_version = self._aspect.meta_model_version
        self.see = self._aspect.see

        self.properties = self._aspect.properties
        self.operations = self._aspect.operations
        self.events = self._aspect.events

        self._movement = self._get_current_value()

    @staticmethod
    def _get_current_value():
        response = requests.request("get", "url_to_movement_API")

        if response.status_code != 200:
            raise Exception(response.text)
        else:
            movement = json.loads(response.text)

            return movement

    def refresh_data(self):
        self._movement = self._get_current_value()

    @property
    def is_moving(self):
        return self._movement["isMoving"]

    @property
    def position(self):
        return self._movement["position"]

    @property
    def speed(self):
        return self._movement["speed"]

    @property
    def speed_limit_warning(self):
        return self._movement["speedLimitWarning"]


# Class usage
movement = MovementAspect("path_to_turtle_file")
# Get a movement values
print(movement.is_moving)
print(movement.position)
print(movement.speed)
print(movement.speed_limit_warning)
# Show static aspect data
print(movement.name)
print(movement.urn)
print(movement.preferred_names)
print(movement.descriptions)
print(movement.meta_model_version)
print(movement.see)
print(movement.properties)
print(movement.operations)
print(movement.events)

Note that the attributes on Aspect Model objects are read-only.

SAMM Aspect Meta Model in Python

Introduction

The SAMM Aspect Meta Model is defined by multiple Turtle files in the public ESMF GitHub Repository. The project is developed in Java and the releases are published as JAR files.

Python applications that work with Aspect Models and RDF may need the SAMM as a Python package. Therefore, the project SAMM Aspect Meta Model for Python was created. It is set up to extract the RDF Turtle files from the released SAMM artifact or its Github repository and pack them into a Python project.

If you are not sure whether you need the SAMM Aspect Meta Model as a dependency you probably don’t need it because it does not contain any Python functionality. It is only intended for working with Aspect Models on RDF level.

Installation

The package is released on PyPI under the name esmf-samm-aspect-meta-model. The package can be imported to a Python project by adding the package as a dependency.

If you are using Poetry as a dependency manager you can execute the following commands:

poetry add samm-aspect-meta-model
poetry install

The pyproject.toml file of your project should then include the following:

[tool.poetry.dependencies]
samm-aspect-meta-model = "^x.y.z"

In the future it is planned to publish all packages of the Python SDK on public repositories. The authentication will then not be required anymore.