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

Dependency diagram orchestrata_ultimate.domaingen.constraints

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

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/src/orchestrata_ultimate/domaingen/constraints.py

"""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"