|
16 | 16 |
|
17 | 17 | import hashlib |
18 | 18 | from pathlib import Path |
19 | | -from typing import Optional, Union |
| 19 | +from typing import TYPE_CHECKING, Optional, Union |
20 | 20 |
|
21 | 21 | from rdflib import RDF, BNode, Graph, Namespace |
22 | 22 | from rdflib.term import Node |
|
26 | 26 | from rocrate_validator.errors import BadSyntaxError |
27 | 27 | from rocrate_validator.models import Severity |
28 | 28 |
|
| 29 | +if TYPE_CHECKING: |
| 30 | + from rocrate_validator.requirements.shacl.models import Shape |
| 31 | + |
29 | 32 | # set up logging |
30 | 33 | logger = logging.getLogger(__name__) |
31 | 34 |
|
@@ -296,3 +299,39 @@ def load_shapes_from_graph(g: Graph) -> ShapesList: |
296 | 299 | subgraphs[shape] = subgraph |
297 | 300 |
|
298 | 301 | return ShapesList(node_shapes, property_shapes, subgraphs, g) |
| 302 | + |
| 303 | + |
| 304 | +def resolve_parent_shape( |
| 305 | + shapes_graph: Graph, source_shape_node: Node, shapes_registry |
| 306 | +) -> Optional[Shape]: |
| 307 | + """ |
| 308 | + Try to resolve the parent NodeShape/PropertyShape for a BNode constraint node. |
| 309 | +
|
| 310 | + When a SPARQL constraint (sh:sparql) or inline constraint BNode fires a violation, |
| 311 | + pyshacl reports the BNode as `sh:sourceShape`. That BNode is not registered |
| 312 | + in the ShapesRegistry directly; instead the containing NodeShape is. |
| 313 | + This helper walks up via sh:sparql and sh:property predicates to find it. |
| 314 | + """ |
| 315 | + from rocrate_validator.requirements.shacl.models import Shape |
| 316 | + |
| 317 | + if not isinstance(source_shape_node, BNode): |
| 318 | + return None |
| 319 | + SHACL = Namespace(SHACL_NS) |
| 320 | + # Predicates via which a NodeShape/PropertyShape can own a constraint BNode |
| 321 | + parent_predicates = [SHACL.sparql, SHACL.property] |
| 322 | + for predicate in parent_predicates: |
| 323 | + for parent_node in shapes_graph.subjects(predicate, source_shape_node): |
| 324 | + try: |
| 325 | + parent_shape = shapes_registry.get_shape( |
| 326 | + Shape.compute_key(shapes_graph, parent_node) |
| 327 | + ) |
| 328 | + if parent_shape is not None: |
| 329 | + logger.debug( |
| 330 | + "Resolved parent shape %s for SPARQL/inline constraint BNode %s", |
| 331 | + parent_shape.key, |
| 332 | + source_shape_node, |
| 333 | + ) |
| 334 | + return parent_shape |
| 335 | + except (ValueError, KeyError): |
| 336 | + continue |
| 337 | + return None |
0 commit comments