pygeoapi does not respect header q values
Description When sending a request to pygeoapi with multiple values in accept parameter, pygeoapi takes the first item instead of evaluating the q values of all options sent in the header.
Steps to Reproduce Steps to reproduce the behavior:
- Send a request to pygeoapi with variable q values, and a lower q value as your first header:
Accept: application/json;q=0.5,application/ld+json;q=1 - View response content format
Expected behavior pygeoapi evaluates the full header context to decide the most appropriate header to send in the response.
Screenshots/Tracebacks If applicable, add screenshots to help explain your problem.
Environment
- OS: linux
- Python version: 3.10/3.12
- pygeoapi version: 0.20.dev0
Additional context Add any other context about the problem here.
Untested:
diff --git a/pygeoapi/api/__init__.py b/pygeoapi/api/__init__.py
index 7ce3ec4..854d667 100644
--- a/pygeoapi/api/__init__.py
+++ b/pygeoapi/api/__init__.py
@@ -339,7 +339,7 @@ class APIRequest:
h = headers.get('accept', headers.get('Accept', '')).strip() # noqa
(fmts, mimes) = zip(*FORMAT_TYPES.items())
# basic support for complex types (i.e. with "q=0.x")
- for type_ in (t.split(';')[0].strip() for t in h.split(',') if t):
+ for type_ in sorted(h.split(','), key=lambda x: x.split('q=')[-1], reverse=True): # noqa
if type_ in mimes:
idx_ = mimes.index(type_)
format_ = fmts[idx_]
Its worth noting we technically allow the type of preference setting for the following headers Accept, Content-Encoding, Accept-Language. I am in favor on consolidating the logic for extracting out the choice of header to a common util function to also address https://github.com/geopython/pygeoapi/issues/1591
def get_choice_from_headers(headers: dict,
header_name: str,
all: bool = False) -> Any:
"""
Gets choices from a request dictionary,
considering numerical ordering of preferences.
Supported are complex preference strings (e.g. "fr-CH, fr;q=0.9, en;q=0.8")
:param headers: `dict` of request headers.
:param header_name: Name of request header.
:param all: bool to return one or all header values.
:returns: Sorted choices from header
"""
try:
# Clean headers and select header of interest
cleaned_headers = {k.lower(): v for k, v in headers.items()}
header = cleaned_headers[header_name.lower()].split(',')
except:
LOGGER.warning(f'Requested header not found: {header_name}')
return
# Parse choices, extracting optional q values (defaults to 1.0)
choices = []
for i, part in enumerate(header):
match = re.match(r'^([^;]+)(?:;q=([\d.]+))?$', part.strip())
if match:
value, q_value = match.groups()
q_value = float(q_value) if q_value else 1.0
# Sort choices by q value and index
if 0 <= q_value <= 1:
heapq.heappush(choices, (1 / q_value, i, value))
# Drop q value
sorted_choices = [choice[-1] for choice in choices]
# Return one or all choices
return sorted_choices if all else sorted_choices[0]
This Issue has been inactive for 90 days. As per RFC4, in order to manage maintenance burden, it will be automatically closed in 7 days.
This is still waiting for review in https://github.com/geopython/pygeoapi/pull/1952