Skip to content

Commit

Permalink
Merge pull request #270 from RDFLib/assets
Browse files Browse the repository at this point in the history
Commits for new release
  • Loading branch information
ashleysommer authored Oct 23, 2024
2 parents 8206d7c + 3106d41 commit 11ce2fe
Show file tree
Hide file tree
Showing 21 changed files with 2,930 additions and 482 deletions.
235 changes: 110 additions & 125 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ commands_pre =
commands =
- poetry show
poetry run pytest --cov=pyshacl test/
poetry run pytest -v --log-level=INFO --cov=pyshacl test/
- poetry run coverage combine --append
poetry run coverage report -m
poetry run coverage html -i
Expand Down
Binary file added pyshacl/assets/dash.pickle
Binary file not shown.
2,366 changes: 2,366 additions & 0 deletions pyshacl/assets/dash.ttl

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions pyshacl/assets/make_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@
with open("./schema.ttl", "rb") as f:
g = Graph(store=store, identifier=identifier, bind_namespaces='core').parse(file=f)
with open("./schema.pickle", "wb") as f:
pickle.dump((store, identifier), f, protocol=4) # protocol 5 only works in python 3.8+
pickle.dump((store, identifier), f, protocol=5)

identifier = URIRef("http://www.w3.org/ns/shacl#")
store = Memory(identifier=identifier)
with open("./shacl.ttl", "rb") as f:
g = Graph(store=store, identifier=identifier, bind_namespaces='core').parse(file=f)
with open("./shacl.pickle", "wb") as f:
pickle.dump((store, identifier), f, protocol=4) # protocol 5 only works in python 3.8+
pickle.dump((store, identifier), f, protocol=5)

identifier = URIRef("http://datashapes.org/dash")
store = Memory(identifier=identifier)
with open("./dash.ttl", "rb") as f:
g = Graph(store=store, identifier=identifier, bind_namespaces='core').parse(file=f)
with open("./dash.pickle", "wb") as f:
pickle.dump((store, identifier), f, protocol=5)

identifier = URIRef("http://www.w3.org/ns/shacl-shacl#")
store = Memory(identifier=identifier)
with open("./shacl-shacl.ttl", "rb") as f:
g = Graph(store=store, identifier=identifier, bind_namespaces='core').parse(file=f)
with open("./shacl-shacl.pickle", "wb") as f:
pickle.dump((store, identifier), f, protocol=4) # protocol 5 only works in python 3.8+
pickle.dump((store, identifier), f, protocol=5)
Binary file modified pyshacl/assets/schema.pickle
Binary file not shown.
Binary file modified pyshacl/assets/shacl-shacl.pickle
Binary file not shown.
Binary file modified pyshacl/assets/shacl.pickle
Binary file not shown.
12 changes: 10 additions & 2 deletions pyshacl/constraints/constraint_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ def make_v_result_description(
severity_desc = "Validation Result"
source_shape_text = stringify_node(sg, self.shape.node)
severity_node_text = stringify_node(sg, severity)
focus_node_text = stringify_node(datagraph or sg, focus_node)
try:
focus_node_text = stringify_node(datagraph or sg, focus_node)
except (LookupError, ValueError):
# focus node doesn't exist in the datagraph. We can deal.
focus_node_text = str(focus_node)
desc = "{} in {} ({}):\n\tSeverity: {}\n\tSource Shape: {}\n\tFocus Node: {}\n".format(
severity_desc,
constraint_name,
Expand All @@ -176,7 +180,11 @@ def make_v_result_description(
focus_node_text,
)
if value_node is not None:
val_node_string = stringify_node(datagraph or sg, value_node)
try:
val_node_string = stringify_node(datagraph or sg, value_node)
except (LookupError, ValueError):
# value node doesn't exist in the datagraph.
val_node_string = str(value_node)
desc += "\tValue Node: {}\n".format(val_node_string)
if result_path is None and self.shape.is_property_shape:
result_path = self.shape.path()
Expand Down
9 changes: 6 additions & 3 deletions pyshacl/constraints/core/cardinality_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) ->
p = self.shape.path()
if p:
p = stringify_node(self.shape.sg.graph, p)
m = "Less than {} values on {}->{}".format(
str(self.min_count.value), stringify_node(datagraph, focus_node), p
)
try:
focus_string = stringify_node(datagraph, focus_node)
except (LookupError, ValueError):
# focus node doesn't exist in the datagraph. We can deal.
focus_string = str(focus_node)
m = "Less than {} values on {}->{}".format(str(self.min_count.value), focus_string, p)
else:
m = "Less than {} values on {}".format(str(self.min_count.value), stringify_node(datagraph, focus_node))
return [Literal(m)]
Expand Down
34 changes: 25 additions & 9 deletions pyshacl/constraints/core/logical_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,16 @@ def constraint_name(cls) -> str:
return "NotConstraintComponent"

def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) -> List[rdflib.Literal]:
try:
value_node_str = stringify_node(datagraph, value_node)
except (LookupError, ValueError):
# value node doesn't exist in the datagraph.
value_node_str = str(value_node)
if len(self.not_list) == 1:
m = f"Node {stringify_node(datagraph, value_node)} must not conform to shape {stringify_node(self.shape.sg.graph, self.not_list[0])}"
m = f"Node {value_node_str} must not conform to shape {stringify_node(self.shape.sg.graph, self.not_list[0])}"
else:
nots_list = " , ".join(stringify_node(self.shape.sg.graph, n) for n in self.not_list)
m = f"Node {stringify_node(datagraph, value_node)} must not conform to any shapes in {nots_list}"
m = f"Node {value_node_str} must not conform to any shapes in {nots_list}"
return [rdflib.Literal(m)]

