RDFLib / pySHACL

A Python validator for SHACL
Apache License 2.0
241 stars 63 forks source link

Is a shape (whether node or property) restricted to one instance of sh:not? #217

Closed ajnelson-nist closed 7 months ago

ajnelson-nist commented 7 months ago

I've encountered something pyshacl is telling me is not allowed, but I'm having a hard time finding this in the spec and SHACL-SHACL.

Given this ontology graph:

@prefix ex: <http://example.org/ontology/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .

ex:ClassA
    a owl:Class ;
    owl:disjointWith ex:ClassC ;
    .

ex:ClassB
    a owl:Class ;
    owl:disjointWith ex:ClassC ;
    .

ex:ClassC
    a owl:Class ;
    owl:disjointWith
        ex:ClassA ,
        ex:ClassB
        ;
    .

And given this data graph:

@prefix ex: <http://example.org/ontology/> .
@prefix kb: <http://example.org/kb/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

kb:Thing-1
    a
        ex:ClassA ,
        ex:ClassB
        ;
    rdfs:comment "This individual is consistent per OWL and should validate with SHACL."@en ;
    .

kb:Thing-2
    a
        ex:ClassA ,
        ex:ClassC
        ;
    rdfs:comment "This individual is inconsistent per OWL and should not validate with SHACL."@en ;
    .

kb:Thing-3
    a
        ex:ClassB ,
        ex:ClassC
        ;
    rdfs:comment "This individual is inconsistent per OWL and should not validate with SHACL."@en ;
    .

I would like to write a SHACL shapes graph that implements the OWL disjointedness checks. If I didn't have class B and just had one disjointedness to concern myself with, I would try this graph:

@prefix ex: <http://example.org/ontology/> .
@prefix sh-ex: <http://example.org/shapes/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .

sh-ex:ClassC-shape
    a sh:NodeShape ;
    sh:not [
        a sh:NodeShape ;
        sh:class ex:ClassA ;
    ] ;
    sh:targetClass ex:ClassC ;
    .

And this gives me the validation report I expect - kb:Thing-2 alone is flagged:

pyshacl \
      --metashacl \
      --shacl try-shapes-one.ttl \
      --ont-graph try-ontology.ttl \
      try-data.ttl
