Skip to content

Commit

Permalink
Merge pull request #265 from ajnelson-nist/type_constraint_initializers
Browse files Browse the repository at this point in the history
Type Constraint initializers
  • Loading branch information
ashleysommer authored Oct 4, 2024
2 parents a41a058 + 48bfbcd commit c1aad57
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 48 deletions.
2 changes: 1 addition & 1 deletion pyshacl/constraints/advanced/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
class ExpressionConstraint(ConstraintComponent):
shacl_constraint_component = SH_ExpressionConstraintComponent

def __init__(self, shape: 'Shape'):
def __init__(self, shape: 'Shape') -> None:
super(ExpressionConstraint, self).__init__(shape)
self.expr_nodes = list(self.shape.objects(SH_expression))
if len(self.expr_nodes) < 1:
Expand Down
2 changes: 1 addition & 1 deletion pyshacl/constraints/constraint_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ConstraintComponent(object, metaclass=abc.ABCMeta):

shacl_constraint_component: URIRef = URIRef("urn:notimplemented")

def __init__(self, shape: 'Shape'):
def __init__(self, shape: 'Shape') -> None:
"""
:param shape:
Expand Down
5 changes: 3 additions & 2 deletions pyshacl/constraints/core/cardinality_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pyshacl.errors import ConstraintLoadError
from pyshacl.pytypes import GraphLike, RDFNode, SHACLExecutor
from pyshacl.rdfutil import stringify_node
from pyshacl.shape import Shape

XSD_integer = XSD.integer
SH_minCount = SH.minCount
Expand All @@ -32,7 +33,7 @@ class MinCountConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_MinCountConstraintComponent

def __init__(self, shape, min_count_objects: Optional[List[RDFNode]] = None):
def __init__(self, shape: Shape, min_count_objects: Optional[List[RDFNode]] = None) -> None:
super(MinCountConstraintComponent, self).__init__(shape)
if min_count_objects is None:
min_count = list(self.shape.objects(SH_minCount))
Expand Down Expand Up @@ -118,7 +119,7 @@ class MaxCountConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_MaxCountConstraintComponent

def __init__(self, shape, max_count_objects: Optional[List[RDFNode]] = None):
def __init__(self, shape: Shape, max_count_objects: Optional[List[RDFNode]] = None) -> None:
super(MaxCountConstraintComponent, self).__init__(shape)
if max_count_objects is None:
max_count = list(self.shape.objects(SH_maxCount))
Expand Down
9 changes: 5 additions & 4 deletions pyshacl/constraints/core/logical_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pyshacl.errors import ConstraintLoadError, ReportableRuntimeError, ShapeRecursionWarning, ValidationFailure
from pyshacl.pytypes import GraphLike, SHACLExecutor
from pyshacl.rdfutil import stringify_node
from pyshacl.shape import Shape

SH_not = SH["not"]
SH_and = SH["and"]
Expand All @@ -37,7 +38,7 @@ class NotConstraintComponent(ConstraintComponent):
shape_expecting = True
list_taking = False

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(NotConstraintComponent, self).__init__(shape)
not_list = list(self.shape.objects(SH_not))
if len(not_list) < 1:
Expand Down Expand Up @@ -140,7 +141,7 @@ class AndConstraintComponent(ConstraintComponent):
shape_expecting = True
list_taking = True

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(AndConstraintComponent, self).__init__(shape)
and_list = list(self.shape.objects(SH_and))
if len(and_list) < 1:
Expand Down Expand Up @@ -236,7 +237,7 @@ class OrConstraintComponent(ConstraintComponent):
shape_expecting = True
list_taking = True

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(OrConstraintComponent, self).__init__(shape)
or_list = list(self.shape.objects(SH_or))
if len(or_list) < 1:
Expand Down Expand Up @@ -332,7 +333,7 @@ class XoneConstraintComponent(ConstraintComponent):
shape_expecting = True
list_taking = True

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(XoneConstraintComponent, self).__init__(shape)
xone_nodes = list(self.shape.objects(SH_xone))
if len(xone_nodes) < 1:
Expand Down
36 changes: 25 additions & 11 deletions pyshacl/constraints/core/other_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
"""
https://www.w3.org/TR/shacl/#core-components-others
"""
from typing import Dict, List, cast
import logging
from typing import Dict, List, Set, Union, cast

import rdflib
from rdflib.term import IdentifiedNode

from pyshacl.constraints.constraint_component import ConstraintComponent
from pyshacl.consts import RDFS, SH, RDF_type, SH_property
from pyshacl.errors import ConstraintLoadError, ReportableRuntimeError
from pyshacl.pytypes import GraphLike, RDFNode, SHACLExecutor
from pyshacl.rdfutil import stringify_node
from pyshacl.shape import Shape

SH_InConstraintComponent = SH.InConstraintComponent
SH_ClosedConstraintComponent = SH.ClosedConstraintComponent
Expand All @@ -34,24 +37,29 @@ class InConstraintComponent(ConstraintComponent):
shape_expecting = False
list_taking = True

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(InConstraintComponent, self).__init__(shape)
in_vals = list(self.shape.objects(SH_in))
if len(in_vals) < 1:
in_val_lists: List[IdentifiedNode] = list(self.shape.objects(SH_in))
if len(in_val_lists) < 1:
raise ConstraintLoadError(
"InConstraintComponent must have at least one sh:in predicate.",
"https://www.w3.org/TR/shacl/#InConstraintComponent",
)
elif len(in_vals) > 1:
elif len(in_val_lists) > 1:
raise ConstraintLoadError(
"InConstraintComponent must have at most one sh:in predicate.",
"https://www.w3.org/TR/shacl/#InConstraintComponent",
)
self.in_list = in_vals[0]
self.in_list: IdentifiedNode = in_val_lists[0]
sg = self.shape.sg.graph

in_vals = set(sg.items(self.in_list))
self.in_vals = in_vals
self.in_vals: Set[RDFNode] = set()
for item in sg.items(self.in_list):
if not isinstance(item, (rdflib.BNode, rdflib.Literal, rdflib.URIRef)):
logging.debug("item = %r.", item)
logging.debug("type(item) = %r.", type(item))
raise TypeError("item in sh:in predicate is neither URIRef, BNode, or Literal.")
self.in_vals.add(item)

@classmethod
def constraint_parameters(cls) -> List[rdflib.URIRef]:
Expand Down Expand Up @@ -100,7 +108,7 @@ class ClosedConstraintComponent(ConstraintComponent):

ALWAYS_IGNORE = {(RDF_type, RDFS.Resource)}

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(ClosedConstraintComponent, self).__init__(shape)
sg = self.shape.sg.graph
closed_vals = list(self.shape.objects(SH_closed))
Expand All @@ -122,11 +130,17 @@ def __init__(self, shape):
)
assert isinstance(closed_vals[0], rdflib.Literal), "sh:closed must take a xsd:boolean literal."
self.is_closed = bool(closed_vals[0].value)
self.ignored_props = set()
self.ignored_props: Set[Union[rdflib.BNode, rdflib.Literal, rdflib.URIRef]] = set()
for i in ignored_vals:
try:
items = set(sg.items(i))
for list_item in items:
if not isinstance(list_item, (rdflib.BNode, rdflib.Literal, rdflib.URIRef)):
logging.debug("list_item = %r.", list_item)
logging.debug("type(list_item) = %r.", type(list_item))
raise TypeError(
"sh:ignoredProperties linked something that is neither URIRef, BNode, or Literal."
)
self.ignored_props.add(list_item)
except ValueError:
continue
Expand Down Expand Up @@ -269,7 +283,7 @@ class HasValueConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_HasValueConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(HasValueConstraintComponent, self).__init__(shape)
has_value_set = set(self.shape.objects(SH_hasValue))
if len(has_value_set) < 1:
Expand Down
9 changes: 5 additions & 4 deletions pyshacl/constraints/core/property_pair_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pyshacl.helper.path_helper import shacl_path_to_sparql_path
from pyshacl.pytypes import GraphLike, SHACLExecutor
from pyshacl.rdfutil import stringify_node
from pyshacl.shape import Shape

SH_equals = SH.equals
SH_disjoint = SH.disjoint
Expand All @@ -35,7 +36,7 @@ class EqualsConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_EqualsConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(EqualsConstraintComponent, self).__init__(shape)
property_compare_set = set(self.shape.objects(SH_equals))
if len(property_compare_set) < 1:
Expand Down Expand Up @@ -162,7 +163,7 @@ class DisjointConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_DisjointConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(DisjointConstraintComponent, self).__init__(shape)
property_compare_set = set(self.shape.objects(SH_disjoint))
if len(property_compare_set) < 1:
Expand Down Expand Up @@ -282,7 +283,7 @@ class LessThanConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_LessThanConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(LessThanConstraintComponent, self).__init__(shape)
property_compare_set = set(self.shape.objects(SH_lessThan))
if len(property_compare_set) < 1:
Expand Down Expand Up @@ -433,7 +434,7 @@ class LessThanOrEqualsConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_LessThanOrEqualsConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(LessThanOrEqualsConstraintComponent, self).__init__(shape)
property_compare_set = set(self.shape.objects(SH_lessThanOrEquals))
if len(property_compare_set) < 1:
Expand Down
47 changes: 30 additions & 17 deletions pyshacl/constraints/core/string_based_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""
https://www.w3.org/TR/shacl/#core-components-string
"""
import logging
import re
from typing import Dict, List

