Skip to content

Commit abb01be

Browse files
committed
Make Result.serialize work more like Graph.serialize
This patch makes the following changes to `Result.serialize`. * Return str by default instead of bytes. * Use "txt" as the default tabular serialization format. * Use "turtle" as the default graph serialization format. * Support both typing.IO[bytes] and typing.TextIO destinations. Corresponding changes are made to the specific serializers also. This patch also changes how text is written to typing.IO[bytes] in serializers to ensure that the buffer is flushed and detatched from the TextIOWrapper once the serialization function completes so it can be used normally afterwards. This patch further includes a bunch of additional type hints.
1 parent 1729243 commit abb01be

28 files changed

+1642
-340
lines changed

rdflib/graph.py

Lines changed: 155 additions & 83 deletions
Large diffs are not rendered by default.

rdflib/parser.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import sys
1717

1818
from io import BytesIO, TextIOBase, TextIOWrapper, StringIO, BufferedIOBase
19+
from typing import Optional, Union
1920

2021
from urllib.request import Request
2122
from urllib.request import url2pathname
@@ -44,7 +45,7 @@ class Parser(object):
4445
def __init__(self):
4546
pass
4647

47-
def parse(self, source, sink):
48+
def parse(self, source, sink, **args):
4849
pass
4950

5051

@@ -214,7 +215,12 @@ def __repr__(self):
214215

215216

216217
def create_input_source(
217-
source=None, publicID=None, location=None, file=None, data=None, format=None
218+
source=None,
219+
publicID=None,
220+
location=None,
221+
file=None,
222+
data: Optional[Union[str, bytes, bytearray]] = None,
223+
format=None,
218224
):
219225
"""
220226
Return an appropriate InputSource instance for the given

rdflib/plugin.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,21 @@
3636
UpdateProcessor,
3737
)
3838
from rdflib.exceptions import Error
39-
from typing import Type, TypeVar
39+
from typing import (
40+
TYPE_CHECKING,
41+
Any,
42+
Dict,
43+
Generic,
44+
Iterator,
45+
Optional,
46+
Tuple,
47+
Type,
48+
TypeVar,
49+
overload,
50+
)
51+
52+
if TYPE_CHECKING:
53+
from pkg_resources import EntryPoint
4054

4155
__all__ = ["register", "get", "plugins", "PluginException", "Plugin", "PKGPlugin"]
4256

@@ -51,42 +65,47 @@
5165
"rdf.plugins.updateprocessor": UpdateProcessor,
5266
}
5367

54-
_plugins = {}
68+
_plugins: Dict[Tuple[str, Type[Any]], "Plugin"] = {}
5569

5670

5771
class PluginException(Error):
5872
pass
5973

6074

61-
class Plugin(object):
62-
def __init__(self, name, kind, module_path, class_name):
75+
PluginT = TypeVar("PluginT")
76+
77+
78+
class Plugin(Generic[PluginT]):
79+
def __init__(
80+
self, name: str, kind: Type[PluginT], module_path: str, class_name: str
81+
):
6382
self.name = name
6483
self.kind = kind
6584
self.module_path = module_path
6685
self.class_name = class_name
67-
self._class = None
86+
self._class: Optional[Type[PluginT]] = None
6887

69-
def getClass(self):
88+
def getClass(self) -> Type[PluginT]:
7089
if self._class is None:
7190
module = __import__(self.module_path, globals(), locals(), [""])
7291
self._class = getattr(module, self.class_name)
7392
return self._class
7493

7594

76-
class PKGPlugin(Plugin):
77-
def __init__(self, name, kind, ep):
95+
class PKGPlugin(Plugin[PluginT]):
96+
def __init__(self, name: str, kind: Type[PluginT], ep: "EntryPoint"):
7897
self.name = name
7998
self.kind = kind
8099
self.ep = ep
81-
self._class = None
100+
self._class: Optional[Type[PluginT]] = None
82101

83-
def getClass(self):
102+
def getClass(self) -> Type[PluginT]:
84103
if self._class is None:
85104
self._class = self.ep.load()
86105
return self._class
87106

88107

89-
def register(name: str, kind, module_path, class_name):
108+
def register(name: str, kind: Type[Any], module_path, class_name):
90109
"""
91110
Register the plugin for (name, kind). The module_path and
92111
class_name should be the path to a plugin class.
@@ -95,16 +114,13 @@ def register(name: str, kind, module_path, class_name):
95114
_plugins[(name, kind)] = p
96115

97116

98-
PluginT = TypeVar("PluginT")
99-
100-
101117
def get(name: str, kind: Type[PluginT]) -> Type[PluginT]:
102118
"""
103119
Return the class for the specified (name, kind). Raises a
104120
PluginException if unable to do so.
105121
"""
106122
try:
107-
p = _plugins[(name, kind)]
123+
p: Plugin[PluginT] = _plugins[(name, kind)]
108124
except KeyError:
109125
raise PluginException("No plugin registered for (%s, %s)" % (name, kind))
110126
return p.getClass()
@@ -121,7 +137,21 @@ def get(name: str, kind: Type[PluginT]) -> Type[PluginT]:
121137
_plugins[(ep.name, kind)] = PKGPlugin(ep.name, kind, ep)
122138

123139

