From 4809c1123ab67ff6ea282fd1e6a95dab2603bf12 Mon Sep 17 00:00:00 2001 From: Benjamin Webb Date: Tue, 23 Jun 2026 09:21:45 -0400 Subject: [PATCH 1/2] Adds bytes as optional return type --- pygeoapi/formatter/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pygeoapi/formatter/base.py b/pygeoapi/formatter/base.py index f566df114..f56712779 100644 --- a/pygeoapi/formatter/base.py +++ b/pygeoapi/formatter/base.py @@ -56,7 +56,9 @@ def __init__(self, formatter_def: dict): self.geom = formatter_def.get('geom', False) self.attachment = formatter_def.get('attachment', False) - def write(self, options: dict = {}, data: dict | None = None) -> str: + def write( + self, options: dict = {}, data: dict | None = None + ) -> str | bytes: """ Generate data in specified format From a5af1d47657fd8d1cdda285fa3a58e8f969001ea Mon Sep 17 00:00:00 2001 From: Benjamin Webb Date: Tue, 23 Jun 2026 09:19:30 -0400 Subject: [PATCH 2/2] Use formatter filetype as dataset_formatters key Cleanup implementation and reduce amount of list comprehension --- pygeoapi/api/__init__.py | 32 +++++++++++--------- pygeoapi/api/collection.py | 4 +-- pygeoapi/api/environmental_data_retrieval.py | 15 ++++----- pygeoapi/api/itemtypes.py | 19 +++++------- pygeoapi/formatter/base.py | 2 +- pygeoapi/util.py | 6 ++-- 6 files changed, 38 insertions(+), 40 deletions(-) diff --git a/pygeoapi/api/__init__.py b/pygeoapi/api/__init__.py index e54b90e6a..4097b215a 100644 --- a/pygeoapi/api/__init__.py +++ b/pygeoapi/api/__init__.py @@ -206,12 +206,12 @@ def __init__(self, request, supported_locales): self._raw_locale, self._locale = self._get_locale(request.headers, supported_locales) - # Determine format - self._format = self._get_format(request.headers) - # Get received headers self._headers = self.get_request_headers(request.headers) + # Determine format + self._format = self._get_format() + @classmethod def from_flask(cls, request, supported_locales) -> 'APIRequest': """Factory class similar to with_data, but only for flask requests""" @@ -298,12 +298,10 @@ def _get_locale(self, headers: dict, return raw, default_locale - def _get_format(self, headers: dict, - extra_formats: dict = {}) -> Union[str, None]: + def _get_format(self, extra_formats: dict = {}) -> Union[str, None]: """ Get `Request` format type from query parameters or headers. - :param headers: Dict of Request headers :param extra_formats: Dict of extra dataset specific formats :returns: format value or None if not found/specified @@ -317,19 +315,25 @@ def _get_format(self, headers: dict, # Format not specified: get from Accept headers (MIME types) # e.g. Accept: 'text/html;q=0.5,application/ld+json' - types_ = get_choice_from_headers(headers, 'accept', all=True) + types_ = get_choice_from_headers(self.headers, 'accept', all=True) if types_ is None: return - merged_format_types = FORMAT_TYPES | extra_formats - - (fmts, mimes) = zip(*merged_format_types.items()) - mimes2 = [m.split(';')[0] for m in mimes] + # Add formatters to accepted format types + extra_formats_mimes = { + k: v.mimetype for k, v in extra_formats.items() + if hasattr(v, 'mimetype') + } + merged_format_types = FORMAT_TYPES | extra_formats_mimes + # Lookup formatter by mimetype + mimes = { + merged_format_types[k].split(';')[0]: k + for k in merged_format_types + } for type_ in types_: - if type_ in mimes2: - idx_ = mimes2.index(type_) - return fmts[idx_] + if type_ in mimes: + return mimes[type_] @property def data(self) -> bytes: diff --git a/pygeoapi/api/collection.py b/pygeoapi/api/collection.py index 3b1f24922..f90a38a1f 100644 --- a/pygeoapi/api/collection.py +++ b/pygeoapi/api/collection.py @@ -265,8 +265,8 @@ def gen_collection(api, request, dataset: str, data['links'].append({ 'type': value.mimetype, 'rel': 'items', - 'title': l10n.translate(f'Items as {key}', locale_), # noqa - 'href': f'{api.get_collections_url()}/{dataset}/items?f={value.f}' # noqa + 'title': l10n.translate(f'Items as {value.name}', locale_), # noqa + 'href': f'{api.get_collections_url()}/{dataset}/items?f={key}' # noqa }) # OAPIF Part 2 - list supported CRSs and StorageCRS diff --git a/pygeoapi/api/environmental_data_retrieval.py b/pygeoapi/api/environmental_data_retrieval.py index 08b591f93..8a0a3a1b0 100644 --- a/pygeoapi/api/environmental_data_retrieval.py +++ b/pygeoapi/api/environmental_data_retrieval.py @@ -298,13 +298,11 @@ def get_collection_edr_query(api: API, request: APIRequest, if dataset_formatters: LOGGER.debug(f'Dataset formatters: {dataset_formatters}') - request._format = request._get_format( - request.get_request_headers(request.headers), - {v.f: v.mimetype for v in dataset_formatters.values()}) + request._format = request._get_format(dataset_formatters) LOGGER.debug(f'Request format: {request.format}') - if not request.is_valid(dataset_formatters.keys()): + if not request.is_valid(dataset_formatters): return api.get_format_exception(request) crs_transform_spec = None @@ -475,9 +473,8 @@ def get_collection_edr_query(api: API, request: APIRequest, content = render_j2_template(api.tpl_config, tpl_config, 'collections/edr/query.html', data, api.default_locale) - elif request.format in [df.f for df in dataset_formatters.values()]: - formatter = [v for v in dataset_formatters.values() if - v.f == request.format][0] + elif request.format in dataset_formatters: + formatter = dataset_formatters[request.format] try: content = formatter.write( @@ -581,10 +578,10 @@ def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, '$ref': f"{OPENAPI_YAML['oaedr']}/parameters/{eqe['qt']}Coords.yaml" # noqa } + # Add data formatters to OAS dataset_formatters = get_dataset_formatters(v) coll_f_parameter = deepcopy(get_oas_30_parameters(cfg, locale))['f'] # noqa - for key, value in dataset_formatters.items(): - coll_f_parameter['schema']['enum'].append(value.f) + coll_f_parameter['schema']['enum'].extend(dataset_formatters) paths[eqe['path']] = { 'get': { diff --git a/pygeoapi/api/itemtypes.py b/pygeoapi/api/itemtypes.py index 65c93aa0a..fd7c3c16b 100644 --- a/pygeoapi/api/itemtypes.py +++ b/pygeoapi/api/itemtypes.py @@ -358,13 +358,11 @@ def get_collection_items( if dataset_formatters: LOGGER.debug(f'Dataset formatters: {dataset_formatters}') - request._format = request._get_format( - request.get_request_headers(request.headers), - {v.f: v.mimetype for v in dataset_formatters.values()}) + request._format = request._get_format(dataset_formatters) LOGGER.debug(f'Request format: {request.format}') - if not request.is_valid(dataset_formatters.keys()): + if not request.is_valid(dataset_formatters): return api.get_format_exception(request) crs_transform_spec = None @@ -600,8 +598,8 @@ def get_collection_items( content['links'].append({ 'type': value.mimetype, 'rel': 'alternate', - 'title': f'This document as {key}', - 'href': f'{uri}?f={value.name}{serialized_query_params}' + 'title': f'This document as {value.name}', + 'href': f'{uri}?f={key}{serialized_query_params}' }) next_link = False @@ -685,9 +683,8 @@ def get_collection_items( 'collections/items/index.html', content, request.locale) return headers, HTTPStatus.OK, content - elif request.format in [df.f for df in dataset_formatters.values()]: - formatter = [v for v in dataset_formatters.values() if - v.f == request.format][0] + elif request.format in dataset_formatters: + formatter = dataset_formatters[request.format] try: content = formatter.write( @@ -1109,10 +1106,10 @@ def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, v.get('limits', {}) ) + # Add data formatters to OAS dataset_formatters = get_dataset_formatters(v) coll_f_parameter = deepcopy(oas_30_parameters)['f'] - for key, value in dataset_formatters.items(): - coll_f_parameter['schema']['enum'].append(value.f) + coll_f_parameter['schema']['enum'].extend(dataset_formatters) paths[items_path] = { 'get': { diff --git a/pygeoapi/formatter/base.py b/pygeoapi/formatter/base.py index f56712779..e9dcad4c2 100644 --- a/pygeoapi/formatter/base.py +++ b/pygeoapi/formatter/base.py @@ -65,7 +65,7 @@ def write( :param options: formatting options :param data: dict representation of GeoJSON object - :returns: string representation of format + :returns: string or bytes representation of format """ raise NotImplementedError() diff --git a/pygeoapi/util.py b/pygeoapi/util.py index b60e187a0..6d3155092 100644 --- a/pygeoapi/util.py +++ b/pygeoapi/util.py @@ -768,17 +768,17 @@ def get_dataset_formatters(dataset: dict) -> dict: dataset_formatters = {} provider_type = get_provider_default(dataset['providers'])['type'] - for key, value in PLUGINS['formatter'].items(): + for key in PLUGINS['formatter']: # workaround to keep items-based collections supporting CSV if provider_type not in ['feature', 'record']: continue df2 = load_plugin('formatter', {'name': key}) - dataset_formatters[key] = df2 + dataset_formatters[df2.f] = df2 for df in dataset.get('formatters', []): df2 = load_plugin('formatter', df) - dataset_formatters[df2.name] = df2 + dataset_formatters[df2.f] = df2 return dataset_formatters