fix(shaclgen): emit sh:maxCount 0 for zero maximum_cardinality#12
Open
jdsika wants to merge 1 commit into
Open
fix(shaclgen): emit sh:maxCount 0 for zero maximum_cardinality#12jdsika wants to merge 1 commit into
jdsika wants to merge 1 commit into
Conversation
jdsika
added a commit
that referenced
this pull request
May 2, 2026
Apply same fix as fix/shaclgen-maxcount-zero branch to develop. Change truthiness checks to explicit `is not None` comparisons for minimum_cardinality and maximum_cardinality in SHACL generator. See: #12
jdsika
added a commit
that referenced
this pull request
May 2, 2026
Restore shaclgen.py (accidentally emptied) and apply the is-not-None fix for minimum/maximum_cardinality checks. See: #12
abe3f1c to
4f0020c
Compare
7 tasks
ae4b34a to
5544abc
Compare
5544abc to
69f833a
Compare
ce9d0f8 to
5657d56
Compare
5657d56 to
b9760a8
Compare
Python truthiness (`if s.maximum_cardinality:`) treats 0 as falsy, so `maximum_cardinality: 0`, `minimum_cardinality: 0` and `exact_cardinality: 0` emitted no constraint at all. `maximum_cardinality: 0` (SHACL `sh:maxCount 0`, "property must not appear") is the idiomatic way to suppress an inherited slot on a subclass via slot_usage, and owlgen already emits `owl:maxCardinality 0` for it -- so the SHACL and OWL output silently diverged. Use explicit `is not None` checks for minimum_cardinality, maximum_cardinality and exact_cardinality, matching the pattern already used in owlgen.py and docgen.py. Precedence: an explicit minimum_cardinality wins over the `required` fallback in the elif cascade, consistent with owlgen.py (which uses the same `if minimum_cardinality is not None ... elif required` order), so `required: true` + `minimum_cardinality: 0` yields `sh:minCount 0`. That combination is a schema-authoring contradiction (the metamodel documents minimum_cardinality as a multivalued-slot count); the explicit, more specific constraint is emitted. Tests cover maximum_cardinality: 0, exact_cardinality: 0, minimum_cardinality: 0, and the required + minimum_cardinality: 0 precedence case. Signed-off-by: Carlo van Driesten <carlo.van-driesten@bmw.de>
b9760a8 to
c40dfa4
Compare
3 tasks
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix a Python truthiness bug in the SHACL generator that prevents
sh:maxCount 0andsh:minCount 0from being emitted whenmaximum_cardinality: 0orminimum_cardinality: 0is set in a LinkML schema.Problem
In
shaclgen.py, the cardinality checks use Python truthiness:Since
0evaluates asFalsein Python, settingmaximum_cardinality: 0(which should emitsh:maxCount 0meaning "this property MUST NOT appear") produces no output at all.Root Cause
The condition
if s.maximum_cardinality:fails when the value is0because Python treats0as falsy. The correct check isif s.maximum_cardinality is not None:which distinguishes "not set" from "explicitly set to zero".Fix
Changed both checks to use explicit
is not Nonecomparisons:This matches the pattern already used in the OWL generator (
owlgen.pylines 627-640) for the same attributes.Verification
sh:maxCount 0(means "property must not exist on any conforming node")is not Noneand emitsowl:maxCardinality 0is not Nonefor the same field (line 693)sh:maxCount 0appears in generated outputUse Case
This is needed for modeling class hierarchies where subclasses restrict inherited properties. For example,
slot_usagewithmaximum_cardinality: 0is the idiomatic way in LinkML to express "this inherited slot is not applicable on this subclass" --- but without this fix, the SHACL output silently omits the constraint.How was this tested?
ChildWithZeroMaxCardclass totests/linkml/test_generators/input/shaclgen/cardinality.yamltest_zero_maximum_cardinality_emits_maxcountregression test totest_shaclgen.pyis not Nonecheck)Note on
exact_cardinalityThe
elif s.exact_cardinality:branches (lines 174, 184) have the same truthiness issue for the value0. However,exact_cardinality: 0is semantically degenerate (a list with exactly zero items is the same as a forbidden property) and extremely unlikely in practice. This fix focuses on the common and semantically meaningful case. A follow-up can addressexact_cardinalityif needed.Areas of uncertainty
exact_cardinality" above — aligningexact_cardinality: 0handling is deliberately out of scope here and may deserve an upstream issue of its own.Checklist
AI Assistance
If you used AI tools while preparing this PR, you are still the author and responsible for understanding, verifying, and defending your submission. Please engage with reviewers personally rather than through your agent during feedback and revisions. See our AI Covenant for details.