Python API Reference¶
Install¶
pip install pyshifty # Python 3.9+
import shifty # the package imports as `shifty`
Graph inputs throughout the API can be a str (Turtle text), bytes,
pathlib.Path, or an rdflib.Graph.
Note
Where shapes are read from. If you pass a single graph (omit shapes
or pass None), shifty reads both the shape definitions and the data from
that one graph. If you pass a separate shapes graph, the schema is
compiled only from the shapes graph — SHACL vocabulary that happens to
sit in the data graph is ignored, never compiled into constraints. See
Shapes and data graphs for the cross-frontend rule.
validate¶
The primary validation entry point is compatible with the pyshacl interface:
conforms, report_graph, results_text = shifty.validate(data, shapes)
data— the data graph to validateshapes— the SHACL shapes graph (optional; if omitted, shapes are read fromdata)Returns:
(bool, rdflib.Graph, str)— conforms flag, W3Csh:ValidationReportgraph, human-readable summary
shapes = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:PersonShape a sh:NodeShape ;
sh:targetClass ex:Person ;
sh:property [
sh:path ex:name ;
sh:minCount 1 ;
sh:datatype xsd:string ;
] ;
sh:property [
sh:path ex:age ;
sh:maxCount 1 ;
sh:datatype xsd:integer ;
] .
"""
data = """
@prefix ex: <http://example.org/> .
ex:Alice a ex:Person ; ex:name "Alice" ; ex:age 30 .
ex:Bob a ex:Person .
"""
conforms, report_graph, results_text = shifty.validate(data, shapes)
# conforms → False
# report_graph → rdflib.Graph with sh:ValidationReport
# results_text → human-readable summary
Keyword arguments¶
Argument |
Description |
|---|---|
|
|
|
Optional list of named shape IRIs to use as top-level validation entry points; referenced helper shapes are still evaluated normally |
|
|
# Skip inference, validate data only
conforms, report, text = shifty.validate(data, shapes, infer=False)
# Use data ∪ shapes for path/SPARQL evaluation
conforms, report, text = shifty.validate(data, shapes, graph_mode="union")
# Validate only selected named shapes as entry points
conforms, report, text = shifty.validate(
data,
shapes,
shape_names=["http://example.org/PersonShape"],
)
Embedded shapes¶
When you pass two graphs, the schema is compiled only from shapes; shape
definitions in data are not read. To validate against shapes that are
embedded in the data graph, omit the second argument or pass None so the
single graph is used for both roles:
conforms, report, text = shifty.validate("combined.ttl")
conforms, report, text = shifty.validate(combined_graph, None)
Do not pass an empty rdflib.Graph() for this case — an empty graph is treated
as an explicit empty shapes graph and will produce no violations.
To validate data against shapes that live in both a dedicated shapes graph
and the data graph, union them into the shapes argument yourself (shapes
accepts a list of graphs, unioned before evaluation) — shifty will not read
shapes from the data side automatically.
validate_algebra¶
Returns structured Violation objects instead of an RDF report graph:
result = shifty.validate_algebra(data, shapes)
print(result.conforms) # False
for v in result.violations:
print(v.focus_node) # IRI of the failing focus node
print(v.statement_id) # stable statement id
print(v.constraint_id) # statement-level algebra id
print(v.shape_name) # shape that targeted this node (if any)
for r in v.reasons:
print(r.message) # human-readable failure description
print(r.path) # property path checked, if applicable
print(r.value) # the offending value node
print(r.constraint_kind) # ConstraintKind.Cardinality, Sparql, ...
print(r.constraint.render)
Accepts the same infer= and graph_mode= keyword arguments as validate().
Algebraic provenance¶
validate_algebra() exposes the algebraic operator that produced each
validation cause:
Reason.constraintA
Constraintobject for the originating algebra node. It hasid,kind,render,definition, andjsonfields.Reason.constraint_kindA stable enum such as
ConstraintKind.Cardinality,ConstraintKind.ClassMembership,ConstraintKind.ValueType,ConstraintKind.NodeKind,ConstraintKind.Conjunction,ConstraintKind.Disjunction, orConstraintKind.Sparql. Use this for branching instead of parsing messages or Rust class names.Violation.statement_id/Violation.constraint_idThe top-level statement identity. This is the stable key shared with
RepairSession.witnesses().Reason.constraint_idThe specific nested algebra node that produced the validation cause. This may differ from
Violation.constraint_idwhen the violated shape is a conjunction, disjunction, or another composed constraint.
The usual validation-to-repair join is:
result = shifty.validate_algebra(data, shapes, infer=False)
session = shifty.RepairSession(shapes, data, infer=False)
witnesses = {
(w.focus, w.statement_id, w.constraint_id): w
for w in session.witnesses()
}
for v in result.violations:
witness = witnesses.get((v.focus_node, v.statement_id, v.constraint_id))
for r in v.reasons:
if r.constraint_kind == shifty.ConstraintKind.Cardinality:
print("count constraint:", r.constraint.definition)
elif r.constraint_kind == shifty.ConstraintKind.Sparql:
print("SPARQL diagnostic:", r.sparql_diagnostic)
if witness is not None:
print(witness.repair_tree().explain())
PreparedValidator¶
For repeated validation against the same shapes graph, compile once:
validator = shifty.PreparedValidator(shapes)
# Validate many data graphs against the compiled shapes
for data_file in data_files:
conforms, report, text = validator.validate(data_file)
# Or use the structured result form
result = validator.validate_algebra(data, infer=False)
File inputs¶
import pathlib
conforms, report, text = shifty.validate(
pathlib.Path("data.ttl"),
pathlib.Path("shapes.ttl"),
)
pathlib.Path inputs are parsed directly by Rust. rdflib.Graph inputs
use N-Triples for the Python-to-Rust transfer to avoid rdflib’s slower Turtle
serializer.
Property witnesses¶
validate / validate_algebra report violations. PreparedValidator.witnesses()
is their inverse: for every focus node that conforms to a target/profile node
shape, it returns the values each sh:property shape’s sh:path resolved to.
Useful when a SHACL profile doubles as an extraction schema — e.g. disambiguating
several same-typed sensors on a piece of equipment via sh:qualifiedValueShape.
shapes = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix zea: <http://example.org/zea#> .
@prefix ex: <http://example.org/> .
ex:VavProfile a sh:NodeShape ;
sh:targetClass ex:Vav ;
sh:property [
zea:role ex:OutsideAirTempRole ;
sh:path ex:hasPoint ;
sh:qualifiedValueShape [ sh:hasValue ex:oat ] ;
sh:qualifiedMinCount 1 ;
sh:qualifiedMaxCount 1 ;
] ;
sh:property [
zea:role ex:ReturnAirTempRole ;
sh:path ex:hasPoint ;
sh:qualifiedValueShape [ sh:hasValue ex:rat ] ;
sh:qualifiedMinCount 1 ;
sh:qualifiedMaxCount 1 ;
] .
ex:OutsideAirTempRole zea:roleName "outsideAirTemp" .
ex:ReturnAirTempRole zea:roleName "returnAirTemp" .
"""
data = """
@prefix ex: <http://example.org/> .
ex:vav1 a ex:Vav ; ex:hasPoint ex:oat, ex:rat, ex:sat, ex:mat .
"""
validator = shifty.PreparedValidator(shapes)
for w in validator.witnesses(data, key_path="zea:role/zea:roleName"):
print(w.focus, w.key, w.values)
# <http://example.org/vav1> outsideAirTemp ['<http://example.org/oat>']
# <http://example.org/vav1> returnAirTemp ['<http://example.org/rat>']
key_path is a SPARQL 1.1 property path expression — sequence /, alternation
|, inverse ^, and the Kleene forms */+/? are all supported —
evaluated from each sh:property shape’s own node, over the shapes graph, to
produce a stable key. The key above isn’t a direct annotation on the property
shape — it lives one hop further away, through an intermediate role-descriptor
node — which a bare predicate lookup couldn’t reach but a path can. A direct
annotation (zea:roleName "outsideAirTemp" right on the sh:property shape)
would just be key_path="zea:roleName"; a descriptor that points at the
property shape instead of the other way around would use an inverse hop,
key_path="^zea:describes/zea:roleName". Prefixes resolve against the shapes
document’s declared @prefixes.
Each PropertyWitness has:
Attribute |
Description |
|---|---|
|
The focus node that conformed |
|
The node shape (application profile) it conformed to |
|
The lexical value reached by |
|
The deduped |
infer¶
Run SHACL-AF sh:rule entries to a fixed point:
result = shifty.infer(data, rules)
print(result.inferred_count) # number of newly derived triples
g = result.graph() # rdflib.Graph with original + inferred data
rules = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
ex:RectangleShape a sh:NodeShape ;
sh:targetClass ex:Rectangle ;
sh:rule [
a sh:TripleRule ;
sh:subject sh:this ;
sh:predicate ex:area ;
sh:object [ sh:path ex:width ] ;
] .
"""
data = """
@prefix ex: <http://example.org/> .
ex:r1 a ex:Rectangle ; ex:width 3 ; ex:height 2 .
"""
result = shifty.infer(data, rules)
print(result.inferred_count) # 1
g = result.graph() # graph contains ex:r1 ex:area 3 .
If rules are embedded in the data graph, omit the second argument or pass None:
result = shifty.infer(combined_data_and_rules)
result = shifty.infer(combined_data_and_rules, None)
Passing rdflib.Graph() as the second argument means “run with an explicit
empty rules graph” — no embedded rules will be parsed.
graph_mode¶
Both validate() and validate_algebra() accept a graph_mode keyword argument
that controls which triples are visible to path traversal and SPARQL evaluation:
Mode |
Behaviour |
|---|---|
|
Focus nodes come from the data graph; path traversal and SPARQL use data only |
|
Focus nodes from data; paths and SPARQL use data ∪ shapes |
|
Focus nodes and evaluation both use data ∪ shapes |
infer() does not accept graph_mode.
shape_names¶
Both validate() and validate_algebra() accept shape_names=[...] to
validate only selected named shapes as top-level entry points:
result = shifty.validate_algebra(
data,
shapes,
shape_names=["http://example.org/PersonShape"],
)
Only target-bearing statements owned by the selected named shapes are used as
entry points. Dependencies referenced from those entries are still evaluated
normally, including helper shapes reached through sh:node, sh:property,
qualified value shapes, and boolean shape expressions. Shape names may be
passed as bare IRIs or wrapped in angle brackets.
For full API documentation including the RepairSession interface, see
docs.rs/shifty-engine.