Skip to content
Closed
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
13 changes: 13 additions & 0 deletions test/invalid_individuals.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@prefix si: <https://si-digital-framework.org/SI#> .
@prefix prefixes: <https://si-digital-framework.org/SI/prefixes/> .
@prefix units: <https://si-digital-framework.org/SI/units/> .
@prefix ex: <https://example.org/tmp#> .


ex:invalid_unit_1 a si:PrefixedUnit ;
si:hasPrefix prefixes:milli ;
si:hasBaseUnit units:kilogram .

ex:invalid_unit_2 a si:PrefixedUnit ;
si:hasPrefix prefixes:milli ;
si:hasBaseUnit units:neper .
80 changes: 75 additions & 5 deletions test/run_test.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import rdflib
from pathlib import Path

from shacl_utils import SHACLutils

common_knowledge_bases = {
"si": {"path": "TTL/si.ttl", "format": "ttl"},
"prefixes": {"path": "TTL/prefixes.ttl", "format": "ttl"},
"units": {"path": "TTL/units.ttl", "format": "ttl"},
}

constraint_shapes = {
"test": {"path": "test/test_constraints.ttl", "format": "ttl"},
}


def test_syntax():
error_status = False

this_dir = Path(__file__).parent
ttl_file_names = (this_dir.parent / "TTL" ).glob("*.ttl")
ttl_file_names = (this_dir.parent / "TTL").glob("*.ttl")

try:
g = rdflib.Graph()
Expand All @@ -18,8 +31,8 @@ def test_syntax():

return g, error_status

def test_semantics(g):

def test_semantics(g):
error_status = False

# some not very elaborate test
Expand All @@ -28,17 +41,74 @@ def test_semantics(g):

return error_status


def test_valid_individuals():
uh = SHACLutils()

invalid_individuals = {
"ex": {"path": "test/valid_individuals.ttl", "format": "ttl"},
}

# SHACL validation
data = invalid_individuals
onto = common_knowledge_bases
shapes = constraint_shapes

data_graph = uh.load_knowledge_bases(data | onto)
shapes_graph = uh.load_knowledge_bases(shapes)

conforms, results_graph = uh.validate_against_constraints(data_graph, shapes_graph)

error_status = not conforms # if data conforms to shape, this means no error
return error_status


def test_invalid_individuals():
uh = SHACLutils()

invalid_individuals = {
"ex": {"path": "test/invalid_individuals.ttl", "format": "ttl"},
}

# SHACL validation
data = invalid_individuals
onto = common_knowledge_bases
shapes = constraint_shapes

data_graph = uh.load_knowledge_bases(data | onto)
shapes_graph = uh.load_knowledge_bases(shapes)

conforms, results_graph = uh.validate_against_constraints(data_graph, shapes_graph)

for s, p, o in results_graph.triples((None, rdflib.SH["result"], None)):
focusNode = results_graph.value(o, rdflib.SH["focusNode"])
message = results_graph.value(o, rdflib.SH["resultMessage"])

print("Focus node: ", focusNode)
print("Result message: ", message)
print("")

error_status = conforms # if data conforms to shape, this means error
return error_status


def main():
g, syntax_error = test_syntax()

if syntax_error:
raise ImportError("The syntax of the generated files is not ok.")

semantic_error = test_semantics(g)
if semantic_error:
raise ValueError("A requirement regarding the content of the output files is not met.")
raise ValueError(
"A requirement regarding the content of the output files is not met."
)

shacl_error_valid_ind = test_valid_individuals()

shacl_error_invalid_ind = test_invalid_individuals()

print(syntax_error, semantic_error, shacl_error_valid_ind, shacl_error_invalid_ind)

print(syntax_error, semantic_error)

if __name__ == "__main__":
main()
43 changes: 43 additions & 0 deletions test/shacl_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import owlrl
from pyshacl import validate
import rdflib
from rdflib import OWL


class SHACLutils:
def load_knowledge_bases(self, knowledge_bases):
# load them into rdflib-graph
g_rdf = rdflib.Graph()
for kb, kb_val in knowledge_bases.items():
g_rdf.parse(kb_val["path"], format=kb_val["format"])

return g_rdf

def run_reasoner(self, g_rdf):
# infer implicit triples by reasoning
owlrl.DeductiveClosure(owlrl.RDFS_OWLRL_Semantics).expand(g_rdf)

return g_rdf

def remove_sameAs(self, g_rdf):
# remove owl:sameAs relations, if they only cover identity
for subj, pred, obj in g_rdf.triples((None, OWL.sameAs, None)):
# if pred.startswith(rdflib.RDFS):
if subj == obj:
g_rdf.remove((subj, pred, obj))

return g_rdf

def show_all_triples(self, g_rdf):
for subj, pred, obj in g_rdf.triples((None, None, None)):
print(subj, pred, obj)

def validate_against_constraints(self, g_data, g_shapes, verbose=False):
r = validate(g_data, shacl_graph=g_shapes, inference="both", advanced=False)

conforms, results_graph, results_text = r

if verbose:
print(results_text)

return conforms, results_graph
37 changes: 37 additions & 0 deletions test/test_constraints.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

@prefix si: <https://si-digital-framework.org/SI#> .
@prefix prefixes: <https://si-digital-framework.org/SI/prefixes/> .
@prefix units: <https://si-digital-framework.org/SI/units/> .
@prefix test: <https://example.org/test#> .


# base units shall be units, but no prefixed units
test:range_of_hasBaseUnit a sh:NodeShape ;
sh:targetClass si:PrefixedUnit ;
sh:property [
sh:path si:hasBaseUnit ;
sh:maxCount 1 ;
sh:class si:MeasurementUnit ;
sh:not [
a sh:NodeShape;
sh:class si:PrefixedUnit;
] ;
sh:nodeKind sh:IRI ;
] .


# only use base units, that are allowed with prefixes (without a prefix restriction)
test:no_prefixRestriction a sh:NodeShape ;
sh:targetClass si:PrefixedUnit ;
sh:property [
sh:path ( si:hasBaseUnit si:prefixRestriction );
sh:maxCount 1 ;
sh:not [
a sh:NodeShape;
sh:hasValue true;
];
] .
12 changes: 12 additions & 0 deletions test/valid_individuals.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@prefix si: <https://si-digital-framework.org/SI#> .
@prefix prefixes: <https://si-digital-framework.org/SI/prefixes/> .
@prefix units: <https://si-digital-framework.org/SI/units/> .
@prefix ex: <https://example.org/tmp#> .

ex:valid_unit_1 a si:PrefixedUnit ;
si:hasPrefix prefixes:milli ;
si:hasBaseUnit units:gram .

ex:valid_unit_2 a si:PrefixedUnit ;
si:hasPrefix prefixes:mega ;
si:hasBaseUnit units:second .