Skip to content

Commit

Permalink
Merge pull request #6 from eccenca/develop
Browse files Browse the repository at this point in the history
v1.0.0beta4
  • Loading branch information
muddymudskipper authored Jul 12, 2024
2 parents bbd1c38 + 6a91721 commit 4cc4586
Show file tree
Hide file tree
Showing 19 changed files with 172 additions and 119 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/)

## [1.0.0beta4] 2024-07-12

### Fixed

- fixed errors on CMEM instances with self-signed/invalid certificates

### Added

- valid OWL profiles can be read on the Reason plugin input instead of validating the ontology in the plugin

### Changed

- use DCMI Metadata Terms for provenance
- new icons

## [1.0.0beta3] 2024-07-09

Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ parameters to include inferred axiom generators:

Validate the input ontology against OWL profiles (DL, EL, QL, RL, and Full). The ontology is annotated in the output graph.

### Process valid OWL profiles from input

If enabled along with the "Validate OWL2 profiles" parameter, the list of valid profiles is taken from the plugin input,
without validating the ontology against the profiles in the plugin. The inputs need to include the entity paths "profile"
for the valid profiles, and "ontology" for the ontology IRI. If the "Validate OWL2 profiles" parameter is enabled in the
"Validate" plugin, it can be directly connected to the input of the "Reason" plugin.


### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.
Expand Down Expand Up @@ -138,7 +146,8 @@ Raise an error if inconsistencies are found. If enabled, the plugin does not out
### Validate OWL2 profiles

Validate the input ontology against OWL profiles (DL, EL, QL, RL, and Full). The valid profiles are added to the output
Markdown file and the ontology is annotated in the output graph. The plugin outputs the profiles using the path "profile".
Markdown file and the ontology is annotated in the output graph. The plugin outputs the profiles with path "profile",
and the ontology IRI with path "ontology".

### Maximum RAM Percentage

