示例#1
0
    def __init__(self, sg, node, p=False, path=None, logger=None):
        """
        Shape
        :type sg: pyshacl.shapes_graph.ShapesGraph
        :type node: rdflib.term.Node
        :type p: bool
        :type path: rdflib.Node
        :type logger: logging.Logger
        """
        self.logger = logger or logging.getLogger(__name__)
        self.sg = sg
        self.node = node
        self._p = p
        self._path = path
        self._advanced = False

        deactivated_vals = set(self.objects(SH_deactivated))
        if len(deactivated_vals) > 1:
            # TODO:coverage: we don't have any tests for invalid shapes
            raise ShapeLoadError(
                "A SHACL Shape cannot have more than one sh:deactivated predicate.",
                "https://www.w3.org/TR/shacl/#deactivated")
        elif len(deactivated_vals) < 1:
            self._deactivated = False
        else:
            d = next(iter(deactivated_vals))
            if not isinstance(d, rdflib.Literal):
                # TODO:coverage: we don't have any tests for invalid shapes
                raise ShapeLoadError(
                    "The value of sh:deactivated predicate on a SHACL Shape must be a Literal.",
                    "https://www.w3.org/TR/shacl/#deactivated")
            self._deactivated = bool(d.value)
        severity = set(self.objects(SH_severity))
        if len(severity):
            self._severity = next(iter(severity))
        else:
            self._severity = SH_Violation
        messages = set(self.objects(SH_message))
        if len(messages):
            self._messages = messages
        else:
            self._messages = None
        names = set(self.objects(SH_name))
        if len(names):
            self._names = iter(names)
        else:
            self._names = None
        descriptions = set(self.objects(SH_description))
        if len(descriptions):
            self._descriptions = iter(descriptions)
        else:
            self._descriptions = None
示例#2
0
    def __init__(self, sg, node, p=False, path=None, logger=None):
        """

        :type sg: pyshacl.shacl_graph.SHACLGraph
        :type node: rdflib.term.Node
        :type p: bool
        """
        if logger is None:
            logger = logging.getLogger(__name__)
        self.logger = logger
        self.sg = sg
        self.node = node
        self._p = p
        self._path = path
        if (id(sg), node) in self.__class__.all_shapes:
            raise ShapeLoadError("That shape has already been loaded!", "None")
        self.__class__.all_shapes[(id(sg), node)] = self

        deactivated_vals = set(self.objects(SH_deactivated))
        if len(deactivated_vals) > 1:
            raise ShapeLoadError("A SHACL Shape cannot have more than one sh:deactivated predicate.",
                                 "https://www.w3.org/TR/shacl/#deactivated")
        elif len(deactivated_vals) < 1:
            self._deactivated = False
        else:
            d = next(iter(deactivated_vals))
            if not isinstance(d, rdflib.Literal):
                raise ShapeLoadError(
                    "The value of sh:deactivated predicate on a SHACL Shape must be a Literal.",
                    "https://www.w3.org/TR/shacl/#deactivated")
            self._deactivated = bool(d.value)
        severity = set(self.objects(SH_severity))
        if len(severity):
            self._severity = next(iter(severity))
        else:
            self._severity = SH_Violation
        messages = set(self.objects(SH_message))
        if len(messages):
            self._messages = messages
        else:
            self._messages = None
        names = set(self.objects(SH_name))
        if len(names):
            self._names = iter(names)
        else:
            self._names = None
        descriptions = set(self.objects(SH_description))
        if len(descriptions):
            self._descriptions = iter(descriptions)
        else:
            self._descriptions = None
示例#3
0
 def order(self):
     order_nodes = list(self.objects(SH_order))
     if len(order_nodes) < 1:
         return Decimal("0.0")
     if len(order_nodes) > 1:
         raise ShapeLoadError(
             "A SHACL Shape can have only one sh:order property.",
             "https://www.w3.org/TR/shacl-af/#rules-order")
     order_node = next(iter(order_nodes))
     if not isinstance(order_node, rdflib.Literal):
         raise ShapeLoadError(
             "A SHACL Shape must be a numeric literal.",
             "https://www.w3.org/TR/shacl-af/#rules-order")
     return Decimal(order_node.value)
