Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ and this project adheres to [Python PEP 440 Versioning](https://www.python.org/d
- CLI now accepts multiple data graph paths and adds a `--validate-each` flag.
- New `validate_each()` entrypoint for per-graph validation in library code.

### Fixed
- SPARQL constraint validation no longer misidentifies `VALUES` in property paths or predicate names.
- Fixes #301

## [0.30.1] - 2025-03-15

### Fixed
Expand Down
3 changes: 3 additions & 0 deletions pyshacl/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
from io import BufferedReader
from typing import List, Union, cast

from prettytable import PrettyTable

from pyshacl import __version__, validate, validate_each
Expand Down Expand Up @@ -424,6 +425,7 @@ def _col_widther(s, w):
i += w
return '\n'.join(s2)


def write_validation_output(args, is_conform: bool, v_graph, v_text: str) -> None:
if args.format == 'human':
args.output.write(v_text)
Expand Down Expand Up @@ -468,5 +470,6 @@ def write_validation_output(args, is_conform: bool, v_graph, v_text: str) -> Non
v_graph = v_graph.decode('utf-8')
args.output.write(v_graph)


if __name__ == "__main__":
main()
1 change: 0 additions & 1 deletion pyshacl/entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from .validator import Validator, assign_baked_in
from .validator_conformance import check_dash_result


DataGraphInput = Union[GraphLike, BufferedIOBase, TextIOBase, str, bytes]
MultiDataGraphInput = Sequence[DataGraphInput]

Expand Down
6 changes: 5 additions & 1 deletion pyshacl/helper/sparql_query_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ class SPARQLQueryHelper(object):
bind_sg_regex = re.compile(r"([\s{}()])[\$\?]shapesGraph", flags=re.M)
bind_cs_regex = re.compile(r"([\s{}()])[\$\?]currentShape", flags=re.M)
has_minus_regex = re.compile(r"^(?:[^#]*|M)(?!#)#?[^\?\$\#]M?INUS[\s\{]", flags=re.M | re.I)
has_values_regex = re.compile(r"^(?:[^#]*|V)(?!#)#?[^\?\$\#]V?ALUES[\s\{]", flags=re.M | re.I)
# Match keyword VALUES without catching predicate names like ex:allowedValues
# Supports VALUES(?x), VALUES ?x, and VALUES { ... }
has_values_regex = re.compile(
r"^(?!\s*#).*?(?<![\w\-\:])VALUES\b(?:\s*(?:\(|[\?\$]\w+|\{|\w|\s))", flags=re.M | re.I
)
has_service_regex = re.compile(r"^(?:[^#]*|S)(?!#)#?[^\?\$\#]S?ERVICE[\s\<]", flags=re.M | re.I)
has_nested_select_regex = re.compile(
r"SELECT[\s\(\)\$\?\a-z]*\{[^\}]*SELECT\s+((?:(?:[\?\$]\w+\s+)|(?:\*\s+))+)", flags=re.M | re.I
Expand Down
63 changes: 63 additions & 0 deletions test/issues/test_301.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
"""
https://github.com/RDFLib/pySHACL/issues/301
"""
import sys

import rdflib

import pyshacl

DATA_TTL = """\
@prefix ex: <http://example.org/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

ex:myVariable
a ex:Variable, owl:NamedIndividual ;
ex:name "test_variable" ;
ex:datatype xsd:integer ;
ex:allowedValues ( "1"^^xsd:integer "test"^^xsd:string "5"^^xsd:integer) .
"""

SHAPES_TTL = """\
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

ex:TestShape
a sh:NodeShape ;
sh:targetClass ex:Variable ;
sh:sparql [
a sh:SPARQLConstraint ;
sh:message "Test constraint" ;
sh:select '''
PREFIX ex: <http://example.org/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT $this WHERE {
$this ex:allowedValues ?list .
?list rdf:rest/rdf:first ?val .
}
''' ;
] .
"""


def test_301():
data_g = rdflib.Graph().parse(data=DATA_TTL, format="turtle")
shapes_g = rdflib.Graph().parse(data=SHAPES_TTL, format="turtle")
conforms, report_graph, results_text = pyshacl.validate(
data_g,
shacl_graph=shapes_g,
advanced=True,
debug=False,
)
assert not conforms
assert not isinstance(report_graph, pyshacl.errors.ValidationFailure)
assert "Test constraint" in results_text


if __name__ == "__main__":
test_301()
sys.exit(test_301())
Loading