def evaluate(self, executor: SHACLExecutor, datagraph: GraphLike, focus_value_nodes: Dict, _evaluation_path: List):
Expand Down Expand Up @@ -172,7 +177,12 @@ def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) ->
)
and_node_strings.append(f"({and_node_string1})")
and_node_string = " and ".join(and_node_strings)
m = "Node {} must conform to all shapes in {}".format(stringify_node(datagraph, value_node), and_node_string)
try:
value_node_str = stringify_node(datagraph, value_node)
except (LookupError, ValueError):
# value node doesn't exist in the datagraph.
value_node_str = str(value_node)
m = f"Node {value_node_str} must conform to all shapes in {and_node_string}"
return [rdflib.Literal(m)]

def evaluate(
Expand Down Expand Up @@ -277,9 +287,12 @@ def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) ->
)
or_node_strings.append(f"({or_node_string1})")
or_node_string = " and ".join(or_node_strings)
m = "Node {} must conform to one or more shapes in {}".format(
stringify_node(datagraph, value_node), or_node_string
)
try:
value_node_str = stringify_node(datagraph, value_node)
except (LookupError, ValueError):
# value node doesn't exist in the datagraph.
value_node_str = str(value_node)
m = f"Node {value_node_str} must conform to one or more shapes in {or_node_string}"
return [rdflib.Literal(m)]

def evaluate(
Expand Down Expand Up @@ -384,9 +397,12 @@ def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) ->
)
xone_node_strings.append(f"({xone_node_string1})")
xone_node_string = " and ".join(xone_node_strings)
m = "Node {} must conform to exactly one shape in {}".format(
stringify_node(datagraph, value_node), xone_node_string
)
try:
value_node_str = stringify_node(datagraph, value_node)
except (LookupError, ValueError):
# value node doesn't exist in the datagraph.
value_node_str = str(value_node)
m = f"Node {value_node_str} must conform to exactly one shape in {xone_node_string}"
return [rdflib.Literal(m)]

