Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 2283: Add type-review support for SPARQL query results from Graph.query #2320

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions rdflib/graph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""\

"""
RDFLib defines the following kinds of Graphs:

* :class:`~rdflib.graph.Graph`
Expand Down Expand Up @@ -1522,6 +1521,7 @@ def query(
initNs: Optional[Mapping[str, Any]] = None, # noqa: N803
initBindings: Optional[Mapping[str, Identifier]] = None, # noqa: N803
use_store_provided: bool = True,
*args: Any,
**kwargs: Any,
) -> query.Result:
"""
Expand Down
21 changes: 19 additions & 2 deletions rdflib/plugins/sparql/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@
from rdflib.plugins.sparql.operators import TrueFilter, and_
from rdflib.plugins.sparql.operators import simplify as simplifyFilters
from rdflib.plugins.sparql.parserutils import CompValue, Expr
from rdflib.plugins.sparql.sparql import Prologue, Query, Update
from rdflib.plugins.sparql.sparql import (
AskQuery,
ConstructQuery,
DescribeQuery,
Prologue,
Query,
SelectQuery,
Update,
)

# ---------------------------
# Some convenience methods
Expand Down Expand Up @@ -947,7 +955,16 @@ def translateQuery(
_traverseAgg(res, visitor=analyse)
_traverseAgg(res, _addVars)

return Query(prologue, res)
if q[1].name == "AskQuery":
return AskQuery(prologue, res)
elif q[1].name == "ConstructQuery":
return ConstructQuery(prologue, res)
elif q[1].name == "DescribeQuery":
return DescribeQuery(prologue, res)
elif q[1].name == "SelectQuery":
return SelectQuery(prologue, res)
else:
return Query(prologue, res)


class ExpressionNotCoveredException(Exception): # noqa: N818
Expand Down
31 changes: 31 additions & 0 deletions rdflib/plugins/sparql/sparql.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import itertools
import typing as t
from collections.abc import Mapping, MutableMapping

# TODO - import Self from typing_extensions when Python < 3.11.
from typing import (
TYPE_CHECKING,
Any,
Expand All @@ -14,6 +16,7 @@
Iterable,
List,
Optional,
Self,
Tuple,
TypeVar,
Union,
Expand All @@ -25,6 +28,7 @@
from rdflib.graph import ConjunctiveGraph, Dataset, Graph
from rdflib.namespace import NamespaceManager
from rdflib.plugins.sparql.parserutils import CompValue
from rdflib.plugins.sparql.processor import prepareQuery
from rdflib.term import BNode, Identifier, Literal, Node, URIRef, Variable

if TYPE_CHECKING:
Expand Down Expand Up @@ -489,6 +493,33 @@ def __init__(self, prologue: Prologue, algebra: CompValue):
self.algebra = algebra
self._original_args: Tuple[str, Mapping[str, str], Optional[str]]

@classmethod
def prepare(
cls,
queryString: str,
initNs: Optional[Mapping[str, Any]] = None,
base: Optional[str] = None,
) -> Self:
result = prepareQuery(queryString, initNs, base)
assert isinstance(result, cls)
return cls(result.prologue, result.algebra)


class AskQuery(Query):
pass


class ConstructQuery(Query):
pass


class DescribeQuery(Query):
pass


class SelectQuery(Query):
pass


class Update:
"""
Expand Down
148 changes: 148 additions & 0 deletions test/test_typing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
#!/usr/bin/env python3

# Portions of this file contributed by NIST are governed by the following
# statement:
#
# This software was developed at the National Institute of Standards
# and Technology by employees of the Federal Government in the course
# of their official duties. Pursuant to title 17 Section 105 of the
Expand Down Expand Up @@ -26,6 +31,12 @@
# TODO Bug - rdflib.plugins.sparql.prepareQuery() will run fine if this
# test is run, but mypy can't tell the symbol is exposed.
import rdflib.plugins.sparql.processor
from rdflib.plugins.sparql.sparql import (
AskQuery,
ConstructQuery,
DescribeQuery,
SelectQuery,
)
from rdflib.query import ResultRow
from rdflib.term import IdentifiedNode, Identifier, Node

Expand Down Expand Up @@ -132,3 +143,140 @@ def test_rdflib_query_exercise() -> None:

python_iri: str = kb_https_uriref.toPython()
assert python_iri == "https://example.org/kb/y"


def _test_rdflib_ask_query_result_graph() -> rdflib.Graph:
graph = rdflib.Graph()
graph.add(
(
rdflib.URIRef("http://example.org/kb/a"),
rdflib.URIRef("http://example.org/kb/b"),
rdflib.URIRef("http://example.org/kb/c"),
)
)
return graph


def test_rdflib_ask_query_result_exercise_0() -> None:
"""
This test shows minimally necessary type review runtime statements for an ASK query.
"""
graph = _test_rdflib_ask_query_result_graph()
ask_query_0 = """\
ASK { ?s ?p ?o . }
"""
result_0 = graph.query(AskQuery.prepare(ask_query_0))
assert result_0 is True


def test_rdflib_ask_query_result_exercise_1() -> None:
"""
This test shows minimally necessary type review runtime statements for an ASK query.
"""
graph = _test_rdflib_ask_query_result_graph()
ask_query_1 = """\
ASK { <http://example.org/kb/a> <http://example.org/kb/b> <http://example.org/kb/c> . }
"""
result_1 = graph.query(AskQuery.prepare(ask_query_1))
assert result_1 is True


def test_rdflib_ask_query_result_exercise_2() -> None:
"""
This test shows minimally necessary type review runtime statements for an ASK query.
"""
graph = _test_rdflib_ask_query_result_graph()
ask_query_2 = """\
ASK { <http://example.org/kb/a> <http://example.org/kb/a> <http://example.org/kb/a> . }
"""
result_2 = graph.query(AskQuery.prepare(ask_query_2))
assert result_2 is False


def test_rdflib_construct_query_result_exercise() -> None:
"""
This test shows minimally necessary type review runtime statements for a CONSTRUCT query.
"""

graph0 = rdflib.Graph()
graph1 = rdflib.Graph()

graph0.add(
(
rdflib.URIRef("http://example.org/kb/a"),
rdflib.URIRef("http://example.org/kb/b"),
rdflib.URIRef("http://example.org/kb/c"),
)
)

construct_query = """\
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
CONSTRUCT {
?s rdfs:comment "seen"@en .
?s rdfs:seeAlso ?s .
}
WHERE {
?s ?p ?o .
}
"""
for result in graph0.query(ConstructQuery.prepare(construct_query)):
graph1.add(result)

subjects0: Set[rdflib.term.Node] = {x for x in graph0.subjects(None, None)}
subjects1: Set[rdflib.term.Node] = {x for x in graph1.subjects(None, None)}
assert len(subjects0) == 1
assert subjects0 == subjects1


def test_rdflib_describe_query_result_exercise() -> None:
"""
This test shows minimally necessary type review runtime statements for a DESCRIBE query.
"""

graph = rdflib.Graph()
graph.add(
(
rdflib.URIRef("http://example.org/kb/a"),
rdflib.URIRef("http://example.org/kb/b"),
rdflib.URIRef("http://example.org/kb/c"),
)
)

expected: Set[rdflib.URIRef] = {rdflib.URIRef("http://example.org/kb/a")}
computed: Set[rdflib.URIRef] = set()
describe_query = """\
DESCRIBE ?s
"""
for result in graph.query(DescribeQuery.prepare(describe_query)):
if isinstance(result[0], rdflib.URIRef):
computed.add(result[0])
assert expected == computed


def test_rdflib_select_query_result_exercise() -> None:
"""
This test shows minimally necessary type review runtime statements for a SELECT query.
"""

graph = rdflib.Graph()
graph.add(
(
rdflib.URIRef("http://example.org/kb/a"),
rdflib.URIRef("http://example.org/kb/b"),
rdflib.URIRef("http://example.org/kb/c"),
)
)

# Assemble set of all triple-objects (position 2) that are URIRefs.
expected: Set[rdflib.URIRef] = {rdflib.URIRef("http://example.org/kb/b")}
computed: Set[rdflib.URIRef] = set()
select_query = """\
SELECT ?s ?p ?o
WHERE {
?s ?p ?o .
}
"""
for result in graph.query(SelectQuery.prepare(select_query)):
if isinstance(result[2], rdflib.URIRef):
computed.add(result[2])
assert expected == computed
Loading