示例#4
0
 def check_params(self, target_declaration):
     param_kv = {}
     for p in self.parameters:
         path = p.path()
         name = p.localname
         vals = set(self.sg.objects(target_declaration, path))
         if len(vals) < 1:
             if p.optional:
                 continue
             raise ShapeLoadError(
                 "sh:target does not have a value for {}".format(name),
                 "https://www.w3.org/TR/shacl-js/#JSTargetType",
             )
         if len(vals) > 1:
             warn(
                 Warning(
                     "Found more than one value for {} on sh:target. Using just first one."
                     .format(name)))
         param_kv[name] = next(iter(vals))
     return param_kv
示例#5
0
    def _build_node_shape_cache(self):
        """
        :returns: [Shape]
        :rtype: list(pyshacl.shape.Shape)
        """
        g = self.graph
        defined_node_shapes = set(g.subjects(RDF_type, SH_NodeShape))
        for s in defined_node_shapes:
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) > 0:
                # TODO:coverage: we don't have any tests for invalid shapes
                raise ShapeLoadError(
                    "A shape defined as a NodeShape cannot be the subject of a 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#node-shapes")

        defined_prop_shapes = set(g.subjects(RDF_type, SH_PropertyShape))
        found_prop_shapes_paths = dict()
        for s in defined_prop_shapes:
            if s in defined_node_shapes:
                # TODO:coverage: we don't have any tests for invalid shapes
                raise ShapeLoadError(
                    "A shape defined as a NodeShape cannot also be defined as a PropertyShape.",
                    "https://www.w3.org/TR/shacl/#node-shapes")
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) < 1:
                # TODO:coverage: we don't have any tests for invalid shapes
                raise ShapeLoadError(
                    "A shape defined as a PropertyShape must be the subject of a 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            elif len(path_vals) > 1:
                # TODO:coverage: we don't have any tests for invalid shapes
                raise ShapeLoadError(
                    "A shape defined as a PropertyShape cannot have more than one 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            found_prop_shapes_paths[s] = path_vals[0]

        has_target_class = {s for s, o in g.subject_objects(SH_targetClass)}
        has_target_node = {s for s, o in g.subject_objects(SH_targetNode)}
        has_target_objects_of = {s for s, o in g.subject_objects(SH_targetObjectsOf)}
        has_target_subjects_of = {s for s, o in g.subject_objects(SH_targetSubjectsOf)}
        subject_shapes = set(has_target_class).union(
            set(has_target_node).union(
                set(has_target_objects_of).union(
                    set(has_target_subjects_of))))

        # implicit shapes: their subjects must be shapes
        subject_of_property = {s for s, o in g.subject_objects(SH_property)}
        subject_of_node = {s for s, o in g.subject_objects(SH_node)}
        subject_shapes = subject_shapes.union(
            set(subject_of_property).union(
                set(subject_of_node)))

        # shape-expecting properties, their values must be shapes.
        value_of_property = {o for s, o in g.subject_objects(SH_property)}
        value_of_node = {o for s, o in g.subject_objects(SH_node)}
        value_of_not = {o for s, o in g.subject_objects(SH_not)}
        value_of_qvs = {o for s, o in g.subject_objects(SH_qualifiedValueShape)}
        value_of_shape_expecting = set(value_of_property).union(
            set(value_of_node).union(
                set(value_of_not).union(
                    set(value_of_qvs))))

        value_of_and = {o for s, o in g.subject_objects(SH_and)}
        value_of_or = {o for s, o in g.subject_objects(SH_or)}
        value_of_xone = {o for s, o in g.subject_objects(SH_xone)}
        value_of_s_list_expecting = set(value_of_and).union(
            set(value_of_or).union(
                set(value_of_xone)))

        for l in value_of_s_list_expecting:
            list_contents = set(g.items(l))
            if len(list_contents) < 1:
                # TODO:coverage: we don't have any tests for invalid shape lists
                raise ShapeLoadError(
                    "A Shape-Expecting & List-Expecting predicate should get a well-formed RDF list with 1 or more members.",
                    "https://www.w3.org/TR/shacl/#shapes-recursion")
            for s in list_contents:
                value_of_shape_expecting.add(s)

        found_node_shapes = set()
        found_prop_shapes = set()
        for s in subject_shapes:
            if s in defined_node_shapes or s in defined_prop_shapes:
                continue
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) < 1:
                found_node_shapes.add(s)
            elif len(path_vals) > 1:
                # TODO:coverage: we don't have any tests for invalid implicit shapes
                raise ShapeLoadError(
                    "An implicit PropertyShape cannot have more than one 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            else:
                # TODO:coverage: we don't have this case where an implicit shape is a property shape.
                found_prop_shapes.add(s)
                found_prop_shapes_paths[s] = path_vals[0]
        for s in value_of_shape_expecting:
            if s in defined_node_shapes or s in defined_prop_shapes or \
                    s in found_prop_shapes or s in found_node_shapes:
                continue
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) < 1:
                found_node_shapes.add(s)
            elif len(path_vals) > 1:
                # TODO:coverage: we don't have any tests for invalid implicit shapes
                raise ShapeLoadError(
                    "An implicit PropertyShape cannot have more than one 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            else:
                found_prop_shapes.add(s)
                found_prop_shapes_paths[s] = path_vals[0]
        for node_shape in defined_node_shapes.union(found_node_shapes):
            if node_shape in self._node_shape_cache:
                # TODO:coverage: we don't have any tests where a shape is loaded twice
                raise ShapeLoadError("That shape has already been loaded!",
                                     "None")
            s = Shape(self, node_shape, p=False, logger=self.logger)
            self._node_shape_cache[node_shape] = s
        for prop_shape in defined_prop_shapes.union(found_prop_shapes):
            if prop_shape in self._node_shape_cache:
                # TODO:coverage: we don't have any tests where a shape is loaded twice
                raise ShapeLoadError("That shape has already been loaded!",
                                     "None")
            prop_shape_path = found_prop_shapes_paths[prop_shape]
            s = Shape(self, prop_shape, p=True, path=prop_shape_path, logger=self.logger)
            self._node_shape_cache[prop_shape] = s
示例#6
0
    def _find_shapes(self):
        """
        :returns: [Shape]
        :rtype: list(pyshacl.shape.Shape)
        """
        g = self.graph
        defined_node_shapes = set(g.subjects(RDF_type, SH_NodeShape))
        for s in defined_node_shapes:
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) > 0:
                raise ShapeLoadError(
                    "A shape defined as a NodeShape cannot be the subject of a 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#node-shapes")

        defined_prop_shapes = set(g.subjects(RDF_type, SH_PropertyShape))
        found_prop_shapes_paths = dict()
        for s in defined_prop_shapes:
            if s in defined_node_shapes:
                raise ShapeLoadError(
                    "A shape defined as a NodeShape cannot also be defined as a PropertyShape.",
                    "https://www.w3.org/TR/shacl/#node-shapes")
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) < 1:
                raise ShapeLoadError(
                    "A shape defined as a PropertyShape must be the subject of a 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            elif len(path_vals) > 1:
                raise ShapeLoadError(
                    "A shape defined as a PropertyShape cannot have more than one 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            found_prop_shapes_paths[s] = path_vals[0]

        has_target_class = {s for s, o in g.subject_objects(SH_targetClass)}
        has_target_node = {s for s, o in g.subject_objects(SH_targetNode)}
        has_target_objects_of = {
            s
            for s, o in g.subject_objects(SH_targetObjectsOf)
        }
        has_target_subjects_of = {
            s
            for s, o in g.subject_objects(SH_targetSubjectsOf)
        }
        subject_shapes = set(has_target_class).union(
            set(has_target_node).union(
                set(has_target_objects_of).union(set(has_target_subjects_of))))

        value_of_property = {o for s, o in g.subject_objects(SH_property)}
        value_of_node = {o for s, o in g.subject_objects(SH_node)}
        value_of_not = {o for s, o in g.subject_objects(SH_not)}
        value_of_qvs = {
            o
            for s, o in g.subject_objects(SH_qualifiedValueShape)
        }
        value_of_shape_expecting = set(value_of_property).union(
            set(value_of_node).union(
                set(value_of_not).union(set(value_of_qvs))))

        value_of_and = {o for s, o in g.subject_objects(SH_and)}
        value_of_or = {o for s, o in g.subject_objects(SH_or)}
        value_of_xone = {o for s, o in g.subject_objects(SH_xone)}
        value_of_s_list_expecting = set(value_of_and).union(
            set(value_of_or).union(set(value_of_xone)))

        for l in value_of_s_list_expecting:
            list_contents = set(g.items(l))
            if len(list_contents) < 1:
                raise ShapeLoadError(
                    "A Shape-Expecting & List-Expecting predicate should get a well-formed RDF list with 1 or more members.",
                    "https://www.w3.org/TR/shacl/#shapes-recursion")
            for s in list_contents:
                value_of_shape_expecting.add(s)

        found_node_shapes = set()
        found_prop_shapes = set()
        for s in subject_shapes:
            if s in defined_node_shapes or s in defined_prop_shapes:
                continue
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) < 1:
                found_node_shapes.add(s)
            elif len(path_vals) > 1:
                raise ShapeLoadError(
                    "An implicit PropertyShape cannot have more than one 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            else:
                found_prop_shapes.add(s)
                found_prop_shapes_paths[s] = path_vals[0]
        for s in value_of_shape_expecting:
            if s in defined_node_shapes or s in defined_prop_shapes or \
                    s in found_prop_shapes or s in found_node_shapes:
                continue
            path_vals = list(g.objects(s, SH_path))
            if len(path_vals) < 1:
                found_node_shapes.add(s)
            elif len(path_vals) > 1:
                raise ShapeLoadError(
                    "An implicit PropertyShape cannot have more than one 'sh:path' predicate.",
                    "https://www.w3.org/TR/shacl/#property-shapes")
            else:
                found_prop_shapes.add(s)
                found_prop_shapes_paths[s] = path_vals[0]
        created_node_shapes = set()
        for node_shape in defined_node_shapes.union(found_node_shapes):
            s = Shape(self, node_shape, False, logger=self.logger)
            created_node_shapes.add(s)
        created_prop_shapes = set()
        for prop_shape in defined_prop_shapes.union(found_prop_shapes):
            prop_shape_path = found_prop_shapes_paths[prop_shape]
            s = Shape(self,
                      prop_shape,
                      True,
                      path=prop_shape_path,
                      logger=self.logger)
            created_prop_shapes.add(s)
        return list(created_node_shapes.union(created_prop_shapes))
示例#7
0
def value_nodes_from_path(sg, focus, path_val, target_graph, recursion=0):
    # Link: https://www.w3.org/TR/shacl/#property-paths
    if isinstance(path_val, URIRef):
        return set(target_graph.objects(focus, path_val))
    elif isinstance(path_val, Literal):
        raise ShapeLoadError(
            "Values of a property path cannot be a Literal.",
            "https://www.w3.org/TR/shacl/#property-paths",
        )
    # At this point, path_val _must_ be a BNode
    # TODO, the path_val BNode must be value of exactly one sh:path subject in the SG.
    if recursion >= 10:
        raise ReportableRuntimeError("Path traversal depth is too much!")
    find_list = set(sg.graph.objects(path_val, RDF.first))
    if len(find_list) > 0:
        first_node = next(iter(find_list))
        rest_nodes = set(sg.graph.objects(path_val, RDF.rest))
        go_deeper = True
        if len(rest_nodes) < 1:
            if recursion == 0:
                raise ReportableRuntimeError(
                    "A list of SHACL Paths must contain at least two path items."
                )
            else:
                go_deeper = False
        rest_node = next(iter(rest_nodes))
        if rest_node == RDF.nil:
            if recursion == 0:
                raise ReportableRuntimeError(
                    "A list of SHACL Paths must contain at least two path items."
                )
            else:
                go_deeper = False
        this_level_nodes = value_nodes_from_path(sg,
                                                 focus,
                                                 first_node,
                                                 target_graph,
                                                 recursion=recursion + 1)
        if not go_deeper:
            return this_level_nodes
        found_value_nodes = set()
        for tln in iter(this_level_nodes):
            value_nodes = value_nodes_from_path(sg,
                                                tln,
                                                rest_node,
                                                target_graph,
                                                recursion=recursion + 1)
            found_value_nodes.update(value_nodes)
        return found_value_nodes

    find_inverse = set(sg.graph.objects(path_val, SH_inversePath))
    if len(find_inverse) > 0:
        inverse_path = next(iter(find_inverse))
        return set(target_graph.subjects(inverse_path, focus))

    find_alternatives = set(sg.graph.objects(path_val, SH_alternativePath))
    if len(find_alternatives) > 0:
        alternatives_list = next(iter(find_alternatives))
        all_collected = set()
        visited_alternatives = 0
        for a in sg.graph.items(alternatives_list):
            found_nodes = value_nodes_from_path(sg,
                                                focus,
                                                a,
                                                target_graph,
                                                recursion=recursion + 1)
            visited_alternatives += 1
            all_collected.update(found_nodes)
        if visited_alternatives < 2:
            raise ReportableRuntimeError(
                "List of SHACL alternate paths must have at least two path items."
            )
        return all_collected

    find_zero_or_more = set(sg.graph.objects(path_val, SH_zeroOrMorePath))
    if len(find_zero_or_more) > 0:
        zm_path = next(iter(find_zero_or_more))
        collection_set = set()
        # Note, the zero-or-more path always includes the current subject too!
        collection_set.add(focus)
        found_nodes = value_nodes_from_path(sg,
                                            focus,
                                            zm_path,
                                            target_graph,
                                            recursion=recursion + 1)
        search_deeper_nodes = set(iter(found_nodes))
        while len(search_deeper_nodes) > 0:
            current_node = search_deeper_nodes.pop()
            if current_node in collection_set:
                continue
            collection_set.add(current_node)
            found_more_nodes = value_nodes_from_path(sg,
                                                     current_node,
                                                     zm_path,
                                                     target_graph,
                                                     recursion=recursion + 1)
            search_deeper_nodes.update(found_more_nodes)
        return collection_set

    find_one_or_more = set(sg.graph.objects(path_val, SH_oneOrMorePath))
    if len(find_one_or_more) > 0:
        one_or_more_path = next(iter(find_one_or_more))
        collection_set = set()
        found_nodes = value_nodes_from_path(sg,
                                            focus,
                                            one_or_more_path,
                                            target_graph,
                                            recursion=recursion + 1)
        # Note, the one-or-more path should _not_ include the current focus
        search_deeper_nodes = set(iter(found_nodes))
        while len(search_deeper_nodes) > 0:
            current_node = search_deeper_nodes.pop()
            if current_node in collection_set:
                continue
            collection_set.add(current_node)
            found_more_nodes = value_nodes_from_path(sg,
                                                     current_node,
                                                     one_or_more_path,
                                                     target_graph,
                                                     recursion=recursion + 1)
            search_deeper_nodes.update(found_more_nodes)
        return collection_set

    find_zero_or_one = set(sg.graph.objects(path_val, SH_zeroOrOnePath))
    if len(find_zero_or_one) > 0:
        zero_or_one_path = next(iter(find_zero_or_one))
        collection_set = set()
        # Note, the zero-or-one path always includes the current subject too!
        collection_set.add(focus)
        found_nodes = value_nodes_from_path(sg,
                                            focus,
                                            zero_or_one_path,
                                            target_graph,
                                            recursion=recursion + 1)
        collection_set.update(found_nodes)
        return collection_set
    remaining = set(sg.graph.predicate_objects(path_val))
    if len(remaining) > 0:
        raise ShapeLoadError(
            "{} is not a known property for a sh:path. Malformed shape?".
            format(str(next(iter(remaining))[0])),
            "https://www.w3.org/TR/shacl/#property-paths",
        )

    raise ShapeLoadError(
        "Cannot get any values from sh:path property. Malformed shape?",
        "https://www.w3.org/TR/shacl/#property-paths")
示例#8
0
    def __init__(
        self,
        sg: 'ShapesGraph',
        node: Union[URIRef, BNode],
        p=False,
        path: Optional[Union[URIRef, BNode]] = None,
        logger=None,
    ):
        """
        Shape
        :type sg: ShapesGraph
        :type node: URIRef | BNode
        :type p: bool
        :type path: URIRef | BNode | None
        :type logger: logging.Logger
        """
        self.logger = logger or logging.getLogger(__name__)
        self.sg = sg
        self.node = node
        self._p = p
        self._path = path
        self._advanced = False

        deactivated_vals = set(self.objects(SH_deactivated))
        if len(deactivated_vals) > 1:
            # TODO:coverage: we don't have any tests for invalid shapes
            raise ShapeLoadError(
                "A SHACL Shape cannot have more than one sh:deactivated predicate.",
                "https://www.w3.org/TR/shacl/#deactivated",
            )
        elif len(deactivated_vals) < 1:
            self._deactivated = False  # type: bool
        else:
            d = next(iter(deactivated_vals))
            if not isinstance(d, Literal):
                # TODO:coverage: we don't have any tests for invalid shapes
                raise ShapeLoadError(
                    "The value of sh:deactivated predicate on a SHACL Shape must be a Literal.",
                    "https://www.w3.org/TR/shacl/#deactivated",
                )
            self._deactivated = bool(d.value)
        severity = set(self.objects(SH_severity))
        if len(severity):
            self._severity = next(
                iter(severity))  # type: Union[URIRef, BNode, Literal]
        else:
            self._severity = SH_Violation
        messages = set(self.objects(SH_message))
        if len(messages):
            self._messages = messages  # type: Set
        else:
            self._messages = set()
        names = set(self.objects(SH_name))
        if len(names):
            self._names = names  # type: Set
        else:
            self._names = set()
        descriptions = set(self.objects(SH_description))
        if len(descriptions):
            self._descriptions = descriptions  # type: Set
        else:
            self._descriptions = set()