def evaluate(
Expand Down
41 changes: 25 additions & 16 deletions pyshacl/constraints/core/string_based_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""
import logging
import re
from typing import Dict, List
from typing import Dict, List, cast

import rdflib
from rdflib.namespace import XSD
Expand Down Expand Up @@ -261,7 +261,7 @@ class PatternConstraintComponent(StringBasedConstraintBase):

def __init__(self, shape: Shape) -> None:
super(PatternConstraintComponent, self).__init__(shape)
patterns_found: List[RDFNode] = []
patterns_found: List[rdflib.Literal] = []
for pattern_found in self.shape.objects(SH_pattern):
if not isinstance(pattern_found, rdflib.Literal):
raise ConstraintLoadError(
Expand All @@ -274,14 +274,32 @@ def __init__(self, shape: Shape) -> None:
"PatternConstraintComponent must have at least one sh:pattern predicate.",
"https://www.w3.org/TR/shacl/#PatternConstraintComponent",
)
self.string_rules = patterns_found
self.string_rules = cast(List[RDFNode], patterns_found)
flags_found = set(self.shape.objects(SH_flags))
if len(flags_found) > 0:
# Just get the first found flags
self.flags = next(iter(flags_found))
else:
self.flags = None

re_flags = 0
if self.flags:
flags = str(self.flags.value).lower()
case_insensitive = 'i' in flags
if case_insensitive:
re_flags |= re.I
m = 'm' in flags
if m:
re_flags |= re.M
self.compiled_cache = {}
for p in patterns_found:
if p.value is not None and len(p.value) > 1:
re_pattern = str(p.value)
else:
re_pattern = str(p)
re_matcher = re.compile(re_pattern, re_flags)
self.compiled_cache[p] = re_matcher

@classmethod
def constraint_parameters(cls) -> List[rdflib.URIRef]:
return [SH_pattern]
Expand Down Expand Up @@ -311,18 +329,9 @@ def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) ->
def _evaluate_string_rule(self, r, target_graph, f_v_dict):
reports = []
non_conformant = False
assert isinstance(r, rdflib.Literal)
re_flags = 0
if self.flags:
flags = str(self.flags.value).lower()
case_insensitive = 'i' in flags
if case_insensitive:
re_flags |= re.I
m = 'm' in flags
if m:
re_flags |= re.M
re_pattern = str(r.value)
re_matcher = re.compile(re_pattern, re_flags)
re_matcher = self.compiled_cache.get(r, None)
if re_matcher is None:
raise RuntimeError(f"No compiled regex for {r}")
for f, value_nodes in f_v_dict.items():
for v in value_nodes:
match = False
Expand Down Expand Up @@ -379,7 +388,7 @@ def constraint_name(cls) -> str:
return "LanguageInConstraintComponent"

def make_generic_messages(self, datagraph: GraphLike, focus_node, value_node) -> List[rdflib.Literal]:
m = "String language is not in {}".format(stringify_node(datagraph, self.string_rules[0]))
m = "String language is not in {}".format(stringify_node(self.shape.sg.graph, self.string_rules[0]))
return [rdflib.Literal(m)]

def _evaluate_string_rule(self, r, target_graph, f_v_dict):
Expand Down
4 changes: 3 additions & 1 deletion pyshacl/rdfutil/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def mix_datasets(
for i in base_ds.store.contexts(None)
]
if isinstance(base_ds, rdflib.Dataset) and len(base_named_graphs) < 1:
# rdflib.Dataset always includes the DEFAULT_GRAPH_ID named graph
# but a conjunctive graph does not. It _could_ return no graphs.
base_named_graphs = [
rdflib.Graph(base_ds.store, DATASET_DEFAULT_GRAPH_ID, namespace_manager=base_ds.namespace_manager)
]
Expand Down Expand Up @@ -355,5 +357,5 @@ def clone_node(
cloned_o = clone_node(graph, o, target_graph, recursion=recursion + 1, deep_clone=deep_clone)
target_graph.add((new_node, cloned_p, cloned_o))
else:
new_node = rdflib.term.Identifier(str(node))
raise ValueError(f"Cannot clone node of type {type(node)}")
return new_node
Loading

0 comments on commit 11ce2fe

Please sign in to comment.