concepts.trr379.de/patches/shaclgen_annotations.diff
Stephan Heunis b1114a6d47
Some checks failed
Codespell / Check for spelling errors (pull_request) Successful in 20s
Validate examples and verify unmodified conversion / lint (pull_request) Failing after 20s
Model checks / lint (pull_request) Failing after 1m10s
Codespell / Check for spelling errors (push) Successful in 20s
Model checks / lint (push) Failing after 1m6s
Validate examples and verify unmodified conversion / lint (push) Failing after 54s
sync shaclgen patch with latest from datalad-concepts
2025-10-08 22:22:53 +02:00

102 lines
5 KiB
Diff

This patches shaclgen.py to allow improved handling of slot annotations, sh:order, and
prefixes. Firstly, it introduces the --exclude-order flag that allows to completely
disable the automatic assignment of the sh:order field to exported property shapes. See:
https://github.com/psychoinformatics-de/shacl-vue/issues/107#issuecomment-2866073117
Secondly, if the --exclude-order flag is not used, the patch prevents creation of faulty
sh:order lists by prioritizing sh:order provided via annotation, since sh:order should
just be a single numerical value. See for context:
https://github.com/linkml/linkml/pull/2111#issuecomment-2116208229
Then, for a similar reason as before, it only sets sh:path automatically if the value
has not already been set via annotation.
Lastly, it introduces the --include-stripped-prefixes flag which allows inclusion of
prefixes that are unused and hence stripped during rdflib serialization. This makes it
possible to specify namespaces in the prefixes section of a schema that should propagate
to the exported SHACL for downstream use.
--- shaclgen.py
+++ shaclgen.py
@@ -30,6 +30,10 @@ class ShaclGenerator(Generator):
"""parameterized suffix to be appended. No suffix per default."""
include_annotations: bool = False
"""True means include all class / slot / type annotations in generated Node or Property shapes"""
+ exclude_order: bool = False
+ """True means exclude the automatic addition of sh:order to Property shapes"""
+ include_stripped_prefixes: bool = False
+ """True means include the unused prefixes that are typically stripped during rdflib graph serialization"""
exclude_imports: bool = False
"""If True, elements from imported ontologies won't be included in the generator's output"""
generatorname = os.path.basename(__file__)
@@ -53,6 +57,8 @@ class ShaclGenerator(Generator):
def serialize(self, **args) -> str:
g = self.as_graph()
data = g.serialize(format="turtle" if self.format in ["owl", "ttl"] else self.format)
+ if self.include_stripped_prefixes:
+ data = self._add_stripped_prefixes(data)
return data
def as_graph(self) -> Graph:
@@ -114,8 +120,6 @@ class ShaclGenerator(Generator):
g.add((pnode, p, Literal(v)))
prop_pv(SH.path, slot_uri)
- prop_pv_literal(SH.order, order)
- order += 1
prop_pv_literal(SH.name, s.title)
prop_pv_literal(SH.description, s.description)
# minCount
@@ -223,6 +227,18 @@ class ShaclGenerator(Generator):
default_value = ifabsent_processor.process_slot(s, c)
if default_value:
prop_pv(SH.defaultValue, default_value)
+
+ # Add the order automatically unless the --exclude-order flag was supplied
+ if not self.exclude_order:
+ # sh:order may already have been added to the graph via annotations
+ if not (pnode, SH.order, None) in g:
+ prop_pv_literal(SH.order, order)
+ order += 1
+
+ # sh:path may already have been added to the graph via annotations
+ if not (pnode, SH.path, None) in g:
+ prop_pv(SH.path, slot_uri)
+
return g
@@ -337,6 +353,17 @@ class ShaclGenerator(Generator):
Collection(g, list_node, list(ignored_properties))
return list_node
+
+ def _add_stripped_prefixes(self, data):
+ # First remove all prefix lines from data string
+ lines = [ln for ln in data.splitlines() if not ln.strip().startswith("@prefix")]
+ # Now generate alphabetized lines from all prefixes:
+ prefix_lines = [
+ f"@prefix {str(pfx.prefix_prefix)}: <{pfx.prefix_reference}> ."
+ for pfx in sorted(self.schema.prefixes.values(), key=lambda p: str(p.prefix_prefix).lower())
+ ]
+ # return the new prefix lines joined with prefix-stripped data
+ return "\n".join(prefix_lines + [""] + lines)
def add_simple_data_type(func: Callable, r: ElementName) -> None:
@@ -366,6 +393,19 @@ def add_simple_data_type(func: Callable, r: ElementName) -> None:
show_default=True,
help="Use --include-annotations to include annotations of slots, types, and classes in the generated SHACL shapes.",
)
+@click.option(
+ "--exclude-order/--include-order",
+ default=False,
+ show_default=True,
+ help="Use --exclude-order to exclude sh:order from being added automatically to generated SHACL property shapes. "
+ "Default behavior adds order. This flag has no influence on sh:order being added via annotations.",
+)
+@click.option(
+ "--include-stripped-prefixes/--exclude-stripped-prefixes",
+ default=False,
+ show_default=True,
+ help="Use --include-stripped-prefixes to include prefixes that are unused and hence stripped during rdflib serialization.",
+)
@click.option(
"--exclude-imports/--include-imports",
default=False,