query.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. from __future__ import annotations
  2. import itertools
  3. import types
  4. import warnings
  5. from io import BytesIO
  6. from typing import (
  7. IO,
  8. TYPE_CHECKING,
  9. Any,
  10. BinaryIO,
  11. Dict,
  12. Iterator,
  13. List,
  14. Mapping,
  15. MutableSequence,
  16. Optional,
  17. Tuple,
  18. Union,
  19. cast,
  20. overload,
  21. )
  22. from urllib.parse import urlparse
  23. from urllib.request import url2pathname
  24. __all__ = [
  25. "Processor",
  26. "UpdateProcessor",
  27. "Result",
  28. "ResultRow",
  29. "ResultParser",
  30. "ResultSerializer",
  31. "ResultException",
  32. "EncodeOnlyUnicode",
  33. ]
  34. import rdflib.term
  35. if TYPE_CHECKING:
  36. from rdflib.graph import Graph, _TripleType
  37. from rdflib.plugins.sparql.sparql import Query, Update
  38. from rdflib.term import Identifier, Variable
  39. class Processor:
  40. """
  41. Query plugin interface.
  42. This module is useful for those wanting to write a query processor
  43. that can plugin to rdf. If you are wanting to execute a query you
  44. likely want to do so through the Graph class query method.
  45. """
  46. def __init__(self, graph: Graph):
  47. pass
  48. # type error: Missing return statement
  49. def query( # type: ignore[empty-body]
  50. self,
  51. strOrQuery: Union[str, Query], # noqa: N803
  52. initBindings: Mapping[str, Identifier] = {}, # noqa: N803
  53. initNs: Mapping[str, Any] = {}, # noqa: N803
  54. DEBUG: bool = False, # noqa: N803
  55. ) -> Mapping[str, Any]:
  56. pass
  57. class UpdateProcessor:
  58. """Update plugin interface.
  59. This module is useful for those wanting to write an update
  60. processor that can plugin to rdflib. If you are wanting to execute
  61. an update statement you likely want to do so through the Graph
  62. class update method.
  63. !!! example "New in version 4.0"
  64. """
  65. def __init__(self, graph: Graph):
  66. pass
  67. def update(
  68. self,
  69. strOrQuery: Union[str, Update], # noqa: N803
  70. initBindings: Mapping[str, Identifier] = {}, # noqa: N803
  71. initNs: Mapping[str, Any] = {}, # noqa: N803
  72. ) -> None:
  73. pass
  74. class ResultException(Exception): # noqa: N818
  75. pass
  76. class EncodeOnlyUnicode:
  77. """This is a crappy work-around for http://bugs.python.org/issue11649"""
  78. def __init__(self, stream: BinaryIO):
  79. self.__stream = stream
  80. def write(self, arg):
  81. if isinstance(arg, str):
  82. self.__stream.write(arg.encode("utf-8"))
  83. else:
  84. self.__stream.write(arg)
  85. def __getattr__(self, name: str) -> Any:
  86. return getattr(self.__stream, name)
  87. class ResultRow(Tuple[rdflib.term.Identifier, ...]):
  88. """A single result row allows accessing bindings as attributes or with []
  89. ```python
  90. >>> from rdflib import URIRef, Variable
  91. >>> rr=ResultRow({ Variable('a'): URIRef('urn:cake') }, [Variable('a')])
  92. >>> rr[0]
  93. rdflib.term.URIRef('urn:cake')
  94. >>> rr[1]
  95. Traceback (most recent call last):
  96. ...
  97. IndexError: tuple index out of range
  98. >>> rr.a
  99. rdflib.term.URIRef('urn:cake')
  100. >>> rr.b
  101. Traceback (most recent call last):
  102. ...
  103. AttributeError: b
  104. >>> rr['a']
  105. rdflib.term.URIRef('urn:cake')
  106. >>> rr['b']
  107. Traceback (most recent call last):
  108. ...
  109. KeyError: 'b'
  110. >>> rr[Variable('a')]
  111. rdflib.term.URIRef('urn:cake')
  112. ```
  113. !!! example "New in version 4.0"
  114. """
  115. labels: Mapping[str, int]
  116. def __new__(cls, values: Mapping[Variable, Identifier], labels: List[Variable]):
  117. # type error: Value of type variable "Self" of "__new__" of "tuple" cannot be "ResultRow" [type-var]
  118. # type error: Generator has incompatible item type "Optional[Identifier]"; expected "_T_co" [misc]
  119. instance = super(ResultRow, cls).__new__(cls, (values.get(v) for v in labels)) # type: ignore[type-var, misc, unused-ignore]
  120. instance.labels = dict((str(x[1]), x[0]) for x in enumerate(labels))
  121. return instance
  122. def __getattr__(self, name: str) -> Identifier:
  123. if name not in self.labels:
  124. raise AttributeError(name)
  125. return tuple.__getitem__(self, self.labels[name])
  126. # type error: Signature of "__getitem__" incompatible with supertype "tuple"
  127. # type error: Signature of "__getitem__" incompatible with supertype "Sequence"
  128. def __getitem__(self, name: Union[str, int, Any]) -> Identifier: # type: ignore[override]
  129. try:
  130. # type error: Invalid index type "Union[str, int, Any]" for "tuple"; expected type "int"
  131. return tuple.__getitem__(self, name) # type: ignore[index]
  132. except TypeError:
  133. if name in self.labels:
  134. # type error: Invalid index type "Union[str, int, slice, Any]" for "Mapping[str, int]"; expected type "str"
  135. return tuple.__getitem__(self, self.labels[name]) # type: ignore[index]
  136. if str(name) in self.labels: # passing in variable object
  137. return tuple.__getitem__(self, self.labels[str(name)])
  138. raise KeyError(name)
  139. @overload
  140. def get(self, name: str, default: Identifier) -> Identifier: ...
  141. @overload
  142. def get(
  143. self, name: str, default: Optional[Identifier] = ...
  144. ) -> Optional[Identifier]: ...
  145. def get(
  146. self, name: str, default: Optional[Identifier] = None
  147. ) -> Optional[Identifier]:
  148. try:
  149. return self[name]
  150. except KeyError:
  151. return default
  152. def asdict(self) -> Dict[str, Identifier]:
  153. return dict((v, self[v]) for v in self.labels if self[v] is not None)
  154. class Result:
  155. """
  156. A common class for representing query result.
  157. There is a bit of magic here that makes this appear like different
  158. Python objects, depending on the type of result.
  159. If the type is "SELECT", iterating will yield lists of ResultRow objects
  160. If the type is "ASK", iterating will yield a single bool (or
  161. bool(result) will return the same bool)
  162. If the type is "CONSTRUCT" or "DESCRIBE" iterating will yield the
  163. triples.
  164. `len(result)` also works.
  165. """
  166. def __init__(self, type_: str):
  167. if type_ not in ("CONSTRUCT", "DESCRIBE", "SELECT", "ASK"):
  168. raise ResultException("Unknown Result type: %s" % type_)
  169. self.type = type_
  170. #: variables contained in the result.
  171. self.vars: Optional[List[Variable]] = None
  172. """a list of variables contained in the result"""
  173. self._bindings: MutableSequence[Mapping[Variable, Identifier]] = None # type: ignore[assignment]
  174. self._genbindings: Optional[Iterator[Mapping[Variable, Identifier]]] = None
  175. self.askAnswer: Optional[bool] = None
  176. self.graph: Optional[Graph] = None
  177. @property
  178. def bindings(self) -> MutableSequence[Mapping[Variable, Identifier]]:
  179. """
  180. a list of variable bindings as dicts
  181. """
  182. if self._genbindings:
  183. self._bindings += list(self._genbindings)
  184. self._genbindings = None
  185. return self._bindings
  186. @bindings.setter
  187. def bindings(
  188. self,
  189. b: Union[
  190. MutableSequence[Mapping[Variable, Identifier]],
  191. Iterator[Mapping[Variable, Identifier]],
  192. ],
  193. ) -> None:
  194. if isinstance(b, (types.GeneratorType, itertools.islice)):
  195. self._genbindings = b
  196. self._bindings = []
  197. else:
  198. # type error: Incompatible types in assignment (expression has type "Union[MutableSequence[Mapping[Variable, Identifier]], Iterator[Mapping[Variable, Identifier]]]", variable has type "MutableSequence[Mapping[Variable, Identifier]]")
  199. self._bindings = b # type: ignore[assignment]
  200. @staticmethod
  201. def parse(
  202. source: Optional[IO] = None,
  203. format: Optional[str] = None,
  204. content_type: Optional[str] = None,
  205. **kwargs: Any,
  206. ) -> Result:
  207. """Parse a query result from a source."""
  208. from rdflib import plugin
  209. if format:
  210. plugin_key = format
  211. elif content_type:
  212. plugin_key = content_type.split(";", 1)[0]
  213. else:
  214. plugin_key = "xml"
  215. parser = plugin.get(plugin_key, ResultParser)()
  216. # type error: Argument 1 to "parse" of "ResultParser" has incompatible type "Optional[IO[Any]]"; expected "IO[Any]"
  217. return parser.parse(
  218. source, content_type=content_type, **kwargs # type:ignore[arg-type]
  219. )
  220. def serialize(
  221. self,
  222. destination: Optional[Union[str, IO]] = None,
  223. encoding: str = "utf-8",
  224. format: str = "xml",
  225. **args: Any,
  226. ) -> Optional[bytes]:
  227. """
  228. Serialize the query result.
  229. Args:
  230. destination: Path of file output or BufferedIOBase object
  231. to write the output to. If `None` this function
  232. will return the output as `bytes`
  233. encoding: Encoding of output.
  234. format: The format used for serialization.
  235. See [sparql.results module][rdflib.plugins.sparql.results]
  236. for all builtin SPARQL result serialization.
  237. Further serializer can be loaded [as plugin][rdflib.plugin].
  238. Some example formats are
  239. [csv][rdflib.plugins.sparql.results.csvresults.CSVResultSerializer],
  240. [json][rdflib.plugins.sparql.results.jsonresults.JSONResultSerializer],
  241. [txt][rdflib.plugins.sparql.results.txtresults.TXTResultSerializer]
  242. or
  243. [xml][rdflib.plugins.sparql.results.xmlresults.XMLResultSerializer]
  244. Returns:
  245. Serialized result, when destination is not given.
  246. """
  247. if self.type in ("CONSTRUCT", "DESCRIBE"):
  248. # type error: Item "None" of "Optional[Graph]" has no attribute "serialize"
  249. # type error: Incompatible return value type (got "Union[bytes, str, Graph, Any]", expected "Optional[bytes]")
  250. return self.graph.serialize( # type: ignore[union-attr,return-value]
  251. destination, encoding=encoding, format=format, **args
  252. )
  253. """stolen wholesale from graph.serialize"""
  254. from rdflib import plugin
  255. serializer = plugin.get(format, ResultSerializer)(self)
  256. if destination is None:
  257. streamb: BytesIO = BytesIO()
  258. stream2 = EncodeOnlyUnicode(streamb) # TODO: Remove the need for this
  259. # TODO: All QueryResult serializers should write to a Bytes Stream.
  260. # type error: Argument 1 to "serialize" of "ResultSerializer" has incompatible type "EncodeOnlyUnicode"; expected "IO[Any]"
  261. serializer.serialize(stream2, encoding=encoding, **args) # type: ignore[arg-type]
  262. return streamb.getvalue()
  263. if hasattr(destination, "write"):
  264. stream = cast(IO[bytes], destination)
  265. serializer.serialize(stream, encoding=encoding, **args)
  266. else:
  267. location = cast(str, destination)
  268. scheme, netloc, path, params, query, fragment = urlparse(location)
  269. if scheme == "file":
  270. if netloc != "":
  271. raise ValueError(
  272. f"the file URI {location!r} has an authority component which is not supported"
  273. )
  274. os_path = url2pathname(path)
  275. else:
  276. os_path = location
  277. with open(os_path, "wb") as stream:
  278. serializer.serialize(stream, encoding=encoding, **args)
  279. return None
  280. def __len__(self) -> int:
  281. if self.type == "ASK":
  282. return 1
  283. elif self.type == "SELECT":
  284. return len(self.bindings)
  285. else:
  286. # type error: Argument 1 to "len" has incompatible type "Optional[Graph]"; expected "Sized"
  287. return len(self.graph) # type: ignore[arg-type]
  288. def __bool__(self) -> bool:
  289. if self.type == "ASK":
  290. # type error: Incompatible return value type (got "Optional[bool]", expected "bool")
  291. return self.askAnswer # type: ignore[return-value]
  292. else:
  293. return len(self) > 0
  294. def __iter__(
  295. self,
  296. ) -> Iterator[Union[_TripleType, bool, ResultRow]]:
  297. if self.type in ("CONSTRUCT", "DESCRIBE"):
  298. # type error: Item "None" of "Optional[Graph]" has no attribute "__iter__" (not iterable)
  299. for t in self.graph: # type: ignore[union-attr]
  300. yield t
  301. elif self.type == "ASK":
  302. # type error: Incompatible types in "yield" (actual type "Optional[bool]", expected type "Union[Tuple[Identifier, Identifier, Identifier], bool, ResultRow]") [misc]
  303. yield self.askAnswer # type: ignore[misc]
  304. elif self.type == "SELECT":
  305. # this iterates over ResultRows of variable bindings
  306. if self._genbindings:
  307. for b in self._genbindings:
  308. if b: # don't add a result row in case of empty binding {}
  309. self._bindings.append(b)
  310. # type error: Argument 2 to "ResultRow" has incompatible type "Optional[List[Variable]]"; expected "List[Variable]"
  311. yield ResultRow(b, self.vars) # type: ignore[arg-type]
  312. self._genbindings = None
  313. else:
  314. for b in self._bindings:
  315. if b: # don't add a result row in case of empty binding {}
  316. # type error: Argument 2 to "ResultRow" has incompatible type "Optional[List[Variable]]"; expected "List[Variable]"
  317. yield ResultRow(b, self.vars) # type: ignore[arg-type]
  318. def __getattr__(self, name: str) -> Any:
  319. if self.type in ("CONSTRUCT", "DESCRIBE") and self.graph is not None:
  320. # type error: "Graph" has no attribute "__getattr__"
  321. return self.graph.__getattr__(self, name) # type: ignore[attr-defined]
  322. elif self.type == "SELECT" and name == "result":
  323. warnings.warn(
  324. "accessing the 'result' attribute is deprecated."
  325. " Iterate over the object instead.",
  326. DeprecationWarning,
  327. stacklevel=2,
  328. )
  329. # copied from __iter__, above
  330. # type error: Item "None" of "Optional[List[Variable]]" has no attribute "__iter__" (not iterable)
  331. return [(tuple(b[v] for v in self.vars)) for b in self.bindings] # type: ignore[union-attr]
  332. else:
  333. raise AttributeError("'%s' object has no attribute '%s'" % (self, name))
  334. def __eq__(self, other: Any) -> bool:
  335. try:
  336. if self.type != other.type:
  337. return False
  338. if self.type == "ASK":
  339. return self.askAnswer == other.askAnswer
  340. elif self.type == "SELECT":
  341. return self.vars == other.vars and self.bindings == other.bindings
  342. else:
  343. return self.graph == other.graph
  344. except Exception:
  345. return False
  346. class ResultParser:
  347. def __init__(self):
  348. pass
  349. # type error: Missing return statement
  350. def parse(self, source: IO, **kwargs: Any) -> Result: # type: ignore[empty-body]
  351. """return a Result object"""
  352. pass # abstract
  353. class ResultSerializer:
  354. def __init__(self, result: Result):
  355. self.result = result
  356. def serialize(self, stream: IO, encoding: str = "utf-8", **kwargs: Any) -> None:
  357. """return a string properly serialized"""
  358. pass # abstract