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

Develop #2

Merged
merged 6 commits into from
Jul 1, 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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ 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/)

## [Unreleased]

### Fixed

- valid range of "Maximum RAM Percentage" parameter in Validate plugin (1-100)

### Changed

- complete validation for IRI parameters
- Remove "Annnotate inferred subclass axioms" parameter

## [1.0.0alpha3] 2024-06-28

Expand Down
2 changes: 1 addition & 1 deletion README-public.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cmem-plugin-reason

This [eccenca](https://eccenca.com) [Corporate Memory](https://documentation.eccenca.com) workflow plugin performs reasoning using [ROBOT](http://robot.obolibrary.org/). It takes an OWL ontology and a data graph as inputs and writes the reasoning result to a specified graph.
This [eccenca](https://eccenca.com) [Corporate Memory](https://documentation.eccenca.com) workflow plugin performs reasoning using [ROBOT](http://robot.obolibrary.org/).

ROBOT is published under the [BSD 3-Clause "New" or "Revised" License](https://choosealicense.com/licenses/bsd-3-clause/).
Copyright © 2015, the Authors
Expand Down
57 changes: 53 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

# cmem-plugin-reason

Reasoning with ROBOT

This eccenca Corporate Memory workflow plugin performs reasoning using [ROBOT](http://robot.obolibrary.org/). It takes an OWL ontology and a data graph as inputs and writes the reasoning result to a specified graph.
This [eccenca](https://eccenca.com) [Corporate Memory](https://documentation.eccenca.com) workflow plugin bundle contains plugins performing reasoning (Reason) and ontology consistency checking (Validate) using [ROBOT](http://robot.obolibrary.org/).

[![eccenca Corporate Memory](https://img.shields.io/badge/eccenca-Corporate%20Memory-orange)](https://documentation.eccenca.com) [![workflow](https://github.com/eccenca/cmem-plugin-pyshacl/actions/workflows/check.yml/badge.svg)](https://github.com/eccenca/cmem-plugin-pyshacl/actions) [![pypi version](https://img.shields.io/pypi/v/cmem-plugin-reason)](https://pypi.org/project/cmem-plugin-reason/) [![license](https://img.shields.io/pypi/l/cmem-plugin-reason)](https://pypi.org/project/cmem-plugin-reasom)

Expand All @@ -29,6 +27,7 @@ Alternatively, the _build_ and _installation_ process can be initiated with the
➜ task deploy
```

# Reason
## Options

### Data graph IRI
Expand All @@ -46,7 +45,6 @@ The IRI of the output graph for the reasoning result.

:warning: Existing graphs will be overwritten.


### Reasoner

The following reasoner options are supported:
Expand Down Expand Up @@ -84,3 +82,54 @@ parameters to include inferred axiom generators:
- ObjectPropertyRange
- ObjectPropertyDomain

### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.

:warning: Setting the percentage too high may result in an out of memory error.

# Validate

In case ontology inconsistencies are found, the plugin outputs the explanation as text in Markdown format using the path "text".

## Options

### Ontology graph IRI

The IRI of the input ontology graph. The graph IRI is selected from a list of graphs of type`owl:Ontology`.

### Reasoner

The following reasoner options are supported:
- [ELK](https://code.google.com/p/elk-reasoner/) (elk)
- [Expression Materializing Reasoner](http://static.javadoc.io/org.geneontology/expression-materializing-reasoner/0.1.3/org/geneontology/reasoner/ExpressionMaterializingReasoner.html) (emr)
- [HermiT](http://www.hermit-reasoner.com/) (hermit)
- [JFact](http://jfact.sourceforge.net/) (jfact)
- [Structural Reasoner](http://owlcs.github.io/owlapi/apidocs_4/org/semanticweb/owlapi/reasoner/structural/StructuralReasoner.html) (structural)
- [Whelk](https://github.com/balhoff/whelk) (whelk)

### Produce output graph

If enabled, an explanation graph is created.

### Output graph IRI

The IRI of the output graph for the reasoning result.

:warning: Existing graphs will be overwritten.

### Write markdown explanation file

If enabled, an explanation markdown file is written to the project.

:warning: Existing files will be overwritten.

### Stop at inconsistencies

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

### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.

:warning: Setting the percentage too high may result in an out of memory error.
59 changes: 11 additions & 48 deletions cmem_plugin_reason/plugin_reason.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Reasoning workflow plugin module"""

import shlex
from collections.abc import Sequence
from datetime import UTC, datetime
from pathlib import Path
from subprocess import run
Expand All @@ -12,7 +11,6 @@
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 @@ -27,6 +25,7 @@
ROBOT,
create_xml_catalog_file,
get_graphs_tree,
remove_temp,
send_result,
)

Expand Down Expand Up @@ -59,7 +58,7 @@
param_type=StringParameterType(),
name="result_graph_iri",
label="Result graph IRI",
description="The IRI of the output graph for the reasoning result. ⚠️ Existing graph "
description="The IRI of the output graph for the reasoning result. ⚠️ Existing graphs "
"will be overwritten.",
),
PluginParameter(
Expand Down Expand Up @@ -160,19 +159,10 @@
description="",
default_value=False,
),
PluginParameter(
param_type=BoolParameterType(),
name="annotate_inferred_axioms",
label="Annnotate inferred subclass axioms",
description="Annotate inferred subclass axioms. ⚠️ This parameter can only be enabled "
"if the only enabled axiom generator is SubClass.",
default_value=False,
advanced=True,
),
],
)
class ReasonPlugin(WorkflowPlugin):
"""Robot reasoning plugin"""
"""Reason plugin"""

def __init__( # noqa: PLR0913
self,
Expand All @@ -194,10 +184,8 @@ def __init__( # noqa: PLR0913
sub_class: bool = True,
sub_data_property: bool = False,
sub_object_property: bool = False,
annotate_inferred_axioms: bool = False,
max_ram_percentage: int = MAX_RAM_PERCENTAGE_DEFAULT,
) -> None:
"""Init"""
self.axioms = {
"SubClass": sub_class,
"EquivalentClass": equivalent_class,
Expand All @@ -214,16 +202,13 @@ def __init__( # noqa: PLR0913
"ObjectPropertyRange": object_property_range,
"ObjectPropertyDomain": object_property_domain,
}

errors = ""
iris = {
"Data graph IRI": data_graph_iri,
"Ontology graph IRI": ontology_graph_iri,
"Result graph IRI": result_graph_iri,
}
not_iri = sorted([k for k, v in iris.items() if not validators.url(v)])
if not_iri:
errors += f"Invalid IRI for parameters: {', '.join(not_iri)}. "
if not validators.url(data_graph_iri):
errors += 'Invalid IRI for parameter "Data graph IRI". '
if not validators.url(ontology_graph_iri):
errors += 'Invalid IRI for parameter "Ontology graph IRI". '
if not validators.url(result_graph_iri):
errors += 'Invalid IRI for parameter "Result graph IRI". '
if result_graph_iri and result_graph_iri == data_graph_iri:
errors += "Result graph IRI cannot be the same as the data graph IRI. "
if result_graph_iri and result_graph_iri == ontology_graph_iri:
Expand All @@ -232,21 +217,14 @@ def __init__( # noqa: PLR0913
errors += 'Invalid value for parameter "Reasoner". '
if True not in self.axioms.values():
errors += "No axiom generator selected. "
if annotate_inferred_axioms and [k for k, v in self.axioms.items() if v] != ["SubClass"]:
errors += (
'Parameter "Annnotate inferred subclass axioms" can only be enabled if the only '
"enabled axiom generator is SubClass. "
)
if max_ram_percentage not in range(1, 101):
errors += 'Invalid value for parameter "Maximum RAM Percentage". '
if errors:
raise ValueError(errors[:-1])

self.data_graph_iri = data_graph_iri
self.ontology_graph_iri = ontology_graph_iri
self.result_graph_iri = result_graph_iri
self.reasoner = reasoner
self.annotate_inferred_axioms = str(annotate_inferred_axioms).lower()
self.max_ram_percentage = max_ram_percentage
self.temp = f"reason_{uuid4().hex}"

Expand Down Expand Up @@ -275,7 +253,6 @@ def reason(self, graphs: dict) -> None:
"--collapse-import-closure false "
f"reason --reasoner {self.reasoner} "
f'--axiom-generators "{axioms}" '
f"--annotate-inferred-axioms {self.annotate_inferred_axioms} "
f"--include-indirect true "
f"--exclude-duplicate-axioms true "
f"--exclude-owl-thing true "
Expand Down Expand Up @@ -305,21 +282,7 @@ def reason(self, graphs: dict) -> None:
raise OSError(response.stderr.decode())
raise OSError("ROBOT error")

def clean_up(self, graphs: dict) -> None:
"""Remove temporary files"""
files = ["catalog-v001.xml", "result.ttl"]
files += list(graphs.values())
for file in files:
try:
(Path(self.temp) / file).unlink()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove file {file} ({err})")
try:
Path(self.temp).rmdir()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove directory {self.temp} ({err})")

def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: # noqa: ARG002
def execute(self, inputs: tuple, context: ExecutionContext) -> None: # noqa: ARG002
"""Execute plugin"""
setup_cmempy_user_access(context.user)
graphs = get_graphs_tree((self.data_graph_iri, self.ontology_graph_iri))
Expand All @@ -328,4 +291,4 @@ def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None
self.reason(graphs)
setup_cmempy_user_access(context.user)
send_result(self.result_graph_iri, Path(self.temp) / "result.ttl")
self.clean_up(graphs)
remove_temp(self, ["catalog-v001.xml", "result.ttl", *graphs.values()])
62 changes: 23 additions & 39 deletions cmem_plugin_reason/plugin_validate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Ontology consistency validation workflow plugin module"""

import shlex
from collections.abc import Sequence
from datetime import UTC, datetime
from pathlib import Path
from subprocess import run
Expand All @@ -22,7 +21,7 @@
from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin
from cmem_plugin_base.dataintegration.types import BoolParameterType, StringParameterType
from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access
from pathvalidate import validate_filename
from pathvalidate import is_valid_filename

from cmem_plugin_reason.utils import (
MAX_RAM_PERCENTAGE_DEFAULT,
Expand All @@ -33,6 +32,7 @@
ROBOT,
create_xml_catalog_file,
get_graphs_tree,
remove_temp,
send_result,
)

Expand All @@ -50,7 +50,8 @@
param_type=BoolParameterType(),
name="write_md",
label="Write Markdown explanation file",
description="Write Markdownn file with explanation to project.",
description="Write Markdown file with explanation to project. ⚠️ Existing files will "
"be overwritten.",
default_value=False,
),
PluginParameter(
Expand All @@ -65,7 +66,7 @@
name="output_graph_iri",
label="Output graph IRI",
description="The IRI of the output graph for the inconsistency validation. ⚠️ Existing "
"graph will be overwritten.",
"graphs will be overwritten.",
),
PluginParameter(
param_type=StringParameterType(),
Expand All @@ -85,7 +86,7 @@
],
)
class ValidatePlugin(WorkflowPlugin):
"""Example Workflow Plugin: Random Values"""
"""Validate plugin"""

def __init__( # noqa: PLR0913
self,
Expand All @@ -100,28 +101,26 @@ def __init__( # noqa: PLR0913
) -> None:
errors = ""
if not validators.url(ontology_graph_iri):
errors += "Invalid IRI for parameter Ontology graph IRI. "
if reasoner not in REASONERS:
errors += "Invalid value for parameter Reasoner. "
errors += 'Invalid IRI for parameter "Ontology graph IRI." '
if produce_graph and not validators.url(output_graph_iri):
errors += "Invalid IRI for parameter Output graph IRI. "
if write_md:
try:
validate_filename(md_filename)
except: # noqa: E722
errors += "Invalid filename for parameter Output filename. "
if max_ram_percentage not in range(1, 100):
errors += "Invalid value for parameter Maximum RAM Percentage. "
errors += 'Invalid IRI for parameter "Output graph IRI". '
if produce_graph and output_graph_iri == ontology_graph_iri:
errors += "Output graph IRI cannot be the same as the Ontology graph IRI. "
if reasoner not in REASONERS:
errors += 'Invalid value for parameter "Reasoner". '
if write_md and not is_valid_filename(md_filename):
errors += 'Invalid filename for parameter "Output filename". '
if max_ram_percentage not in range(1, 101):
errors += 'Invalid value for parameter "Maximum RAM Percentage". '
if errors:
raise ValueError(errors[:-1])

self.ontology_graph_iri = ontology_graph_iri
self.reasoner = reasoner
self.produce_graph = produce_graph
self.output_graph_iri = output_graph_iri
self.write_md = write_md
self.stop_at_inconsistencies = stop_at_inconsistencies
self.md_filename = md_filename if md_filename and write_md else "mdfile.md"
self.md_filename = md_filename if write_md else "mdfile.md"
self.max_ram_percentage = max_ram_percentage
self.temp = f"reason_{uuid4().hex}"

Expand Down Expand Up @@ -176,35 +175,20 @@ def make_resource(self, context: ExecutionContext) -> None:
replace=True,
)

def clean_up(self, graphs: dict) -> None:
"""Remove temporary files"""
files = ["catalog-v001.xml", "output.ttl", self.md_filename]
files += list(graphs.values())
for file in files:
try:
(Path(self.temp) / file).unlink()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove file {file} ({err})")
try:
Path(self.temp).rmdir()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove directory {self.temp} ({err})")

def execute(
self,
inputs: Sequence[Entities], # noqa: ARG002
context: ExecutionContext,
) -> Entities | None:
def execute(self, inputs: tuple, context: ExecutionContext) -> Entities | None: # noqa: ARG002
"""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)
files = ["catalog-v001.xml", self.md_filename, *graphs.values()]
if self.produce_graph:
files.append("output.ttl")

text = (Path(self.temp) / self.md_filename).read_text()
if text == "No explanations found.":
self.clean_up(graphs)
remove_temp(self, files)
return None

if self.produce_graph:
Expand All @@ -215,7 +199,7 @@ def execute(
setup_cmempy_user_access(context.user)
self.make_resource(context)

self.clean_up(graphs)
remove_temp(self, files)

if self.stop_at_inconsistencies:
raise RuntimeError("Inconsistencies found in Ontology.")
Expand Down
Loading
Loading