pygeoapi icon indicating copy to clipboard operation
pygeoapi copied to clipboard

pygeoapi does not respect header q values

Open webb-ben opened this issue 10 months ago • 4 comments

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:

  1. 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
  2. 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.

webb-ben avatar Feb 28 '25 15:02 webb-ben

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_]

tomkralidis avatar Mar 01 '25 03:03 tomkralidis

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]

webb-ben avatar Mar 01 '25 18:03 webb-ben

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.

github-actions[bot] avatar Jun 01 '25 04:06 github-actions[bot]

This is still waiting for review in https://github.com/geopython/pygeoapi/pull/1952

webb-ben avatar Jun 02 '25 13:06 webb-ben