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

feat: add type hints to rdflib.query and related #2097

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,12 @@ and will be removed for release.
[PR #2057](https://github.com/RDFLib/rdflib/pull/2057).
- `rdflib.graph` have mostly complete type hints.
[PR #2080](https://github.com/RDFLib/rdflib/pull/2080).
- `rdflib.plugins.sparql.algebra` amd `rdflib.plugins.sparql.operators` have
- `rdflib.plugins.sparql.algebra` and `rdflib.plugins.sparql.operators` have
mostly complete type hints.
[PR #2094](https://github.com/RDFLib/rdflib/pull/2094).
- `rdflib.query` and `rdflib.plugins.sparql.results.*` have mostly complete
type hints.
[PR #2097](https://github.com/RDFLib/rdflib/pull/2097).


<!-- -->
Expand Down
6 changes: 4 additions & 2 deletions rdflib/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -1534,12 +1534,14 @@ def query(
except NotImplementedError:
pass # store has no own implementation

if not isinstance(result, query.Result):
# type error: Subclass of "str" and "Result" cannot exist: would have incompatible method signatures
if not isinstance(result, query.Result): # type: ignore[unreachable]
result = plugin.get(cast(str, result), query.Result)
if not isinstance(processor, query.Processor):
processor = plugin.get(processor, query.Processor)(self)

return result(processor.query(query_object, initBindings, initNs, **kwargs))
# type error: Argument 1 to "Result" has incompatible type "Mapping[str, Any]"; expected "str"
return result(processor.query(query_object, initBindings, initNs, **kwargs)) # type: ignore[arg-type]

def update(
self,
Expand Down
2 changes: 1 addition & 1 deletion rdflib/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def plugins(name: Optional[str] = ..., kind: None = ...) -> Iterator[Plugin]:

def plugins(
name: Optional[str] = None, kind: Optional[Type[PluginT]] = None
) -> Iterator[Plugin]:
) -> Iterator[Plugin[PluginT]]:
"""
A generator of the plugins.

Expand Down
28 changes: 19 additions & 9 deletions rdflib/plugins/sparql/results/csvresults.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

"""

This module implements a parser and serializer for the CSV SPARQL result
Expand All @@ -9,23 +11,27 @@

import codecs
import csv
from typing import IO
from typing import IO, Dict, List, Optional, Union

from rdflib import BNode, Literal, URIRef, Variable
from rdflib.plugins.sparql.processor import SPARQLResult
from rdflib.query import Result, ResultParser, ResultSerializer
from rdflib.term import BNode, Identifier, Literal, URIRef, Variable


class CSVResultParser(ResultParser):
def __init__(self):
self.delim = ","

def parse(self, source, content_type=None):
# type error: Signature of "parse" incompatible with supertype "ResultParser"
def parse(self, source: IO, content_type: Optional[str] = None) -> Result: # type: ignore[override]

r = Result("SELECT")

# type error: Incompatible types in assignment (expression has type "StreamReader", variable has type "IO[Any]")
if isinstance(source.read(0), bytes):
# if reading from source returns bytes do utf-8 decoding
source = codecs.getreader("utf-8")(source)
# type error: Incompatible types in assignment (expression has type "StreamReader", variable has type "IO[Any]")
source = codecs.getreader("utf-8")(source) # type: ignore[assignment]

reader = csv.reader(source, delimiter=self.delim)
r.vars = [Variable(x) for x in next(reader)]
Expand All @@ -36,14 +42,16 @@ def parse(self, source, content_type=None):

return r

def parseRow(self, row, v):
def parseRow(
self, row: List[str], v: List[Variable]
) -> Dict[Variable, Union[BNode, URIRef, Literal]]:
return dict(
(var, val)
for var, val in zip(v, [self.convertTerm(t) for t in row])
if val is not None
)

def convertTerm(self, t):
def convertTerm(self, t: str) -> Optional[Union[BNode, URIRef, Literal]]:
if t == "":
return None
if t.startswith("_:"):
Expand All @@ -54,14 +62,14 @@ def convertTerm(self, t):


class CSVResultSerializer(ResultSerializer):
def __init__(self, result):
def __init__(self, result: SPARQLResult):
ResultSerializer.__init__(self, result)

self.delim = ","
if result.type != "SELECT":
raise Exception("CSVSerializer can only serialize select query results")

def serialize(self, stream: IO, encoding: str = "utf-8", **kwargs):
def serialize(self, stream: IO, encoding: str = "utf-8", **kwargs) -> None:

# the serialiser writes bytes in the given encoding
# in py3 csv.writer is unicode aware and writes STRINGS,
Expand All @@ -80,7 +88,9 @@ def serialize(self, stream: IO, encoding: str = "utf-8", **kwargs):
[self.serializeTerm(row.get(v), encoding) for v in self.result.vars] # type: ignore[union-attr]
)

def serializeTerm(self, term, encoding):
def serializeTerm(
self, term: Optional[Identifier], encoding: str
) -> Union[str, Identifier]:
if term is None:
return ""
elif isinstance(term, BNode):
Expand Down
9 changes: 7 additions & 2 deletions rdflib/plugins/sparql/results/graph.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from rdflib import Graph
from __future__ import annotations

from typing import IO, Optional

from rdflib.graph import Graph
from rdflib.query import Result, ResultParser


class GraphResultParser(ResultParser):
def parse(self, source, content_type):
# type error: Signature of "parse" incompatible with supertype "ResultParser"
def parse(self, source: IO, content_type: Optional[str]) -> Result: # type: ignore[override]

res = Result("CONSTRUCT") # hmm - or describe?type_)
res.graph = Graph()
Expand Down
30 changes: 18 additions & 12 deletions rdflib/plugins/sparql/results/jsonresults.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import json
from typing import IO, Any, Dict
from typing import IO, Any, Dict, Mapping, MutableSequence, Optional

from rdflib import BNode, Literal, URIRef, Variable
from rdflib.query import Result, ResultException, ResultParser, ResultSerializer
from rdflib.term import BNode, Identifier, Literal, URIRef, Variable

"""A Serializer for SPARQL results in JSON:

Expand All @@ -17,18 +19,20 @@


class JSONResultParser(ResultParser):
def parse(self, source, content_type=None):
# type error: Signature of "parse" incompatible with supertype "ResultParser"
def parse(self, source: IO, content_type: Optional[str] = None) -> Result: # type: ignore[override]
inp = source.read()
if isinstance(inp, bytes):
inp = inp.decode("utf-8")
return JSONResult(json.loads(inp))


class JSONResultSerializer(ResultSerializer):
def __init__(self, result):
def __init__(self, result: Result):
ResultSerializer.__init__(self, result)

def serialize(self, stream: IO, encoding: str = None): # type: ignore[override]
# type error: Signature of "serialize" incompatible with supertype "ResultSerializer"
def serialize(self, stream: IO, encoding: str = None) -> None: # type: ignore[override]

res: Dict[str, Any] = {}
if self.result.type == "ASK":
Expand All @@ -49,7 +53,7 @@ def serialize(self, stream: IO, encoding: str = None): # type: ignore[override]
else:
stream.write(r)

def _bindingToJSON(self, b):
def _bindingToJSON(self, b: Mapping[Variable, Identifier]) -> Dict[Variable, Any]:
res = {}
for var in b:
j = termToJSON(self, b[var])
Expand All @@ -59,7 +63,7 @@ def _bindingToJSON(self, b):


class JSONResult(Result):
def __init__(self, json):
def __init__(self, json: Dict[str, Any]):
self.json = json
if "boolean" in json:
type_ = "ASK"
Expand All @@ -76,17 +80,17 @@ def __init__(self, json):
self.bindings = self._get_bindings()
self.vars = [Variable(x) for x in json["head"]["vars"]]

def _get_bindings(self):
ret = []
def _get_bindings(self) -> MutableSequence[Mapping[Variable, Identifier]]:
ret: MutableSequence[Mapping[Variable, Identifier]] = []
for row in self.json["results"]["bindings"]:
outRow = {}
outRow: Dict[Variable, Identifier] = {}
for k, v in row.items():
outRow[Variable(k)] = parseJsonTerm(v)
ret.append(outRow)
return ret


def parseJsonTerm(d):
def parseJsonTerm(d: Dict[str, str]) -> Identifier:
"""rdflib object (Literal, URIRef, BNode) for the given json-format dict.

input is like:
Expand All @@ -107,7 +111,9 @@ def parseJsonTerm(d):
raise NotImplementedError("json term type %r" % t)


def termToJSON(self, term):
def termToJSON(
self: JSONResultSerializer, term: Optional[Identifier]
) -> Optional[Dict[str, str]]:
if isinstance(term, URIRef):
return {"type": "uri", "value": str(term)}
elif isinstance(term, Literal):
Expand Down
29 changes: 20 additions & 9 deletions rdflib/plugins/sparql/results/rdfresults.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from rdflib import RDF, Graph, Namespace, Variable
from typing import IO, Any, MutableMapping, Optional, Union

from rdflib.graph import Graph
from rdflib.namespace import RDF, Namespace
from rdflib.query import Result, ResultParser
from rdflib.term import Node, Variable

RS = Namespace("http://www.w3.org/2001/sw/DataAccess/tests/result-set#")


class RDFResultParser(ResultParser):
def parse(self, source, **kwargs):
def parse(self, source: Union[IO, Graph], **kwargs: Any) -> Result:
return RDFResult(source, **kwargs)


class RDFResult(Result):
def __init__(self, source, **kwargs):
def __init__(self, source: Union[IO, Graph], **kwargs: Any):

if not isinstance(source, Graph):
graph = Graph()
Expand Down Expand Up @@ -40,20 +44,27 @@ def __init__(self, source, **kwargs):
Result.__init__(self, type_)

if type_ == "SELECT":
self.vars = [Variable(v) for v in graph.objects(rs, RS.resultVariable)]
# type error: Argument 1 to "Variable" has incompatible type "Node"; expected "str"
self.vars = [Variable(v) for v in graph.objects(rs, RS.resultVariable)] # type: ignore[arg-type]

self.bindings = []

for s in graph.objects(rs, RS.solution):
sol = {}
sol: MutableMapping[Variable, Optional[Node]] = {}
for b in graph.objects(s, RS.binding):
sol[Variable(graph.value(b, RS.variable))] = graph.value(
# type error: Argument 1 to "Variable" has incompatible type "Optional[Node]"; expected "str"
sol[Variable(graph.value(b, RS.variable))] = graph.value( # type: ignore[arg-type]
b, RS.value
)
self.bindings.append(sol)
# error: Argument 1 to "append" of "list" has incompatible type "MutableMapping[Variable, Optional[Node]]"; expected "Mapping[Variable, Identifier]"
self.bindings.append(sol) # type: ignore[arg-type]
elif type_ == "ASK":
self.askAnswer = askAnswer.value
if askAnswer.value is None:
# type error: Item "Node" of "Optional[Node]" has no attribute "value"
# type error: Item "None" of "Optional[Node]" has no attribute "value"
self.askAnswer = askAnswer.value # type: ignore[union-attr]
# type error: Item "Node" of "Optional[Node]" has no attribute "value"
# type error: Item "None" of "Optional[Node]" has no attribute "value"
if askAnswer.value is None: # type: ignore[union-attr]
raise Exception("Malformed boolean in ask answer!")
elif type_ == "CONSTRUCT":
self.graph = g
19 changes: 14 additions & 5 deletions rdflib/plugins/sparql/results/tsvresults.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"""

import codecs
import typing
from typing import IO, Union

from pyparsing import (
FollowedBy,
Expand All @@ -16,7 +18,6 @@
ZeroOrMore,
)

from rdflib import Literal as RDFLiteral
from rdflib.plugins.sparql.parser import (
BLANK_NODE_LABEL,
IRIREF,
Expand All @@ -29,6 +30,9 @@
)
from rdflib.plugins.sparql.parserutils import Comp, CompValue, Param
from rdflib.query import Result, ResultParser
from rdflib.term import BNode
from rdflib.term import Literal as RDFLiteral
from rdflib.term import URIRef

ParserElement.setDefaultWhitespaceChars(" \n")

Expand Down Expand Up @@ -59,11 +63,13 @@


class TSVResultParser(ResultParser):
def parse(self, source, content_type=None):
# type error: Signature of "parse" incompatible with supertype "ResultParser" [override]
def parse(self, source: IO, content_type: typing.Optional[str] = None) -> Result: # type: ignore[override]

if isinstance(source.read(0), bytes):
# if reading from source returns bytes do utf-8 decoding
source = codecs.getreader("utf-8")(source)
# type error: Incompatible types in assignment (expression has type "StreamReader", variable has type "IO[Any]")
source = codecs.getreader("utf-8")(source) # type: ignore[assignment]

r = Result("SELECT")

Expand All @@ -80,11 +86,14 @@ def parse(self, source, content_type=None):
continue

row = ROW.parseString(line, parseAll=True)
r.bindings.append(dict(zip(r.vars, (self.convertTerm(x) for x in row))))
# type error: Generator has incompatible item type "object"; expected "Identifier"
r.bindings.append(dict(zip(r.vars, (self.convertTerm(x) for x in row)))) # type: ignore[misc]

return r

def convertTerm(self, t):
def convertTerm(
self, t: Union[object, RDFLiteral, BNode, CompValue, URIRef]
) -> typing.Optional[Union[object, BNode, URIRef, RDFLiteral]]:
if t is NONE_VALUE:
return None
if isinstance(t, CompValue):
Expand Down