Skip to content

shacl

Utilities for interacting with SHACL Shapes Graphs more easily.

Classes:

Functions:

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
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:

Source code in rdflib/extras/shacl.py
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