Skip to content

Commit

Permalink
refactor(xmlvalidate): improve error message (DEV-4201) (#1196)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nora-Olivia-Ammann authored Oct 3, 2024
1 parent 081a6f2 commit 6b386e9
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 53 deletions.
72 changes: 32 additions & 40 deletions src/dsp_tools/commands/xml_validate/models/input_problems.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,35 @@
from pathlib import Path

from rdflib import Graph
from rdflib.term import Node

from dsp_tools.commands.xml_validate.models.validation import UnexpectedComponent
from dsp_tools.models.custom_warnings import DspToolsUserWarning

LIST_SEPARATOR = "\n - "
INDENT = "\n "
GRAND_SEPARATOR = "\n\n----------------------------\n"


@dataclass
class ValidationResult:
source_constraint_component: Node
res_iri: Node
res_class: Node
property: Node
results_message: str
value: str | None = None


@dataclass
class UnexpectedComponent:
component_type: str


@dataclass
class UnexpectedResults:
components: list[UnexpectedComponent]
validation_result: Graph

def save_inform_user(self, cwdr: Path) -> None:
save_path = cwdr / f"validation_result_{datetime.now()!s}.ttl"
def save_inform_user(self, results_graph: Graph, shacl: Graph, data: Graph) -> None:
cwdr = Path.cwd()
prefix = f"{datetime.now()!s}"
components = sorted(x.component_type for x in self.components)
msg = (
f"Unexpected violations were found in the validation results:"
f"{LIST_SEPARATOR}{LIST_SEPARATOR.join(components)}\n"
f"The validation report was saved here: {save_path}\n"
f"Please contact the dsp-tools development team with this information."
f"Please contact the development team with the files starting with the timestamp '{prefix}' "
f"in the directory '{cwdr}'."
)
self.validation_result.serialize(save_path)
save_path = cwdr / f"{prefix}validation_result.ttl"
results_graph.serialize(save_path)
shacl_p = cwdr / f"{prefix}shacl.ttl"
shacl.serialize(shacl_p)
data_p = cwdr / f"{prefix}data.ttl"
data.serialize(data_p)
warnings.warn(DspToolsUserWarning(msg))


Expand All @@ -55,11 +45,6 @@ class AllProblems:
problems: list[InputProblem]
unexpected_results: UnexpectedResults | None

def communicate_with_the_user(self, cwdr: Path) -> str:
if self.unexpected_results:
self.unexpected_results.save_inform_user(cwdr)
return self.get_msg()

def get_msg(self) -> str:
coll = self._make_collection()
msg = [x.get_msg() for x in coll]
Expand All @@ -82,10 +67,23 @@ class ResourceProblemCollection:
problems: list[InputProblem]

def get_msg(self) -> str:
msg = [f"Resource ID: {self.res_id} | Resource Type: {self.problems[0].res_type}"]
sorted_problems = sorted(self.problems, key=lambda x: x.sort_value())
msg.extend([x.get_msg() for x in sorted_problems])
return "\n".join(msg)
prop_msg = self._msg_for_properties()
return f"Resource ID: {self.res_id} | Resource Type: {self.problems[0].res_type}\n{prop_msg}"

def _msg_for_properties(self) -> str:
grouped = self._make_collection()
out_list = []
for prop_name, problem in grouped.items():
problem_list = [x.get_msg() for x in problem]
out_list.append((prop_name, f"{prop_name}{LIST_SEPARATOR}{LIST_SEPARATOR.join(problem_list)}"))
sorted_list = sorted(out_list, key=lambda x: x[0])
return "\n".join([x[1] for x in sorted_list])

def _make_collection(self) -> dict[str, list[InputProblem]]:
grouped_dict = defaultdict(list)
for problem in self.problems:
grouped_dict[problem.sort_value()].append(problem)
return grouped_dict

def sort_value(self) -> str:
return self.res_id
Expand Down Expand Up @@ -113,10 +111,7 @@ class MaxCardinalityViolation(InputProblem):
expected_cardinality: str

def get_msg(self) -> str:
return (
f"Maximum Cardinality Violation:"
f"{INDENT}Property: {self.prop_name} | Expected Cardinality: {self.expected_cardinality}"
)
return f"Maximum Cardinality Violation | Expected Cardinality: {self.expected_cardinality}"

def sort_value(self) -> str:
return self.prop_name
Expand All @@ -127,10 +122,7 @@ class MinCardinalityViolation(InputProblem):
expected_cardinality: str

def get_msg(self) -> str:
return (
f"Minimum Cardinality Violation:"
f"{INDENT}Property: {self.prop_name} | Expected Cardinality: {self.expected_cardinality}"
)
return f"Minimum Cardinality Violation | Expected Cardinality: {self.expected_cardinality}"

def sort_value(self) -> str:
return self.prop_name
Expand All @@ -139,7 +131,7 @@ def sort_value(self) -> str:
@dataclass
class NonExistentCardinalityViolation(InputProblem):
def get_msg(self) -> str:
return f"The resource class does not have a cardinality for{INDENT}Property: {self.prop_name}"
return "The resource class does not have a cardinality for this property."

def sort_value(self) -> str:
return self.prop_name
29 changes: 29 additions & 0 deletions src/dsp_tools/commands/xml_validate/models/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

from dataclasses import dataclass

from rdflib import Graph
from rdflib.term import Node


@dataclass
class ValidationReport:
conforms: bool
validation_graph: Graph
shacl_graph: Graph
data_graph: Graph


@dataclass
class UnexpectedComponent:
component_type: str


@dataclass
class ValidationResult:
source_constraint_component: Node
res_iri: Node
res_class: Node
property: Node
results_message: str
value: str | None = None
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from dsp_tools.commands.xml_validate.models.input_problems import MaxCardinalityViolation
from dsp_tools.commands.xml_validate.models.input_problems import MinCardinalityViolation
from dsp_tools.commands.xml_validate.models.input_problems import NonExistentCardinalityViolation
from dsp_tools.commands.xml_validate.models.input_problems import UnexpectedComponent
from dsp_tools.commands.xml_validate.models.input_problems import UnexpectedResults
from dsp_tools.commands.xml_validate.models.input_problems import ValidationResult
from dsp_tools.commands.xml_validate.models.validation import UnexpectedComponent
from dsp_tools.commands.xml_validate.models.validation import ValidationResult


def reformat_validation_graph(results_graph: Graph, data_graph: Graph) -> AllProblems:
Expand All @@ -27,7 +27,7 @@ def reformat_validation_graph(results_graph: Graph, data_graph: Graph) -> AllPro
"""
reformatted_results = _reformat_result_graph(results_graph, data_graph)
input_problems, unexpected_components = _transform_violations_into_input_problems(reformatted_results)
unexpected = UnexpectedResults(unexpected_components, results_graph) if unexpected_components else None
unexpected = UnexpectedResults(unexpected_components) if unexpected_components else None
return AllProblems(input_problems, unexpected)


Expand Down
30 changes: 22 additions & 8 deletions src/dsp_tools/commands/xml_validate/xml_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
from lxml import etree
from rdflib import SH
from rdflib import Graph
from termcolor import cprint

from dsp_tools.commands.xml_validate.api_connection import OntologyConnection
from dsp_tools.commands.xml_validate.api_connection import ShaclValidator
from dsp_tools.commands.xml_validate.deserialise_input import deserialise_xml
from dsp_tools.commands.xml_validate.make_data_rdf import make_data_rdf
from dsp_tools.commands.xml_validate.models.data_deserialised import ProjectDeserialised
from dsp_tools.commands.xml_validate.models.data_rdf import DataRDF
from dsp_tools.commands.xml_validate.models.validation import ValidationReport
from dsp_tools.commands.xml_validate.reformat_validaton_result import reformat_validation_graph
from dsp_tools.commands.xml_validate.sparql.construct_shapes import construct_shapes_graph
from dsp_tools.models.custom_warnings import DspToolsUserWarning
Expand Down Expand Up @@ -40,13 +42,20 @@ def xml_validate(filepath: Path, api_url: str, dev_route: bool) -> bool: # noqa
ontologies = _get_project_ontos(onto_con)
data_graph = data_rdf.make_graph() + ontologies
val = ShaclValidator(api_url)
conforms, result = _validate(val, ontologies, data_graph)
if conforms:
print("\n\nValidation passed!")
report = _validate(val, ontologies, data_graph)
if report.conforms:
cprint("\n Validation passed! ", color="green", attrs=["bold", "reverse"])
else:
reformatted = reformat_validation_graph(result, data_graph)
msg = reformatted.communicate_with_the_user(Path.cwd())
print(msg)
reformatted = reformat_validation_graph(report.validation_graph, data_graph)
problem_msg = reformatted.get_msg()
cprint("\n Validation errors found! ", color="light_red", attrs=["bold", "reverse"])
print(problem_msg)
if reformatted.unexpected_results:
reformatted.unexpected_results.save_inform_user(
results_graph=report.validation_graph,
shacl=report.shacl_graph,
data=data_graph,
)
return True


Expand All @@ -59,13 +68,18 @@ def _inform_about_experimental_feature() -> None:
warnings.warn(DspToolsUserWarning(LIST_SEPARATOR.join(what_is_validated)))


def _validate(validator: ShaclValidator, onto_graph: Graph, data_graph: Graph) -> tuple[bool, Graph]:
def _validate(validator: ShaclValidator, onto_graph: Graph, data_graph: Graph) -> ValidationReport:
shapes = construct_shapes_graph(onto_graph)
shape_str = shapes.serialize(format="ttl")
data_str = data_graph.serialize(format="ttl")
results = validator.validate(data_str, shape_str)
conforms = bool(next(results.objects(None, SH.conforms)))
return conforms, results
return ValidationReport(
conforms=conforms,
validation_graph=results,
shacl_graph=shapes,
data_graph=data_graph,
)


def _get_project_ontos(onto_con: OntologyConnection) -> Graph:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from dsp_tools.commands.xml_validate.models.input_problems import MaxCardinalityViolation
from dsp_tools.commands.xml_validate.models.input_problems import MinCardinalityViolation
from dsp_tools.commands.xml_validate.models.input_problems import NonExistentCardinalityViolation
from dsp_tools.commands.xml_validate.models.input_problems import UnexpectedComponent
from dsp_tools.commands.xml_validate.models.input_problems import ValidationResult
from dsp_tools.commands.xml_validate.models.validation import UnexpectedComponent
from dsp_tools.commands.xml_validate.models.validation import ValidationResult
from dsp_tools.commands.xml_validate.reformat_validaton_result import _extract_one_violation
from dsp_tools.commands.xml_validate.reformat_validaton_result import _reformat_one_violation

Expand Down

0 comments on commit 6b386e9

Please sign in to comment.