Expand All @@ -11,8 +12,9 @@
from pyshacl.constraints.constraint_component import ConstraintComponent
from pyshacl.consts import RDF, SH, XSD_WHOLE_INTEGERS
from pyshacl.errors import ConstraintLoadError, ReportableRuntimeError
from pyshacl.pytypes import GraphLike, SHACLExecutor
from pyshacl.pytypes import GraphLike, RDFNode, SHACLExecutor
from pyshacl.rdfutil import stringify_node
from pyshacl.shape import Shape

RDF_langString = RDF.langString
XSD_string = XSD.string
Expand All @@ -37,9 +39,9 @@ class StringBasedConstraintBase(ConstraintComponent):

shacl_constraint_component = rdflib.URIRef("urn:notimplemented")

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(StringBasedConstraintBase, self).__init__(shape)
self.string_rules = []
self.string_rules: List[RDFNode] = []
self.allow_multi_rules = True

@classmethod
Expand Down Expand Up @@ -98,7 +100,7 @@ class MinLengthConstraintComponent(StringBasedConstraintBase):

shacl_constraint_component = SH_MinLengthConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(MinLengthConstraintComponent, self).__init__(shape)
self.allow_multi_rules = False
patterns_found = list(self.shape.objects(SH_minLength))
Expand Down Expand Up @@ -179,7 +181,7 @@ class MaxLengthConstraintComponent(StringBasedConstraintBase):