Validation Report
Conforms: False
Results (1):
Constraint Violation in NotConstraintComponent (http://www.w3.org/ns/shacl#NotConstraintComponent):
    Severity: sh:Violation
    Source Shape: sh-ex:ClassC-shape
    Focus Node: kb:Thing-2
    Value Node: kb:Thing-2
    Message: Node kb:Thing-2 conforms to shape [ rdf:type sh:NodeShape ; sh:class ex:ClassA ]

But, if I try one naïve maneuver and tie another anonymous node shape via sh:not ...

@prefix ex: <http://example.org/ontology/> .
@prefix sh-ex: <http://example.org/shapes/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .

sh-ex:ClassC-shape
    a sh:NodeShape ;
    sh:not
        [
            a sh:NodeShape ;
            sh:class ex:ClassA ;
        ] ,
        [
            a sh:NodeShape ;
            sh:class ex:ClassB ;
        ]
        ;
    sh:targetClass ex:ClassC ;
    .

... I get an interesting message from pyshacl, which seems to be sourced from here (in today's code-state):

pyshacl \
      --metashacl \
      --shacl try-shapes-direct.ttl \
      --ont-graph try-ontology.ttl \
      try-data.ttl
ConstraintLoadError: NotConstraintComponent must have at most one sh:not predicate.
For reference, see https://www.w3.org/TR/shacl/#NotConstraintComponent
Validator encountered a Constraint Load Error:
NotConstraintComponent must have at most one sh:not predicate.
For reference, see https://www.w3.org/TR/shacl/#NotConstraintComponent

I have other ways to satisfy my disjointedness-review goal (and I'll note those in a follow-on comment), but I'm interested in seeing the source of this constraint on sh:not usage.

The referenced specification section (4.6.1) neither demonstrates a second sh:not, nor makes any statement on the predicate's cardinality.

From the SHACL-SHACL graph (linked in Section C), all of the occurrences of sh:not in that file fit at once on my computer screen, and nothing among the occurrences appears to assign an upper bound on how many times it can be used. (And I concluded likewise when I ran the graph through a Turtle syntax normalizer, in case the line breaks and bracket wrapping were throwing me.)

Is this max-cardinality-1 constraint on sh:not a bit more than is in the spec?

ajnelson-nist commented 7 months ago

As an aside, I am able to implement this C-not-A, C-not-B disjointedness in two other ways.

Using a union ...

@prefix ex: <http://example.org/ontology/> .
@prefix sh-ex: <http://example.org/shapes/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .

sh-ex:ClassC-shape
    a sh:NodeShape ;
    sh:not [
        a sh:NodeShape ;
        sh:or (
            [
                a sh:NodeShape ;
                sh:class ex:ClassA ;
            ]
            [
                a sh:NodeShape ;
                sh:class ex:ClassB ;
            ]
        ) ;
    ] ;
    sh:targetClass ex:ClassC ;
    .

... flags the individuals I expect:

pyshacl \
      --metashacl \
      --shacl try-shapes-or.ttl \
      --ont-graph try-ontology.ttl \
      try-data.ttl
Validation Report
Conforms: False
Results (2):
Constraint Violation in NotConstraintComponent (http://www.w3.org/ns/shacl#NotConstraintComponent):
    Severity: sh:Violation
    Source Shape: sh-ex:ClassC-shape
    Focus Node: kb:Thing-2
    Value Node: kb:Thing-2
    Message: Node kb:Thing-2 conforms to shape [ rdf:type sh:NodeShape ; sh:or ( [ rdf:type sh:NodeShape ; sh:class ex:ClassA ] [ rdf:type sh:NodeShape ; sh:class ex:ClassB ] ) ]
Constraint Violation in NotConstraintComponent (http://www.w3.org/ns/shacl#NotConstraintComponent):
    Severity: sh:Violation
    Source Shape: sh-ex:ClassC-shape
    Focus Node: kb:Thing-3
    Value Node: kb:Thing-3
    Message: Node kb:Thing-3 conforms to shape [ rdf:type sh:NodeShape ; sh:or ( [ rdf:type sh:NodeShape ; sh:class ex:ClassA ] [ rdf:type sh:NodeShape ; sh:class ex:ClassB ] ) ]

Using indirect node shapes ...

@prefix ex: <http://example.org/ontology/> .
@prefix sh-ex: <http://example.org/shapes/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .

sh-ex:ClassC-shape
    a sh:NodeShape ;
    sh:node
        [
            a sh:NodeShape ;
            sh:not [
                a sh:NodeShape ;
                sh:class ex:ClassA ;
            ] ;
        ] ,
        [
            a sh:NodeShape ;
            sh:not [
                a sh:NodeShape ;
                sh:class ex:ClassB ;
            ] ;
        ]
        ;
    sh:targetClass ex:ClassC ;
    .

... flags the individuals I expect:

pyshacl \
      --metashacl \
      --shacl try-shapes-indirect.ttl \
      --ont-graph try-ontology.ttl \
      try-data.ttl
Validation Report
Conforms: False
Results (2):
Constraint Violation in NodeConstraintComponent (http://www.w3.org/ns/shacl#NodeConstraintComponent):
    Severity: sh:Violation
    Source Shape: sh-ex:ClassC-shape
    Focus Node: kb:Thing-2
    Value Node: kb:Thing-2
    Message: Value does not conform to every Shape in ('[ rdf:type sh:NodeShape ; sh:not [ rdf:type sh:NodeShape ; sh:class ex:ClassA ] ]', '[ rdf:type sh:NodeShape ; sh:not [ rdf:type sh:NodeShape ; sh:class ex:ClassB ] ]'). See details for more information.
    Details:
        Constraint Violation in NotConstraintComponent (http://www.w3.org/ns/shacl#NotConstraintComponent):
            Severity: sh:Violation
            Source Shape: [ rdf:type sh:NodeShape ; sh:not [ rdf:type sh:NodeShape ; sh:class ex:ClassA ] ]
            Focus Node: kb:Thing-2
            Value Node: kb:Thing-2
            Message: Node kb:Thing-2 conforms to shape [ rdf:type sh:NodeShape ; sh:class ex:ClassA ]
Constraint Violation in NodeConstraintComponent (http://www.w3.org/ns/shacl#NodeConstraintComponent):
    Severity: sh:Violation
    Source Shape: sh-ex:ClassC-shape
    Focus Node: kb:Thing-3
    Value Node: kb:Thing-3
    Message: Value does not conform to every Shape in ('[ rdf:type sh:NodeShape ; sh:not [ rdf:type sh:NodeShape ; sh:class ex:ClassA ] ]', '[ rdf:type sh:NodeShape ; sh:not [ rdf:type sh:NodeShape ; sh:class ex:ClassB ] ]'). See details for more information.
    Details:
        Constraint Violation in NotConstraintComponent (http://www.w3.org/ns/shacl#NotConstraintComponent):
            Severity: sh:Violation
            Source Shape: [ rdf:type sh:NodeShape ; sh:not [ rdf:type sh:NodeShape ; sh:class ex:ClassB ] ]
            Focus Node: kb:Thing-3
            Value Node: kb:Thing-3
            Message: Node kb:Thing-3 conforms to shape [ rdf:type sh:NodeShape ; sh:class ex:ClassB ]
ashleysommer commented 7 months ago

Hi @ajnelson-nist Thanks for filing this issue. I've just re-read the whole section on sh:not in the Spec, and I agree it is not clear. The description:

sh:not specifies the condition that each value node cannot conform to a given shape. This is comparable to negation and the logical "not" operator.

and the Textual Definition:

For each value node v: A failure MUST be reported if the conformance checking of v against the shape $not produces a failure. Otherwise, if v conforms to the shape $not, there is validation result with v as sh:value.

both mention just a single shape for sh:not on the constraint, but do not say it cannot have more than one.

However, the "parameters" section contains:

| Property | Summary and Syntax Rules | | sh:not | The shape to negate. The values of sh:not in a shape must be well-formed shapes.|

That mentions values plural. So to me that indeed shows multiple values are allowed.

So this is a bug. Even though you've shown it is relatively easy to work around, it will still be fixed to allow multiple values for sh:not. This was not picked up earlier because there are no tests in the W3C SHACL Test Suite (SHT) nor the Datashapes SHACL Test Suite (DASH) that contain a case with multiple values for sh:not, and as you pointed out, there are no examples in the Spec doc showing it.

ajnelson-nist commented 7 months ago

Thank you for the review! I look forward to the fix.

ashleysommer commented 7 months ago

@ajnelson-nist two releases today (v0.24.1 for users of Python 3.7 + RDFLib 6.2, and v0.25.0 for users of Python 3.8+ & RDFlib 7.0.0) - both contain this fix.

ajnelson-nist commented 7 months ago

Many thanks!