SHACL 1.2#
RDF 1.2 makes a statement into something you can describe, and SPARQL 1.2 makes it something you can query. The remaining question is how to require it: if your model says that every employment must record when it started and ended, something has to check that the annotations are there and well-formed.
That is what SHACL 1.2 adds, and this chapter covers
the two constraint components rudof implements for it:
sh:reificationRequired and sh:reifierShape.
Preliminaries: install and configure rudof#
%pip install -q "pyrudof>=0.3.22"
Note: you may need to restart the kernel to use updated packages.
from pyrudof import Rudof
rudof = Rudof()
The data#
Three researchers and their employers, written with the RDF 1.2 annotation
syntax. Each :employer statement should carry the years it covers:
:timblhas two complete spells at CERN;:vinthas a spell with a start year but no end year;:nobodyhas an employer with no annotation at all.
rudof.read_data("""
prefix : <http://example.org/>
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
:timbl a :Human ;
rdfs:label "Tim Berners-Lee" ;
:employer :CERN {| :start "1984"^^xsd:gYear ; :end "1994"^^xsd:gYear |}
{| :start "1980"^^xsd:gYear ; :end "1980"^^xsd:gYear |} .
:vint a :Human ;
rdfs:label "Vinton Cerf" ;
:employer :Google {| :start "2005"^^xsd:gYear |} .
:nobody a :Human ;
rdfs:label "Nobody" ;
:employer :ACME .
""")
Constraining the annotations#
A SHACL 1.2 shapes graph is an ordinary SHACL shapes graph with two extra keywords available on a property shape:
Keyword |
Means |
|---|---|
|
every value of this path must have a reifier |
|
and every reifier must conform to the node shape |
So the constraint reads: an :employer statement must be annotated, and the annotation
must be an employment spell with exactly one :start and one :end.
rudof.read_shacl("""
prefix : <http://example.org/>
prefix sh: <http://www.w3.org/ns/shacl#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
:Researcher a sh:NodeShape ;
sh:targetClass :Human ;
sh:property [
sh:path :employer ;
sh:nodeKind sh:IRI ;
sh:reificationRequired true ;
sh:reifierShape :EmploymentSpell
] .
:EmploymentSpell a sh:NodeShape ;
sh:property [ sh:path :start ; sh:datatype xsd:gYear ;
sh:minCount 1 ; sh:maxCount 1 ] ;
sh:property [ sh:path :end ; sh:datatype xsd:gYear ;
sh:minCount 1 ; sh:maxCount 1 ] .
""")
Note that :EmploymentSpell is a plain SHACL node shape (nothing about it is specific to
1.2). The new part is only what it is applied to: not a node in the graph, but the reifier
of a statement.
report = rudof.validate_shacl()
print("conforms: ", report.conforms)
print("violations:", len(report))
conforms: False
violations: 2
Two failures, and they are the two different ways this can go wrong. Each one names the constraint component that raised it, which is what tells them apart:
for entry in report:
print(f"path: {entry.path}")
print(f"constraint: {entry.constraint_component}")
print(f"message: {entry.message}")
print()
path: http://example.org/end
constraint: http://www.w3.org/ns/shacl#MinCountConstraintComponent
message: "MinCount(1) not satisfied"
path: http://example.org/employer
constraint: http://www.w3.org/ns/shacl#ReifierShapeConstraintComponent
message: "Reification required but no reifier found for triple {triple} with predicate {pred}"
References#
SHACL 1.2 Core - the specification, including
sh:reifierShape.SHACL 1.2 SPARQL - SPARQL-based constraints, whose message interpolation rudof also implements.