From 4fcf5d9c9de9ec0b4f25c2e0de9f7620ae6ca820 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Sat, 20 Feb 2021 12:39:39 +1000 Subject: [PATCH] Relaxed the Max Evaluation Depth from 28 to 30, we were seeing some real-world cases where meta-shacl was failing on large Shapes Graphs at 28 levels deep. sh:namespace values can now be xsd:anyURI or xsd:string or "literal string", but now cannot be . sh:order can now support xsd:decimal values and xsd:integer values, and can be interchanged at will. --- CHANGELOG.md | 10 +++- pyproject.toml | 2 +- pyshacl/__init__.py | 2 +- pyshacl/helper/sparql_query_helper.py | 17 +++---- pyshacl/parameter.py | 21 ++++++++- pyshacl/shape.py | 15 +++++- test/issues/bad_test_070.py | 66 +++++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 test/issues/bad_test_070.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d94a05..a24dd40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Python PEP 440 Versioning](https://www.python.org/dev/peps/pep-0440/). +## [0.14.3] - 2021-02-20 + +## Changed +- Relaxed the Max Evaluation Depth from 28 to 30, we were seeing some real-world cases where meta-shacl was failing on large Shapes Graphs at 28 levels deep. +- sh:namespace values can now be xsd:anyURI or xsd:string or "literal string", but now cannot be . +- sh:order can now support xsd:decimal values and xsd:integer values, and can be interchanged at will. + + ## [0.14.2] - 2021-01-02 ## Added @@ -12,7 +20,7 @@ and this project adheres to [Python PEP 440 Versioning](https://www.python.org/d ## Fixed - Black and Flake8 issues outstanding from 0.14.1 release. - Workaround a RDFLib bug trying to import `requests` when requests is not required to be installed. - - This bug will still be observed if you use SPARQLConstraints, SPARQLFunction or JSFunction features, but it can be worked around by simply installing `requests` in your python enviornment. + - This bug will still be observed if you use SPARQLConstraints, SPARQLFunction or JSFunction features, but it can be worked around by simply installing `requests` in your python environment. ## [0.14.1] - 2020-12-23 diff --git a/pyproject.toml b/pyproject.toml index 0e629a5..9f3c14d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "pyshacl" -version = "0.14.2" +version = "0.14.3" # Don't forget to change the version number in __init__.py along with this one description = "Python SHACL Validator" license = "Apache-2.0" diff --git a/pyshacl/__init__.py b/pyshacl/__init__.py index af7f212..58f9eac 100644 --- a/pyshacl/__init__.py +++ b/pyshacl/__init__.py @@ -6,7 +6,7 @@ # version compliant with https://www.python.org/dev/peps/pep-0440/ -__version__ = '0.14.2' +__version__ = '0.14.3' # Don't forget to change the version number in pyproject.toml along with this one __all__ = ['validate', 'Validator', '__version__', 'Shape', 'ShapesGraph'] diff --git a/pyshacl/helper/sparql_query_helper.py b/pyshacl/helper/sparql_query_helper.py index fa87511..36d937e 100644 --- a/pyshacl/helper/sparql_query_helper.py +++ b/pyshacl/helper/sparql_query_helper.py @@ -4,8 +4,6 @@ """ import re -from warnings import warn - import rdflib from rdflib import RDF, XSD @@ -173,14 +171,13 @@ def collect_prefixes(self): if prefix == "sh" and isinstance(namespace.value, str): # Known bug in shacl.ttl https://github.com/w3c/data-shapes/issues/125 pass - elif namespace.language is not None or isinstance(namespace.value, str): - warn( - Warning( - "sh:namespace value must be an RDF Literal with type xsd:anyURI.\nLiteral: \"{}\" type={}".format( - namespace.value, namespace.datatype or namespace.language - ) - ) - ) + elif ( + namespace.datatype == XSD.string + or namespace.language is not None + or isinstance(namespace.value, str) + ): + # Its now possible for namespace to be xsd:string or string literal + pass else: raise ConstraintLoadError( "sh:namespace value must be an RDF Literal with type xsd:anyURI.\nLiteral: {} type={}".format( diff --git a/pyshacl/parameter.py b/pyshacl/parameter.py index 46ca6a8..53b0645 100644 --- a/pyshacl/parameter.py +++ b/pyshacl/parameter.py @@ -1,3 +1,4 @@ +from decimal import Decimal from logging import Logger from typing import Union @@ -60,8 +61,24 @@ def __init__(self, sg, param_node, path=None, logger: Union[Logger, None] = None "https://www.w3.org/TR/shacl-af/#functions-example", ) else: - # TODO: check order is a literal with type Int - self.param_order = int(orders[0]) + order = orders[0] + if not isinstance(order, Literal): + raise ConstraintLoadError( + "sh:order value must be a literal of type Decimal or Integer", + "https://www.w3.org/TR/shacl-af/#functions-example", + ) + if isinstance(order.value, Decimal): + order = order.value + elif isinstance(order.value, int): + order = Decimal(order.value) + elif isinstance(order.value, float): + order = Decimal(str(order.value)) + else: + raise ConstraintLoadError( + "sh:order value must be a literal of type Decimal or Integer", + "https://www.w3.org/TR/shacl-af/#functions-example", + ) + self.param_order = order optionals = list(sg.objects(self.node, SH_optional)) if len(optionals) < 1: self.optional = False diff --git a/pyshacl/shape.py b/pyshacl/shape.py index a9b0c5b..d4e2e6e 100644 --- a/pyshacl/shape.py +++ b/pyshacl/shape.py @@ -198,7 +198,17 @@ def order(self): raise ShapeLoadError( "A SHACL Shape must be a numeric literal.", "https://www.w3.org/TR/shacl-af/#rules-order" ) - return Decimal(order_node.value) + if isinstance(order_node.value, Decimal): + order = order_node.value + elif isinstance(order_node.value, int): + order = Decimal(order_node.value) + elif isinstance(order_node.value, float): + order = Decimal(str(order_node.value)) + else: + raise ShapeLoadError( + "A SHACL Shape must be a numeric literal.", "https://www.w3.org/TR/shacl-af/#rules-order" + ) + return order def target_nodes(self): return self.sg.graph.objects(self.node, SH_targetNode) @@ -519,7 +529,8 @@ def validate( return True, [] if _evaluation_path is None: _evaluation_path = [] - elif len(_evaluation_path) >= 28: # 27 is the depth required to successfully do the meta-shacl test + elif len(_evaluation_path) >= 30: + # 27 is the depth required to successfully do the meta-shacl test on shacl.ttl path_str = "->".join((str(e) for e in _evaluation_path)) raise ReportableRuntimeError("Evaluation path too deep!\n{}".format(path_str)) # Lazy import here to avoid an import loop diff --git a/test/issues/bad_test_070.py b/test/issues/bad_test_070.py new file mode 100644 index 0000000..ad46b58 --- /dev/null +++ b/test/issues/bad_test_070.py @@ -0,0 +1,66 @@ +from rdflib import Graph + +from pyshacl import validate + + +""" +https://github.com/RDFLib/pySHACL/issues/70 +""" + +data_text = """\ +@prefix ns0: . +@prefix dc: . +@prefix ns1: . +@prefix sosa: . +@prefix xsd: . +@prefix rdfs: . +@prefix rdfs: . +@prefix geo: . +@prefix sf: . + + + ns0:asWKT "POINT(45.75 4.85)"^^ns0:wktLiteral ; + a . + + + ns0:hasGeometry ; + a ns0:Feature . + + a , . + + dc:identifier "q1" ; + ns1:numericValue "0.27121272683143616" ; + ns1:unit ; + a ns1:QuantityValue . + + + a sosa:Observation ; + sosa:hasFeatureOfInterest ; + sosa:hasResult ; + sosa:madeBySensor ; + sosa:observedProperty ; + sosa:resultTime "2018-01-01T12:36:12Z"^^xsd:dateTime . +""" +shacl_url = """https://raw.githubusercontent.com/rapw3k/DEMETER/master/models/SHACL/demeterAgriProfile-SHACL.ttl""" + + +def test_070(): + g = Graph().parse(data=data_text, format="turtle") + try: + conforms, g, s = validate( + g, + shacl_graph=shacl_url, + shacl_graph_format="turtle", + abort_on_error=False, + meta_shacl=False, + debug=True, + advanced=True, + ) + except Exception as e: + print(e) + raise + assert conforms + + +if __name__ == "__main__": + test_070()