shacl_constraint_component = SH_MaxLengthConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(MaxLengthConstraintComponent, self).__init__(shape)
self.allow_multi_rules = False
patterns_found = list(self.shape.objects(SH_maxLength))
Expand Down Expand Up @@ -257,20 +259,21 @@ class PatternConstraintComponent(StringBasedConstraintBase):

shacl_constraint_component = SH_PatternConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(PatternConstraintComponent, self).__init__(shape)
patterns_found = list(self.shape.objects(SH_pattern))
patterns_found: List[RDFNode] = []
for pattern_found in self.shape.objects(SH_pattern):
if not isinstance(pattern_found, rdflib.Literal):
raise ConstraintLoadError(
"PatternConstraintComponent sh:pattern must be a RDF Literal node.",
"https://www.w3.org/TR/shacl/#PatternConstraintComponent",
)
patterns_found.append(pattern_found)
if len(patterns_found) < 1:
raise ConstraintLoadError(
"PatternConstraintComponent must have at least one sh:pattern predicate.",
"https://www.w3.org/TR/shacl/#PatternConstraintComponent",
)
for p in patterns_found:
if not isinstance(p, rdflib.Literal):
raise ConstraintLoadError(
"PatternConstraintComponent sh:pattern must be a RDF Literal node.",
"https://www.w3.org/TR/shacl/#PatternConstraintComponent",
)
self.string_rules = patterns_found
flags_found = set(self.shape.objects(SH_flags))
if len(flags_found) > 0:
Expand All @@ -289,9 +292,19 @@ def constraint_name(cls) -> str:

def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) -> List[rdflib.Literal]:
if len(self.string_rules) < 2:
m = "Value does not match pattern '{}'".format(str(self.string_rules[0].value))
string_rule = self.string_rules[0]
assert isinstance(string_rule, rdflib.Literal)
m = "Value does not match pattern '{}'".format(str(string_rule.value))
else:
rules = "', '".join(str(c.value) for c in self.string_rules)
# Inform type system that all the string rules are Literals.
_string_rules: List[rdflib.Literal] = []
for string_rule in self.string_rules:
if not isinstance(string_rule, rdflib.Literal):
logging.debug("string_rule = %r.", string_rule)
logging.debug("type(string_rule) = %r.", type(string_rule))
raise TypeError("Non-Literal entered string_rules list.")
_string_rules.append(string_rule)
rules = "', '".join(str(c.value) for c in _string_rules)
m = "Value does not match every pattern in ('{}')".format(rules)
return [rdflib.Literal(m)]

Expand Down Expand Up @@ -341,7 +354,7 @@ class LanguageInConstraintComponent(StringBasedConstraintBase):
shape_expecting = False
list_taking = True

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(LanguageInConstraintComponent, self).__init__(shape)
self.allow_multi_rules = False
language_ins_found = list(self.shape.objects(SH_languageIn))
Expand Down Expand Up @@ -424,7 +437,7 @@ class UniqueLangConstraintComponent(StringBasedConstraintBase):

shacl_constraint_component = SH_UniqueLangConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(UniqueLangConstraintComponent, self).__init__(shape)
self.allow_multi_rules = False
is_unique_lang = set(self.shape.objects(SH_uniqueLang))
Expand Down
7 changes: 4 additions & 3 deletions pyshacl/constraints/core/value_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from pyshacl.errors import ConstraintLoadError
from pyshacl.pytypes import GraphLike, SHACLExecutor
from pyshacl.rdfutil import stringify_node
from pyshacl.shape import Shape

RDF_langString = RDF.langString
RDFS_Datatype = RDFS.Datatype
Expand Down Expand Up @@ -59,7 +60,7 @@ class ClassConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_ClassConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(ClassConstraintComponent, self).__init__(shape)
class_rules = list(self.shape.objects(SH_class))
if len(class_rules) < 1:
Expand Down Expand Up @@ -168,7 +169,7 @@ class DatatypeConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_DatatypeConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(DatatypeConstraintComponent, self).__init__(shape)
datatype_rules = list(self.shape.objects(SH_datatype))
if len(datatype_rules) < 1:
Expand Down Expand Up @@ -276,7 +277,7 @@ class NodeKindConstraintComponent(ConstraintComponent):

shacl_constraint_component = SH_NodeKindConstraintComponent

def __init__(self, shape):
def __init__(self, shape: Shape) -> None:
super(NodeKindConstraintComponent, self).__init__(shape)
nodekind_rules = list(self.shape.objects(SH_nodeKind))
if len(nodekind_rules) < 1:
Expand Down
Loading

0 comments on commit c1aad57

Please sign in to comment.