Skip to content
Snippets Groups Projects
Commit d7b0555b authored by Matthias Stefan Bodenbenner's avatar Matthias Stefan Bodenbenner
Browse files

9.1.0 - added license field to semantics

parent 76a523ce
Branches
Tags 9.1.0
No related merge requests found
Pipeline #348047 passed
[![Build](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/badges/master/pipeline.svg)](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/commits/master) [![Build](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/badges/master/pipeline.svg)](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/commits/master)
# Python Unified Device Interface # Python Unified Device Interface
Current stable version: 9.0.1 Current stable version: 9.1.0
## Installation ## Installation
1. Install the WZL-UDI package via pip 1. Install the WZL-UDI package via pip
...@@ -58,6 +58,9 @@ Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) ...@@ -58,6 +58,9 @@ Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation)
## Recent changes ## Recent changes
**9.1.0** - 2024-01-17
- the license for profiles, metadata and data is now provided anc can be specified in the config file
**9.0.1** - 2024-01-11 **9.0.1** - 2024-01-11
- bug fix of semantic name resolution - bug fix of semantic name resolution
......
...@@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: ...@@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read() long_description = fh.read()
setup(name='wzl-udi', setup(name='wzl-udi',
version='9.0.1', version='9.1.0',
url='https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python', url='https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python',
project_urls={ project_urls={
"Bug Tracker": "https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python/-/issues", "Bug Tracker": "https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python/-/issues",
......
...@@ -19,7 +19,7 @@ from .error import ChildNotFoundException ...@@ -19,7 +19,7 @@ from .error import ChildNotFoundException
from .function import Function from .function import Function
from .measurement import Measurement from .measurement import Measurement
from .parameter import Parameter from .parameter import Parameter
from .semantics import Namespaces from .semantics import Namespaces, Semantics
from ..utils import root_logger from ..utils import root_logger
from ..utils.constants import HTTP_GET from ..utils.constants import HTTP_GET
from ..utils.error import SerialisationException, DeviceException, UserException from ..utils.error import SerialisationException, DeviceException, UserException
...@@ -399,12 +399,22 @@ class Component(Element): ...@@ -399,12 +399,22 @@ class Component(Element):
try: try:
return super().resolve_semantic_path(suffix) return super().resolve_semantic_path(suffix)
except ChildNotFoundException: except ChildNotFoundException:
# check if the path fits one of the components children
for child in self.children: for child in self.children:
try: try:
return child.resolve_semantic_path(suffix) return child.resolve_semantic_path(suffix)
except ChildNotFoundException: except ChildNotFoundException:
continue continue
# check if the profile of this component imports a shape which matches the path
imported_profiles = list(self._metadata_profile.objects(predicate=Namespaces.owl.imports))
for imported_profile in imported_profiles:
if imported_profile.toPython().replace(Semantics.namespace, '') == suffix:
# TODO implement loading and returning of the base components profile
# (is not present as element, so might require adaption of the method signature,
# might also be called recursively, if the base component has own bases which are queried)
raise ChildNotFoundException('Profiles of base components can currently not be returned.')
raise ChildNotFoundException('Could not resolve the semantic path.') raise ChildNotFoundException('Could not resolve the semantic path.')
@property @property
......
...@@ -6,6 +6,7 @@ from typing import Any, Dict, List ...@@ -6,6 +6,7 @@ from typing import Any, Dict, List
import rdflib import rdflib
from .error import ChildNotFoundException from .error import ChildNotFoundException
from .semantics import Namespaces, Semantics
from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET
from ..utils.error import SerialisationException from ..utils.error import SerialisationException
...@@ -101,12 +102,15 @@ class Element(ABC): ...@@ -101,12 +102,15 @@ class Element(ABC):
shape_filename = os.path.join(profiles_path, f"{self._profilename}.shacl.ttl") shape_filename = os.path.join(profiles_path, f"{self._profilename}.shacl.ttl")
self._metadata_profile = rdflib.Graph() self._metadata_profile = rdflib.Graph()
self._metadata_profile.parse(shape_filename) self._metadata_profile.parse(shape_filename)
self._metadata_profile.add((rdflib.URIRef(Semantics.namespace[self._profilename]), Namespaces.dcterms.license,
Semantics.profile_license))
# load metadata # load metadata
self._semantic_name = f'{parent_name}{self.uuid[4:].capitalize()}' self._semantic_name = f'{parent_name}{self.uuid[4:].capitalize()}'
metadata_filename = os.path.join(metadata_path, f"{self._semantic_name}.ttl") metadata_filename = os.path.join(metadata_path, f"{self._semantic_name}.ttl")
self._metadata = rdflib.Graph() self._metadata = rdflib.Graph()
self._metadata.parse(metadata_filename) self._metadata.parse(metadata_filename)
self._metadata.add((rdflib.URIRef(self._semantic_name), Namespaces.schema.license, Semantics.metadata_license))
@abstractmethod @abstractmethod
def serialize_semantics(self, kind: str) -> rdflib.Graph: def serialize_semantics(self, kind: str) -> rdflib.Graph:
......
...@@ -197,6 +197,7 @@ class Measurement(Figure): ...@@ -197,6 +197,7 @@ class Measurement(Figure):
data_graph.add((observation_subject, Namespaces.sosa.hasResult, data_graph.add((observation_subject, Namespaces.sosa.hasResult,
Semantics.namespace[f'{self._semantic_name}Measurement'])) Semantics.namespace[f'{self._semantic_name}Measurement']))
data_graph.add((observation_subject, Namespaces.sosa.madeBySensor, sensor_triples[0][2])) data_graph.add((observation_subject, Namespaces.sosa.madeBySensor, sensor_triples[0][2]))
data_graph.add((observation_subject, Namespaces.schema.license, Semantics.data_license))
# create result node # create result node
unit_triples = list(self._metadata.triples((None, Namespaces.qudt.applicableUnit, None))) unit_triples = list(self._metadata.triples((None, Namespaces.qudt.applicableUnit, None)))
...@@ -207,6 +208,7 @@ class Measurement(Figure): ...@@ -207,6 +208,7 @@ class Measurement(Figure):
data_graph.add((measurement_subject, Namespaces.rdf.type, rdflib.URIRef(Namespaces.soil.Measurement))) data_graph.add((measurement_subject, Namespaces.rdf.type, rdflib.URIRef(Namespaces.soil.Measurement)))
data_graph.add((measurement_subject, Namespaces.sosa.isResultOf, observation_subject)) data_graph.add((measurement_subject, Namespaces.sosa.isResultOf, observation_subject))
data_graph.add((measurement_subject, Namespaces.qudt.unit, unit_triples[0][2])) data_graph.add((measurement_subject, Namespaces.qudt.unit, unit_triples[0][2]))
data_graph.add((measurement_subject, Namespaces.schema.license, Semantics.data_license))
rdf_value = Figure.serialize_value(data_graph, self.__getitem__('value', 0)) rdf_value = Figure.serialize_value(data_graph, self.__getitem__('value', 0))
......
import re
import urllib
import rdflib import rdflib
from ..utils.constants import URL_PATTERN
class Semantics(object): class Semantics(object):
prefix: str = None prefix: str = None
url: str = None url: str = None
namespace: rdflib.Namespace = None namespace: rdflib.Namespace = None
profile_license: rdflib.term.Identifier = rdflib.URIRef("https://spdx.org/licenses/CC-BY-4.0.html")
metadata_license: rdflib.term.Identifier = rdflib.URIRef("https://spdx.org/licenses/CC-BY-NC-ND-4.0.html")
data_license: rdflib.term.Identifier = rdflib.Literal("All rights reserved.")
def __init__(self, config: dict[str, str]): def __init__(self, config: dict[str, str]):
Semantics.prefix = config['prefix'] Semantics.prefix = config['prefix']
Semantics.url = config['url'] Semantics.url = config['url']
Semantics.namespace = rdflib.Namespace(config['url']) Semantics.namespace = rdflib.Namespace(config['url'])
if 'profile-license' in config:
if re.match(URL_PATTERN, config['profile-license']):
Semantics.profile_license = rdflib.URIRef(config['profile-license'])
else:
Semantics.profile_license = rdflib.Literal(config['profile-license'])
if 'metadata-license' in config:
if re.match(URL_PATTERN, config['metadata-license']):
Semantics.metadata_license = rdflib.URIRef(config['metadata-license'])
else:
Semantics.metadata_license = rdflib.Literal(config['metadata-license'])
if 'data-license' in config:
if re.match(URL_PATTERN, config['data-license']):
Semantics.data_license = rdflib.URIRef(config['data-license'])
else:
Semantics.data_license = rdflib.Literal(config['data-license'])
class Namespaces(object): class Namespaces(object):
dcterms = rdflib.namespace.DCTERMS
m4i = rdflib.Namespace('http://w3id.org/nfdi4ing/metadata4ing#') m4i = rdflib.Namespace('http://w3id.org/nfdi4ing/metadata4ing#')
owl = rdflib.Namespace('http://www.w3.org/2002/07/owl#')
quantitykind = rdflib.Namespace('http://qudt.org/vocab/quantitykind/') quantitykind = rdflib.Namespace('http://qudt.org/vocab/quantitykind/')
qudt = rdflib.Namespace('http://qudt.org/schema/qudt/') qudt = rdflib.Namespace('http://qudt.org/schema/qudt/')
rdf = rdflib.namespace.RDF rdf = rdflib.namespace.RDF
......
...@@ -298,6 +298,7 @@ class StreamScheduler(object): ...@@ -298,6 +298,7 @@ class StreamScheduler(object):
# try to send semantic data package # try to send semantic data package
try: try:
url, semantic_data = job.semantic_data(self._model) url, semantic_data = job.semantic_data(self._model)
url = url.replace('https://', '').replace('http://', '')
if self._dataformat == 'json': if self._dataformat == 'json':
message = semantic_data.serialize(format='json-ld') message = semantic_data.serialize(format='json-ld')
elif self._dataformat == 'xml': elif self._dataformat == 'xml':
......
import re
HTTP_GET = 0 HTTP_GET = 0
HTTP_OPTIONS = 1 HTTP_OPTIONS = 1
BASE_UUID_PATTERN = r'[0-9A-Za-z-_]{3,}' BASE_UUID_PATTERN = r'[0-9A-Za-z-_]{3,}'
URL_PATTERN = re.compile(
r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment