diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b5a83..6a39aa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p The ontology IRI on the input overrides the plugin setting. - update execution report - Output graph IRI selectable from existing graphs +- When "input_profiles" is enabled the ontology IRI and list of valid OWL2 profiles is now taken from the config port. +The list of valid profiles is a comma-separated string (e.g. "Full,DL") ## [1.0.0beta4] 2024-07-12 diff --git a/README.md b/README.md index a445a73..9bde745 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,8 @@ Validate the input ontology against OWL profiles (DL, EL, QL, RL, and Full). The ### 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, the latter overriding the setting in the "Reason" plugin. -If the "Validate OWL2 profiles" parameter is enabled in the "Validate" plugin, it can be directly connected to the input +If enabled along with the "Validate OWL2 profiles" parameter, the ontology IRI and list of valid profiles is taken from the config port input, +without validating the ontology against the profiles in the plugin. If the "Validate OWL2 profiles" parameter is enabled in the "Validate" plugin, it can be directly connected to the input of the "Reason" plugin. diff --git a/cmem_plugin_reason/plugin_reason.py b/cmem_plugin_reason/plugin_reason.py index 176836b..e56eebc 100644 --- a/cmem_plugin_reason/plugin_reason.py +++ b/cmem_plugin_reason/plugin_reason.py @@ -1,6 +1,5 @@ """Reasoning workflow plugin module""" -from collections.abc import Sequence from datetime import UTC, datetime from os import environ from pathlib import Path @@ -12,12 +11,12 @@ from cmem.cmempy.dp.proxy.graph import get from cmem_plugin_base.dataintegration.context import ExecutionContext, ExecutionReport from cmem_plugin_base.dataintegration.description import Icon, Plugin, PluginParameter -from cmem_plugin_base.dataintegration.entity import Entities, EntityPath, EntitySchema +from cmem_plugin_base.dataintegration.entity import EntityPath, EntitySchema from cmem_plugin_base.dataintegration.parameter.choice import ChoiceParameterType from cmem_plugin_base.dataintegration.parameter.graph import GraphParameterType from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin -from cmem_plugin_base.dataintegration.ports import FixedNumberOfInputs, FixedSchemaPort -from cmem_plugin_base.dataintegration.types import BoolParameterType +from cmem_plugin_base.dataintegration.ports import FixedNumberOfInputs +from cmem_plugin_base.dataintegration.types import BoolParameterType, StringParameterType from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access from inflection import underscore from urllib3.exceptions import InsecureRequestWarning @@ -177,11 +176,20 @@ name="input_profiles", label="Process valid OWL profiles from input", description="""If the "validate OWL profiles" parameter is enabled, the valid profiles - and ontology IRI is taken from the input (paths "profile" and "ontology") instead of - running the validation in the plugin.""", + and ontology IRI is taken from the config port input (parameters "valid_profiles" and + "ontology_graph_iri") instead of from running the validation in the plugin. The valid + profiles input is a comma-separated string (e.g. "Full,DL").""", default_value=False, advanced=True, ), + PluginParameter( + param_type=StringParameterType(), + name="valid_profiles", + label="Valid OWL2 profiles", + description="Valid OWL2 profiles for the processed ontology.", + default_value="", + visible=False, + ), ], ) class ReasonPlugin(WorkflowPlugin): @@ -210,6 +218,7 @@ def __init__( # noqa: PLR0913, C901 validate_profile: bool = False, input_profiles: bool = False, max_ram_percentage: int = MAX_RAM_PERCENTAGE_DEFAULT, + valid_profiles: str = "", ) -> None: self.axioms = { "SubClass": sub_class, @@ -254,14 +263,12 @@ def __init__( # noqa: PLR0913, C901 self.validate_profile = validate_profile self.input_profiles = input_profiles self.max_ram_percentage = max_ram_percentage + self.valid_profiles = valid_profiles for k, v in self.axioms.items(): self.__dict__[underscore(k)] = v - if validate_profile and input_profiles: - self.input_ports = FixedNumberOfInputs([FixedSchemaPort(self.generate_input_schema())]) - else: - self.input_ports = FixedNumberOfInputs([]) + self.input_ports = FixedNumberOfInputs([]) self.output_port = None def generate_input_schema(self) -> EntitySchema: @@ -324,17 +331,7 @@ def reason(self, graphs: dict) -> None: raise OSError(response.stderr.decode()) raise OSError("ROBOT error") - 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] - valid_profiles = values[paths.index("profile")] - else: - valid_profiles = validate_profiles(self, graphs) - post_profiles(self, valid_profiles) - - def _execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: + def _execute(self, context: ExecutionContext) -> None: """`Execute plugin""" setup_cmempy_user_access(context.user) graphs = get_graphs_tree( @@ -346,7 +343,11 @@ def _execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Non setup_cmempy_user_access(context.user) send_result(self.output_graph_iri, Path(self.temp) / "result.ttl") if self.validate_profile: - self.post_valid_profiles(inputs, graphs) + if self.input_profiles: + valid_profiles = self.valid_profiles.split(",") + else: + valid_profiles = validate_profiles(self, graphs) + post_profiles(self, valid_profiles) post_provenance(self, get_provenance(self, context)) context.report.update( @@ -357,7 +358,7 @@ def _execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Non ) ) - def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: + def execute(self, inputs: None, context: ExecutionContext) -> None: # noqa: ARG002 """Validate input, execute plugin with temporary directory""" context.report.update( ExecutionReport( @@ -365,27 +366,5 @@ def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None operation_desc="ontologies and data graphs processed.", ) ) - if self.input_profiles: - errors = "" - values = next(inputs[0].entities).values - paths = [p.path for p in inputs[0].schema.paths] - if not inputs: - raise OSError( - 'Input entities needed if "Process valid OWL profiles from input" is enabled' - ) - if "profile" not in paths: - errors += 'No value for "profile" given on input. ' - if "ontology" not in paths: - errors += 'No value for "ontology" given on input. ' - self.ontology_graph_iri = values[paths.index("ontology")][0] - if not validators.url(self.ontology_graph_iri): - errors += 'Invalid IRI for parameter "Ontology graph IRI". ' - if self.ontology_graph_iri == self.data_graph_iri: - errors += "Ontology graph IRI cannot be the same as the data graph IRI. " - if self.ontology_graph_iri == self.output_graph_iri: - errors += "Ontology graph IRI cannot be the same as the output graph IRI. " - if errors: - raise ValueError(errors[:-1]) - with TemporaryDirectory() as self.temp: - self._execute(inputs, context) + self._execute(context) diff --git a/cmem_plugin_reason/plugin_validate.py b/cmem_plugin_reason/plugin_validate.py index 475ff32..be185a9 100644 --- a/cmem_plugin_reason/plugin_validate.py +++ b/cmem_plugin_reason/plugin_validate.py @@ -127,9 +127,9 @@ def __init__( # noqa: PLR0913 def generate_output_schema(self) -> EntitySchema: """Generate the output schema.""" - paths = [EntityPath("markdown"), EntityPath("ontology")] + paths = [EntityPath("markdown"), EntityPath("ontology_graph_iri")] if self.validate_profile: - paths.append(EntityPath("profile")) + paths.append(EntityPath("valid_profiles")) return EntitySchema(type_uri="validate", paths=paths) def get_graphs(self, graphs: dict, context: ExecutionContext) -> None: @@ -191,7 +191,7 @@ def make_entities(self, text: str, valid_profiles: list) -> Entities: """Make entities""" values = [[text], [self.ontology_graph_iri]] if self.validate_profile: - values.append(valid_profiles) + values.append([",".join(valid_profiles)]) entities = [ Entity( uri="https://eccenca.com/plugin_validateontology/result", @@ -246,7 +246,7 @@ def _execute(self, context: ExecutionContext) -> Entities: return self.make_entities(text, valid_profiles) - def execute(self, inputs: tuple, context: ExecutionContext) -> Entities: # noqa: ARG002 + def execute(self, inputs: None, context: ExecutionContext) -> Entities: # noqa: ARG002 """Remove temp files on error""" context.report.update( ExecutionReport( diff --git a/cmem_plugin_reason/utils.py b/cmem_plugin_reason/utils.py index c1df298..ab276a8 100644 --- a/cmem_plugin_reason/utils.py +++ b/cmem_plugin_reason/utils.py @@ -230,7 +230,7 @@ def post_profiles(plugin: WorkflowPlugin, valid_profiles: list) -> None: query = f""" INSERT DATA {{ GRAPH <{plugin.output_graph_iri}> {{ - <{plugin.ontology_graph_iri}> + <{plugin.ontology_graph_iri}> a ; "{profiles}" . }} }} diff --git a/tests/test_elk.ttl b/tests/test_elk.ttl index d848de9..209378b 100644 --- a/tests/test_elk.ttl +++ b/tests/test_elk.ttl @@ -126,5 +126,6 @@ vocab:pet_owner . -vocab: "DL" , "Full" . +vocab: a owl:Ontology ; + "DL" , "Full" . diff --git a/tests/test_emr.ttl b/tests/test_emr.ttl index 8a0f1f2..2efbc00 100644 --- a/tests/test_emr.ttl +++ b/tests/test_emr.ttl @@ -126,4 +126,5 @@ vocab:pet_owner . -vocab: "DL" , "Full" . +vocab: a owl:Ontology ; + "DL" , "Full" . diff --git a/tests/test_hermit.ttl b/tests/test_hermit.ttl index 173d935..54086b8 100644 --- a/tests/test_hermit.ttl +++ b/tests/test_hermit.ttl @@ -194,4 +194,5 @@ . -vocab: "DL" , "Full" . +vocab: a owl:Ontology ; + "DL" , "Full" . diff --git a/tests/test_jfact.ttl b/tests/test_jfact.ttl index 173d935..54086b8 100644 --- a/tests/test_jfact.ttl +++ b/tests/test_jfact.ttl @@ -194,4 +194,5 @@ . -vocab: "DL" , "Full" . +vocab: a owl:Ontology ; + "DL" , "Full" . diff --git a/tests/test_reason.py b/tests/test_reason.py index ec51a21..2b86e89 100644 --- a/tests/test_reason.py +++ b/tests/test_reason.py @@ -77,9 +77,10 @@ def test_reasoner(reasoner: str, err_list: list) -> list: class_assertion=True, property_assertion=True, validate_profile=True, - ).execute((), context=TestExecutionContext()) + ).execute(None, context=TestExecutionContext()) result = get_remote_graph(REASON_RESULT_GRAPH_IRI) + result.serialize(f"temp/{reasoner}.ttl", format="turtle") test = Graph().parse(Path(__path__[0]) / f"test_{reasoner}.ttl", format="turtle") if to_isomorphic(result) != to_isomorphic(test): err_list.append(reasoner) @@ -91,7 +92,7 @@ def test_validate(errors: str) -> str: output_graph_iri=OUTPUT_GRAPH_IRI, validate_profile=True, md_filename=MD_FILENAME, - ).execute((), context=TestExecutionContext(PROJECT_ID)) + ).execute(None, context=TestExecutionContext(PROJECT_ID)) val_errors = "" md_test = (Path(__path__[0]) / "test_validate.md").read_text() @@ -99,8 +100,8 @@ def test_validate(errors: str) -> str: if next(iter(result.entities)).values[0][0] != md_test: # type: ignore[union-attr] val_errors += 'EntityPath "markdown" output error. ' - if next(iter(result.entities)).values[2] != ["Full", "DL", "EL", "QL", "RL"]: # type: ignore[union-attr] - val_errors += 'EntityPath "profile" output error. ' + if next(iter(result.entities)).values[2][0] != "Full,DL,EL,QL,RL": # type: ignore[union-attr] + val_errors += 'EntityPath "valid_profile" output error. ' if md_test != get_resource(PROJECT_ID, MD_FILENAME).decode(): val_errors += "Markdown file error. " diff --git a/tests/test_structural.ttl b/tests/test_structural.ttl index df51dc2..339878e 100644 --- a/tests/test_structural.ttl +++ b/tests/test_structural.ttl @@ -97,4 +97,5 @@ vocab:animal . -vocab: "DL" , "Full" . +vocab: a owl:Ontology ; + "DL" , "Full" . diff --git a/tests/test_validate_output.ttl b/tests/test_validate_output.ttl index e2fb07a..cec5c12 100644 --- a/tests/test_validate_output.ttl +++ b/tests/test_validate_output.ttl @@ -18,5 +18,6 @@ a owl:Class . - "Full", "DL", "EL", "QL", "RL" . + a owl:Ontology ; + "Full", "DL", "EL", "QL", "RL" . diff --git a/tests/test_whelk.ttl b/tests/test_whelk.ttl index 4341388..ea179a4 100644 --- a/tests/test_whelk.ttl +++ b/tests/test_whelk.ttl @@ -188,4 +188,5 @@ . -vocab: "DL" , "Full" . +vocab: a owl:Ontology ; + "DL" , "Full" .