Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.0.0beta3 #5

Merged
merged 16 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ 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.0beta3] 2024-07-09

### Fixed

- temporary files are now removed when an error occurs

### Added

- parameter for validating the input ontology against OWL2 profiles (DL, EL, QL, RL, and Full)
- Validate plugin outputs valid profiles with path "profile"

### Changed

- Validate plugin outputs the Markdown result with path "markdown"

## [1.0.0beta2] 2024-07-04

### Fixed
Expand All @@ -27,7 +43,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

- complete validation for IRI parameters
- remove "Annnotate inferred subclass axioms" parameter in Reason plugin
- new icons

## [1.0.0alpha3] 2024-06-28

Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ parameters to include inferred axiom generators:
- ObjectPropertyRange
- ObjectPropertyDomain

### Validate OWL2 profiles

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

### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.
Expand All @@ -90,7 +94,7 @@ Maximum heap size for the Java virtual machine in the DI container running the r

# Validate

The plugin outputs the explanation as text in Markdown format using the path "text".
The plugin outputs the explanation as text in Markdown format using the path "markdown".

## Options

Expand Down Expand Up @@ -129,9 +133,13 @@ The filename of the Markdown file with the explanation of inconsistencies.
:warning: Existing files will be overwritten.

### Stop at inconsistencies

Raise an error if inconsistencies are found. If enabled, the plugin does not output entities.

### 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".

### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.
Expand Down
33 changes: 21 additions & 12 deletions cmem_plugin_reason/plugin_reason.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Reasoning workflow plugin module"""

import shlex
from datetime import UTC, datetime
from pathlib import Path
from subprocess import run
from tempfile import TemporaryDirectory
from time import time
from uuid import uuid4

import validators.url
from cmem.cmempy.dp.proxy.graph import get
Expand All @@ -22,12 +20,16 @@
ONTOLOGY_GRAPH_IRI_PARAMETER,
REASONER_PARAMETER,
REASONERS,
ROBOT,
VALIDATE_PROFILES_PARAMETER,
create_xml_catalog_file,
get_graphs_tree,
get_provenance,
post_profiles,
post_provenance,
remove_temp,
robot,
send_result,
validate_profiles,
)


Expand All @@ -41,6 +43,7 @@
parameters=[
REASONER_PARAMETER,
ONTOLOGY_GRAPH_IRI_PARAMETER,
VALIDATE_PROFILES_PARAMETER,
MAX_RAM_PERCENTAGE_PARAMETER,
PluginParameter(
param_type=GraphParameterType(
Expand Down Expand Up @@ -184,6 +187,7 @@ def __init__( # noqa: PLR0913
sub_class: bool = True,
sub_data_property: bool = False,
sub_object_property: bool = False,
validate_profile: bool = False,
max_ram_percentage: int = MAX_RAM_PERCENTAGE_DEFAULT,
) -> None:
self.axioms = {
Expand Down Expand Up @@ -239,14 +243,13 @@ def __init__( # noqa: PLR0913
self.ontology_graph_iri = ontology_graph_iri
self.output_graph_iri = output_graph_iri
self.reasoner = reasoner
self.validate_profile = validate_profile
self.max_ram_percentage = max_ram_percentage
self.temp = f"reason_{uuid4().hex}"

def get_graphs(self, graphs: dict, context: ExecutionContext) -> None:
"""Get graphs from CMEM"""
if not Path(self.temp).exists():
Path(self.temp).mkdir(parents=True)
for graph in graphs:
self.log.info(f"Fetching graph {graph}.")
with (Path(self.temp) / graphs[graph]).open("w", encoding="utf-8") as file:
setup_cmempy_user_access(context.user)
file.write(get(graph).text)
Expand All @@ -262,7 +265,6 @@ def reason(self, graphs: dict) -> None:
data_location = f"{self.temp}/{graphs[self.data_graph_iri]}"
utctime = str(datetime.fromtimestamp(int(time()), tz=UTC))[:-6].replace(" ", "T") + "Z"
cmd = (
f"java -XX:MaxRAMPercentage={self.max_ram_percentage} -jar {ROBOT} "
f'merge --input "{data_location}" '
"--collapse-import-closure false "
f"reason --reasoner {self.reasoner} "
Expand All @@ -286,22 +288,29 @@ def reason(self, graphs: dict) -> None:
f'--typed-annotation dc:created "{utctime}" xsd:dateTime '
f'--output "{self.temp}/result.ttl"'
)
response = run(shlex.split(cmd), check=False, capture_output=True) # noqa: S603
response = robot(cmd, self.max_ram_percentage)
if response.returncode != 0:
if response.stdout:
raise OSError(response.stdout.decode())
if response.stderr:
raise OSError(response.stderr.decode())
raise OSError("ROBOT error")

def execute(self, inputs: tuple, context: ExecutionContext) -> None: # noqa: ARG002
"""Execute plugin"""
def _execute(self, context: ExecutionContext) -> None:
"""`Execute plugin"""
setup_cmempy_user_access(context.user)
graphs = get_graphs_tree((self.data_graph_iri, self.ontology_graph_iri))
self.get_graphs(graphs, context)
create_xml_catalog_file(self.temp, graphs)
self.reason(graphs)
setup_cmempy_user_access(context.user)
send_result(self.output_graph_iri, Path(self.temp) / "result.ttl")
post_provenance(self, context)
if self.validate_profile:
post_profiles(self, validate_profiles(self, graphs))
post_provenance(self, get_provenance(self, context))
remove_temp(self)

def execute(self, inputs: tuple, context: ExecutionContext) -> None: # noqa: ARG002
"""Remove temp files on error"""
with TemporaryDirectory() as self.temp:
self._execute(context)
83 changes: 56 additions & 27 deletions cmem_plugin_reason/plugin_validate.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Ontology consistency validation workflow plugin module"""

import shlex
from datetime import UTC, datetime
from pathlib import Path
from subprocess import run
from tempfile import TemporaryDirectory
from time import time
from uuid import uuid4

import validators.url
from cmem.cmempy.dp.proxy.graph import get
Expand All @@ -29,12 +27,15 @@
ONTOLOGY_GRAPH_IRI_PARAMETER,
REASONER_PARAMETER,
REASONERS,
ROBOT,
VALIDATE_PROFILES_PARAMETER,
create_xml_catalog_file,
get_graphs_tree,
get_provenance,
post_profiles,
post_provenance,
remove_temp,
robot,
send_result,
validate_profiles,
)


Expand All @@ -51,6 +52,7 @@
REASONER_PARAMETER,
ONTOLOGY_GRAPH_IRI_PARAMETER,
MAX_RAM_PERCENTAGE_PARAMETER,
VALIDATE_PROFILES_PARAMETER,
PluginParameter(
param_type=BoolParameterType(),
name="write_md",
Expand Down Expand Up @@ -100,6 +102,7 @@ def __init__( # noqa: PLR0913
output_graph_iri: str = "",
write_md: bool = False,
md_filename: str = "",
validate_profile: bool = False,
stop_at_inconsistencies: bool = False,
max_ram_percentage: int = MAX_RAM_PERCENTAGE_DEFAULT,
) -> None:
Expand All @@ -125,25 +128,23 @@ def __init__( # noqa: PLR0913
self.write_md = write_md
self.stop_at_inconsistencies = stop_at_inconsistencies
self.md_filename = md_filename if write_md else "mdfile.md"
self.validate_profile = validate_profile
self.max_ram_percentage = max_ram_percentage
self.temp = f"reason_{uuid4().hex}"

def get_graphs(self, graphs: dict, context: ExecutionContext) -> None:
"""Get graphs from CMEM"""
if not Path(self.temp).exists():
Path(self.temp).mkdir(parents=True)
for graph in graphs:
self.log.info(f"Fetching graph {graph}.")
with (Path(self.temp) / graphs[graph]).open("w", encoding="utf-8") as file:
setup_cmempy_user_access(context.user)
file.write(get(graph).text)

def validate(self, graphs: dict) -> None:
def explain(self, graphs: dict) -> None:
"""Reason"""
data_location = f"{self.temp}/{graphs[self.ontology_graph_iri]}"
utctime = str(datetime.fromtimestamp(int(time()), tz=UTC))[:-6].replace(" ", "T") + "Z"

cmd = (
f"java -XX:MaxRAMPercentage={self.max_ram_percentage} -jar {ROBOT} "
f'merge --input "{data_location}" '
f"explain --reasoner {self.reasoner} -M inconsistency "
f'--explanation "{self.temp}/{self.md_filename}"'
Expand All @@ -160,7 +161,7 @@ def validate(self, graphs: dict) -> None:
f'--output "{self.temp}/output.ttl"'
)

response = run(shlex.split(cmd), check=False, capture_output=True) # noqa: S603
response = robot(cmd, self.max_ram_percentage)
if response.returncode != 0:
if response.stdout:
raise OSError(response.stdout.decode())
Expand All @@ -177,38 +178,66 @@ def make_resource(self, context: ExecutionContext) -> None:
replace=True,
)

def execute(self, inputs: tuple, context: ExecutionContext) -> Entities | None: # noqa: ARG002
def add_profiles(self, valid_profiles: list) -> list:
"""Add profile validation result to output"""
with (Path(self.temp) / self.md_filename).open("a") as mdfile:
mdfile.write("\n\n\n# Valid Profiles:\n")
if valid_profiles:
profiles_str = "\n- ".join(valid_profiles)
mdfile.write(f"- {profiles_str}\n")
if self.produce_graph:
post_profiles(self, valid_profiles)
return valid_profiles

def make_entities(self, text: str, valid_profiles: list) -> Entities:
"""Make entities"""
values = [[text]]
paths = [EntityPath(path="markdown")]
if self.validate_profile:
values.append(valid_profiles)
paths.append(EntityPath(path="profile"))
entities = [
Entity(
uri="https://eccenca.com/plugin_validateontology/result",
values=values,
),
]
schema = EntitySchema(
type_uri="https://eccenca.com/plugin_validateontology/type",
paths=paths,
)
return Entities(entities=entities, schema=schema)

def _execute(self, context: ExecutionContext) -> Entities:
"""Run the workflow operator."""
setup_cmempy_user_access(context.user)
graphs = get_graphs_tree((self.ontology_graph_iri,))
self.get_graphs(graphs, context)
create_xml_catalog_file(self.temp, graphs)
self.validate(graphs)
self.explain(graphs)

if self.produce_graph:
setup_cmempy_user_access(context.user)
send_result(self.output_graph_iri, Path(self.temp) / "output.ttl")
setup_cmempy_user_access(context.user)
post_provenance(self, context)
post_provenance(self, get_provenance(self, context))

valid_profiles = (
self.add_profiles(validate_profiles(self, graphs)) if self.validate_profile else []
)

if self.write_md:
setup_cmempy_user_access(context.user)
self.make_resource(context)
text = (Path(self.temp) / self.md_filename).read_text()

remove_temp(self)
text = (Path(self.temp) / self.md_filename).read_text()

if self.stop_at_inconsistencies and text != "No explanations found.":
raise RuntimeError("Inconsistencies found in Ontology.")

entities = [
Entity(
uri="https://eccenca.com/plugin_validateontology/md",
values=[[text]],
)
]
schema = EntitySchema(
type_uri="https://eccenca.com/plugin_validateontology/text",
paths=[EntityPath(path="text")],
)
return Entities(entities=iter(entities), schema=schema)
return self.make_entities(text, valid_profiles)

def execute(self, inputs: tuple, context: ExecutionContext) -> Entities: # noqa: ARG002
"""Remove temp files on error"""
with TemporaryDirectory() as self.temp:
return self._execute(context)
Loading
Loading