|
@@ -2,11 +2,11 @@
|
|
|
|
|
|
import logging
|
|
|
import re
|
|
|
-from typing import Any, Callable, Dict, List, Optional, Union
|
|
|
-from urllib import parse
|
|
|
-
|
|
|
+import arklog
|
|
|
import pkg_resources
|
|
|
import rdflib
|
|
|
+from typing import Any, Callable, Dict, List, Optional, Union
|
|
|
+from urllib import parse
|
|
|
from fastapi import FastAPI, Query, Request, Response
|
|
|
from fastapi.responses import JSONResponse
|
|
|
from rdflib import ConjunctiveGraph, Dataset, Graph, Literal, URIRef
|
|
@@ -16,6 +16,8 @@ from rdflib.plugins.sparql.evalutils import _eval
|
|
|
from rdflib.plugins.sparql.parserutils import CompValue
|
|
|
from rdflib.plugins.sparql.sparql import QueryContext, SPARQLError
|
|
|
|
|
|
+arklog.set_config_logging()
|
|
|
+
|
|
|
|
|
|
class SparqlEndpoint(FastAPI):
|
|
|
"""SPARQL endpoint for services and storage of heterogeneous data."""
|
|
@@ -40,24 +42,31 @@ class SparqlEndpoint(FastAPI):
|
|
|
""""""
|
|
|
return mime.split(",")[0] in ("text/turtle",)
|
|
|
|
|
|
-
|
|
|
- def __init__(
|
|
|
- self,
|
|
|
- *args: Any,
|
|
|
- title: str,
|
|
|
- description: str,
|
|
|
- version: str,
|
|
|
- functions: Dict[str, Callable[..., Any]],
|
|
|
- graph: Union[Graph, ConjunctiveGraph, Dataset] = ConjunctiveGraph(),
|
|
|
- **kwargs: Any,
|
|
|
- ):
|
|
|
+ async def requested_result_type(self, request: Request, operation: str) -> str:
|
|
|
+ logging.debug("Getting mime type.")
|
|
|
+ output_mime_type = request.headers["accept"]
|
|
|
+ # TODO Ugly hack, fix later (Fuseki sends options)
|
|
|
+ output_mime_type = output_mime_type.split(",")[0]
|
|
|
+ if isinstance(output_mime_type, list):
|
|
|
+ return output_mime_type[0]
|
|
|
+
|
|
|
+ # TODO Use match or dict for this
|
|
|
+ if not output_mime_type:
|
|
|
+ logging.warning("No mime type provided. Setting mimetype to 'application/xml'.")
|
|
|
+ return "application/xml"
|
|
|
+ if operation == "Construct Query" and (self.is_json_mime_type(output_mime_type) or self.is_csv_mime_type(output_mime_type)):
|
|
|
+ return "text/turtle"
|
|
|
+ if operation == "Construct Query" and output_mime_type == "application/xml":
|
|
|
+ return "application/rdf+xml"
|
|
|
+ return output_mime_type
|
|
|
+
|
|
|
+ def __init__(self, *args: Any, title: str, description: str, version: str, functions: Dict[str, Callable[..., Any]], graph: Union[Graph, ConjunctiveGraph, Dataset] = ConjunctiveGraph(), **kwargs: Any):
|
|
|
""""""
|
|
|
self.graph = graph
|
|
|
self.functions = functions
|
|
|
self.title = title
|
|
|
self.description = description
|
|
|
self.version = version
|
|
|
-
|
|
|
super().__init__(*args, title=title, description=description, version=version, **kwargs)
|
|
|
rdflib.plugins.sparql.CUSTOM_EVALS["evalCustomFunctions"] = self.eval_custom_functions
|
|
|
api_responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = {
|
|
@@ -95,7 +104,9 @@ class SparqlEndpoint(FastAPI):
|
|
|
|
|
|
@self.get("/", name="SPARQL endpoint", description="", responses=api_responses)
|
|
|
async def sparql_endpoint_get(request: Request, query: Optional[str] = Query(None)) -> Response:
|
|
|
+ logging.debug("Received GET request.")
|
|
|
if not query:
|
|
|
+ logging.warning("No query provided in GET request!")
|
|
|
return JSONResponse({"error": "No query provided."})
|
|
|
|
|
|
graph_ns = {}
|
|
@@ -121,30 +132,26 @@ class SparqlEndpoint(FastAPI):
|
|
|
content={"message": "Error executing the SPARQL query on the RDFLib Graph"},
|
|
|
)
|
|
|
|
|
|
- # Format and return results depending on Accept mime type in request header
|
|
|
- output_mime_type = request.headers["accept"]
|
|
|
- if not output_mime_type:
|
|
|
- output_mime_type = "application/xml"
|
|
|
- # Handle mime type for construct queries
|
|
|
- if query_operation == "Construct Query" and (self.is_json_mime_type(output_mime_type) or self.is_csv_mime_type(output_mime_type)):
|
|
|
- output_mime_type = "text/turtle"
|
|
|
- # TODO: support JSON-LD for construct query?
|
|
|
- # g.serialize(format='json-ld', indent=4)
|
|
|
- if query_operation == "Construct Query" and output_mime_type == "application/xml":
|
|
|
- output_mime_type = "application/rdf+xml"
|
|
|
-
|
|
|
- if self.is_csv_mime_type(output_mime_type):
|
|
|
- return Response(query_results.serialize(format="csv"), media_type=output_mime_type)
|
|
|
- elif self.is_json_mime_type(output_mime_type):
|
|
|
- return Response(query_results.serialize(format="json"), media_type=output_mime_type)
|
|
|
- elif self.is_xml_mime_type(output_mime_type):
|
|
|
- return Response(query_results.serialize(format="xml"), media_type=output_mime_type)
|
|
|
- elif self.is_turtle_mime_type(output_mime_type):
|
|
|
- return Response(query_results.serialize(format="turtle"), media_type=output_mime_type)
|
|
|
- return Response(query_results.serialize(format="xml"), media_type="application/sparql-results+xml")
|
|
|
+ logging.debug(f"{type(query_results)=}")
|
|
|
+ output_mime_type = await self.requested_result_type(request, query_operation)
|
|
|
+ logging.debug(f"Returning {output_mime_type}.")
|
|
|
+ try:
|
|
|
+ if self.is_csv_mime_type(output_mime_type):
|
|
|
+ return Response(query_results.serialize(format="csv"), media_type=output_mime_type)
|
|
|
+ elif self.is_json_mime_type(output_mime_type):
|
|
|
+ return Response(query_results.serialize(format="json"), media_type=output_mime_type)
|
|
|
+ elif self.is_xml_mime_type(output_mime_type):
|
|
|
+ return Response(query_results.serialize(format="xml"), media_type=output_mime_type)
|
|
|
+ elif self.is_turtle_mime_type(output_mime_type):
|
|
|
+ return Response(query_results.serialize(format="turtle"), media_type=output_mime_type)
|
|
|
+ return Response(query_results.serialize(format="xml"), media_type="application/sparql-results+xml")
|
|
|
+ except Exception as e:
|
|
|
+ logging.exception(e)
|
|
|
+ return JSONResponse(status_code=400, content={"message": "Error executing the SPARQL query on the RDFLib Graph"})
|
|
|
|
|
|
@self.post("/", name="SPARQL endpoint", description="", responses=api_responses)
|
|
|
async def sparql_endpoint_post(request: Request, query: Optional[str] = Query(None)) -> Response:
|
|
|
+ logging.debug("Received POST request.")
|
|
|
if not query:
|
|
|
# Handle federated query services which provide the query in the body
|
|
|
query_body = await request.body()
|
|
@@ -168,6 +175,7 @@ class SparqlEndpoint(FastAPI):
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
query_results = []
|
|
|
+ logging.debug("Custom evaluation.")
|
|
|
for eval_part in evalPart(ctx, part.p):
|
|
|
# Checks if the function is a URI (custom function)
|
|
|
if hasattr(part.expr, "iri"):
|