Python API reference¶
The pyshifty package exposes the engine through PyO3
bindings. Install with pip install pyshifty; import as shifty.
Distribution |
|
Stability |
Stable, except interfaces explicitly marked experimental |
Related |
This page covers validation and inference. The evidence and shape-map interfaces have their own pages: Evidence reference and Shape map reference. The experimental repair API is in Repair reference.
Graph inputs¶
Every entry point accepts the same input type, written GraphInput below:
str— Turtle text, a local file path, or an HTTP(S) URLbytes— Turtle bytespathlib.Path— a file, parsed in Rust from its extensionrdflib.Graph
A list or tuple of these is merged at the triple level first.
pathlib.Path is the fastest file form, because the file never crosses the
Python/Rust boundary as text. Existing string paths use the same behavior: a
directory raises IsADirectoryError, and a missing RDF-looking filename
such as shapes.ttl raises FileNotFoundError. Long or multiline strings
are Turtle and are never probed as paths. This policy applies to every list or
tuple member. HTTP(S) URLs are fetched once and passed as bytes. rdflib.Graph
inputs are serialized as Turtle so namespace bindings required by SHACL-SPARQL
queries and rules survive. URL formats are inferred from the response content
type or the final URL suffix; Turtle is the fallback.
Note
Which graph shapes come from. Passing a single graph (omitting
shacl_graph, or passing None) makes that graph both the shapes and
the data. Passing a separate shapes graph compiles the schema only from
it — SHACL vocabulary sitting in the data graph is ignored, never turned
into constraints. An explicitly empty shapes graph raises ValueError by
default; it is not the same as None. See
Shapes graphs and data graphs.
validate¶
shifty.validate(
data_graph,
shacl_graph=None,
*,
graph_mode="union",
shape_names=None,
infer=True,
in_place=False,
minimum_severity="info",
sort_results=True,
on_unsupported="ignore",
base=None,
) -> tuple[bool, rdflib.Graph, str]
The report model and three-value return form used by pyshacl.validate.
Compare keyword arguments when migrating existing code. Returns (conforms,
report_graph, results_text): the boolean, a W3C sh:ValidationReport as an
rdflib.Graph, and that report rendered for a human. Shifty additionally
rejects an explicitly empty shapes graph by default.
Requires rdflib at call time, since it constructs the report graph.
Argument |
Meaning |
|---|---|
|
The RDF data to validate. |
|
The shapes graph. |
|
|
|
Named shape IRIs to use as top-level entry points. Referenced helper shapes are still evaluated normally. Bare or angle-bracketed. |
|
Run SHACL-AF |
|
Add inferred triples to a caller-owned |
|
|
|
Deterministic ordering of results. Default |
|
|
|
Base IRI for resolving relative IRIs while parsing. |
conforms, report_graph, results_text = shifty.validate(data, shapes)
conforms, report, text = shifty.validate(data, shapes, infer=False)
conforms, report, text = shifty.validate(
data, shapes, shape_names=["http://example.org/PersonShape"],
)
validate_algebra¶
shifty.validate_algebra(data_graph, shacl_graph=None, **same_keywords)
-> AlgebraResult
The algebraic evaluator returns structured objects instead of an RDF report.
Its constraint traversal differs from validate(); see
Result interfaces. It does not require rdflib and is useful
when a program consumes the findings directly.
result = shifty.validate_algebra(data, shapes)
for violation in result.violations:
for reason in violation.reasons:
print(violation.focus_node, reason.constraint_kind, reason.path)
AlgebraResult¶
Field |
Meaning |
|---|---|
|
Whether the run conformed, at the configured |
|
One |
|
The findings rendered for a human. |
Violation¶
Field |
Meaning |
|---|---|
|
The node that failed. |
|
One |
|
The most severe severity among its reasons. |
|
The IRI of the shape that targeted this node, when the shape is a named RDF node. |
|
Stable statement identity. |
|
The algebra id of the statement’s top-level shape. |
Reason¶
Field |
Meaning |
|---|---|
|
A stable enum naming the algebra operator that failed. Branch on this
rather than parsing |
|
The property path checked, where one applies. |
|
The offending value node. Falls back to the focus node when the failure is an absence and there is no offending value. |
|
Engine-generated description. For display, not for matching on. |
|
The shape’s own |
|
This reason’s effective SHACL severity. |
|
The |
|
The specific nested algebra node responsible. Differs from
|
|
For a cardinality failure, how many values along the path satisfied the
qualifier; |
|
The statement this reason belongs to. |
|
Query diagnostic, for a |
ConstraintKind¶
Cardinality, ValueType, ClassMembership, NodeKind,
Constant, Closed, Conjunction, Disjunction, Negation,
Equals, Disjoint, LessThan, LessThanOrEquals, UniqueLang,
Expression, Sparql, Top, Unknown.
These name algebra operators, not SHACL keywords. sh:minCount,
sh:maxCount, and sh:qualifiedMinCount all surface as Cardinality,
because the compiler lowers them to one counting operator — see
How shapes are compiled. Use reason.constraint when you need to
distinguish them.
Reading validation results in code works through consuming these.
infer¶
shifty.infer(data_graph, shapes_graph=None, *,
in_place=False, on_unsupported="ignore", base=None) -> InferResult
Runs SHACL-AF sh:rule entries to a fixed point. Note it takes no
graph_mode.
result = shifty.infer(data, rules)
result.inferred_count # number of newly derived triples
result.diagnostics # non-fatal lowering warnings / unsupported features
result.inferred_ntriples # just the derived delta, as N-Triples text
result.graph_ntriples # original + inferred, as N-Triples text
result.graph() # the same, as an rdflib.Graph
in_place¶
infer(), validate(), validate_algebra(), and the two
PreparedValidator methods accept in_place=True, which writes the triples
SHACL-AF inference derived straight into a caller-owned rdflib.Graph passed
as the data graph, rather than returning a separate copy. Only the derived delta
crosses back from Rust, and a triple derived about a blank node lands on the
blank node the caller’s graph already holds. It requires an rdflib.Graph
input and is off by default, so existing calls are unaffected. See
Run SHACL-AF inference and Validate a graph.
PreparedValidator¶
shifty.PreparedValidator(shacl_graph, *, base=None)
Parses, lowers, normalizes, and plans a shapes graph once, for reuse across many data graphs. This is the right tool whenever the schema is fixed and the data changes, which is most batch and service workloads.
An explicitly empty shapes graph raises ValueError.
validator = shifty.PreparedValidator(shapes)
validator.diagnostics # non-fatal lowering diagnostics
conforms, report, text = validator.validate(data)
result = validator.validate_algebra(data, infer=False)
validate and validate_algebra take the data graph positionally and
accept graph_mode, shape_names, infer, in_place, minimum_severity,
sort_results, and on_unsupported as keywords, with the same meanings as
the module-level functions.
PreparedValidator.witnesses¶
validator.witnesses(data_graph, *, key_path=None, graph_mode="union",
infer=True, on_unsupported="ignore")
-> list[PropertyWitness]
The inverse of validation. For every focus node that conforms to a
target-bearing node shape, it returns the values each sh:property shape’s
sh:path resolved to — so a SHACL profile can double as an extraction
schema.
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 ;
] .
ex:OutsideAirTempRole zea:roleName "outsideAirTemp" .
"""
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>']
key_path is a SPARQL 1.1 property path — sequence /, alternation |,
inverse ^, and the Kleene forms *, +, ? — evaluated from each
sh:property shape’s own node over the shapes graph. The example key is not
a direct annotation on the property shape; it is one hop further, through a
role-descriptor node, which a bare predicate lookup could not reach. A direct
annotation would be key_path="zea:roleName", and a descriptor pointing at
the property shape would be key_path="^zea:describes/zea:roleName".
Prefixes resolve against the shapes document’s @prefix declarations.
|
Meaning |
|---|---|
|
The focus node that conformed. |
|
The node shape it conformed to. |
|
The lexical value reached by |
|
Deduped |
For a richer version of the same idea, with typed keys, typed terms, and partial bindings for non-conforming nodes, see Shape map reference.
Diagnostics¶
PreparedValidator, EvidenceSession, RepairSession,
InferResult, and — since 0.5 — the AlgebraResult returned by
validate_algebra() all expose .diagnostics for non-fatal lowering
warnings and unsupported features.
validate() and PreparedValidator.validate() return the
(conforms, report_graph, results_text) tuple, which has
nowhere to put a diagnostic. They instead emit one
shifty.ShaclDiagnosticWarning per diagnostic, so a rule that could not run
does not read as a clean pass:
import warnings
with warnings.catch_warnings(record=True) as raised:
warnings.simplefilter("always")
conforms, report, text = shifty.validate(data, shapes)
# Or silence them:
warnings.filterwarnings("ignore", category=shifty.ShaclDiagnosticWarning)
Invalid shapes diagnostics are different: a malformed SPARQL query or an
unresolved query prefix raises ValueError while the operation is prepared.
They never leave an API call running with that constraint or rule omitted.