Expand Down
3 changes: 3 additions & 0 deletions cmem_plugin_reason/file-icons--owl.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions cmem_plugin_reason/fluent--brain-circuit-24-regular.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 48 additions & 10 deletions cmem_plugin_reason/plugin_reason.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Reasoning workflow plugin module"""

from collections.abc import Sequence
from datetime import UTC, datetime
from os import environ
from pathlib import Path
from tempfile import TemporaryDirectory
from time import time
Expand All @@ -9,6 +11,7 @@
from cmem.cmempy.dp.proxy.graph import get
from cmem_plugin_base.dataintegration.context import ExecutionContext
from cmem_plugin_base.dataintegration.description import Icon, Plugin, PluginParameter
from cmem_plugin_base.dataintegration.entity import Entities
from cmem_plugin_base.dataintegration.parameter.graph import GraphParameterType
from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin
from cmem_plugin_base.dataintegration.types import BoolParameterType, StringParameterType
Expand All @@ -26,16 +29,17 @@
get_provenance,
post_profiles,
post_provenance,
remove_temp,
robot,
send_result,
validate_profiles,
)

environ["SSL_VERIFY"] = "false"


@Plugin(
label="Reason",
icon=Icon(file_name="reason.png", package=__package__),
icon=Icon(file_name="fluent--brain-circuit-24-regular.svg", package=__package__),
description="Performs OWL reasoning.",
documentation="""A task performing OWL reasoning. With an OWL ontology and a data graph as input
the reasoning result is written to a specified graph. The following reasoners are supported:
Expand Down Expand Up @@ -162,6 +166,16 @@
description="",
default_value=False,
),
PluginParameter(
param_type=BoolParameterType(),
name="input_profiles",
label="Process valid OWL profiles from input",
description="""If the "validate OWL profiles" parameter is enabled, take values from the
input (paths "profile" and "ontology") instead of running the validation in the plugin.
""",
default_value=False,
advanced=True,
),
],
)
class ReasonPlugin(WorkflowPlugin):
Expand All @@ -188,6 +202,7 @@ def __init__( # noqa: PLR0913
sub_data_property: bool = False,
sub_object_property: bool = False,
validate_profile: bool = False,
input_profiles: bool = False,
max_ram_percentage: int = MAX_RAM_PERCENTAGE_DEFAULT,
) -> None:
self.axioms = {
Expand Down Expand Up @@ -244,6 +259,7 @@ def __init__( # noqa: PLR0913
self.output_graph_iri = output_graph_iri
self.reasoner = reasoner
self.validate_profile = validate_profile
self.input_profiles = input_profiles
self.max_ram_percentage = max_ram_percentage

def get_graphs(self, graphs: dict, context: ExecutionContext) -> None:
Expand Down Expand Up @@ -282,9 +298,8 @@ def reason(self, graphs: dict) -> None:
f"--language-annotation rdfs:comment "
f'"Reasoning result set of <{self.data_graph_iri}> and '
f'<{self.ontology_graph_iri}>" en '
f'--link-annotation prov:wasDerivedFrom "{self.data_graph_iri}" '
f"--link-annotation prov:wasDerivedFrom "
f'"{self.ontology_graph_iri}" '
f'--link-annotation dc:source "{self.data_graph_iri}" '
f'--link-annotation dc:source "{self.ontology_graph_iri}" '
f'--typed-annotation dc:created "{utctime}" xsd:dateTime '
f'--output "{self.temp}/result.ttl"'
)
Expand All @@ -296,8 +311,32 @@ def reason(self, graphs: dict) -> None:
raise OSError(response.stderr.decode())
raise OSError("ROBOT error")

def _execute(self, context: ExecutionContext) -> None:
def post_valid_profiles(self, inputs: Sequence[Entities], graphs: dict) -> None:
"""Post valid profiles. Optionally get valid profiles from input."""
if self.input_profiles:
values = next(inputs[0].entities).values
paths = [p.path for p in inputs[0].schema.paths]
validated_ontology = values[paths.index("ontology")][0]
valid_profiles = values[paths.index("profile")]
if validated_ontology != self.ontology_graph_iri:
raise ValueError(
"The ontology IRI validated with Validate differs from the input ontology IRI."
)
else:
valid_profiles = validate_profiles(self, graphs)
post_profiles(self, valid_profiles)

def _execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None:
"""`Execute plugin"""
if self.input_profiles:
if not inputs:
raise OSError(
'Input entities needed if "Process valid OWL profiles from input" is enabled'
)
paths = [p.path for p in inputs[0].schema.paths]
if "profile" not in paths or "ontology" not in paths:
raise ValueError("Invalid input for processing OWL profiles")

setup_cmempy_user_access(context.user)
graphs = get_graphs_tree((self.data_graph_iri, self.ontology_graph_iri))
self.get_graphs(graphs, context)
Expand All @@ -306,11 +345,10 @@ def _execute(self, context: ExecutionContext) -> None:
setup_cmempy_user_access(context.user)
send_result(self.output_graph_iri, Path(self.temp) / "result.ttl")
if self.validate_profile:
post_profiles(self, validate_profiles(self, graphs))
self.post_valid_profiles(inputs, graphs)
post_provenance(self, get_provenance(self, context))
remove_temp(self)

def execute(self, inputs: tuple, context: ExecutionContext) -> None: # noqa: ARG002
def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None:
"""Remove temp files on error"""
with TemporaryDirectory() as self.temp:
self._execute(context)
self._execute(inputs, context)
13 changes: 8 additions & 5 deletions cmem_plugin_reason/plugin_validate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Ontology consistency validation workflow plugin module"""

from datetime import UTC, datetime
from os import environ
from pathlib import Path
from tempfile import TemporaryDirectory
from time import time
Expand Down Expand Up @@ -38,16 +39,18 @@
validate_profiles,
)

environ["SSL_VERIFY"] = "false"


@Plugin(
label="Validate",
label="Validate OWL consistency",
description="Validates the consistency of an OWL ontology.",
documentation="""A task validating the consistency of an OWL ontology and generating an
explanation if inconsistencies are found. The explanation can be written to the project as a
Markdown file and/or to a specified graph. The Markdown string is also provided as an output
entity using the path "text". The following reasoners are supported: ELK, Expression
Materializing Reasoner, HermiT, JFact, Structural Reasoner and Whelk.""",
icon=Icon(package=__package__, file_name="validate.png"),
icon=Icon(file_name="file-icons--owl.svg", package=__package__),
parameters=[
REASONER_PARAMETER,
ONTOLOGY_GRAPH_IRI_PARAMETER,
Expand Down Expand Up @@ -156,7 +159,7 @@ def explain(self, graphs: dict) -> None:
f'--language-annotation rdfs:label "Ontology Validation Result {utctime}" en '
f"--language-annotation rdfs:comment "
f'"Ontology validation of <{self.ontology_graph_iri}>" en '
f'--link-annotation prov:wasDerivedFrom "{self.ontology_graph_iri}" '
f'--link-annotation dc:source "{self.ontology_graph_iri}" '
f'--typed-annotation dc:created "{utctime}" xsd:dateTime '
f'--output "{self.temp}/output.ttl"'
)
Expand Down Expand Up @@ -191,8 +194,8 @@ def add_profiles(self, valid_profiles: list) -> list:

def make_entities(self, text: str, valid_profiles: list) -> Entities:
"""Make entities"""
values = [[text]]
paths = [EntityPath(path="markdown")]
values = [[text], [self.ontology_graph_iri]]
paths = [EntityPath(path="markdown"), EntityPath(path="ontology")]
if self.validate_profile:
values.append(valid_profiles)
paths.append(EntityPath(path="profile"))
Expand Down
Binary file removed cmem_plugin_reason/reason.png
Binary file not shown.
27 changes: 3 additions & 24 deletions cmem_plugin_reason/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"""Common constants and functions"""

import json
import re
import shlex
import unicodedata
from collections import OrderedDict
from pathlib import Path
from secrets import token_hex
from shutil import rmtree
from subprocess import CompletedProcess, run
from xml.etree.ElementTree import Element, SubElement, tostring

Expand Down Expand Up @@ -71,16 +68,6 @@
)


def convert_iri_to_filename(value: str) -> str:
"""Convert IRI to filename"""
value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
value = re.sub(r"\.", "_", value.lower())
value = re.sub(r"/", "_", value.lower())
value = re.sub(r"[^\w\s-]", "", value.lower())
value = re.sub(r"[-\s]+", "-", value).strip("-_")
return value + ".nt"


def create_xml_catalog_file(dir_: str, graphs: dict) -> None:
"""Create XML catalog file"""
file_name = Path(dir_) / "catalog-v001.xml"
Expand All @@ -103,12 +90,12 @@ def get_graphs_tree(graph_iris: tuple) -> dict:
graphs = {}
for graph_iri in graph_iris:
if graph_iri not in graphs:
graphs[graph_iri] = convert_iri_to_filename(graph_iri)
graphs[graph_iri] = f"{token_hex(8)}.nt"
tree = get_graph_import_tree(graph_iri)
for value in tree["tree"].values():
for iri in value:
if iri not in graphs:
graphs[iri] = convert_iri_to_filename(iri)
graphs[iri] = f"{token_hex(8)}.nt"
return graphs


Expand All @@ -122,14 +109,6 @@ def send_result(iri: str, filepath: Path) -> None:
)


def remove_temp(plugin: WorkflowPlugin) -> None:
"""Remove temporary files"""
try:
rmtree(plugin.temp)
except (OSError, FileNotFoundError) as err:
plugin.log.warning(f"Cannot remove directory {plugin.temp} ({err})")


def post_provenance(plugin: WorkflowPlugin, prov: dict | None) -> None:
"""Post provenance"""
if not prov:
Expand All @@ -141,7 +120,7 @@ def post_provenance(plugin: WorkflowPlugin, prov: dict | None) -> None:
insert_query = f"""
INSERT DATA {{
GRAPH <{plugin.output_graph_iri}> {{
<{plugin.output_graph_iri}> <http://www.w3.org/ns/prov#wasGeneratedBy>
<{plugin.output_graph_iri}> <http://purl.org/dc/terms/creator>
<{prov["plugin_iri"]}> .
<{prov["plugin_iri"]}> a <{prov["plugin_type"]}>,
<https://vocab.eccenca.com/di/CustomTask> .
Expand Down
Binary file removed cmem_plugin_reason/validate.png
Binary file not shown.
Loading

0 comments on commit 4cc4586

Please sign in to comment.