csvresults.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. """
  2. This module implements a parser and serializer for the CSV SPARQL result
  3. formats
  4. http://www.w3.org/TR/sparql11-results-csv-tsv/
  5. """
  6. from __future__ import annotations
  7. import codecs
  8. import csv
  9. from io import BufferedIOBase, TextIOBase
  10. from typing import IO, Dict, List, Optional, Union, cast
  11. from rdflib.plugins.sparql.processor import SPARQLResult
  12. from rdflib.query import Result, ResultParser, ResultSerializer
  13. from rdflib.term import BNode, Identifier, Literal, URIRef, Variable
  14. class CSVResultParser(ResultParser):
  15. """Parses SPARQL CSV results into a Result object."""
  16. def __init__(self):
  17. self.delim = ","
  18. # type error: Signature of "parse" incompatible with supertype "ResultParser"
  19. def parse(self, source: IO, content_type: Optional[str] = None) -> Result: # type: ignore[override]
  20. r = Result("SELECT")
  21. # type error: Incompatible types in assignment (expression has type "StreamReader", variable has type "IO[Any]")
  22. if isinstance(source.read(0), bytes):
  23. # if reading from source returns bytes do utf-8 decoding
  24. # type error: Incompatible types in assignment (expression has type "StreamReader", variable has type "IO[Any]")
  25. source = codecs.getreader("utf-8")(source) # type: ignore[assignment]
  26. reader = csv.reader(source, delimiter=self.delim)
  27. r.vars = [Variable(x) for x in next(reader)]
  28. r.bindings = []
  29. for row in reader:
  30. r.bindings.append(self.parseRow(row, r.vars))
  31. return r
  32. def parseRow(
  33. self, row: List[str], v: List[Variable]
  34. ) -> Dict[Variable, Union[BNode, URIRef, Literal]]:
  35. return dict(
  36. (var, val)
  37. for var, val in zip(v, [self.convertTerm(t) for t in row])
  38. if val is not None
  39. )
  40. def convertTerm(self, t: str) -> Optional[Union[BNode, URIRef, Literal]]:
  41. if t == "":
  42. return None
  43. if t.startswith("_:"):
  44. return BNode(t) # or generate new IDs?
  45. if t.startswith("http://") or t.startswith("https://"): # TODO: more?
  46. return URIRef(t)
  47. return Literal(t)
  48. class CSVResultSerializer(ResultSerializer):
  49. """Serializes SPARQL results into CSV format."""
  50. def __init__(self, result: SPARQLResult):
  51. ResultSerializer.__init__(self, result)
  52. self.delim = ","
  53. if result.type != "SELECT":
  54. raise Exception("CSVSerializer can only serialize select query results")
  55. def serialize(self, stream: IO, encoding: str = "utf-8", **kwargs) -> None:
  56. # the serialiser writes bytes in the given encoding
  57. # in py3 csv.writer is unicode aware and writes STRINGS,
  58. # so we encode afterward
  59. import codecs
  60. # TODO: Find a better solution for all this casting
  61. writable_stream = cast(Union[TextIOBase, BufferedIOBase], stream)
  62. if isinstance(writable_stream, TextIOBase):
  63. string_stream: TextIOBase = writable_stream
  64. else:
  65. byte_stream = cast(BufferedIOBase, writable_stream)
  66. string_stream = cast(TextIOBase, codecs.getwriter(encoding)(byte_stream))
  67. out = csv.writer(string_stream, delimiter=self.delim)
  68. vs = [self.serializeTerm(v, encoding) for v in self.result.vars] # type: ignore[union-attr]
  69. out.writerow(vs)
  70. for row in self.result.bindings:
  71. out.writerow(
  72. [self.serializeTerm(row.get(v), encoding) for v in self.result.vars] # type: ignore[union-attr]
  73. )
  74. def serializeTerm(
  75. self, term: Optional[Identifier], encoding: str
  76. ) -> Union[str, Identifier]:
  77. if term is None:
  78. return ""
  79. elif isinstance(term, BNode):
  80. return f"_:{term}"
  81. else:
  82. return term