Skip to content

Commit d11a5ef

Browse files
authored
feat: add type hints to rdflib.query and related (#2097)
Add type hints to `rdflib.query` and result format implementations, also add/adjust ignores and type hints in other modules to accommodate the changes. This does not include any runtime changes.
1 parent 8a58ae8 commit d11a5ef

18 files changed

+262
-125
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,12 @@ and will be removed for release.
170170
[PR #2057](https://github.com/RDFLib/rdflib/pull/2057).
171171
- `rdflib.graph` have mostly complete type hints.
172172
[PR #2080](https://github.com/RDFLib/rdflib/pull/2080).
173-
- `rdflib.plugins.sparql.algebra` amd `rdflib.plugins.sparql.operators` have
173+
- `rdflib.plugins.sparql.algebra` and `rdflib.plugins.sparql.operators` have
174174
mostly complete type hints.
175175
[PR #2094](https://github.com/RDFLib/rdflib/pull/2094).
176+
- `rdflib.query` and `rdflib.plugins.sparql.results.*` have mostly complete
177+
type hints.
178+
[PR #2097](https://github.com/RDFLib/rdflib/pull/2097).
176179

177180

178181
<!-- -->

rdflib/graph.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,12 +1534,14 @@ def query(
15341534
except NotImplementedError:
15351535
pass # store has no own implementation
15361536

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

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

15441546
def update(
15451547
self,

rdflib/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def plugins(name: Optional[str] = ..., kind: None = ...) -> Iterator[Plugin]:
168168

169169
def plugins(
170170
name: Optional[str] = None, kind: Optional[Type[PluginT]] = None
171-
) -> Iterator[Plugin]:
171+
) -> Iterator[Plugin[PluginT]]:
172172
"""
173173
A generator of the plugins.
174174

rdflib/plugins/sparql/results/csvresults.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
"""
24
35
This module implements a parser and serializer for the CSV SPARQL result
@@ -9,23 +11,27 @@
911

1012
import codecs
1113
import csv
12-
from typing import IO
14+
from typing import IO, Dict, List, Optional, Union
1315

14-
from rdflib import BNode, Literal, URIRef, Variable
16+
from rdflib.plugins.sparql.processor import SPARQLResult
1517
from rdflib.query import Result, ResultParser, ResultSerializer
18+
from rdflib.term import BNode, Identifier, Literal, URIRef, Variable
1619

1720

1821
class CSVResultParser(ResultParser):
1922
def __init__(self):
2023
self.delim = ","
2124

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

2428
r = Result("SELECT")
2529

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

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

3743
return r
3844

39-
def parseRow(self, row, v):
45+
def parseRow(
46+
self, row: List[str], v: List[Variable]
47+
) -> Dict[Variable, Union[BNode, URIRef, Literal]]:
4048
return dict(
4149
(var, val)
4250
for var, val in zip(v, [self.convertTerm(t) for t in row])
4351
if val is not None
4452
)
4553

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

5563

5664
class CSVResultSerializer(ResultSerializer):
57-
def __init__(self, result):
65+
def __init__(self, result: SPARQLResult):
5866
ResultSerializer.__init__(self, result)
5967

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

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

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

83-
def serializeTerm(self, term, encoding):
91+
def serializeTerm(
92+
self, term: Optional[Identifier], encoding: str
93+
) -> Union[str, Identifier]:
8494
if term is None:
8595
return ""
8696
elif isinstance(term, BNode):

rdflib/plugins/sparql/results/graph.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
from rdflib import Graph
1+
from __future__ import annotations
2+
3+
from typing import IO, Optional
4+
5+
from rdflib.graph import Graph
26
from rdflib.query import Result, ResultParser
37

48

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

813
res = Result("CONSTRUCT") # hmm - or describe?type_)
914
res.graph = Graph()

rdflib/plugins/sparql/results/jsonresults.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
from __future__ import annotations
2+
13
import json
2-
from typing import IO, Any, Dict
4+
from typing import IO, Any, Dict, Mapping, MutableSequence, Optional
35

4-
from rdflib import BNode, Literal, URIRef, Variable
56
from rdflib.query import Result, ResultException, ResultParser, ResultSerializer
7+
from rdflib.term import BNode, Identifier, Literal, URIRef, Variable
68

79
"""A Serializer for SPARQL results in JSON:
810
@@ -17,18 +19,20 @@
1719

1820

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

2629

2730
class JSONResultSerializer(ResultSerializer):
28-
def __init__(self, result):
31+
def __init__(self, result: Result):
2932
ResultSerializer.__init__(self, result)
3033

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

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

52-
def _bindingToJSON(self, b):
56+
def _bindingToJSON(self, b: Mapping[Variable, Identifier]) -> Dict[Variable, Any]:
5357
res = {}
5458
for var in b:
5559
j = termToJSON(self, b[var])
@@ -59,7 +63,7 @@ def _bindingToJSON(self, b):
5963

6064

6165
class JSONResult(Result):
62-
def __init__(self, json):
66+
def __init__(self, json: Dict[str, Any]):
6367
self.json = json
6468
if "boolean" in json:
6569
type_ = "ASK"
@@ -76,17 +80,17 @@ def __init__(self, json):
7680
self.bindings = self._get_bindings()
7781
self.vars = [Variable(x) for x in json["head"]["vars"]]
7882

79-
def _get_bindings(self):
80-
ret = []
83+
def _get_bindings(self) -> MutableSequence[Mapping[Variable, Identifier]]:
84+
ret: MutableSequence[Mapping[Variable, Identifier]] = []
8185
for row in self.json["results"]["bindings"]:
82-
outRow = {}
86+
outRow: Dict[Variable, Identifier] = {}
8387
for k, v in row.items():
8488
outRow[Variable(k)] = parseJsonTerm(v)
8589
ret.append(outRow)
8690
return ret
8791

8892

89-
def parseJsonTerm(d):
93+
def parseJsonTerm(d: Dict[str, str]) -> Identifier:
9094
"""rdflib object (Literal, URIRef, BNode) for the given json-format dict.
9195
9296
input is like:
@@ -107,7 +111,9 @@ def parseJsonTerm(d):
107111
raise NotImplementedError("json term type %r" % t)
108112

109113

110-
def termToJSON(self, term):
114+
def termToJSON(
115+
self: JSONResultSerializer, term: Optional[Identifier]
116+
) -> Optional[Dict[str, str]]:
111117
if isinstance(term, URIRef):
112118
return {"type": "uri", "value": str(term)}
113119
elif isinstance(term, Literal):

rdflib/plugins/sparql/results/rdfresults.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
from rdflib import RDF, Graph, Namespace, Variable
1+
from typing import IO, Any, MutableMapping, Optional, Union
2+
3+
from rdflib.graph import Graph
4+
from rdflib.namespace import RDF, Namespace
25
from rdflib.query import Result, ResultParser
6+
from rdflib.term import Node, Variable
37

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

610

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

1115

1216
class RDFResult(Result):
13-
def __init__(self, source, **kwargs):
17+
def __init__(self, source: Union[IO, Graph], **kwargs: Any):
1418

1519
if not isinstance(source, Graph):
1620
graph = Graph()
@@ -40,20 +44,27 @@ def __init__(self, source, **kwargs):
4044
Result.__init__(self, type_)
4145

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

4550
self.bindings = []
4651

4752
for s in graph.objects(rs, RS.solution):
48-
sol = {}
53+
sol: MutableMapping[Variable, Optional[Node]] = {}
4954
for b in graph.objects(s, RS.binding):
50-
sol[Variable(graph.value(b, RS.variable))] = graph.value(
55+
# type error: Argument 1 to "Variable" has incompatible type "Optional[Node]"; expected "str"
56+
sol[Variable(graph.value(b, RS.variable))] = graph.value( # type: ignore[arg-type]
5157
b, RS.value
5258
)
53-
self.bindings.append(sol)
59+
# error: Argument 1 to "append" of "list" has incompatible type "MutableMapping[Variable, Optional[Node]]"; expected "Mapping[Variable, Identifier]"
60+
self.bindings.append(sol) # type: ignore[arg-type]
5461
elif type_ == "ASK":
55-
self.askAnswer = askAnswer.value
56-
if askAnswer.value is None:
62+
# type error: Item "Node" of "Optional[Node]" has no attribute "value"
63+
# type error: Item "None" of "Optional[Node]" has no attribute "value"
64+
self.askAnswer = askAnswer.value # type: ignore[union-attr]
65+
# type error: Item "Node" of "Optional[Node]" has no attribute "value"
66+
# type error: Item "None" of "Optional[Node]" has no attribute "value"
67+
if askAnswer.value is None: # type: ignore[union-attr]
5768
raise Exception("Malformed boolean in ask answer!")
5869
elif type_ == "CONSTRUCT":
5970
self.graph = g

rdflib/plugins/sparql/results/tsvresults.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"""
66

77
import codecs
8+
import typing
9+
from typing import IO, Union
810

911
from pyparsing import (
1012
FollowedBy,
@@ -16,7 +18,6 @@
1618
ZeroOrMore,
1719
)
1820

19-
from rdflib import Literal as RDFLiteral
2021
from rdflib.plugins.sparql.parser import (
2122
BLANK_NODE_LABEL,
2223
IRIREF,
@@ -29,6 +30,9 @@
2930
)
3031
from rdflib.plugins.sparql.parserutils import Comp, CompValue, Param
3132
from rdflib.query import Result, ResultParser
33+
from rdflib.term import BNode
34+
from rdflib.term import Literal as RDFLiteral
35+
from rdflib.term import URIRef
3236

3337
ParserElement.setDefaultWhitespaceChars(" \n")
3438

@@ -59,11 +63,13 @@
5963

6064

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

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

6874
r = Result("SELECT")
6975

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

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

8592
return r
8693

87-
def convertTerm(self, t):
94+
def convertTerm(
95+
self, t: Union[object, RDFLiteral, BNode, CompValue, URIRef]
96+
) -> typing.Optional[Union[object, BNode, URIRef, RDFLiteral]]:
8897
if t is NONE_VALUE:
8998
return None
9099
if isinstance(t, CompValue):

0 commit comments

Comments
 (0)