28 KiB
title, description, icon
| title | description | icon |
|---|---|---|
| SHACL Validation | Generate W3C SHACL shapes from OWL ontologies, validate RDF knowledge graphs against structural and semantic constraints, and surface structured violation reports. | shield-check |
What Is SHACL Validation?
SHACL (Shapes Constraint Language) is a standard for validating graph-based data. While an ontology defines the conceptual schema (the "what" exists in your domain), SHACL defines the structural rules and constraints (the "how" it should be structured).
In Semantica, SHACLGenerator produces constraint rules (shapes) based on your ontology, and the public run_shacl_validation function evaluates your actual data against these rules. If a node violates a rule (e.g., missing a required property or using the wrong datatype), a detailed violation report is generated. The historical _run_pyshacl name remains available as a compatibility alias.
Why Use SHACL Validation?
Data validation is critical before running analytics, exporting data, or feeding it into production models. SHACL acts as a data quality gate that ensures your graph data is structurally sound. Use it to catch:
- Missing required properties (e.g., a customer without an email address).
- Datatype mismatches (e.g., a string where a number was expected).
- Cardinality breaches (e.g., a person with three primary addresses).
When To Use / When Not To Use
- When to Use: You have a complex, interconnected knowledge graph and need to validate the relationships and structural integrity of the nodes across the graph. SHACL excels at ensuring that merged, highly connected data conforms to your business rules.
- When NOT to Use: If you are simply validating a flat JSON payload or a single incoming API request. For flat data or single records, use simpler, faster libraries like Pydantic or JSONSchema.
Key Terms Explained
Before diving in, here are a few concepts you'll encounter:
- RDF (Resource Description Framework): A standard way of representing data as a graph. It treats information as connected "triplets" (Subject → Predicate → Object).
- OWL (Web Ontology Language): A language used to build ontologies. It defines the classes and properties that exist in your domain.
- SHACL Shapes: The actual validation rules. A "Shape" targets a specific class in your data (like
Person) and defines the constraints it must follow (like "must have one birthdate"). - Turtle (.ttl): A popular, human-readable file format for storing RDF graph data and SHACL shapes.
Typical Workflow
A typical SHACL validation pipeline follows this lifecycle:
- Ontology: Build an ontology representing your domain.
- SHACL Shapes: Generate shapes from that ontology.
- Data Graph: Prepare your knowledge graph.
- Validation: Validate the knowledge graph against the SHACL shapes.
- Violation Report: Analyze the report for errors.
- Remediation: Fix the data or pipeline and re-validate.
Universal Example: Employee & Department
Let's look at a simple, universally understood example: ensuring every Employee belongs to a Department and has an employee_id.
from semantica.context import ContextGraph
from semantica.ontology import OntologyGenerator, SHACLGenerator, PropertyShape
from semantica.ontology import run_shacl_validation
# 1. Prepare your data graph
graph = ContextGraph()
graph.add_node("emp-1", "Employee", "Alice", employee_id="E001")
graph.add_node("emp-2", "Employee", "Bob") # Missing employee_id, will cause a violation!
# 2. Build the ontology
ontology = (
OntologyGenerator(base_uri="https://company.example.com/ontology/", min_occurrences=1)
.generate_from_graph(graph.to_dict(), name="CompanyOntology")
)
# 3. Generate SHACL Shapes
shacl_gen = SHACLGenerator(base_uri="https://company.example.com/shapes/", severity="Violation")
shacl_graph = shacl_gen.generate(ontology)
# Inject mandatory constraints
BASE = "https://company.example.com/ontology/"
for ns in shacl_graph.node_shapes:
if "Employee" in ns.target_class:
ns.property_shapes.append(
PropertyShape(path=f"{BASE}employee_id", min_count=1, severity="Violation")
)
# Serialize shapes to Turtle
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
# 4. Prepare your RDF data graph
# (For validation, serialize your graph instances to RDF. Here we use a Turtle string.)
data_ttl = """
@prefix ex: <https://company.example.com/ontology/> .
<http://example.org/emp-1> a ex:Employee ;
ex:employee_id "E001" .
<http://example.org/emp-2> a ex:Employee .
"""
# 5. Run Validation
report = run_shacl_validation(data_ttl, shacl_ttl)
# 6. Analyze the Report
print(f"Graph conforms: {report.conforms}")
if not report.conforms:
report.explain_violations() # Populates human-readable explanations
for v in report.violations:
print(f"Violation: {v.explanation}")
Now, let's explore the workflow in more depth.
Step 1 — Build the ontology from your merged graph
SHACL shapes are derived from an ontology. If you already have one from a previous run, skip this step.
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
from semantica.ontology import OntologyGenerator
graph = ContextGraph()
ctx = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=graph,
graph_expansion=True,
)
# Load the merged CTI data — in production this would be your full 12,000-node graph
ctx.store(
[
"APT29 is a Russian state-sponsored threat actor targeting NATO governments.",
"CVE-2024-3400 is a critical vulnerability in PAN-OS exploited by APT29.",
"HAMMERTOSS is a backdoor malware family used by APT29 for C2 over Twitter.",
"PAN-OS is a network operating system developed by Palo Alto Networks.",
],
extract_entities=True,
extract_relationships=True,
)
ontology = (
OntologyGenerator(base_uri="https://cti.example.org/ontology/", min_occurrences=1)
.generate_from_graph(graph.to_dict(), name="CyberOntology")
)
print(f"Classes inferred: {len(ontology.get('classes', []))}")
# Classes inferred: 4 → ThreatActor, Vulnerability, Malware, Platform
Step 2 — Generate SHACL shapes from the ontology
SHACLGenerator produces a SHACLGraph with one NodeShape per OWL class.
from semantica.ontology import SHACLGenerator
shacl_gen = SHACLGenerator(
base_uri="https://cti.example.org/shapes/",
include_inherited=True, # propagate parent-class constraints to sub-classes
severity="Violation", # default severity for all generated shapes
quality_tier="standard", # constraint strictness: "minimal" | "standard" | "strict"
)
shacl_graph = shacl_gen.generate(ontology)
print(f"Node shapes generated: {len(shacl_graph.node_shapes)}")
# Node shapes generated: 4 — one per class
for ns in shacl_graph.node_shapes:
print(f" {ns.target_class} ({len(ns.property_shapes)} property constraints)")
# https://cti.example.org/ontology/ThreatActor (2 property constraints)
# https://cti.example.org/ontology/Vulnerability (3 property constraints)
# https://cti.example.org/ontology/Malware (2 property constraints)
# https://cti.example.org/ontology/Platform (1 property constraint)
The generated shapes tell you what the pipeline observed. They do not yet encode what your domain requires. The next section shows how to inject domain-specific mandatory constraints.
Step 3 — Inject domain constraints
Add mandatory PropertyShape constraints the pipeline cannot infer from data alone.
from semantica.ontology import PropertyShape
BASE = "https://cti.example.org/ontology/"
for node_shape in shacl_graph.node_shapes:
if "Malware" in node_shape.target_class:
# family is required — missing it causes a Violation
node_shape.property_shapes.append(
PropertyShape(
path=f"{BASE}family",
min_count=1,
severity="Violation",
)
)
# attribution_confidence is recommended — missing it causes a Warning
node_shape.property_shapes.append(
PropertyShape(
path=f"{BASE}attribution_confidence",
min_count=1,
datatype="http://www.w3.org/2001/XMLSchema#float",
severity="Warning",
)
)
if "Vulnerability" in node_shape.target_class:
# cvss_score is required by your detection rules
node_shape.property_shapes.append(
PropertyShape(
path=f"{BASE}cvss_score",
min_count=1,
datatype="http://www.w3.org/2001/XMLSchema#float",
severity="Violation",
)
)
if "ThreatActor" in node_shape.target_class:
# name is required; nation_state classification is recommended
node_shape.property_shapes.append(
PropertyShape(path=f"{BASE}name", min_count=1, severity="Violation")
)
node_shape.property_shapes.append(
PropertyShape(path=f"{BASE}nation_state", min_count=1, severity="Warning")
)
# Serialise the final shape graph to Turtle for reuse and version control
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
with open("cti_shapes.ttl", "w") as f:
f.write(shacl_ttl)
print("Shapes written to cti_shapes.ttl")
You can also construct shapes manually from scratch — useful when you need to express constraints the generator would never infer, such as a regex pattern on a CVE ID field:
from semantica.ontology import NodeShape, PropertyShape, SHACLGraph
# Require CVE IDs to match the canonical NIST format
cve_id_shape = NodeShape(
target_class="https://cti.example.org/ontology/Vulnerability",
name="VulnerabilityShape",
closed=False,
severity="Violation",
property_shapes=[
PropertyShape(
path="https://cti.example.org/ontology/cve_id",
min_count=1,
pattern=r"^CVE-\d{4}-\d{4,}$", # e.g. CVE-2024-3400
severity="Violation",
),
],
)
# Inject into the existing shacl_graph or build a standalone SHACLGraph
Step 4 — Run validation and read the report
Serialize the graph to RDF, then run run_shacl_validation against the shapes.
from semantica.ontology import run_shacl_validation
# Prepare your RDF data string (since export_rdf primarily exports structural metadata,
# you typically serialize your custom data graph to Turtle using rdflib or similar).
data_ttl = """
@prefix ex: <https://cti.example.org/ontology/> .
<http://example.org/malware-002> a ex:Malware .
<http://example.org/vuln-003> a ex:Vulnerability ;
ex:cve_id "CVE24-3400" .
"""
# Run SHACL validation
report = run_shacl_validation(
data_ttl,
shacl_ttl,
data_graph_format="turtle",
shacl_format="turtle",
)
# High-level summary
print(f"Conforms : {report.conforms}")
# Conforms : False ← at least one Violation found
print(f"Violations : {report.violation_count}")
# Violations : 3
print(f"Warnings : {report.warning_count}")
# Warnings : 2
print(report.summary())
# Graph does NOT conform: 3 violation(s).
The summary tells you something is wrong. Now drill into the details.
Step 5 — Understand the violations
Each SHACLViolation identifies the node, property path, and fix required.
if not report.conforms:
# Populate plain-English explanations for every violation
report.explain_violations()
# Iterate and print the explanations
for v in report.violations:
print(v.explanation)
# Node <http://example.org/malware-002> is missing required property
# <https://cti.example.org/ontology/family>. At least 1 value(s) are required.
# Node <http://example.org/vuln-003> is missing required property
# <https://cti.example.org/ontology/cvss_score>. At least 1 value(s) are required.
# Node <http://example.org/vuln-003> has value 'CVE24-3400' for
# <https://cti.example.org/ontology/cve_id> which does not match the required pattern.
# Iterate for programmatic triage
for v in report.violations:
print(f"VIOLATION node={v.focus_node}")
print(f" path={v.result_path}")
print(f" rule={v.constraint}")
print(f" msg ={v.message}")
if v.value:
print(f" val ={v.value}")
if v.explanation:
print(f" fix ={v.explanation}")
print()
# Warnings are lower severity — review but do not block
for w in report.warnings:
print(f"WARNING {w.focus_node} {w.result_path} {w.message}")
The output maps directly to remediation tasks: malware-002 needs a family property added; vuln-003 needs a cvss_score and its cve_id corrected to the canonical format.
Step 6 — Auto-remediate common violations
Flag or patch nodes missing required properties, then re-validate to confirm.
# Parse the report into a dict for programmatic processing
report_dict = report.to_dict()
# Collect nodes missing the 'family' property
missing_family = [
v["focus_node"]
for v in report_dict.get("violations", [])
if "family" in (v.get("result_path") or "")
]
print(f"Malware nodes missing 'family': {len(missing_family)}")
# In production: queue these for analyst enrichment or apply a default
# e.g. graph.update_node(node_id, {"family": "UNKNOWN — requires triage"})
# After remediation, re-run validation to confirm the fix
# (re-export the patched graph to Turtle first, then call run_shacl_validation again)
report2 = run_shacl_validation(patched_data_ttl, shacl_ttl)
print(f"Violations after remediation: {report2.violation_count}")
# Violations after remediation: 0
Common Pitfalls
-
Assuming the ontology automatically enforces data quality:
SHACLGeneratorgenerates shapes based on what it observes in the data. If your data is missing a field, the generator won't know it was mandatory unless you explicitly inject the constraint (as shown in Step 3). -
Passing
ContextGraphdirectly to SHACL validators: Therun_shacl_validationfunction expects an RDF string (like Turtle format), not a raw Python dictionary orContextGraphobject. -
Forgetting RDF serialization: You must serialize your graph (often via a temporary file using
export_rdf) before validating it. -
Treating validation as a one-time step: Validation should be integrated as an automated step in your CI/CD pipeline or data ingestion flow, acting as a recurring gatekeeper rather than a one-off script.
-
Ignoring validation reports: A graph that does not conform must be remediated. Failing to review the
violation_countand address the issues negates the purpose of SHACL validation. -
Validating
sh:class/sh:noderange checks on a property that declaresrdfs:rangewith RDFS entailment on: RDFS is an entailment rule, not a constraint. When pyshacl runs withinference="rdfs", it infers the range class onto every object of the property, so class-based constraints on that property can never fail — the report saysconforms: Trueon data that does not conform:from pyshacl import validate from rdflib import Graph data = Graph() data.parse( data=""" @prefix ex: <https://example.org/ns#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . ex:contains rdfs:domain ex:Container ; rdfs:range ex:Item . ex:box a ex:Container ; ex:contains ex:notAnItem . ex:notAnItem a ex:Fish . """, format="turtle", ) shapes = Graph() shapes.parse( data=""" @prefix ex: <https://example.org/ns#> . @prefix sh: <http://www.w3.org/ns/shacl#> . ex:ContainerShape a sh:NodeShape ; sh:targetClass ex:Container ; sh:property [ sh:path ex:contains ; sh:class ex:Item ] . """, format="turtle", ) for inference in ("none", "rdfs"): conforms, _, _ = validate(data, shacl_graph=shapes, inference=inference) print(inference, conforms) # none False <- correct: notAnItem is a Fish, not an Item # rdfs True <- the entailment manufactured the typeMitigations: prefer not to declare
rdfs:rangeon properties you intend to constrain withsh:class; when class membership is the thing under test, run validation without RDFS entailment (inference="none"); or express the check as a constraint the entailment cannot satisfy (for example a literal property constraint). Note the trade-off: with entailment off,sh:targetClassno longer reaches subclasses, so subclass hierarchies need explicit typing or inference-aware target selection. Semantica's ownrun_shacl_validationwrapper already calls pyshacl withinference="none", so this pitfall only bites when callingpyshacl.validatedirectly with entailment enabled. -
Trusting
conforms: Truewithout checking the inference mode: an inference-enabled run can hide the exact violations the shapes were written to catch (see above). Record which inference mode validation ran under alongside the result, and re-run shape sets that containsh:class/sh:nodewith entailment off before treating a pass as authoritative.
Domain Examples
A DoD CTI team enforces STIX-compatible constraints on a threat graph before sharing it with ISAC partners. Every ThreatActor must declare a name and every Vulnerability must carry a cvss_score. The validation gate runs automatically on each nightly sync.
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
from semantica.ontology import OntologyGenerator, SHACLGenerator, PropertyShape
from semantica.ontology import run_shacl_validation
graph = ContextGraph()
ctx = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=graph,
graph_expansion=True,
)
ctx.store([
"APT29 is a Russian state-sponsored threat actor targeting NATO governments.",
"CVE-2024-3400 is a critical PAN-OS vulnerability with CVSS 10.0, exploited by APT29.",
"HAMMERTOSS is a backdoor malware family used by APT29 for C2 over Twitter and GitHub.",
], extract_entities=True, extract_relationships=True)
ontology = (
OntologyGenerator(base_uri="https://cti.dod.mil/ontology/", min_occurrences=1)
.generate_from_graph(graph.to_dict(), name="CTIOntology")
)
shacl_gen = SHACLGenerator(
base_uri="https://cti.dod.mil/shapes/",
include_inherited=True,
severity="Violation",
)
shacl_graph = shacl_gen.generate(ontology)
# STIX-aligned mandatory fields
for ns in shacl_graph.node_shapes:
if "ThreatActor" in ns.target_class:
ns.property_shapes.append(
PropertyShape(path="https://cti.dod.mil/ontology/name", min_count=1, severity="Violation")
)
ns.property_shapes.append(
PropertyShape(path="https://cti.dod.mil/ontology/nation_state", min_count=1, severity="Warning")
)
if "Vulnerability" in ns.target_class:
ns.property_shapes.append(
PropertyShape(path="https://cti.dod.mil/ontology/cvss_score", min_count=1, severity="Violation")
)
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
# Prepare RDF data string
data_ttl = """
@prefix ex: <https://cti.dod.mil/ontology/> .
<http://example.org/apt29> a ex:ThreatActor .
<http://example.org/cve-2024-3400> a ex:Vulnerability .
<http://example.org/hammertoss> a ex:Malware .
"""
report = run_shacl_validation(data_ttl, shacl_ttl)
print(f"CTI graph conforms : {report.conforms}")
print(f"Violations : {report.violation_count}")
print(f"Warnings : {report.warning_count}")
if not report.conforms:
report.explain_violations()
for v in report.violations:
print(v.explanation)
# Blocks the nightly ISAC share until violations are resolved
A SOC team validates zero-trust policy nodes before publishing them to the policy enforcement point. Every Policy node must carry a version (semver format) and an effective_date. A node missing either field is a Violation that blocks publication.
from semantica.context import ContextGraph
from semantica.ontology import OntologyGenerator, SHACLGenerator, PropertyShape
from semantica.ontology import run_shacl_validation
graph = ContextGraph()
graph.add_node("policy-001", "Policy", "MFA Required for Tier-1 Resources",
version="1.0.0", effective_date="2025-01-01", owner="security_team")
graph.add_node("policy-002", "Policy", "Admin Access Requires PAM Checkout")
# policy-002 has no version or effective_date — Violations expected
ontology = (
OntologyGenerator(base_uri="https://zerotrust.corp/ontology/", min_occurrences=1)
.generate_from_graph(graph.to_dict(), name="ZeroTrustOntology")
)
shacl_gen = SHACLGenerator(base_uri="https://zerotrust.corp/shapes/", severity="Violation")
shacl_graph = shacl_gen.generate(ontology)
BASE = "https://zerotrust.corp/ontology/"
for ns in shacl_graph.node_shapes:
if "Policy" in ns.target_class:
ns.property_shapes += [
PropertyShape(
path=f"{BASE}version",
min_count=1,
pattern=r"^\d+\.\d+\.\d+$", # semver
severity="Violation",
),
PropertyShape(
path=f"{BASE}effective_date",
min_count=1,
datatype="http://www.w3.org/2001/XMLSchema#date",
severity="Violation",
),
]
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
# Prepare RDF data string
data_ttl = """
@prefix ex: <https://zerotrust.corp/ontology/> .
<http://example.org/policy-001> a ex:Policy ;
ex:version "1.0.0" ;
ex:effective_date "2025-01-01"^^<http://www.w3.org/2001/XMLSchema#date> .
<http://example.org/policy-002> a ex:Policy .
"""
report = run_shacl_validation(data_ttl, shacl_ttl)
print(f"Policy graph conforms: {report.conforms}")
# Policy graph conforms: False
for v in report.violations:
print(f" VIOLATION: {v.focus_node} — {v.result_path} — {v.message}")
# VIOLATION: ...policy-002 — ...version — Less than 1 values on ...version
# VIOLATION: ...policy-002 — ...effective_date — Less than 1 values on ...effective_date
A clinical informatics team validates trial ontology nodes before loading them into the trial registry system. Every ClinicalTrial node must declare a phase (one of Phase I–IV), a primary_endpoint, and a principal_investigator. Missing any of these blocks registry submission.
from semantica.ontology import LLMOntologyGenerator, SHACLGenerator, PropertyShape
from semantica.ontology import run_shacl_validation
from semantica.export import export_rdf
import tempfile, os
llm_gen = LLMOntologyGenerator(provider="openai", model="gpt-4o")
ontology = llm_gen.generate_ontology_from_text(
"""
A phase II oncology trial studies the efficacy of Compound XR-401 in NSCLC patients.
The trial is led by Principal Investigator Dr. Sarah Chen at Memorial Sloan Kettering.
Primary endpoint: overall response rate at 24 weeks.
Secondary endpoint: progression-free survival.
"""
)
shacl_gen = SHACLGenerator(base_uri="https://purl.obolibrary.org/obo/TRIAL_shapes/")
shacl_graph = shacl_gen.generate(ontology)
TRIAL = "https://purl.obolibrary.org/obo/TRIAL_"
for ns in shacl_graph.node_shapes:
if "ClinicalTrial" in ns.target_class or "Trial" in ns.target_class:
ns.property_shapes += [
PropertyShape(
path=f"{TRIAL}phase",
min_count=1,
in_values=["Phase I", "Phase II", "Phase III", "Phase IV"],
severity="Violation",
),
PropertyShape(
path=f"{TRIAL}primary_endpoint",
min_count=1,
severity="Violation",
),
PropertyShape(
path=f"{TRIAL}principal_investigator",
min_count=1,
severity="Warning",
),
]
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
print(f"SHACL shapes generated — {len(shacl_graph.node_shapes)} node shapes")
# SHACL shapes generated — 5 node shapes
# Validate trial data
# Serialize the ontology as data to validate against the shapes
tmp = tempfile.NamedTemporaryFile(suffix=".ttl", delete=False)
tmp.close()
export_rdf(ontology, tmp.name, format="turtle")
with open(tmp.name) as f:
data_ttl = f.read()
os.unlink(tmp.name)
report = run_shacl_validation(data_ttl, shacl_ttl)
print(f"Trial data conforms: {report.conforms}")
print(f"Warnings : {report.warning_count}")
A credit risk team validates every LoanApplication node against Basel III CRE20 mandatory fields (ltv, pd, lgd, asset_class) before the application enters the credit model. Any missing field is a Violation that rejects the record.
from semantica.context import ContextGraph
from semantica.ontology import OntologyGenerator, SHACLGenerator, PropertyShape
from semantica.ontology import run_shacl_validation
graph = ContextGraph()
graph.add_node("loan-001", "LoanApplication", "Prime mortgage APP-2025-88421",
ltv=0.78, pd=0.023, lgd=0.45, asset_class="CRE")
graph.add_node("loan-002", "LoanApplication", "SME working capital facility",
ltv=0.65)
# loan-002 is missing pd, lgd, asset_class — three Violations expected
ontology = (
OntologyGenerator(base_uri="https://basel.eba.eu/ontology/", min_occurrences=1)
.generate_from_graph(graph.to_dict(), name="BaselRiskOntology")
)
shacl_gen = SHACLGenerator(base_uri="https://basel.eba.eu/shapes/", severity="Violation")
shacl_graph = shacl_gen.generate(ontology)
BASE = "https://basel.eba.eu/ontology/"
for ns in shacl_graph.node_shapes:
if "LoanApplication" in ns.target_class:
for field in ["ltv", "pd", "lgd", "asset_class"]:
ns.property_shapes.append(
PropertyShape(
path=f"{BASE}{field}",
min_count=1,
severity="Violation",
)
)
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
# Prepare RDF data string
data_ttl = """
@prefix ex: <https://basel.eba.eu/ontology/> .
<http://example.org/loan-001> a ex:LoanApplication ;
ex:ltv "0.78" ;
ex:pd "0.023" ;
ex:lgd "0.45" ;
ex:asset_class "CRE" .
<http://example.org/loan-002> a ex:LoanApplication ;
ex:ltv "0.65" .
"""
report = run_shacl_validation(data_ttl, shacl_ttl)
print(f"Loan portfolio conforms: {report.conforms}")
# Loan portfolio conforms: False
print(f"Violations : {report.violation_count}")
# Violations : 3
for v in report.violations:
print(f" [{v.severity}] {v.focus_node.split('/')[-1]} — {v.result_path.split('/')[-1]}")
# [Violation] loan-002 — pd
# [Violation] loan-002 — lgd
# [Violation] loan-002 — asset_class
# Export violation report for regulatory audit trail
report_dict = report.to_dict()
Using SHACL validation as a CI/CD gate
Call this function as a pre-publish gate; exit code 1 blocks the pipeline.
import sys
from semantica.ontology import OntologyGenerator, SHACLGenerator
from semantica.ontology import run_shacl_validation
def validate_before_publish(data_graph_str: str, ontology: dict) -> None:
shacl_gen = SHACLGenerator(base_uri="https://example.org/shapes/")
shacl_graph = shacl_gen.generate(ontology)
shacl_ttl = shacl_gen.serialize(shacl_graph, format="turtle")
report = run_shacl_validation(data_graph_str, shacl_ttl)
if not report.conforms:
print(f"Graph validation FAILED — {report.violation_count} violation(s)")
report.explain_violations()
for v in report.violations:
print(v.explanation)
sys.exit(1)
print(f"Graph validation PASSED ({report.warning_count} warning(s))")
Related Guides
- Ontology Management — generate the OWL ontology that SHACL shapes are derived from
- Reasoning & Rules — complement SHACL structural constraints with logical inference rules
- Export & Serialization — serialize graph data to Turtle/RDF/XML for
run_shacl_validationinput - Conflict Resolution — detect and resolve data conflicts before SHACL validation
- Change Management — version-gate SHACL shapes alongside ontology versions