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
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
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)
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
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
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))
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")
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()