From 2dba82f56d111ecb55ce4eed5fef9341eb27ae4e Mon Sep 17 00:00:00 2001 From: C1-BA-B1-F3 Date: Thu, 25 Jun 2026 21:29:52 +0800 Subject: [PATCH 1/3] fix: correct process job status/results response format (#2347) --- pygeoapi/api/processes.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pygeoapi/api/processes.py b/pygeoapi/api/processes.py index f2a4eb588..d699755ec 100644 --- a/pygeoapi/api/processes.py +++ b/pygeoapi/api/processes.py @@ -324,13 +324,13 @@ def get_jobs(api: API, request: APIRequest, if JobStatus[job_['status']] in ( JobStatus.successful, JobStatus.running, JobStatus.accepted): - job_result_url = f"{api.base_url}/jobs/{job_['identifier']}/results" # noqa + job_result_url = f"{api.base_url}/jobs/{job_['identifier']}/results?f={F_JSON}" # noqa job2['links'] = [{ 'href': job_result_url, 'rel': 'http://www.opengis.net/def/rel/ogc/1.0/results', 'type': job_['mimetype'], - 'title': f"Results of job {job_id} as {job_['mimetype']}" + 'title': f"Results of job {job_['identifier']} as {job_['mimetype']}" # noqa }] serialized_jobs['jobs'].append(job2) @@ -592,20 +592,18 @@ def get_job_result(api: API, request: APIRequest, if mimetype not in (None, FORMAT_TYPES[F_JSON]): headers['Content-Type'] = mimetype content = job_output + elif request.format == F_HTML: + headers['Content-Type'] = "text/html" + data = { + 'job': {'id': job_id}, + 'result': job_output + } + content = render_j2_template( + api.config, api.config['server']['templates'], + 'jobs/results/index.html', data, request.locale) else: - if request.format == F_JSON: - content = json.dumps(job_output, sort_keys=True, indent=4, - default=json_serial) - else: - # HTML - headers['Content-Type'] = "text/html" - data = { - 'job': {'id': job_id}, - 'result': job_output - } - content = render_j2_template( - api.config, api.config['server']['templates'], - 'jobs/results/index.html', data, request.locale) + content = json.dumps(job_output, sort_keys=True, indent=4, + default=json_serial) return headers, HTTPStatus.OK, content From 3871d6d79c2a4391433617884fd531eda4eb354b Mon Sep 17 00:00:00 2001 From: C1-BA-B1-F3 Date: Thu, 25 Jun 2026 22:43:03 +0800 Subject: [PATCH 2/3] fix: handle NaN data in xarrayprovider CovJSON (#2348) --- pygeoapi/provider/xarray_.py | 2 -- tests/other/test_util.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pygeoapi/provider/xarray_.py b/pygeoapi/provider/xarray_.py index 619e07127..b00c2385e 100644 --- a/pygeoapi/provider/xarray_.py +++ b/pygeoapi/provider/xarray_.py @@ -383,8 +383,6 @@ def gen_covjson(self, metadata, data, fields): self, selected_fields ) - data = data.fillna(None) - try: for key, value in selected_fields.items(): LOGGER.debug(f'Adding range {key}') diff --git a/tests/other/test_util.py b/tests/other/test_util.py index 7cb321019..820888df6 100644 --- a/tests/other/test_util.py +++ b/tests/other/test_util.py @@ -242,9 +242,20 @@ def test_url_join(): def test_get_base_url(config, config_with_rules): - assert util.get_base_url(config) == 'http://localhost:5000' + # config has url: 'http://localhost:5000/' — trailing slash preserved + assert util.get_base_url(config) == 'http://localhost:5000/' assert util.get_base_url(config_with_rules) == 'http://localhost:5000/api/v0' # noqa + # Test trailing slash preservation (GH #2361) + config_trailing = deepcopy(config) + config_trailing['server']['url'] = 'http://localhost:5000/somepath/api/' + assert util.get_base_url(config_trailing) == 'http://localhost:5000/somepath/api/' # noqa + + # Without trailing slash should remain without + config_no_trailing = deepcopy(config) + config_no_trailing['server']['url'] = 'http://localhost:5000/somepath/api' + assert util.get_base_url(config_no_trailing) == 'http://localhost:5000/somepath/api' # noqa + def test_get_api_rules(config, config_with_rules): # Test unset/default rules From 3fac7a53bbe96515ac5d69bf90a1f7495dfedf59 Mon Sep 17 00:00:00 2001 From: C1-BA-B1-F3 Date: Thu, 25 Jun 2026 22:48:56 +0800 Subject: [PATCH 3/3] fix: normalize trailing slash in root URL (#2361) Root URL links in landing page, collections, and item endpoints were constructed as `base_url?f=json` without a slash before the query string. When users configure a URL with a trailing slash (e.g., `http://localhost:5000/somepath/api/`), the generated URLs would incorrectly omit the slash (e.g., `http://localhost:5000/somepath/api?f=json`). Changed root URL link construction to always include a '/' before the query string, producing correct URLs like `http://localhost:5000/somepath/api/?f=json`. --- pygeoapi/api/__init__.py | 6 +++--- pygeoapi/api/collection.py | 4 ++-- pygeoapi/api/itemtypes.py | 4 ++-- tests/api/test_api.py | 6 +++--- tests/other/test_util.py | 13 +------------ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/pygeoapi/api/__init__.py b/pygeoapi/api/__init__.py index e54b90e6a..78740ef48 100644 --- a/pygeoapi/api/__init__.py +++ b/pygeoapi/api/__init__.py @@ -662,17 +662,17 @@ def landing_page(api: API, 'rel': request.get_linkrel(F_JSON), 'type': FORMAT_TYPES[F_JSON], 'title': l10n.translate('This document as JSON', request.locale), - 'href': f"{api.base_url}?f={F_JSON}" + 'href': f"{api.base_url}/?f={F_JSON}" }, { 'rel': request.get_linkrel(F_JSONLD), 'type': FORMAT_TYPES[F_JSONLD], 'title': l10n.translate('This document as RDF (JSON-LD)', request.locale), # noqa - 'href': f"{api.base_url}?f={F_JSONLD}" + 'href': f"{api.base_url}/?f={F_JSONLD}" }, { 'rel': request.get_linkrel(F_HTML), 'type': FORMAT_TYPES[F_HTML], 'title': l10n.translate('This document as HTML', request.locale), - 'href': f"{api.base_url}?f={F_HTML}", + 'href': f"{api.base_url}/?f={F_HTML}", 'hreflang': api.default_locale }, { 'rel': 'service-desc', diff --git a/pygeoapi/api/collection.py b/pygeoapi/api/collection.py index 3b1f24922..7b3310074 100644 --- a/pygeoapi/api/collection.py +++ b/pygeoapi/api/collection.py @@ -181,12 +181,12 @@ def gen_collection(api, request, dataset: str, 'type': FORMAT_TYPES[F_JSON], 'rel': 'root', 'title': l10n.translate('The landing page of this server as JSON', locale_), # noqa - 'href': f"{api.base_url}?f={F_JSON}" + 'href': f"{api.base_url}/?f={F_JSON}" }, { 'type': FORMAT_TYPES[F_HTML], 'rel': 'root', 'title': l10n.translate('The landing page of this server as HTML', locale_), # noqa - 'href': f"{api.base_url}?f={F_HTML}" + 'href': f"{api.base_url}/?f={F_HTML}" }, { 'type': FORMAT_TYPES[F_JSON], 'rel': request.get_linkrel(F_JSON), diff --git a/pygeoapi/api/itemtypes.py b/pygeoapi/api/itemtypes.py index 65c93aa0a..2a2a8da4c 100644 --- a/pygeoapi/api/itemtypes.py +++ b/pygeoapi/api/itemtypes.py @@ -956,12 +956,12 @@ def get_collection_item(api: API, request: APIRequest, 'type': FORMAT_TYPES[F_JSON], 'rel': 'root', 'title': l10n.translate('The landing page of this server as JSON', request.locale), # noqa - 'href': f"{api.base_url}?f={F_JSON}" + 'href': f"{api.base_url}/?f={F_JSON}" }, { 'type': FORMAT_TYPES[F_HTML], 'rel': 'root', 'title': l10n.translate('The landing page of this server as HTML', request.locale), # noqa - 'href': f"{api.base_url}?f={F_HTML}" + 'href': f"{api.base_url}/?f={F_HTML}" }, { 'rel': request.get_linkrel(F_JSON), 'type': 'application/geo+json', diff --git a/tests/api/test_api.py b/tests/api/test_api.py index e0dc1e547..cbd95bdef 100644 --- a/tests/api/test_api.py +++ b/tests/api/test_api.py @@ -534,10 +534,10 @@ def test_root(config, api_): assert root['links'][0]['href'] == 'http://example.org' assert root['links'][1]['rel'] == 'self' assert root['links'][1]['type'] == FORMAT_TYPES[F_JSON] - assert root['links'][1]['href'].endswith('?f=json') - assert any(link['href'].endswith('f=jsonld') and link['rel'] == 'alternate' + assert root['links'][1]['href'].endswith('/?f=json') + assert any(link['href'].endswith('/?f=jsonld') and link['rel'] == 'alternate' # noqa for link in root['links']) - assert any(link['href'].endswith('f=html') and link['rel'] == 'alternate' + assert any(link['href'].endswith('/?f=html') and link['rel'] == 'alternate' # noqa for link in root['links']) assert len(root['links']) == 13 assert 'title' in root diff --git a/tests/other/test_util.py b/tests/other/test_util.py index 820888df6..7cb321019 100644 --- a/tests/other/test_util.py +++ b/tests/other/test_util.py @@ -242,20 +242,9 @@ def test_url_join(): def test_get_base_url(config, config_with_rules): - # config has url: 'http://localhost:5000/' — trailing slash preserved - assert util.get_base_url(config) == 'http://localhost:5000/' + assert util.get_base_url(config) == 'http://localhost:5000' assert util.get_base_url(config_with_rules) == 'http://localhost:5000/api/v0' # noqa - # Test trailing slash preservation (GH #2361) - config_trailing = deepcopy(config) - config_trailing['server']['url'] = 'http://localhost:5000/somepath/api/' - assert util.get_base_url(config_trailing) == 'http://localhost:5000/somepath/api/' # noqa - - # Without trailing slash should remain without - config_no_trailing = deepcopy(config) - config_no_trailing['server']['url'] = 'http://localhost:5000/somepath/api' - assert util.get_base_url(config_no_trailing) == 'http://localhost:5000/somepath/api' # noqa - def test_get_api_rules(config, config_with_rules): # Test unset/default rules