124-
def plugins(name=None, kind=None):
140+
@overload
141+
def plugins(
142+
name: Optional[str] = ..., kind: Type[PluginT] = ...
143+
) -> Iterator[Plugin[PluginT]]:
144+
...
145+
146+
147+
@overload
148+
def plugins(name: Optional[str] = ..., kind: None = ...) -> Iterator[Plugin]:
149+
...
150+
151+
152+
def plugins(
153+
name: Optional[str] = None, kind: Optional[Type[PluginT]] = None
154+
) -> Iterator[Plugin]:
125155
"""
126156
A generator of the plugins.
127157

rdflib/plugins/serializers/jsonld.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from rdflib.graph import Graph
4242
from rdflib.term import URIRef, Literal, BNode
4343
from rdflib.namespace import RDF, XSD
44+
from typing import IO, Optional
4445

4546
from ..shared.jsonld.context import Context, UNDEF
4647
from ..shared.jsonld.util import json
@@ -53,10 +54,16 @@
5354

5455

5556
class JsonLDSerializer(Serializer):
56-
def __init__(self, store):
57+
def __init__(self, store: Graph):
5758
super(JsonLDSerializer, self).__init__(store)
5859

59-
def serialize(self, stream, base=None, encoding=None, **kwargs):
60+
def serialize(
61+
self,
62+
stream: IO[bytes],
63+
base: Optional[str] = None,
64+
encoding: Optional[str] = None,
65+
**kwargs
66+
):
6067
# TODO: docstring w. args and return value
6168
encoding = encoding or "utf-8"
6269
if encoding not in ("utf-8", "utf-16"):

rdflib/plugins/serializers/n3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class N3Serializer(TurtleSerializer):
1414

1515
short_name = "n3"
1616

17-
def __init__(self, store, parent=None):
17+
def __init__(self, store: Graph, parent=None):
1818
super(N3Serializer, self).__init__(store)
1919
self.keywords.update({OWL.sameAs: "=", SWAP_LOG.implies: "=>"})
2020
self.parent = parent
@@ -109,7 +109,7 @@ def p_clause(self, node, position):
109109
self.write("{")
110110
self.depth += 1
111111
serializer = N3Serializer(node, parent=self)
112-
serializer.serialize(self.stream)
112+
serializer.serialize(self.stream.buffer)
113113
self.depth -= 1
114114
self.write(self.indent() + "}")
115115
return True

rdflib/plugins/serializers/nquads.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from typing import IO, Optional
12
import warnings
23

4+
from rdflib.graph import ConjunctiveGraph, Graph
35
from rdflib.term import Literal
46
from rdflib.serializer import Serializer
57

@@ -9,15 +11,22 @@
911

1012

1113
class NQuadsSerializer(Serializer):
12-
def __init__(self, store):
14+
def __init__(self, store: Graph):
1315
if not store.context_aware:
1416
raise Exception(
1517
"NQuads serialization only makes " "sense for context-aware stores!"
1618
)
1719

1820
super(NQuadsSerializer, self).__init__(store)
19-
20-
def serialize(self, stream, base=None, encoding=None, **args):
21+
self.store: ConjunctiveGraph
22+
23+
def serialize(
24+
self,
25+
stream: IO[bytes],
26+
base: Optional[str] = None,
27+
encoding: Optional[str] = None,
28+
**args
29+
):
2130
if base is not None:
2231
warnings.warn("NQuadsSerializer does not support base.")
2332
if encoding is not None and encoding.lower() != self.encoding.lower():

rdflib/plugins/serializers/nt.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
See <http://www.w3.org/TR/rdf-testcases/#ntriples> for details about the
44
format.
55
"""
6+
from typing import IO, Optional
7+
8+
from rdflib.graph import Graph
69
from rdflib.term import Literal
710
from rdflib.serializer import Serializer
811

912
import warnings
1013
import codecs
1114

15+
from rdflib.util import as_textio
16+
1217
__all__ = ["NTSerializer"]
1318

1419

@@ -17,19 +22,32 @@ class NTSerializer(Serializer):
1722
Serializes RDF graphs to NTriples format.
1823
"""
1924

20-
def __init__(self, store):
25+
def __init__(self, store: Graph):
2126
Serializer.__init__(self, store)
2227
self.encoding = "ascii" # n-triples are ascii encoded
2328

24-
def serialize(self, stream, base=None, encoding=None, **args):
29+
def serialize(
30+
self,
31+
stream: IO[bytes],
32+
base: Optional[str] = None,
33+
encoding: Optional[str] = None,
34+
**args
35+
):
2536
if base is not None:
2637
warnings.warn("NTSerializer does not support base.")
2738
if encoding is not None and encoding.lower() != self.encoding.lower():
2839
warnings.warn("NTSerializer does not use custom encoding.")
2940
encoding = self.encoding
30-
for triple in self.store:
31-
stream.write(_nt_row(triple).encode(self.encoding, "_rdflib_nt_escape"))
32-
stream.write("\n".encode("latin-1"))
41+
42+
with as_textio(
43+
stream,
44+
encoding=self.encoding,
45+
errors="_rdflib_nt_escape",
46+
write_through=True,
47+
) as text_stream:
48+
for triple in self.store:
49+
text_stream.write(_nt_row(triple))
50+
text_stream.write("\n")
3351

3452

3553
class NT11Serializer(NTSerializer):
@@ -39,7 +57,7 @@ class NT11Serializer(NTSerializer):
3957
Exactly like nt - only utf8 encoded.
4058
"""
4159

42-
def __init__(self, store):
60+
def __init__(self, store: Graph):
4361
Serializer.__init__(self, store) # default to utf-8
4462

4563

0 commit comments

Comments
 (0)