Skip to content

Commit 1964642

Browse files
SPARQL result parsing (#2796)
* Remove plugin registration for "application/sparql-results+xml; charset=UTF-8" Remove ResultParser plugin registration with the name "application/sparql-results+xml; charset=UTF-8" since it is never called in internal code. The only place where ResultParser plugins are requested is in "rdflib/query.py:276" ``` if format: plugin_key = format elif content_type: plugin_key = content_type.split(";", 1)[0] else: plugin_key = "xml" parser = plugin.get(plugin_key, ResultParser)() ``` In case of a content-type the charset is split off already. * Resolve TODO: Pull in these from the result implementation plugins for _response_mime_types in sparqlconnector * Register all Parser plugins as SPARQL ResultParser plugins * Avoid overwriting ResultParser plugins --------- Co-authored-by: Edmond Chuc <[email protected]>
1 parent 062b7a9 commit 1964642

File tree

4 files changed

+81
-47
lines changed

4 files changed

+81
-47
lines changed

rdflib/plugin.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -579,18 +579,6 @@ def plugins(
579579
"rdflib.plugins.sparql.results.xmlresults",
580580
"XMLResultParser",
581581
)
582-
register(
583-
"application/sparql-results+xml; charset=UTF-8",
584-
ResultParser,
585-
"rdflib.plugins.sparql.results.xmlresults",
586-
"XMLResultParser",
587-
)
588-
register(
589-
"application/rdf+xml",
590-
ResultParser,
591-
"rdflib.plugins.sparql.results.graph",
592-
"GraphResultParser",
593-
)
594582
register(
595583
"json",
596584
ResultParser,
@@ -627,3 +615,14 @@ def plugins(
627615
"rdflib.plugins.sparql.results.tsvresults",
628616
"TSVResultParser",
629617
)
618+
619+
graph_parsers = {parser.name for parser in plugins(kind=Parser)}
620+
result_parsers = {parser.name for parser in plugins(kind=ResultParser)}
621+
graph_result_parsers = graph_parsers - result_parsers
622+
for parser_name in graph_result_parsers:
623+
register(
624+
parser_name,
625+
ResultParser,
626+
"rdflib.plugins.sparql.results.graph",
627+
"GraphResultParser",
628+
)

rdflib/plugins/stores/sparqlconnector.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from urllib.parse import urlencode
1010
from urllib.request import Request, urlopen
1111

12-
from rdflib.query import Result
12+
from rdflib.plugin import plugins
13+
from rdflib.query import Result, ResultParser
1314
from rdflib.term import BNode
15+
from rdflib.util import FORMAT_MIMETYPE_MAP, RESPONSE_TABLE_FORMAT_MIMETYPE_MAP
1416

1517
log = logging.getLogger(__name__)
1618

@@ -22,16 +24,6 @@ class SPARQLConnectorException(Exception): # noqa: N818
2224
pass
2325

2426

25-
# TODO: Pull in these from the result implementation plugins?
26-
_response_mime_types = {
27-
"xml": "application/sparql-results+xml, application/rdf+xml",
28-
"json": "application/sparql-results+json",
29-
"csv": "text/csv",
30-
"tsv": "text/tab-separated-values",
31-
"application/rdf+xml": "application/rdf+xml",
32-
}
33-
34-
3527
class SPARQLConnector:
3628
"""
3729
this class deals with nitty gritty details of talking to a SPARQL server
@@ -41,7 +33,7 @@ def __init__(
4133
self,
4234
query_endpoint: Optional[str] = None,
4335
update_endpoint: Optional[str] = None,
44-
returnFormat: str = "xml", # noqa: N803
36+
returnFormat: Optional[str] = "xml", # noqa: N803
4537
method: te.Literal["GET", "POST", "POST_FORM"] = "GET",
4638
auth: Optional[Tuple[str, str]] = None,
4739
**kwargs,
@@ -95,7 +87,7 @@ def query(
9587
if default_graph is not None and type(default_graph) is not BNode:
9688
params["default-graph-uri"] = default_graph
9789

98-
headers = {"Accept": _response_mime_types[self.returnFormat]}
90+
headers = {"Accept": self.response_mime_types()}
9991

10092
args = copy.deepcopy(self.kwargs)
10193

@@ -170,7 +162,7 @@ def update(
170162
params["using-named-graph-uri"] = named_graph
171163

172164
headers = {
173-
"Accept": _response_mime_types[self.returnFormat],
165+
"Accept": self.response_mime_types(),
174166
"Content-Type": "application/sparql-update; charset=UTF-8",
175167
}
176168

@@ -188,5 +180,27 @@ def update(
188180
)
189181
)
190182

183+
def response_mime_types(self) -> str:
184+
"""Construct a HTTP-Header Accept field to reflect the supported mime types.
185+
186+
If the return_format parameter is set, the mime types are restricted to these accordingly.
187+
"""
188+
sparql_format_mimetype_map = {
189+
k: FORMAT_MIMETYPE_MAP.get(k, [])
190+
+ RESPONSE_TABLE_FORMAT_MIMETYPE_MAP.get(k, [])
191+
for k in list(FORMAT_MIMETYPE_MAP.keys())
192+
+ list(RESPONSE_TABLE_FORMAT_MIMETYPE_MAP.keys())
193+
}
194+
195+
supported_formats = set()
196+
for plugin in plugins(name=self.returnFormat, kind=ResultParser):
197+
if "/" not in plugin.name:
198+
supported_formats.update(
199+
sparql_format_mimetype_map.get(plugin.name, [])
200+
)
201+
else:
202+
supported_formats.add(plugin.name)
203+
return ", ".join(supported_formats)
204+
191205

192206
__all__ = ["SPARQLConnector", "SPARQLConnectorException"]

rdflib/plugins/stores/sparqlstore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def __init__(
129129
sparql11: bool = True,
130130
context_aware: bool = True,
131131
node_to_sparql: _NodeToSparql = _node_to_sparql,
132-
returnFormat: str = "xml", # noqa: N803
132+
returnFormat: Optional[str] = "xml", # noqa: N803
133133
auth: Optional[Tuple[str, str]] = None,
134134
**sparqlconnector_kwargs,
135135
):

rdflib/util.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,47 @@
7474
_AnyT = TypeVar("_AnyT")
7575

7676

77+
SUFFIX_FORMAT_MAP = {
78+
"xml": "xml",
79+
"rdf": "xml",
80+
"owl": "xml",
81+
"n3": "n3",
82+
"ttl": "turtle",
83+
"nt": "nt",
84+
"trix": "trix",
85+
"xhtml": "rdfa",
86+
"html": "rdfa",
87+
"svg": "rdfa",
88+
"nq": "nquads",
89+
"nquads": "nquads",
90+
"trig": "trig",
91+
"json": "json-ld",
92+
"jsonld": "json-ld",
93+
"json-ld": "json-ld",
94+
}
95+
96+
97+
FORMAT_MIMETYPE_MAP = {
98+
"xml": ["application/rdf+xml"],
99+
"n3": ["text/n3"],
100+
"turtle": ["text/turtle"],
101+
"nt": ["application/n-triples"],
102+
"trix": ["application/trix"],
103+
"rdfa": ["text/html", "application/xhtml+xml"],
104+
"nquads": ["application/n-quads"],
105+
"trig": ["application/trig"],
106+
"json-ld": ["application/ld+json"],
107+
}
108+
109+
110+
RESPONSE_TABLE_FORMAT_MIMETYPE_MAP = {
111+
"xml": ["application/sparql-results+xml"],
112+
"json": ["application/sparql-results+json"],
113+
"csv": ["text/csv"],
114+
"tsv": ["text/tab-separated-values"],
115+
}
116+
117+
77118
def list2set(seq: Iterable[_HashableT]) -> List[_HashableT]:
78119
"""
79120
Return a new list without duplicates.
@@ -331,26 +372,6 @@ def parse_date_time(val: str) -> int:
331372
return t
332373

333374

334-
SUFFIX_FORMAT_MAP = {
335-
"xml": "xml",
336-
"rdf": "xml",
337-
"owl": "xml",
338-
"n3": "n3",
339-
"ttl": "turtle",
340-
"nt": "nt",
341-
"trix": "trix",
342-
"xhtml": "rdfa",
343-
"html": "rdfa",
344-
"svg": "rdfa",
345-
"nq": "nquads",
346-
"nquads": "nquads",
347-
"trig": "trig",
348-
"json": "json-ld",
349-
"jsonld": "json-ld",
350-
"json-ld": "json-ld",
351-
}
352-
353-
354375
def guess_format(fpath: str, fmap: Optional[Dict[str, str]] = None) -> Optional[str]:
355376
"""
356377
Guess RDF serialization based on file suffix. Uses

0 commit comments

Comments
 (0)