Skip to content

Commit

Permalink
feat: add type hints to rdflib.query and related
Browse files Browse the repository at this point in the history
Add type hints to `rdflib.query` and result format implementations, also
add/adjust ignores and type hints in other modules to accomodate the
changes.

This does not include any runtime changes.
  • Loading branch information
aucampia committed Aug 23, 2022
1 parent c487be6 commit d0abdc8
Show file tree
Hide file tree
Showing 17 changed files with 258 additions and 124 deletions.
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
23 changes: 15 additions & 8 deletions rdflib/plugins/sparql/results/txtresults.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import IO, List, Optional
from typing import IO, List, Optional, Union

from rdflib import BNode, Literal, URIRef
from rdflib.namespace import NamespaceManager
from rdflib.query import ResultSerializer
from rdflib.term import Variable
from rdflib.term import BNode, Literal, URIRef, Variable


def _termString(t, namespace_manager: Optional[NamespaceManager]):
def _termString(
t: Optional[Union[URIRef, Literal, BNode]],
namespace_manager: Optional[NamespaceManager],
) -> str:
if t is None:
return "-"
if namespace_manager:
Expand All @@ -26,12 +28,13 @@ class TXTResultSerializer(ResultSerializer):
"""

# TODO FIXME: class specific args should be keyword only.
# type error: Signature of "serialize" incompatible with supertype "ResultSerializer"
def serialize( # type: ignore[override]
self,
stream: IO,
encoding: str,
namespace_manager: Optional[NamespaceManager] = None,
):
) -> None:
"""
return a text table of query results
"""
Expand All @@ -50,13 +53,17 @@ def c(s, w):
raise Exception("Can only pretty print SELECT results!")

if not self.result:
return "(no results)\n"
# type error: No return value expected
return "(no results)\n" # type: ignore[return-value]
else:

keys: List[Variable] = self.result.vars # type: ignore[assignment]
maxlen = [0] * len(keys)
b = [
[_termString(r[k], namespace_manager) for k in keys]
# type error: Value of type "Union[Tuple[IdentifiedNode, IdentifiedNode, Identifier], bool, ResultRow]" is not indexable
# type error: Invalid tuple index type (actual type "Variable", expected type "Union[int, slice]")
# error: Argument 1 to "_termString" has incompatible type "Union[Any, Identifier]"; expected "Union[URIRef, Literal, BNode, None]"
# NOTE on type error: The problem here is that r can be more types than _termString expects because result can be a result of multiple types.
[_termString(r[k], namespace_manager) for k in keys] # type: ignore[index,misc,arg-type]
for r in self.result
]
for r in b:
Expand Down

0 comments on commit d0abdc8

Please sign in to comment.