diff --git a/rdflib/plugin.py b/rdflib/plugin.py index 23699e68d..556b78804 100644 --- a/rdflib/plugin.py +++ b/rdflib/plugin.py @@ -579,18 +579,6 @@ def plugins( "rdflib.plugins.sparql.results.xmlresults", "XMLResultParser", ) -register( - "application/sparql-results+xml; charset=UTF-8", - ResultParser, - "rdflib.plugins.sparql.results.xmlresults", - "XMLResultParser", -) -register( - "application/rdf+xml", - ResultParser, - "rdflib.plugins.sparql.results.graph", - "GraphResultParser", -) register( "json", ResultParser, @@ -627,3 +615,14 @@ def plugins( "rdflib.plugins.sparql.results.tsvresults", "TSVResultParser", ) + +graph_parsers = {parser.name for parser in plugins(kind=Parser)} +result_parsers = {parser.name for parser in plugins(kind=ResultParser)} +graph_result_parsers = graph_parsers - result_parsers +for parser_name in graph_result_parsers: + register( + parser_name, + ResultParser, + "rdflib.plugins.sparql.results.graph", + "GraphResultParser", + ) diff --git a/rdflib/plugins/stores/sparqlconnector.py b/rdflib/plugins/stores/sparqlconnector.py index e2bb83909..529355567 100644 --- a/rdflib/plugins/stores/sparqlconnector.py +++ b/rdflib/plugins/stores/sparqlconnector.py @@ -9,8 +9,10 @@ from urllib.parse import urlencode from urllib.request import Request, urlopen -from rdflib.query import Result +from rdflib.plugin import plugins +from rdflib.query import Result, ResultParser from rdflib.term import BNode +from rdflib.util import FORMAT_MIMETYPE_MAP, RESPONSE_TABLE_FORMAT_MIMETYPE_MAP log = logging.getLogger(__name__) @@ -22,16 +24,6 @@ class SPARQLConnectorException(Exception): # noqa: N818 pass -# TODO: Pull in these from the result implementation plugins? -_response_mime_types = { - "xml": "application/sparql-results+xml, application/rdf+xml", - "json": "application/sparql-results+json", - "csv": "text/csv", - "tsv": "text/tab-separated-values", - "application/rdf+xml": "application/rdf+xml", -} - - class SPARQLConnector: """ this class deals with nitty gritty details of talking to a SPARQL server @@ -41,7 +33,7 @@ def __init__( self, query_endpoint: Optional[str] = None, update_endpoint: Optional[str] = None, - returnFormat: str = "xml", # noqa: N803 + returnFormat: Optional[str] = None, # noqa: N803 method: te.Literal["GET", "POST", "POST_FORM"] = "GET", auth: Optional[Tuple[str, str]] = None, **kwargs, @@ -95,7 +87,7 @@ def query( if default_graph is not None and type(default_graph) is not BNode: params["default-graph-uri"] = default_graph - headers = {"Accept": _response_mime_types[self.returnFormat]} + headers = {"Accept": self.response_mime_types()} args = copy.deepcopy(self.kwargs) @@ -170,7 +162,7 @@ def update( params["using-named-graph-uri"] = named_graph headers = { - "Accept": _response_mime_types[self.returnFormat], + "Accept": self.response_mime_types(), "Content-Type": "application/sparql-update; charset=UTF-8", } @@ -188,5 +180,27 @@ def update( ) ) + def response_mime_types(self) -> str: + """Construct a HTTP-Header Accept field to reflect the supported mime types. + + If the return_format parameter is set, the mime types are restricted to these accordingly. + """ + sparql_format_mimetype_map = { + k: FORMAT_MIMETYPE_MAP.get(k, []) + + RESPONSE_TABLE_FORMAT_MIMETYPE_MAP.get(k, []) + for k in list(FORMAT_MIMETYPE_MAP.keys()) + + list(RESPONSE_TABLE_FORMAT_MIMETYPE_MAP.keys()) + } + + supported_formats = set() + for plugin in plugins(name=self.returnFormat, kind=ResultParser): + if "/" not in plugin.name: + supported_formats.update( + sparql_format_mimetype_map.get(plugin.name, []) + ) + else: + supported_formats.add(plugin.name) + return ", ".join(supported_formats) + __all__ = ["SPARQLConnector", "SPARQLConnectorException"] diff --git a/rdflib/plugins/stores/sparqlstore.py b/rdflib/plugins/stores/sparqlstore.py index f9827cf94..7f66366cb 100644 --- a/rdflib/plugins/stores/sparqlstore.py +++ b/rdflib/plugins/stores/sparqlstore.py @@ -128,7 +128,7 @@ def __init__( sparql11: bool = True, context_aware: bool = True, node_to_sparql: _NodeToSparql = _node_to_sparql, - returnFormat: str = "xml", # noqa: N803 + returnFormat: Optional[str] = None, # noqa: N803 auth: Optional[Tuple[str, str]] = None, **sparqlconnector_kwargs, ): diff --git a/rdflib/util.py b/rdflib/util.py index ab594b5be..96260fc20 100644 --- a/rdflib/util.py +++ b/rdflib/util.py @@ -74,6 +74,47 @@ _AnyT = TypeVar("_AnyT") +SUFFIX_FORMAT_MAP = { + "xml": "xml", + "rdf": "xml", + "owl": "xml", + "n3": "n3", + "ttl": "turtle", + "nt": "nt", + "trix": "trix", + "xhtml": "rdfa", + "html": "rdfa", + "svg": "rdfa", + "nq": "nquads", + "nquads": "nquads", + "trig": "trig", + "json": "json-ld", + "jsonld": "json-ld", + "json-ld": "json-ld", +} + + +FORMAT_MIMETYPE_MAP = { + "xml": ["application/rdf+xml"], + "n3": ["text/n3"], + "turtle": ["text/turtle"], + "nt": ["application/n-triples"], + "trix": ["application/trix"], + "rdfa": ["text/html", "application/xhtml+xml"], + "nquads": ["application/n-quads"], + "trig": ["application/trig"], + "json-ld": ["application/ld+json"], +} + + +RESPONSE_TABLE_FORMAT_MIMETYPE_MAP = { + "xml": ["application/sparql-results+xml"], + "json": ["application/sparql-results+json"], + "csv": ["text/csv"], + "tsv": ["text/tab-separated-values"], +} + + def list2set(seq: Iterable[_HashableT]) -> List[_HashableT]: """ Return a new list without duplicates. @@ -331,26 +372,6 @@ def parse_date_time(val: str) -> int: return t -SUFFIX_FORMAT_MAP = { - "xml": "xml", - "rdf": "xml", - "owl": "xml", - "n3": "n3", - "ttl": "turtle", - "nt": "nt", - "trix": "trix", - "xhtml": "rdfa", - "html": "rdfa", - "svg": "rdfa", - "nq": "nquads", - "nquads": "nquads", - "trig": "trig", - "json": "json-ld", - "jsonld": "json-ld", - "json-ld": "json-ld", -} - - def guess_format(fpath: str, fmap: Optional[Dict[str, str]] = None) -> Optional[str]: """ Guess RDF serialization based on file suffix. Uses