orchestrata_ultimate.domaingen.constraints
Reference › orchestrata_ultimate › domaingen › constraints
constraints turns the domain-model intermediate representation into SHACL constraint shapes for Stardog integrity constraint validation (SPEC-105 FR-008). generate_shacl takes a DomainModel from models and renders one node shape per entity (targeting its class) and one property shape per field and relationship, using the private _property helper. A datatype field gets a datatype and a maximum count of 1, plus a minimum count of 1 when required. A relationship gets a class constraint on its target, a maximum count of 1 when single-valued, and a minimum count of 1 when required. Inheritance is expressed with a node constraint on the parent shape, so a subclass also satisfies its parent. Output is deterministic (sorted, no timestamps) so the constraints stay in step with the ontology from ttl and with the generated code. The package and generator import it. See the domaingen narrative.
At a glance
| Source | src/orchestrata_ultimate/domaingen/constraints.py |
| Lines of code | 64 |
| Classes | 0 |
| Functions | 2 |
| Imports (internal) | orchestrata_ultimate.core.traceability, orchestrata_ultimate.domaingen.models, orchestrata_ultimate.domaingen.ttl |
| Imported by | orchestrata_ultimate.domaingen, orchestrata_ultimate.domaingen.generator |
| Tests that import it | none |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
_property traces |
_property(path: str, **constraints: str) -> str |
|
generate_shacl traces |
generate_shacl(model: DomainModel) -> str |
Render SHACL constraint shapes for the whole domain model (Stardog ICV). |
Source
"""IR -> SHACL constraint shapes for Stardog ICV (SPEC-105 FR-008).
Stardog Integrity Constraint Validation uses SHACL. From the canonical IR we emit
a NodeShape per entity (sh:targetClass) and a PropertyShape per field/relationship:
- datatype field -> sh:datatype + sh:maxCount 1 (+ sh:minCount 1 when required)
- object relationship -> sh:class <target> (+ sh:maxCount 1 when single, sh:minCount 1 when required)
- inheritance -> sh:node <ParentShape> so a subclass also satisfies its parent's shape
Deterministic (sorted, no timestamps), so the constraints round-trip with the
ontology and the generated code.
"""
from __future__ import annotations
from orchestrata_ultimate.core.traceability import traces
from orchestrata_ultimate.domaingen.models import DomainModel
from orchestrata_ultimate.domaingen.ttl import _PY_TO_XSD
@traces(spec="105", story="US-105-002", reqs=["REQ-105-049"])
def _property(path: str, *constraints: str) -> str:
return f"sh:property [ sh:path :{path} ; " + " ; ".join(constraints) + " ]"
@traces(spec="105", story="US-105-002", reqs=["REQ-105-049", "REQ-105-050"])
def generate_shacl(model: DomainModel) -> str:
"""Render SHACL constraint shapes for the whole domain model (Stardog ICV)."""
out = [
f"@prefix : <{model.namespace}> .",
"@prefix sh: <http://www.w3.org/ns/shacl#> .",
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .",
"",
"# SHACL constraints generated from the ontology (SPEC-105). Load into",
"# Stardog ICV, e.g.: stardog icv add <db> constraints.ttl",
"# then `stardog icv report <db>` (or enable guard mode to enforce on write).",
"",
]
for entity in sorted(model.all_entities(), key=lambda e: e.name):
clauses = [f"sh:targetClass :{entity.name}"]
if entity.parent:
clauses.append(f"sh:node :{entity.parent}Shape")
for fld in sorted(entity.fields, key=lambda f: f.name):
parts = [
f"sh:datatype {_PY_TO_XSD.get(fld.type, 'xsd:string')}",
"sh:maxCount 1",
]
if fld.required:
parts.insert(1, "sh:minCount 1")
clauses.append(_property(fld.name, *parts))
for rel in sorted(entity.relationships, key=lambda r: r.name):
parts = [f"sh:class :{rel.target}"]
if rel.required:
parts.append("sh:minCount 1")
if not rel.many:
parts.append("sh:maxCount 1")
clauses.append(_property(rel.name, *parts))
shape = (
f":{entity.name}Shape a sh:NodeShape ;\n "
+ " ;\n ".join(clauses)
+ " ."
)
out.append(shape)
out.append("")
return "\n".join(out).rstrip() + "\n"