Skip to content

# `` shacl

Utilities for interacting with SHACL Shapes Graphs more easily.

Classes:

- `SHACLPathError` –

Functions:

- `build_shacl_path` –

  Build the SHACL Path triples for a path given by a `URIRef` for

- `parse_shacl_path` –

  Parse a valid SHACL path (e.g. the object of a triple with predicate sh:path)

## `` SHACLPathError

Bases: `Exception`

## `` build_shacl_path

    build_shacl_path(path: URIRef | Path, target_graph: Graph | None = None) -> tuple[IdentifiedNode, Graph | None]

Build the SHACL Path triples for a path given by a `URIRef` for simple paths or a `Path` for complex paths.

Returns an `IdentifiedNode` for the path (which should be the object of a triple with predicate `sh:path`) and the graph into which any new triples were added.

Parameters:

- ### `path`

  (`URIRef | Path`) –

  A `URIRef` or a `Path`

- ### `target_graph`

  (`Graph | None`, default: `None` ) –

  Optionally, a `Graph` into which to put constructed triples. If not provided, a new graph will be created

Returns:

- `tuple[IdentifiedNode, Graph | None]` –

  A (path_identifier, graph) tuple where: - path_identifier: If path is a `URIRef`, this is simply the provided path. If path is a `Path`, this is the `BNode` corresponding to the root of the SHACL path expression added to the graph. - graph: None if path is a `URIRef` (as no new triples are constructed). If path is a `Path`, this is either the target_graph provided or a new graph into which the path triples were added.

Source code in `rdflib/extras/shacl.py`

    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221

    def build_shacl_path(
        path: URIRef | Path, target_graph: Graph | None = None
    ) -> tuple[IdentifiedNode, Graph | None]:
        """
        Build the SHACL Path triples for a path given by a [`URIRef`][rdflib.term.URIRef] for
        simple paths or a [`Path`][rdflib.paths.Path] for complex paths.

        Returns an [`IdentifiedNode`][rdflib.term.IdentifiedNode] for the path (which should be
        the object of a triple with predicate `sh:path`) and the graph into which any
        new triples were added.

        Args:
            path: A [`URIRef`][rdflib.term.URIRef] or a [`Path`][rdflib.paths.Path]
            target_graph: Optionally, a [`Graph`][rdflib.graph.Graph] into which to put
                constructed triples. If not provided, a new graph will be created

        Returns:
            A (path_identifier, graph) tuple where:
                - path_identifier: If path is a [`URIRef`][rdflib.term.URIRef], this is simply
                the provided path. If path is a [`Path`][rdflib.paths.Path], this is
                the [`BNode`][rdflib.term.BNode] corresponding to the root of the SHACL
                path expression added to the graph.
                - graph: None if path is a [`URIRef`][rdflib.term.URIRef] (as no new triples
                are constructed). If path is a [`Path`][rdflib.paths.Path], this is either the
                target_graph provided or a new graph into which the path triples were added.
        """
        # If a path is a URI, that's the whole path. No graph needs to be constructed.
        if isinstance(path, URIRef):
            return path, None

        # Create a graph if one was not provided
        if target_graph is None:
            target_graph = Graph()

        # Recurse through the path to build the graph representation
        return _build_path_component(target_graph, path), target_graph

## `` parse_shacl_path

    parse_shacl_path(shapes_graph: Graph, path_identifier: Node) -> Union[URIRef, Path]

Parse a valid SHACL path (e.g. the object of a triple with predicate sh:path) from a `Graph` as a `URIRef` if the path is simply a predicate or a `Path` otherwise.

Parameters:

- ### `shapes_graph`

  (`Graph`) –

  A `Graph` containing the path to be parsed

- ### `path_identifier`

  (`Node`) –

  A `Node` of the path

Returns:

- `Union[URIRef, Path]` –

  A `URIRef` or a `Path`

Source code in `rdflib/extras/shacl.py`

     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108

    def parse_shacl_path(
        shapes_graph: Graph,
        path_identifier: Node,
    ) -> Union[URIRef, Path]:
        """
        Parse a valid SHACL path (e.g. the object of a triple with predicate sh:path)
        from a [`Graph`][rdflib.graph.Graph] as a [`URIRef`][rdflib.term.URIRef] if the path
        is simply a predicate or a [`Path`][rdflib.paths.Path] otherwise.

        Args:
            shapes_graph: A [`Graph`][rdflib.graph.Graph] containing the path to be parsed
            path_identifier: A [`Node`][rdflib.term.Node] of the path

        Returns:
            A [`URIRef`][rdflib.term.URIRef] or a [`Path`][rdflib.paths.Path]
        """
        path: Optional[Union[URIRef, Path]] = None

        # Literals are not allowed.
        if isinstance(path_identifier, Literal):
            raise TypeError("Literals are not a valid SHACL path.")

        # If a path is a URI, that's the whole path.
        elif isinstance(path_identifier, URIRef):
            if path_identifier == RDF.nil:
                raise SHACLPathError(
                    "A list of SHACL Paths must contain at least two path items."
                )
            path = path_identifier

        # Handle Sequence Paths
        elif shapes_graph.value(path_identifier, RDF.first) is not None:
            sequence = list(shapes_graph.items(path_identifier))
            if len(sequence) < 2:
                raise SHACLPathError(
                    "A list of SHACL Sequence Paths must contain at least two path items."
                )
            path = paths.SequencePath(
                *(parse_shacl_path(shapes_graph, path) for path in sequence)
            )

        # Handle sh:inversePath
        elif inverse_path := shapes_graph.value(path_identifier, SH.inversePath):
            path = paths.InvPath(parse_shacl_path(shapes_graph, inverse_path))

        # Handle sh:alternativePath
        elif alternative_path := shapes_graph.value(path_identifier, SH.alternativePath):
            alternatives = list(shapes_graph.items(alternative_path))
            if len(alternatives) < 2:
                raise SHACLPathError(
                    "List of SHACL alternate paths must have at least two path items."
                )
            path = paths.AlternativePath(
                *(
                    parse_shacl_path(shapes_graph, alternative)
                    for alternative in alternatives
                )
            )

        # Handle sh:zeroOrMorePath
        elif zero_or_more_path := shapes_graph.value(path_identifier, SH.zeroOrMorePath):
            path = paths.MulPath(parse_shacl_path(shapes_graph, zero_or_more_path), "*")

        # Handle sh:oneOrMorePath
        elif one_or_more_path := shapes_graph.value(path_identifier, SH.oneOrMorePath):
            path = paths.MulPath(parse_shacl_path(shapes_graph, one_or_more_path), "+")

        # Handle sh:zeroOrOnePath
        elif zero_or_one_path := shapes_graph.value(path_identifier, SH.zeroOrOnePath):
            path = paths.MulPath(parse_shacl_path(shapes_graph, zero_or_one_path), "?")

        # Raise error if none of the above options were found
        elif path is None:
            raise SHACLPathError(f"Cannot parse {repr(path_identifier)} as a SHACL Path.")

        return path