util.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """RDF4J utility functions."""
  2. from __future__ import annotations
  3. import io
  4. import typing as t
  5. from rdflib.graph import DATASET_DEFAULT_GRAPH_ID, Dataset, Graph
  6. from rdflib.plugins.sparql.processor import prepareQuery
  7. from rdflib.term import BNode, IdentifiedNode, URIRef
  8. if t.TYPE_CHECKING:
  9. from rdflib.contrib.rdf4j.client import ObjectType, PredicateType, SubjectType
  10. def build_context_param(
  11. params: dict[str, str],
  12. graph_name: IdentifiedNode | t.Iterable[IdentifiedNode] | str | None = None,
  13. ) -> None:
  14. """Build the RDF4J http context query parameters dictionary.
  15. !!! Note
  16. This mutates the params dictionary key `context`.
  17. Parameters:
  18. params: The `httpx.Request` parameter dictionary.
  19. graph_name: The graph name or iterable of graph names.
  20. This is the `context` query parameter value.
  21. """
  22. if graph_name is not None and isinstance(graph_name, IdentifiedNode):
  23. if graph_name == DATASET_DEFAULT_GRAPH_ID:
  24. # Special RDF4J null value for context-less statements.
  25. params["context"] = "null"
  26. else:
  27. params["context"] = graph_name.n3()
  28. elif graph_name is not None and isinstance(graph_name, str):
  29. params["context"] = URIRef(graph_name).n3()
  30. elif graph_name is not None and isinstance(graph_name, t.Iterable):
  31. # type error: "str" has no attribute "n3"
  32. graph_names = ",".join([x.n3() for x in graph_name]) # type: ignore[attr-defined]
  33. params["context"] = graph_names
  34. def build_spo_param(
  35. params: dict[str, str],
  36. subj: SubjectType = None,
  37. pred: PredicateType = None,
  38. obj: ObjectType = None,
  39. ) -> None:
  40. """Build the RDF4J http subj, predicate, and object query parameters dictionary.
  41. !!! Note
  42. This mutates the params dictionary key `subj`, `pred`, and `obj`.
  43. Parameters:
  44. params: The `httpx.Request` parameter dictionary.
  45. subj: The `subj` query parameter value.
  46. pred: The `pred` query parameter value.
  47. obj: The `obj` query parameter value.
  48. """
  49. if subj is not None:
  50. params["subj"] = subj.n3()
  51. if pred is not None:
  52. params["pred"] = pred.n3()
  53. if obj is not None:
  54. params["obj"] = obj.n3()
  55. def build_infer_param(
  56. params: dict[str, str],
  57. infer: bool = True,
  58. ) -> None:
  59. """Build the RDF4J http infer query parameters dictionary.
  60. !!! Note
  61. This mutates the params dictionary key `infer`.
  62. Parameters:
  63. params: The `httpx.Request` parameter dictionary.
  64. infer: The `infer` query parameter value.
  65. """
  66. if not infer:
  67. params["infer"] = "false"
  68. def rdf_payload_to_stream(
  69. data: str | bytes | t.BinaryIO | Graph | Dataset,
  70. ) -> tuple[t.BinaryIO, bool]:
  71. """Convert an RDF payload into a file-like object.
  72. Parameters:
  73. data: The RDF payload.
  74. This can be a python `str`, `bytes`, `BinaryIO`, or a
  75. [`Graph`][rdflib.graph.Graph] or [`Dataset`][rdflib.graph.Dataset].
  76. Returns:
  77. A tuple containing the file-like object and a boolean indicating whether the
  78. immediate caller should close the stream.
  79. """
  80. stream: t.BinaryIO
  81. if isinstance(data, str):
  82. # Check if it looks like a file path. Assumes file path length is less than 260.
  83. if "\n" not in data and len(data) < 260:
  84. try:
  85. stream = open(data, "rb")
  86. should_close = True
  87. except (FileNotFoundError, OSError):
  88. # Treat as raw string content
  89. stream = io.BytesIO(data.encode("utf-8"))
  90. should_close = False
  91. else:
  92. # Treat as raw string content
  93. stream = io.BytesIO(data.encode("utf-8"))
  94. should_close = False
  95. elif isinstance(data, bytes):
  96. stream = io.BytesIO(data)
  97. should_close = False
  98. elif isinstance(data, (Graph, Dataset)):
  99. if data.context_aware:
  100. stream = io.BytesIO(
  101. data.serialize(format="application/n-quads", encoding="utf-8")
  102. )
  103. else:
  104. stream = io.BytesIO(
  105. data.serialize(format="application/n-triples", encoding="utf-8")
  106. )
  107. should_close = True
  108. else:
  109. # Assume it's already a file-like object
  110. stream = data
  111. should_close = False
  112. return stream, should_close
  113. def build_sparql_query_accept_header(query: str, headers: dict[str, str]):
  114. """Build the SPARQL query accept header.
  115. !!! Note
  116. This mutates the headers dictionary key `Accept`.
  117. Parameters:
  118. query: The SPARQL query.
  119. """
  120. prepared_query = prepareQuery(query)
  121. if prepared_query.algebra.name in ("SelectQuery", "AskQuery"):
  122. headers["Accept"] = "application/sparql-results+json"
  123. elif prepared_query.algebra.name in ("ConstructQuery", "DescribeQuery"):
  124. headers["Accept"] = "application/n-triples"
  125. else:
  126. raise ValueError(f"Unsupported query type: {prepared_query.algebra.name}")
  127. def validate_graph_name(graph_name: URIRef | t.Iterable[URIRef] | str | None):
  128. if (
  129. isinstance(graph_name, BNode)
  130. or isinstance(graph_name, t.Iterable)
  131. and any(isinstance(x, BNode) for x in graph_name)
  132. ):
  133. raise ValueError("Graph name must not be a BNode.")
  134. def validate_no_bnodes(
  135. subj: SubjectType,
  136. pred: PredicateType,
  137. obj: ObjectType,
  138. graph_name: URIRef | t.Iterable[URIRef] | str | None,
  139. ) -> None:
  140. """Validate that the subject, predicate, and object are not BNodes."""
  141. if (
  142. isinstance(subj, BNode)
  143. or isinstance(pred, BNode)
  144. or isinstance(obj, BNode)
  145. or isinstance(graph_name, BNode)
  146. ):
  147. raise ValueError(
  148. "Subject, predicate, and object must not be a BNode: "
  149. f"{subj}, {pred}, {obj}"
  150. )
  151. validate_graph_name(graph_name)