diff --git a/rdflib/graph.py b/rdflib/graph.py index 80ccc3fa8..d4f6037a3 100644 --- a/rdflib/graph.py +++ b/rdflib/graph.py @@ -1,5 +1,4 @@ -"""\ - +""" RDFLib defines the following kinds of Graphs: * :class:`~rdflib.graph.Graph` @@ -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: """ diff --git a/rdflib/plugins/sparql/algebra.py b/rdflib/plugins/sparql/algebra.py index 5cb22d265..5e2e1b982 100644 --- a/rdflib/plugins/sparql/algebra.py +++ b/rdflib/plugins/sparql/algebra.py @@ -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 @@ -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 diff --git a/rdflib/plugins/sparql/sparql.py b/rdflib/plugins/sparql/sparql.py index 235e2dc37..eab317836 100644 --- a/rdflib/plugins/sparql/sparql.py +++ b/rdflib/plugins/sparql/sparql.py @@ -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, @@ -14,6 +16,7 @@ Iterable, List, Optional, + Self, Tuple, TypeVar, Union, @@ -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: @@ -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: """ diff --git a/test/test_typing.py b/test/test_typing.py index 4934b330a..b1c19c637 100644 --- a/test/test_typing.py +++ b/test/test_typing.py @@ -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 @@ -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 @@ -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 { . } +""" + 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 { . } +""" + 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: +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