From 3e0d8fcb25c1c0855b55b0abe1e7bc891c8e1a91 Mon Sep 17 00:00:00 2001 From: Benjamin Sugden Date: Thu, 23 Jan 2025 15:52:17 +0100 Subject: [PATCH] PB-1169: Remove forecast filter from GET search Decided to remove the forecast filtering options from the GET /search request due to colons in the parameter names. Also fixed the POST request body to actually use the parameters that contain a colon. --- app/stac_api/utils.py | 14 +++ app/stac_api/validators_serializer.py | 10 +- app/stac_api/views/general.py | 20 ++-- app/tests/tests_10/test_search_endpoint.py | 114 +++++---------------- 4 files changed, 52 insertions(+), 106 deletions(-) diff --git a/app/stac_api/utils.py b/app/stac_api/utils.py index af354a17..9cff55af 100644 --- a/app/stac_api/utils.py +++ b/app/stac_api/utils.py @@ -309,6 +309,20 @@ def harmonize_post_get_for_search(request): query_param['ids'] = query_param['ids'].split(',') # to array if 'collections' in query_param: query_param['collections'] = query_param['collections'].split(',') # to array + + # Forecast properties can only be filtered with method POST. + # Decision was made as `:` need to be url encoded and (at least for now) we do not need to + # support forecast filtering in the GET request. + if 'forecast:reference_datetime' in query_param: + del query_param['forecast:reference_datetime'] + if 'forecast:horizon' in query_param: + del query_param['forecast:horizon'] + if 'forecast:duration' in query_param: + del query_param['forecast:duration'] + if 'forecast:variable' in query_param: + del query_param['forecast:variable'] + if 'forecast:perturbed' in query_param: + del query_param['forecast:perturbed'] return query_param diff --git a/app/stac_api/validators_serializer.py b/app/stac_api/validators_serializer.py index 56a7d430..509d38e7 100644 --- a/app/stac_api/validators_serializer.py +++ b/app/stac_api/validators_serializer.py @@ -359,11 +359,11 @@ def validate_query_parameters_post_search(self, query_param): "limit", "cursor", "query", - "forecast_reference_datetime", - "forecast_horizon", - "forecast_duration", - "forecast_variable", - "forecast_perturbed" + "forecast:reference_datetime", + "forecast:horizon", + "forecast:duration", + "forecast:variable", + "forecast:perturbed" ] wrong_query_parameters = set(query_param.keys()).difference(set(accepted_query_parameters)) if wrong_query_parameters: diff --git a/app/stac_api/views/general.py b/app/stac_api/views/general.py index 498766ea..662ed813 100644 --- a/app/stac_api/views/general.py +++ b/app/stac_api/views/general.py @@ -93,18 +93,18 @@ def get_queryset(self): queryset = queryset.filter_by_query(dict_query) if 'intersects' in query_param: queryset = queryset.filter_by_intersects(json.dumps(query_param['intersects'])) - if 'forecast_reference_datetime' in query_param: + if 'forecast:reference_datetime' in query_param: queryset = queryset.filter_by_forecast_reference_datetime( - query_param['forecast_reference_datetime'] + query_param['forecast:reference_datetime'] ) - if 'forecast_horizon' in query_param: - queryset = queryset.filter_by_forecast_horizon(query_param['forecast_horizon']) - if 'forecast_duration' in query_param: - queryset = queryset.filter_by_forecast_duration(query_param['forecast_duration']) - if 'forecast_variable' in query_param: - queryset = queryset.filter_by_forecast_variable(query_param['forecast_variable']) - if 'forecast_perturbed' in query_param: - queryset = queryset.filter_by_forecast_perturbed(query_param['forecast_perturbed']) + if 'forecast:horizon' in query_param: + queryset = queryset.filter_by_forecast_horizon(query_param['forecast:horizon']) + if 'forecast:duration' in query_param: + queryset = queryset.filter_by_forecast_duration(query_param['forecast:duration']) + if 'forecast:variable' in query_param: + queryset = queryset.filter_by_forecast_variable(query_param['forecast:variable']) + if 'forecast:perturbed' in query_param: + queryset = queryset.filter_by_forecast_perturbed(query_param['forecast:perturbed']) if settings.DEBUG_ENABLE_DB_EXPLAIN_ANALYZE: logger.debug( diff --git a/app/tests/tests_10/test_search_endpoint.py b/app/tests/tests_10/test_search_endpoint.py index 8043a263..dadb6be3 100644 --- a/app/tests/tests_10/test_search_endpoint.py +++ b/app/tests/tests_10/test_search_endpoint.py @@ -2,6 +2,7 @@ import logging from datetime import datetime from datetime import timedelta +from urllib.parse import quote_plus from django.test import Client from django.test import override_settings @@ -609,15 +610,7 @@ def setUp(self): # pylint: disable=invalid-name self.maxDiff = None # pylint: disable=invalid-name def test_reference_datetime_exact(self): - val = '2025-01-01T13:05:10Z' - response = self.client.get(f"{self.path}?forecast_reference_datetime={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 1) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-1']) - - payload = {"forecast_reference_datetime": f"{val}"} + payload = {"forecast:reference_datetime": "2025-01-01T13:05:10Z"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -625,15 +618,7 @@ def test_reference_datetime_exact(self): for feature in json_data['features']: self.assertIn(feature['id'], ['item-1']) - val = '2025-02-01T13:05:10Z' - response = self.client.get(f"{self.path}?forecast_reference_datetime={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 3) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-2', 'item-3', 'item-4']) - - payload = {"forecast_reference_datetime": f"{val}"} + payload = {"forecast:reference_datetime": "2025-02-01T13:05:10Z"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -642,15 +627,7 @@ def test_reference_datetime_exact(self): self.assertIn(feature['id'], ['item-2', 'item-3', 'item-4']) def test_reference_datetime_range(self): - val = '2025-02-01T00:00:00Z/2025-02-28T00:00:00Z' - response = self.client.get(f"{self.path}?forecast_reference_datetime={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 3) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-2', 'item-3', 'item-4']) - - payload = {"forecast_reference_datetime": f"{val}"} + payload = {"forecast:reference_datetime": "2025-02-01T00:00:00Z/2025-02-28T00:00:00Z"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -659,15 +636,7 @@ def test_reference_datetime_range(self): self.assertIn(feature['id'], ['item-2', 'item-3', 'item-4']) def test_reference_datetime_open_end(self): - val = '2025-02-01T13:05:10Z/..' - response = self.client.get(f"{self.path}?forecast_reference_datetime={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 4) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-2', 'item-3', 'item-4', 'item-5']) - - payload = {"forecast_reference_datetime": f"{val}"} + payload = {"forecast:reference_datetime": "2025-02-01T13:05:10Z/.."} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -676,15 +645,7 @@ def test_reference_datetime_open_end(self): self.assertIn(feature['id'], ['item-2', 'item-3', 'item-4', 'item-5']) def test_reference_datetime_open_start(self): - val = '../2025-02-01T13:05:10Z' - response = self.client.get(f"{self.path}?forecast_reference_datetime={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 4) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-1', 'item-2', 'item-3', 'item-4']) - - payload = {"forecast_reference_datetime": f"{val}"} + payload = {"forecast:reference_datetime": "../2025-02-01T13:05:10Z"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -693,15 +654,7 @@ def test_reference_datetime_open_start(self): self.assertIn(feature['id'], ['item-1', 'item-2', 'item-3', 'item-4']) def test_horizon(self): - val = 'PT3H' - response = self.client.get(f"{self.path}?forecast_horizon={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 1) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-3']) - - payload = {"forecast_horizon": f"{val}"} + payload = {"forecast:horizon": "PT3H"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -710,15 +663,7 @@ def test_horizon(self): self.assertIn(feature['id'], ['item-3']) def test_duration(self): - val = 'PT12H' - response = self.client.get(f"{self.path}?forecast_duration={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 4) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-1', 'item-2', 'item-4', 'item-5']) - - payload = {"forecast_duration": f"{val}"} + payload = {"forecast:duration": "PT12H"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -727,15 +672,7 @@ def test_duration(self): self.assertIn(feature['id'], ['item-1', 'item-2', 'item-4', 'item-5']) def test_variable(self): - val = 'air_temperature' - response = self.client.get(f"{self.path}?forecast_variable={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 2) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-4', 'item-5']) - - payload = {"forecast_variable": f"{val}"} + payload = {"forecast:variable": "air_temperature"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -744,15 +681,7 @@ def test_variable(self): self.assertIn(feature['id'], ['item-4', 'item-5']) def test_perturbed(self): - val = 'True' - response = self.client.get(f"{self.path}?forecast_perturbed={val}") - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 1) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-4']) - - payload = {"forecast_perturbed": f"{val}"} + payload = {"forecast:perturbed": "True"} response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) json_data = response.json() @@ -761,17 +690,8 @@ def test_perturbed(self): self.assertIn(feature['id'], ['item-4']) def test_multiple(self): - response = self.client.get( - f"{self.path}?forecast_perturbed=False&forecast_horizon=PT6H&forecast_variable=T" - ) - self.assertStatusCode(200, response) - json_data = response.json() - self.assertEqual(len(json_data['features']), 2) - for feature in json_data['features']: - self.assertIn(feature['id'], ['item-1', 'item-2']) - payload = { - "forecast_perturbed": "False", "forecast_horizon": "PT6H", "forecast_variable": "T" + "forecast:perturbed": "False", "forecast:horizon": "PT6H", "forecast:variable": "T" } response = self.client.post(self.path, data=payload, content_type="application/json") self.assertStatusCode(200, response) @@ -779,3 +699,15 @@ def test_multiple(self): self.assertEqual(len(json_data['features']), 2) for feature in json_data['features']: self.assertIn(feature['id'], ['item-1', 'item-2']) + + def test_get_request_does_not_filter_forecast(self): + response = self.client.get( + f"{self.path}?" + quote_plus( + "forecast:reference_datetime=2025-01-01T13:05:10Z&" + "forecast:duration=PT12H&" + + "forecast:perturbed=False&" + "forecast:horizon=PT6H&" + "forecast:variable=T" + ) + ) + self.assertStatusCode(200, response) + json_data = response.json() + # As GET request should not filter for forecast expect all 5 features to be returned. + self.assertEqual(len(json_data['features']), 5)