Skip to content

Commit

Permalink
Added support for only HTTP Content Negotiation. Related #82
Browse files Browse the repository at this point in the history
  • Loading branch information
dayures committed Feb 25, 2018
1 parent 25499ad commit 3cddf2b
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 21 deletions.
1 change: 1 addition & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ SPARQLWrapper's changelog:
- Added some documentation about the parameter to indicate the output format
- Fixed typo in width calculation
- Added support for CSV, TSV (PR #98)
- Added support for Only HTTP Content Negotiation (#82)


2016-12-07 1.8.0 - Updated return formats for not content negotiation situations
Expand Down
37 changes: 23 additions & 14 deletions SPARQLWrapper/Wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ def __init__(self, endpoint, updateEndpoint=None, returnFormat=XML, defaultGraph
self.passwd = None
self.http_auth = BASIC
self._defaultGraph = defaultGraph
self.onlyConneg = False # Only Content Negotiation

if returnFormat in _allowedFormats:
self._defaultReturnFormat = returnFormat
Expand All @@ -263,7 +264,8 @@ def __init__(self, endpoint, updateEndpoint=None, returnFormat=XML, defaultGraph

def resetQuery(self):
"""Reset the query, ie, return format, query, default or named graph settings, etc,
are reset to their default values."""
are reset to their default values.
"""
self.parameters = {}
if self._defaultGraph:
self.addParameter("default-graph-uri", self._defaultGraph)
Expand Down Expand Up @@ -302,6 +304,14 @@ def setTimeout(self, timeout):
"""
self.timeout = int(timeout)

def setOnlyConneg(self, onlyConneg):
"""Set this option for allowing (or not) only HTTP Content Negotiation (so dismiss the use of HTTP parameters).
@param onlyConneg: True if only HTTP Content Negotiation is allowed; False is HTTP parameters are allowed also.
@type onlyConneg: bool
"""
self.onlyConneg = onlyConneg

def setRequestMethod(self, method):
"""Set the internal method to use to perform the request for query or
update operations, either URL-encoded (C{SPARQLWrapper.URLENCODED}) or
Expand Down Expand Up @@ -526,17 +536,17 @@ def _getRequestEncodedParameters(self, query=None):
# Virtuoso uses 'format',sparqler uses 'output'
# However, these processors are (hopefully) oblivious to the parameters they do not understand.
# So: just repeat all possibilities in the final URI. UGLY!!!!!!!
for f in _returnFormatSetting:
query_parameters[f] = [self.returnFormat]

# Virtuoso is not supporting a correct Accept header and an unexpected "output"/"format" parameter value. It returns a 406.
# "tsv" and "json-ld" are not supported as a correct "output"/"format" parameter value but "text/tab-separated-values" is a valid value,
# and there is no problem to send both.
if self.returnFormat in [TSV, JSONLD]:
acceptHeader = self._getAcceptHeader() # to obtain the mime-type "text/tab-separated-values"
if "*/*" in acceptHeader:
acceptHeader="" # clear the value in case of "*/*"
query_parameters[f]+= [acceptHeader]
if not self.onlyConneg:
for f in _returnFormatSetting:
query_parameters[f] = [self.returnFormat]
# Virtuoso is not supporting a correct Accept header and an unexpected "output"/"format" parameter value. It returns a 406.
# "tsv" and "json-ld" are not supported as a correct "output"/"format" parameter value but "text/tab-separated-values" is a valid value,
# and there is no problem to send both.
if self.returnFormat in [TSV, JSONLD]:
acceptHeader = self._getAcceptHeader() # to obtain the mime-type "text/tab-separated-values"
if "*/*" in acceptHeader:
acceptHeader="" # clear the value in case of "*/*"
query_parameters[f]+= [acceptHeader]

pairs = (
"%s=%s" % (
Expand Down Expand Up @@ -578,7 +588,7 @@ def _getAcceptHeader(self):
def _createRequest(self):
"""Internal method to create request according a HTTP method. Returns a
C{urllib2.Request} object of the urllib2 Python library
@return: request
@return: request a C{urllib2.Request} object of the urllib2 Python library
"""
request = None

Expand Down Expand Up @@ -641,7 +651,6 @@ def _query(self):
"""
if self.timeout:
socket.setdefaulttimeout(self.timeout)

request = self._createRequest()

try:
Expand Down
11 changes: 6 additions & 5 deletions test/dbpedia-virtuoso.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,12 @@

class SPARQLWrapperTests(unittest.TestCase):

def __generic(self, query, returnFormat, method):
def __generic(self, query, returnFormat, method, onlyConneg=False):
sparql = SPARQLWrapper(endpoint)
sparql.setQuery(prefixes + query)
sparql.setReturnFormat(returnFormat)
sparql.setMethod(method)
sparql.setOnlyConneg(onlyConneg)
try:
result = sparql.query()
except HTTPError:
Expand Down Expand Up @@ -185,13 +186,13 @@ def testSelectByPOSTinCSV(self):
results = result.convert()

def testSelectByGETinTSV(self):
result = self.__generic(selectQueryCSV_TSV, TSV, GET)
result = self.__generic(selectQueryCSV_TSV, TSV, GET, True)
ct = result.info()["content-type"]
assert True in [one in ct for one in _TSV], ct
results = result.convert()

def testSelectByPOSTinTSV(self):
result = self.__generic(selectQueryCSV_TSV, TSV, POST)
result = self.__generic(selectQueryCSV_TSV, TSV, POST, True)
ct = result.info()["content-type"]
assert True in [one in ct for one in _TSV], ct
results = result.convert()
Expand Down Expand Up @@ -281,14 +282,14 @@ def testConstructByPOSTinJSON(self):
self.assertEqual(type(results), ConjunctiveGraph)

def testConstructByGETinJSONLD(self):
result = self.__generic(constructQuery, JSONLD, GET)
result = self.__generic(constructQuery, JSONLD, GET, True)
ct = result.info()["content-type"]
assert True in [one in ct for one in _RDF_JSONLD], "returned Content-Type='%s'." %(ct)
results = result.convert()
self.assertEqual(type(results), ConjunctiveGraph)

def testConstructByPOSTinJSONLD(self):
result = self.__generic(constructQuery, JSONLD, POST)
result = self.__generic(constructQuery, JSONLD, POST, True)
ct = result.info()["content-type"]
assert True in [one in ct for one in _RDF_JSONLD], "returned Content-Type='%s'." %(ct)
results = result.convert()
Expand Down
27 changes: 25 additions & 2 deletions test/wrapper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@

import unittest
import urllib2
from urlparse import urlparse
from urlparse import urlparse, parse_qsl, parse_qs
from urllib2 import Request

from cgi import parse_qs

# prefer local copy to the one which is installed
# hack from http://stackoverflow.com/a/6098238/280539
Expand Down Expand Up @@ -159,25 +158,31 @@ def testReset(self):
self.wrapper.setQuery('CONSTRUCT WHERE {?a ?b ?c}')
self.wrapper.setReturnFormat(N3)
self.wrapper.addParameter('a', 'b')
self.wrapper.setOnlyConneg(True)

request = self._get_request(self.wrapper)
parameters = self._get_parameters_from_request(request)
onlyConneg = self.wrapper.onlyConneg

self.assertEqual('POST', request.get_method())
self.assertTrue(parameters['query'][0].startswith('CONSTRUCT'))
self.assertTrue('rdf+n3' in request.get_header('Accept'))
self.assertTrue('a' in parameters)
self.assertTrue(onlyConneg)

self.wrapper.resetQuery()

request = self._get_request(self.wrapper)
parameters = self._get_parameters_from_request(request)
onlyConneg = self.wrapper.onlyConneg

self.assertEqual('GET', request.get_method())
self.assertTrue(parameters['query'][0].startswith('SELECT'))
self.assertFalse('rdf+n3' in request.get_header('Accept'))
self.assertTrue('sparql-results+xml' in request.get_header('Accept'))
self.assertFalse('a' in parameters)
self.assertFalse('a' in parameters)
self.assertTrue(onlyConneg)

def testSetReturnFormat(self):
with warnings.catch_warnings(record=True) as w:
Expand Down Expand Up @@ -631,6 +636,24 @@ def testSingleLineQueryLine(self):
self.wrapper.setQuery(query)
self.assertTrue(self.wrapper.isSparqlQueryRequest())

def testOnlyConneg(self):
# see issue #82
query = "prefix whatever: <http://example.org/blah#> ASK { ?s ?p ?o }"
self.wrapper.setOnlyConneg(False)
self.wrapper.setQuery(query)
request = self._get_request(self.wrapper)
request_params = dict(parse_qsl(urlparse(request.get_full_url()).query))
for returnFormatSetting in ["format", "output", "results"]: # Obviously _returnFormatSetting is not accessible from SPARQLWrapper, so we copy&paste the possible values
self.assertTrue(returnFormatSetting in request_params, "URL parameter '%s' was not sent, and it was expected" %returnFormatSetting)

#ONLY Content Negotiation
self.wrapper.resetQuery()
self.wrapper.setOnlyConneg(True)
self.wrapper.setQuery(query)
request = self._get_request(self.wrapper)
request_params = dict(parse_qsl(urlparse(request.get_full_url()).query))
for returnFormatSetting in ["format", "output", "results"]: # Obviously _returnFormatSetting is not accessible from SPARQLWrapper, so we copy&paste the possible values
self.assertFalse(returnFormatSetting in request_params, "URL parameter '%s' was sent, and it was not expected (only Content Negotiation)" %returnFormatSetting)


class QueryResult_Test(unittest.TestCase):
Expand Down

0 comments on commit 3cddf2b

Please sign in to comment.