Skip to content

Commit 8729ed9

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 43d8622 commit 8729ed9

23 files changed

+1323
-216
lines changed

rdflib/graph.py

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -348,29 +348,25 @@ def __init__(
348348
self.formula_aware = False
349349
self.default_union = False
350350

351-
def __get_store(self):
351+
@property
352+
def store(self) -> Store: # read-only attr
352353
return self.__store
353354

354-
store = property(__get_store) # read-only attr
355-
356-
def __get_identifier(self):
355+
@property
356+
def identifier(self) -> Node: # read-only attr
357357
return self.__identifier
358358

359-
identifier = property(__get_identifier) # read-only attr
360-
361-
def _get_namespace_manager(self):
359+
@property
360+
def namespace_manager(self) -> NamespaceManager:
361+
"""this graph's namespace-manager"""
362362
if self.__namespace_manager is None:
363363
self.__namespace_manager = NamespaceManager(self)
364364
return self.__namespace_manager
365365

366-
def _set_namespace_manager(self, nm):
367-
self.__namespace_manager = nm
368-
369-
namespace_manager = property(
370-
_get_namespace_manager,
371-
_set_namespace_manager,
372-
doc="this graph's namespace-manager",
373-
)
366+
@namespace_manager.setter
367+
def namespace_manager(self, value: NamespaceManager):
368+
"""this graph's namespace-manager"""
369+
self.__namespace_manager = value
374370

375371
def __repr__(self):
376372
return "<Graph identifier=%s (%s)>" % (self.identifier, type(self))
@@ -993,7 +989,12 @@ def absolutize(self, uri, defrag=1):
993989
# no destination and non-None positional encoding
994990
@overload
995991
def serialize(
996-
self, destination: None, format: str, base: Optional[str], encoding: str, **args
992+
self,
993+
destination: None,
994+
format: str,
995+
base: Optional[str],
996+
encoding: str,
997+
**args,
997998
) -> bytes:
998999
...
9991000

@@ -1054,18 +1055,37 @@ def serialize(
10541055
encoding: Optional[str] = None,
10551056
**args: Any,
10561057
) -> Union[bytes, str, "Graph"]:
1057-
"""Serialize the Graph to destination
1058-
1059-
If destination is None serialize method returns the serialization as
1060-
bytes or string.
1061-
1062-
If encoding is None and destination is None, returns a string
1063-
If encoding is set, and Destination is None, returns bytes
1064-
1065-
Format defaults to turtle.
1066-
1067-
Format support can be extended with plugins,
1068-
but "xml", "n3", "turtle", "nt", "pretty-xml", "trix", "trig" and "nquads" are built in.
1058+
"""
1059+
Serialize the graph.
1060+
1061+
:param destination:
1062+
The destination to serialize the graph to. This can be a path as a
1063+
:class:`str` or :class:`~pathlib.PurePath` object, or it can be a
1064+
:class:`~typing.IO[bytes]` like object. If this parameter is not
1065+
supplied the serialized graph will be returned.
1066+
:type destination: Optional[Union[str, typing.IO[bytes], pathlib.PurePath]]
1067+
:param format:
1068+
The format that the output should be written in. This value
1069+
references a :class:`~rdflib.serializer.Serializer` plugin. Format
1070+
support can be extended with plugins, but `"xml"`, `"n3"`,
1071+
`"turtle"`, `"nt"`, `"pretty-xml"`, `"trix"`, `"trig"`, `"nquads"`
1072+
and `"json-ld"` are built in. Defaults to `"turtle"`.
1073+
:type format: str
1074+
:param base:
1075+
The base IRI for formats that support it. For the turtle format this
1076+
will be used as the `@base` directive.
1077+
:type base: Optional[str]
1078+
:param encoding: Encoding of output.
1079+
:type encoding: Optional[str]
1080+
:param **args:
1081+
Additional arguments to pass to the
1082+
:class:`~rdflib.serializer.Serializer` that will be used.
1083+
:type **args: Any
1084+
:return: The serialized graph if `destination` is `None`.
1085+
:rtype: :class:`bytes` if `destination` is `None` and `encoding` is not `None`.
1086+
:rtype: :class:`bytes` if `destination` is `None` and `encoding` is `None`.
1087+
:return: `self` (i.e. the :class:`~rdflib.graph.Graph` instance) if `destination` is not None.
1088+
:rtype: :class:`~rdflib.graph.Graph` if `destination` is not None.
10691089
"""
10701090

10711091
# if base is not given as attribute use the base set for the graph
@@ -1254,7 +1274,7 @@ def query(
12541274
if none are given, the namespaces from the graph's namespace manager
12551275
are used.
12561276
1257-
:returntype: rdflib.query.Result
1277+
:returntype: :class:`~rdflib.query.Result`
12581278
12591279
"""
12601280

rdflib/namespace/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
import warnings
3-
from typing import TYPE_CHECKING, List, Union, Iterable
3+
from typing import TYPE_CHECKING, List, Tuple, Union, Iterable
44
from unicodedata import category
55

66
from pathlib import Path
@@ -587,7 +587,7 @@ def bind(self, prefix, namespace, override=True, replace=False) -> None:
587587
self.store.bind(prefix, namespace)
588588
insert_trie(self.__trie, str(namespace))
589589

590-
def namespaces(self):
590+
def namespaces(self) -> Iterable[Tuple[str, URIRef]]:
591591
for prefix, namespace in self.store.namespaces():
592592
namespace = URIRef(namespace)
593593
yield prefix, namespace

rdflib/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Parser(object):
4545
def __init__(self):
4646
pass
4747

48-
def parse(self, source, sink):
48+
def parse(self, source, sink, **args):
4949
pass
5050

5151

rdflib/plugins/serializers/n3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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/nt.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import warnings
1313
import codecs
1414

15+
from rdflib.util import as_textio
16+
1517
__all__ = ["NTSerializer"]
1618

1719

@@ -38,9 +40,15 @@ def serialize(
3840
f"Given encoding was: {encoding}"
3941
)
4042

41-
for triple in self.store:
42-
stream.write(_nt_row(triple).encode())
43-
stream.write("\n".encode())
43+
with as_textio(
44+
stream,
45+
encoding=encoding, # TODO: CHECK: self.encoding set removed, why?
46+
errors="_rdflib_nt_escape",
47+
write_through=True,
48+
) as text_stream:
49+
for triple in self.store:
50+
text_stream.write(_nt_row(triple))
51+
text_stream.write("\n")
4452

4553

4654
class NT11Serializer(NTSerializer):

rdflib/plugins/serializers/rdfxml.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import IO, Dict, Optional, Set
1+
from typing import IO, Dict, Optional, Set, cast
22
from rdflib.plugins.serializers.xmlwriter import XMLWriter
33

44
from rdflib.namespace import Namespace, RDF, RDFS # , split_uri
@@ -173,6 +173,8 @@ def serialize(
173173
encoding: Optional[str] = None,
174174
**args,
175175
):
176+
# TODO FIXME: this should be Optional, but it's not because nothing
177+
# treats it as such.
176178
self.__serialized: Dict[Identifier, int] = {}
177179
store = self.store
178180
# if base is given here, use that, if not and a base is set for the graph use that
@@ -239,6 +241,7 @@ def subject(self, subject: Identifier, depth: int = 1):
239241
writer = self.writer
240242

241243
if subject in self.forceRDFAbout:
244+
subject = cast(URIRef, subject)
242245
writer.push(RDFVOC.Description)
243246
writer.attribute(RDFVOC.about, self.relativize(subject))
244247
writer.pop(RDFVOC.Description)
@@ -280,6 +283,7 @@ def subj_as_obj_more_than(ceil):
280283

281284
elif subject in self.forceRDFAbout:
282285
# TODO FIXME?: this looks like a duplicate of first condition
286+
subject = cast(URIRef, subject)
283287
writer.push(RDFVOC.Description)
284288
writer.attribute(RDFVOC.about, self.relativize(subject))
285289
writer.pop(RDFVOC.Description)

rdflib/plugins/serializers/trig.py

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -62,52 +62,45 @@ def serialize(
6262
spacious: Optional[bool] = None,
6363
**args,
6464
):
65-
self.reset()
66-
self.stream = stream
67-
# if base is given here, use that, if not and a base is set for the graph use that
68-
if base is not None:
69-
self.base = base
70-
elif self.store.base is not None:
71-
self.base = self.store.base
72-
73-
if spacious is not None:
74-
self._spacious = spacious
75-
76-
self.preprocess()
77-
78-
self.startDocument()
79-
80-
firstTime = True
81-
for store, (ordered_subjects, subjects, ref) in self._contexts.items():
82-
if not ordered_subjects:
83-
continue
84-
85-
self._references = ref
86-
self._serialized = {}
87-
self.store = store
88-
self._subjects = subjects
89-
90-
if self.default_context and store.identifier == self.default_context:
91-
self.write(self.indent() + "\n{")
92-
else:
93-
if isinstance(store.identifier, BNode):
94-
iri = store.identifier.n3()
95-
else:
96-
iri = self.getQName(store.identifier)
97-
if iri is None:
98-
iri = store.identifier.n3()
99-
self.write(self.indent() + "\n%s {" % iri)
65+
self._serialize_init(stream, base, encoding, spacious)
66+
try:
67+
self.preprocess()
10068

101-
self.depth += 1
102-
for subject in ordered_subjects:
103-
if self.isDone(subject):
69+
self.startDocument()
70+
71+
firstTime = True
72+
for store, (ordered_subjects, subjects, ref) in self._contexts.items():
73+
if not ordered_subjects:
10474
continue
105-
if firstTime:
106-
firstTime = False
107-
if self.statement(subject) and not firstTime:
108-
self.write("\n")
109-
self.depth -= 1
110-
self.write("}\n")
111-
112-
self.endDocument()
113-
stream.write("\n".encode("latin-1"))
75+
76+
self._references = ref
77+
self._serialized = {}
78+
self.store = store
79+
self._subjects = subjects
80+
81+
if self.default_context and store.identifier == self.default_context:
82+
self.write(self.indent() + "\n{")
83+
else:
84+
if isinstance(store.identifier, BNode):
85+
iri = store.identifier.n3()
86+
else:
87+
iri = self.getQName(store.identifier)
88+
if iri is None:
89+
iri = store.identifier.n3()
90+
self.write(self.indent() + "\n%s {" % iri)
91+
92+
self.depth += 1
93+
for subject in ordered_subjects:
94+
if self.isDone(subject):
95+
continue
96+
if firstTime:
97+
firstTime = False
98+
if self.statement(subject) and not firstTime:
99+
self.write("\n")
100+
self.depth -= 1
101+
self.write("}\n")
102+
103+
self.endDocument()
104+
self.write("\n")
105+
finally:
106+
self._serialize_end()

0 commit comments

Comments
 (0)