-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ExpressionConstraintComponent is implemented!
- Use your previously defined SHACL Functions to express complex constraints - Added DASH-tests for ExpressionConstraintComponent - Added advanced tests for ExpressionConstraintComponent, SHACLRules, and SHACLFunctions. - New Advanced features example, showcasing ExpressionConstraint and others features - Allow sh:message to be attached to an expression block, without breaking its functionality - A SHACL Function within a SHACL Expression now must be a list-valued property. - Refactored node-expression and path-expression methods to be common and reusable code - Re-black and isort all source files
- Loading branch information
1 parent
137d8a0
commit d7da1f1
Showing
17 changed files
with
822 additions
and
288 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
"""\ | ||
A cool test that combines a bunch of SHACL-AF features, including: | ||
SHACL Functions (implemented as SPARQL functions) | ||
SHACL Rules | ||
Node Expressions | ||
Expression Constraint | ||
""" | ||
|
||
from pyshacl import validate | ||
from rdflib import Graph | ||
|
||
shacl_file = '''\ | ||
# prefix: ex | ||
@prefix ex: <http://datashapes.org/shasf/tests/expression/advanced.test.shacl#> . | ||
@prefix exOnt: <http://datashapes.org/shasf/tests/expression/advanced.test.ont#> . | ||
@prefix exData: <http://datashapes.org/shasf/tests/expression/advanced.test.data#> . | ||
@prefix owl: <http://www.w3.org/2002/07/owl#> . | ||
@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#> . | ||
<http://datashapes.org/shasf/tests/expression/advanced.test.shacl> | ||
rdf:type owl:Ontology ; | ||
rdfs:label "Test of advanced features" ; | ||
. | ||
ex:concat | ||
a sh:SPARQLFunction ; | ||
rdfs:comment "Concatenates strings $op1 and $op2." ; | ||
sh:parameter [ | ||
sh:path ex:op1 ; | ||
sh:datatype xsd:string ; | ||
sh:description "The first string" ; | ||
] ; | ||
sh:parameter [ | ||
sh:path ex:op2 ; | ||
sh:datatype xsd:string ; | ||
sh:description "The second string" ; | ||
] ; | ||
sh:returnType xsd:string ; | ||
sh:select """ | ||
SELECT ?result | ||
WHERE { | ||
BIND(CONCAT(STR(?op1),STR(?op2)) AS ?result) . | ||
} | ||
""" . | ||
ex:strlen | ||
a sh:SPARQLFunction ; | ||
rdfs:comment "Returns length of the given string." ; | ||
sh:parameter [ | ||
sh:path ex:op1 ; | ||
sh:datatype xsd:string ; | ||
sh:description "The string" ; | ||
] ; | ||
sh:returnType xsd:integer ; | ||
sh:select """ | ||
SELECT ?result | ||
WHERE { | ||
BIND(STRLEN(?op1) AS ?result) . | ||
} | ||
""" . | ||
ex:lessThan | ||
a sh:SPARQLFunction ; | ||
rdfs:comment "Returns True if op1 < op2." ; | ||
sh:parameter [ | ||
sh:path ex:op1 ; | ||
sh:datatype xsd:integer ; | ||
sh:description "The first int" ; | ||
] ; | ||
sh:parameter [ | ||
sh:path ex:op2 ; | ||
sh:datatype xsd:integer ; | ||
sh:description "The second int" ; | ||
] ; | ||
sh:returnType xsd:boolean ; | ||
sh:select """ | ||
SELECT ?result | ||
WHERE { | ||
BIND(IF(?op1 < ?op2, true, false) AS ?result) . | ||
} | ||
""" . | ||
ex:PersonExpressionShape | ||
a sh:NodeShape ; | ||
sh:targetClass exOnt:Person ; | ||
sh:expression [ | ||
sh:message "Person's firstName and lastName together should be less than 35 chars long." ; | ||
ex:lessThan ( | ||
[ ex:strlen ( | ||
[ ex:concat ( [ sh:path exOnt:firstName] [ sh:path exOnt:lastName ] ) ] ) | ||
] | ||
35 ); | ||
] . | ||
ex:PersonRuleShape | ||
a sh:NodeShape ; | ||
sh:targetClass exOnt:Administrator ; | ||
sh:message "An administrator is a person too." ; | ||
sh:rule [ | ||
a sh:TripleRule ; | ||
sh:subject sh:this ; | ||
sh:predicate rdf:type ; | ||
sh:object exOnt:Person ; | ||
] . | ||
''' | ||
|
||
data_graph = ''' | ||
# prefix: ex | ||
@prefix ex: <http://datashapes.org/shasf/tests/expression/advanced.test.data#> . | ||
@prefix exOnt: <http://datashapes.org/shasf/tests/expression/advanced.test.ont#> . | ||
@prefix owl: <http://www.w3.org/2002/07/owl#> . | ||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . | ||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | ||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
ex:Kate | ||
rdf:type exOnt:Person ; | ||
exOnt:firstName "Kate" ; | ||
exOnt:lastName "Jones" ; | ||
. | ||
ex:Jenny | ||
rdf:type exOnt:Administrator ; | ||
exOnt:firstName "Jennifer" ; | ||
exOnt:lastName "Wolfeschlegelsteinhausenbergerdorff" ; | ||
. | ||
''' | ||
|
||
if __name__ == "__main__": | ||
d = Graph().parse(data=data_graph, format="turtle") | ||
s = Graph().parse(data=shacl_file, format="turtle") | ||
conforms, report, message = validate(d, shacl_graph=s, advanced=True, debug=False) | ||
print(message) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
""" | ||
SHACL-AF Advanced Constraints | ||
https://www.w3.org/TR/shacl-af/#ExpressionConstraintComponent | ||
""" | ||
import typing | ||
|
||
from typing import Dict, List | ||
|
||
from rdflib import Literal | ||
|
||
from pyshacl.constraints.constraint_component import ConstraintComponent | ||
from pyshacl.consts import SH, SH_message | ||
from pyshacl.errors import ConstraintLoadError | ||
from pyshacl.helper.expression_helper import nodes_from_node_expression | ||
from pyshacl.pytypes import GraphLike | ||
|
||
|
||
SH_expression = SH.expression | ||
SH_ExpressionConstraintComponent = SH.ExpressionConstraintComponent | ||
|
||
if typing.TYPE_CHECKING: | ||
from pyshacl.shape import Shape | ||
|
||
|
||
class ExpressionConstraint(ConstraintComponent): | ||
|
||
shacl_constraint_component = SH_ExpressionConstraintComponent | ||
|
||
def __init__(self, shape: 'Shape'): | ||
super(ExpressionConstraint, self).__init__(shape) | ||
self.expr_nodes = list(self.shape.objects(SH_expression)) | ||
if len(self.expr_nodes) < 1: | ||
raise ConstraintLoadError( | ||
"ExpressionConstraintComponent must have at least one sh:expression predicate.", | ||
"https://www.w3.org/TR/shacl-af/#ExpressionConstraintComponent", | ||
) | ||
|
||
@classmethod | ||
def constraint_parameters(cls): | ||
return [SH_expression] | ||
|
||
@classmethod | ||
def constraint_name(cls): | ||
return "ExpressionConstraintComponent" | ||
|
||
def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) -> List[Literal]: | ||
return [Literal("Expression evaluation generated constraint did not return true.")] | ||
|
||
def evaluate(self, data_graph: GraphLike, focus_value_nodes: Dict, _evaluation_path: List): | ||
""" | ||
:type data_graph: rdflib.Graph | ||
:type focus_value_nodes: dict | ||
:type _evaluation_path: list | ||
""" | ||
reports = [] | ||
non_conformant = False | ||
for n in self.expr_nodes: | ||
_n, _r = self._evaluate_expression(data_graph, focus_value_nodes, n) | ||
non_conformant = non_conformant or _n | ||
reports.extend(_r) | ||
return (not non_conformant), reports | ||
|
||
def _evaluate_expression(self, data_graph, f_v_dict, expr): | ||
reports = [] | ||
non_conformant = False | ||
messages = list(self.shape.sg.objects(expr, SH_message)) | ||
if len(messages): | ||
messages = [next(iter(messages))] | ||
else: | ||
messages = None | ||
for f, value_nodes in f_v_dict.items(): | ||
for v in value_nodes: | ||
try: | ||
n_set = nodes_from_node_expression(expr, v, data_graph, self.shape.sg) | ||
if ( | ||
isinstance(n_set, (list, set)) | ||
and len(n_set) == 1 | ||
and next(iter(n_set)) in (Literal(True), True) | ||
): | ||
... | ||
else: | ||
non_conformant = non_conformant or True | ||
reports.append( | ||
self.make_v_result( | ||
data_graph, f, value_node=v, source_constraint=expr, extra_messages=messages | ||
) | ||
) | ||
except Exception as e: | ||
print(e) | ||
raise | ||
return non_conformant, reports |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.