From d7b0555b1f06b87e7279ef0f43e91bd40940f413 Mon Sep 17 00:00:00 2001 From: Matthias Bodenbenner <m.bodenbenner@wzl-mq.rwth-aachen.de> Date: Wed, 17 Jan 2024 08:09:58 +0100 Subject: [PATCH] 9.1.0 - added license field to semantics --- README.md | 5 ++++- setup.py | 2 +- src/soil/component.py | 12 +++++++++++- src/soil/element.py | 4 ++++ src/soil/measurement.py | 2 ++ src/soil/semantics.py | 25 +++++++++++++++++++++++++ src/soil/stream.py | 3 ++- src/utils/constants.py | 12 +++++++++++- 8 files changed, 60 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d0ada77..b5322bb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [](https://git-ce.rwth-aachen.de/wzl-mq-ms/forschung-lehre/lava/unified-device-interface/python/commits/master) # Python Unified Device Interface -Current stable version: 9.0.1 +Current stable version: 9.1.0 ## Installation 1. Install the WZL-UDI package via pip @@ -58,6 +58,9 @@ Funded by the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) ## 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 - bug fix of semantic name resolution diff --git a/setup.py b/setup.py index d8f626c..dc65d01 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() setup(name='wzl-udi', - version='9.0.1', + version='9.1.0', url='https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python', project_urls={ "Bug Tracker": "https://git-ce.rwth-aachen.de/wzl-mq-public/soil/python/-/issues", diff --git a/src/soil/component.py b/src/soil/component.py index daca80d..4767d0f 100644 --- a/src/soil/component.py +++ b/src/soil/component.py @@ -19,7 +19,7 @@ from .error import ChildNotFoundException from .function import Function from .measurement import Measurement from .parameter import Parameter -from .semantics import Namespaces +from .semantics import Namespaces, Semantics from ..utils import root_logger from ..utils.constants import HTTP_GET from ..utils.error import SerialisationException, DeviceException, UserException @@ -399,12 +399,22 @@ class Component(Element): try: return super().resolve_semantic_path(suffix) except ChildNotFoundException: + # check if the path fits one of the components children for child in self.children: try: return child.resolve_semantic_path(suffix) except ChildNotFoundException: 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.') @property diff --git a/src/soil/element.py b/src/soil/element.py index cf0c30a..833b7db 100644 --- a/src/soil/element.py +++ b/src/soil/element.py @@ -6,6 +6,7 @@ from typing import Any, Dict, List import rdflib from .error import ChildNotFoundException +from .semantics import Namespaces, Semantics from ..utils.constants import BASE_UUID_PATTERN, HTTP_GET from ..utils.error import SerialisationException @@ -101,12 +102,15 @@ class Element(ABC): shape_filename = os.path.join(profiles_path, f"{self._profilename}.shacl.ttl") self._metadata_profile = rdflib.Graph() self._metadata_profile.parse(shape_filename) + self._metadata_profile.add((rdflib.URIRef(Semantics.namespace[self._profilename]), Namespaces.dcterms.license, + Semantics.profile_license)) # load metadata self._semantic_name = f'{parent_name}{self.uuid[4:].capitalize()}' metadata_filename = os.path.join(metadata_path, f"{self._semantic_name}.ttl") self._metadata = rdflib.Graph() self._metadata.parse(metadata_filename) + self._metadata.add((rdflib.URIRef(self._semantic_name), Namespaces.schema.license, Semantics.metadata_license)) @abstractmethod def serialize_semantics(self, kind: str) -> rdflib.Graph: diff --git a/src/soil/measurement.py b/src/soil/measurement.py index 217f46f..6624efa 100644 --- a/src/soil/measurement.py +++ b/src/soil/measurement.py @@ -197,6 +197,7 @@ class Measurement(Figure): data_graph.add((observation_subject, Namespaces.sosa.hasResult, 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.schema.license, Semantics.data_license)) # create result node unit_triples = list(self._metadata.triples((None, Namespaces.qudt.applicableUnit, None))) @@ -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.sosa.isResultOf, observation_subject)) 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)) diff --git a/src/soil/semantics.py b/src/soil/semantics.py index 00a59c6..7a3f801 100644 --- a/src/soil/semantics.py +++ b/src/soil/semantics.py @@ -1,19 +1,44 @@ +import re +import urllib + import rdflib +from ..utils.constants import URL_PATTERN + class Semantics(object): prefix: str = None url: str = 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]): Semantics.prefix = config['prefix'] Semantics.url = 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): + dcterms = rdflib.namespace.DCTERMS 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/') qudt = rdflib.Namespace('http://qudt.org/schema/qudt/') rdf = rdflib.namespace.RDF diff --git a/src/soil/stream.py b/src/soil/stream.py index 8675e01..0330d86 100644 --- a/src/soil/stream.py +++ b/src/soil/stream.py @@ -124,7 +124,7 @@ class Job(ABC): try: url, data = self._retrieve_semantic_metadata(model) measurement_subject = \ - list((data.subjects(predicate=Namespaces.rdf.type, object=Namespaces.soil.Measurement)))[0] + list((data.subjects(predicate=Namespaces.rdf.type, object=Namespaces.soil.Measurement)))[0] # replace value data.remove((None, Namespaces.qudt.value, None)) @@ -298,6 +298,7 @@ class StreamScheduler(object): # try to send semantic data package try: url, semantic_data = job.semantic_data(self._model) + url = url.replace('https://', '').replace('http://', '') if self._dataformat == 'json': message = semantic_data.serialize(format='json-ld') elif self._dataformat == 'xml': diff --git a/src/utils/constants.py b/src/utils/constants.py index 8ac340e..6865dd0 100644 --- a/src/utils/constants.py +++ b/src/utils/constants.py @@ -1,3 +1,13 @@ +import re + HTTP_GET = 0 HTTP_OPTIONS = 1 -BASE_UUID_PATTERN = r'[0-9A-Za-z-_]{3,}' \ No newline at end of file +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) -- GitLab