Knowledge graphs · Python · Engineering
SHACL with Python: validating engineering knowledge graphs
How can you check whether an engineering knowledge graph is missing sources or evidence? SHACL can automate selected structural checks. This guide provides a small, executable Turtle and Python example: preparing an engineering decision for review, testing deliberately flawed data and interpreting a passing report correctly.
It develops the quality-rule idea introduced in the guide to knowledge graphs in machine vision. All identifiers and data are invented teaching examples, not client or employer information. The aim is a traceable intake check, not automatic engineering approval.
Define the validation contract first
Our example answers a narrow question: is a decision sufficiently linked to enter a technical review? We require exactly one “ReadyForReview” status, at least one source identifier and at least one evidence identifier. This is an original modelling proposal, not an industry standard.
We deliberately validate a review export, not the entire decision lifecycle. Drafts may remain incomplete in the working system. If included in this export, however, they should fail. Applying the same rules indiscriminately to the entire collection would otherwise flag legitimate draft states.
SHACL separates the data graph from rules in a shapes graph. Targets select the nodes to check; constraints define permitted values. sh:minCount requires values, sh:maxCount limits their number and sh:nodeKind sh:IRI requires resource identifiers rather than text literals. The normative reference is the W3C Recommendation [1].
An IRI is an identifier, not evidence of a successful document retrieval. RDF distinguishes resource identifiers from literals; a string containing an address is not the same as the corresponding IRI. See RDF 1.1 [2] for these concepts.
A complete SHACL example with Python
Save the next three blocks as data.ttl, shapes.ttl and validate_graph.py in a separate working directory. You need Python, RDFLib and pySHACL. This example was checked with Python 3.14, RDFLib 7.6.0 and pySHACL 0.40.1. For reproducible experiments use those versions in a separate environment; do not update existing project environments without checking compatibility.
1. Data graph: a decision with references
@prefix ex: <https://example.org/engineering/> .
ex:decision-17 a ex:Decision ;
ex:status ex:ReadyForReview ;
ex:source ex:document-4 ;
ex:evidence ex:test-9 .
ex: is a freely chosen example vocabulary. Neither the document nor the test is described in detail here. This deliberately small dataset exposes the distinction between “a reference exists” and “the evidence is adequate”.
2. Shapes graph: the review-export contract
@prefix ex: <https://example.org/engineering/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
ex:ReviewShape a sh:NodeShape ;
sh:targetClass ex:Decision ;
sh:targetSubjectsOf ex:status ;
sh:class ex:Decision ;
sh:property [
sh:path ex:status ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:hasValue ex:ReadyForReview
] ;
sh:property [
sh:path ex:source ;
sh:minCount 1 ;
sh:nodeKind sh:IRI
] ;
sh:property [
sh:path ex:evidence ;
sh:minCount 1 ;
sh:nodeKind sh:IRI
] .
The two targets form a union: nodes classified as ex:Decision and nodes with ex:status are selected. Together with sh:class, this catches a status record without the required classification. sh:hasValue and the cardinality constraints jointly require exactly the intended status. See targets and constraints [1].
This safeguard has a clear boundary: if both type and status are missing, our targets do not select that record. Even an empty export therefore needs a separate completeness check against expected identifiers. Adding value constraints does not repair gaps in the selection of records to validate.
3. Run validation and return the result
from rdflib import Graph
from pyshacl import validate
data = Graph().parse("data.ttl", format="turtle")
shapes = Graph().parse("shapes.ttl", format="turtle")
conforms, report_graph, report_text = validate(
data,
shacl_graph=shapes,
inference="none",
meta_shacl=True,
advanced=False,
allow_infos=False,
allow_warnings=False,
)
print(report_text)
raise SystemExit(0 if conforms else 1)
Run python validate_graph.py from this directory. The unchanged example should pass. A constraint violation makes the script exit with status 1. It does not overwrite input files. pySHACL returns conformance, an RDF results graph and a readable report; meta_shacl=True also checks the shapes graph. The options are documented in the official project documentation [3].
inference="none" keeps the experiment free from additional RDFS/OWL inference. This is an intentionally limited test configuration. If an ontology or different inference options are introduced later, validate that exact configuration again. Syntax or execution errors are failed checks too, not approval.
Test more than the passing case
Apply each change below separately to a fresh copy of the example. This makes it possible to connect a result to its cause. These counterexamples were run automatically; they confirm this teaching example's behaviour, not the quality of a real engineering dataset.
- Remove the source reference: must fail. This checks whether a missing required value is detected.
- Replace the evidence with
"test-9": must fail. A text literal does not meet the IRI requirement. - Add a second status: must fail. Two distinct values violate the agreed uniqueness requirement.
- Remove only the type, keeping the status: must fail. The second target prevents an unnoticed exclusion here.
- Remove both type and status: passes in this example, even though references remain. This deliberately demonstrates the coverage gap.
- Use an unresolved evidence IRI: passes. Our rules require an identifier, not its content or availability.
When investigating a failure, start with the affected node, then the property path and violated rule. These are elements of the SHACL validation report [1]. A useful domain-facing message should also explain which team needs to supply which data. Forwarding raw validator text to everyone is rarely a workable instruction.
Turn the example into a reliable intake check
For practical use I recommend four separate validation layers. This is an engineering workflow proposal, not an additional claim made by the standard:
- Coverage: compare expected decision identifiers from the export request with identifiers actually checked. Do not silently accept missing or empty deliveries.
- Structure: check required values, permitted states and links. Version at least one valid case and several deliberately invalid cases for each contract.
- Evidence: assess document version, access, applicability and test conditions separately. An existing reference must not automatically mean “confirmed”.
- Technical review: a responsible person decides whether the evidence supports the specific claim. Open questions remain explicitly open.
The validation record should include the data snapshot, shapes version, library versions, options and result. When rules change, run old and new rules against the same test collection. Otherwise it is unclear whether a different failure count reflects better data or weaker validation. Rules and data should be traceable together while retaining distinct ownership.
For a machine-vision decision, evidence might later reference a documented comparison of segmentation methods. Review must still establish whether the images, illumination and acceptance criteria match the current task. SHACL does not perform that engineering assessment.
Frequently asked questions
Does a passing report prove the knowledge graph is correct?
No. It only means that the data actually selected meets the rules actually executed. Our example even passes with evidence that has no content description. Coverage and evidence assessment are therefore separate checks before using an engineering claim.
Should I immediately forbid all properties that are not defined?
Not for this starting point. First stabilise a small set of important handover rules. Additional metadata such as editing notes should not cause a failure here merely because it is absent from the small review contract.
Can this approve an AI-generated graph?
Not on its own. Plausibly structured claims can still be technically wrong. A useful workflow checks structure automatically, compares claims with their actual sources and escalates unsupported relationships to technical review.
Primary sources
- W3C: Shapes Constraint Language (SHACL), Recommendation
- W3C: RDF 1.1 Concepts and Abstract Syntax
- RDFLib: pySHACL – official documentation and source code
Sources checked on 25 September 2026. Vocabulary, example data, counterexamples and adoption workflow are original teaching and modelling proposals. No operational data or project results are disclosed.
