From 2a02a0bb6ec5216ea6298b167d5959dfa7df83f3 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Wed, 10 Sep 2025 16:28:02 +0200 Subject: [PATCH 01/13] refactoring --- cads_processing_api_service/translators.py | 26 +++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 726c6df..36a9957 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -27,16 +27,30 @@ def extract_groups_labels( - groups: list[Any], values: dict[str, str] | None = None + groups: list[Any], labels: dict[str, str] | None = None ) -> dict[str, str]: - if values is None: - values = {} + """Extract labels from groups. + + Parameters + ---------- + groups : list[Any] + List of groups. + values : dict[str, str] | None, optional + Dictionary to populate, by default None + + Returns + ------- + dict[str, str] + Extracted labels, with keys as label ids and values as label names. + """ + if labels is None: + labels = {} for group in groups: if "labels" in group: - values.update(group["labels"]) + labels.update(group["labels"]) elif "groups" in group: - values = extract_groups_labels(group["groups"], values) - return values + labels = extract_groups_labels(group["groups"], labels) + return labels def extract_labels(input_cds_schema: dict[str, Any]) -> dict[str, str]: From ca1b85cf3762f1e9dac7b09721b3282c7cb0f146 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Wed, 10 Sep 2025 17:35:01 +0200 Subject: [PATCH 02/13] docstring --- cads_processing_api_service/translators.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 36a9957..fecbf33 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -163,6 +163,20 @@ def make_request_labels( input_value_ids: Any, cds_input_schema: dict[str, Any], ) -> list[str]: + """Translate request's input value ids into labels. + + Parameters + ---------- + input_value_ids : Any + Input value ids. + cds_input_schema : dict[str, Any] + CDS input schema. + + Returns + ------- + list[str] + List of input value labels. + """ if not isinstance(input_value_ids, list): input_value_ids = [input_value_ids] if cds_input_schema["type"] in ( From 8de2b3dd248f90ac33ad744d8ae7d0fdb81b6dc7 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Wed, 10 Sep 2025 17:41:13 +0200 Subject: [PATCH 03/13] refactoring --- cads_processing_api_service/translators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index fecbf33..5dfb922 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -159,7 +159,7 @@ def translate_cds_form( return ogc_inputs -def make_request_labels( +def make_labels_from_ids( input_value_ids: Any, cds_input_schema: dict[str, Any], ) -> list[str]: @@ -227,7 +227,7 @@ def make_request_labels_group( input_key_id = cds_input_schema["name"] if input_key_id in request: input_value_ids = request[input_key_id] - request_labels = make_request_labels(input_value_ids, cds_input_schema) + request_labels = make_labels_from_ids(input_value_ids, cds_input_schema) cds_form.remove(cds_input_schema) return request_labels elif input_key_id == default: @@ -282,7 +282,7 @@ def translate_request_ids_into_labels( continue if not isinstance(input_value_ids, list): input_value_ids = [input_value_ids] - request_labels[input_key_label] = make_request_labels( + request_labels[input_key_label] = make_labels_from_ids( input_value_ids, cds_input_schema ) return request_labels From a4baaad0625088dacdc309376ffe36c6f7708b90 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Wed, 10 Sep 2025 17:59:45 +0200 Subject: [PATCH 04/13] docstring --- cads_processing_api_service/translators.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 5dfb922..07754c6 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -238,7 +238,20 @@ def make_request_labels_group( def translate_request_ids_into_labels( request: dict[str, Any], cds_form: list[Any] | dict[str, Any] | None ) -> dict[str, Any]: - """Translate request input values into labels.""" + """Translate request input values into labels. + + Parameters + ---------- + request : dict[str, Any] + Request. + cds_form : list[Any] | dict[str, Any] | None + CDS form. + + Returns + ------- + dict[str, Any] + Request with input values translated into labels. + """ cds_form = copy.deepcopy(cds_form) if cds_form is None: cds_form = {} From e39b4f00ffccbbffb429736b8056a8258bb890c7 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Wed, 10 Sep 2025 18:23:12 +0200 Subject: [PATCH 05/13] [WIP] refactor unit tests --- tests/test_10_translators.py | 432 +++++++++++++++++++++-------------- 1 file changed, 259 insertions(+), 173 deletions(-) diff --git a/tests/test_10_translators.py b/tests/test_10_translators.py index 5fb056c..3dc9b4e 100644 --- a/tests/test_10_translators.py +++ b/tests/test_10_translators.py @@ -18,6 +18,8 @@ from typing import Any +import pytest + from cads_processing_api_service import config, translators TEST_INPUT_CDS_SCHEMAS: dict[str, Any] = { @@ -120,56 +122,107 @@ "type": "Child2Widget", "details": {}, }, + "inclusive_group_widget_group_output_false": { + "name": "inclusive_group_widget", + "label": "Inclusive Group Widget", + "type": "InclusiveGroupWidget", + "children": ["child_3", "child_4"], + "details": {"group_output": False}, + }, + "child_3": { + "name": "child_3", + "label": "Child 3", + "type": "Child3Widget", + "details": {}, + }, + "child_4": { + "name": "child_4", + "label": "Child 4", + "type": "Child4Widget", + "details": {}, + }, + "inclusive_group_widget_group_output_true": { + "name": "inclusive_group_widget", + "label": "Inclusive Group Widget", + "type": "InclusiveGroupWidget", + "children": ["child_5", "child_6"], + "details": {"group_output": True}, + }, + "child_5": { + "name": "child_5", + "label": "Child 5", + "type": "Child5Widget", + "details": {}, + }, + "child_6": { + "name": "child_6", + "label": "Child 6", + "type": "Child6Widget", + "details": {}, + }, } -def test_extract_groups_labels() -> None: - test_groups = TEST_INPUT_CDS_SCHEMAS["string_list_array"]["details"]["groups"] - test_values = {"test_value": "Test Value"} - exp_output = { - "test_value": "Test Value", - "val1": "Val1", - "val2": "Val2", - "val3": "Val3", - } - res_output = translators.extract_groups_labels(test_groups, test_values) - assert res_output == exp_output - - test_groups = TEST_INPUT_CDS_SCHEMAS["string_list_array"]["details"]["groups"] - exp_output = {"val1": "Val1", "val2": "Val2", "val3": "Val3"} - res_output = translators.extract_groups_labels(test_groups) - assert res_output == exp_output - - test_groups = TEST_INPUT_CDS_SCHEMAS["string_list_array_groups"]["details"][ - "groups" - ] - exp_output = { - "val1": "Val1", - "val2": "Val2", - "val3": "Val3", - "val4": "Val4", - "val5": "Val5", - "val6": "Val6", - } - res_output = translators.extract_groups_labels(test_groups) - assert res_output == exp_output - - -def test_extract_labels() -> None: - test_inputs_cds_schema = TEST_INPUT_CDS_SCHEMAS["string_list_array"] - exp_output = {"val1": "Val1", "val2": "Val2", "val3": "Val3"} - res_output = translators.extract_labels(test_inputs_cds_schema) - assert res_output == exp_output - - test_inputs_cds_schema = TEST_INPUT_CDS_SCHEMAS["string_list"] - exp_output = {"val1": "Val1", "val2": "Val2", "val3": "Val3"} - res_output = translators.extract_labels(test_inputs_cds_schema) - assert res_output == exp_output - - test_inputs_cds_schema = TEST_INPUT_CDS_SCHEMAS["free_edition_widget"] - exp_output = {} - res_output = translators.extract_labels(test_inputs_cds_schema) - assert res_output == exp_output +@pytest.mark.parametrize( + "groups, labels, expected_output", + [ + ( + TEST_INPUT_CDS_SCHEMAS["string_list_array"]["details"]["groups"], + None, + {"val1": "Val1", "val2": "Val2", "val3": "Val3"}, + ), + ( + TEST_INPUT_CDS_SCHEMAS["string_list_array"]["details"]["groups"], + {"test_value": "Test Label"}, + { + "test_value": "Test Label", + "val1": "Val1", + "val2": "Val2", + "val3": "Val3", + }, + ), + ( + TEST_INPUT_CDS_SCHEMAS["string_list_array_groups"]["details"]["groups"], + None, + { + "val1": "Val1", + "val2": "Val2", + "val3": "Val3", + "val4": "Val4", + "val5": "Val5", + "val6": "Val6", + }, + ), + ], + ids=[ + "StringListArrayWidget without labels", + "StringListArrayWidget with labels", + "StringListArrayWidget with bested groups", + ], +) +def test_extract_groups_labels(groups, labels, expected_output) -> None: + output = translators.extract_groups_labels(groups, labels) + assert output == expected_output + + +@pytest.mark.parametrize( + "cds_schema, expected_output", + [ + ( + TEST_INPUT_CDS_SCHEMAS["string_list_array"], + {"val1": "Val1", "val2": "Val2", "val3": "Val3"}, + ), + ( + TEST_INPUT_CDS_SCHEMAS["string_list"], + {"val1": "Val1", "val2": "Val2", "val3": "Val3"}, + ), + (TEST_INPUT_CDS_SCHEMAS["free_edition_widget"], {}), + ], + ids=["StringListArrayWidget", "StringListWidget", "FreeEditionWidget"], +) +def test_extract_labels(cds_schema, expected_output) -> None: + output = translators.extract_labels(cds_schema) + assert output == expected_output def test_translate_string_list() -> None: @@ -182,132 +235,165 @@ def test_translate_string_list() -> None: assert res_output == exp_ouput -def test_translate_string_list_array() -> None: - test_input = TEST_INPUT_CDS_SCHEMAS["string_list_array"] - exp_ouput = { - "type": "array", - "items": {"type": "string", "enum": ["val1", "val2", "val3"]}, - } - res_output = translators.translate_string_list_array(test_input) - assert res_output == exp_ouput - - test_input = TEST_INPUT_CDS_SCHEMAS["string_list_array_groups"] - exp_ouput = { - "type": "array", - "items": { - "type": "string", - "enum": ["val1", "val2", "val3", "val4", "val5", "val6"], - }, - } - res_output = translators.translate_string_list_array(test_input) - assert res_output == exp_ouput - - -def test_translate_string_choice() -> None: - test_input = TEST_INPUT_CDS_SCHEMAS["string_choice"] - exp_ouput = {"type": "string", "enum": ["val1", "val2", "val3"], "default": "val1"} - res_output = translators.translate_string_choice(test_input) - assert res_output == exp_ouput - - test_input = TEST_INPUT_CDS_SCHEMAS["string_choice_default_list"] - exp_ouput = {"type": "string", "enum": ["val1", "val2", "val3"], "default": "val1"} - res_output = translators.translate_string_choice(test_input) - assert res_output == exp_ouput - - -def test_translate_geographic_extent_map() -> None: - test_input = TEST_INPUT_CDS_SCHEMAS["geographic_extent_map"] - exp_ouput = { - "type": "array", - "minItems": 4, - "maxItems": 4, - "items": {"type": "number"}, - "default": [1, 2, 3, 4], - } - res_output = translators.translate_geographic_extent_map(test_input) - assert res_output == exp_ouput - - test_input = TEST_INPUT_CDS_SCHEMAS["geographic_extent_map_default_dict"] - exp_ouput = { - "type": "array", - "minItems": 4, - "maxItems": 4, - "items": {"type": "number"}, - "default": [1, 2, 3, 4], - } - res_output = translators.translate_geographic_extent_map(test_input) - assert res_output == exp_ouput - - -def test_make_request_labels() -> None: - test_input_value_ids = ["1", "1", "1", "1"] - test_input_cds_schema = TEST_INPUT_CDS_SCHEMAS["geographic_extent_map"] - exp_output = ["North: 1°", "West: 1°", "South: 1°", "East: 1°"] - res_output = translators.make_request_labels( - test_input_value_ids, test_input_cds_schema - ) - assert res_output == exp_output - - test_input_value_ids = [{"latitude": 10, "longitude": 10}] - test_input_cds_schema = TEST_INPUT_CDS_SCHEMAS["geographic_location"] - exp_output = ["Latitude: 10°", "Longitude: 10°"] - res_output = translators.make_request_labels( - test_input_value_ids, test_input_cds_schema - ) - assert res_output == exp_output - - test_input_value_ids = ["val1", "val2"] - test_input_cds_schema = TEST_INPUT_CDS_SCHEMAS["string_list"] - exp_output = ["Val1", "Val2"] - res_output = translators.make_request_labels( - test_input_value_ids, test_input_cds_schema - ) - assert res_output == exp_output - - test_input_value_ids = ["val1", "val4"] - test_input_cds_schema = TEST_INPUT_CDS_SCHEMAS["string_list"] - exp_output = ["Val1", "val4"] - res_output = translators.make_request_labels( - test_input_value_ids, test_input_cds_schema - ) - assert res_output == exp_output - - -def test_translate_request_ids_into_labels() -> None: - request = {"key1": "val1", "key2": "val2"} - cds_schema = None - exp_output = {"key1": "val1", "key2": "val2"} - res_output = translators.translate_request_ids_into_labels(request, cds_schema) - assert res_output == exp_output - - request = { - "string_list": ["val1", "val2"], - "string_choice": "val1", - "unknown_key": "unknown_value", - } - cds_schema = [ - TEST_INPUT_CDS_SCHEMAS["string_list"], - TEST_INPUT_CDS_SCHEMAS["string_choice"], - ] - exp_output = { - "String List": ["Val1", "Val2"], - "String Choice": ["Val1"], - "unknown_key": "unknown_value", - } - res_output = translators.translate_request_ids_into_labels(request, cds_schema) - assert res_output == exp_output - - request = {} - cds_schema = [ - TEST_INPUT_CDS_SCHEMAS["string_choice"], - TEST_INPUT_CDS_SCHEMAS["exclusive_group_widget"], - TEST_INPUT_CDS_SCHEMAS["child_1"], - TEST_INPUT_CDS_SCHEMAS["child_2"], - ] - exp_output = { - "String Choice": ["Val1"], - "Exclusive Group Widget": ["Child 1"], - } +@pytest.mark.parametrize( + "cds_schema, expected_output", + [ + ( + TEST_INPUT_CDS_SCHEMAS["string_list_array"], + { + "type": "array", + "items": {"type": "string", "enum": ["val1", "val2", "val3"]}, + }, + ), + ( + TEST_INPUT_CDS_SCHEMAS["string_list_array_groups"], + { + "type": "array", + "items": { + "type": "string", + "enum": ["val1", "val2", "val3", "val4", "val5", "val6"], + }, + }, + ), + ], + ids=["shallow groups", "nested groups"], +) +def test_translate_string_list_array(cds_schema, expected_output) -> None: + output = translators.translate_string_list_array(cds_schema) + assert output == expected_output + + +@pytest.mark.parametrize( + "cds_schema, expected_output", + [ + ( + TEST_INPUT_CDS_SCHEMAS["string_choice"], + {"type": "string", "enum": ["val1", "val2", "val3"], "default": "val1"}, + ), + ( + TEST_INPUT_CDS_SCHEMAS["string_choice_default_list"], + {"type": "string", "enum": ["val1", "val2", "val3"], "default": "val1"}, + ), + ], + ids=["default as string", "default as list"], +) +def test_translate_string_choice(cds_schema, expected_output) -> None: + output = translators.translate_string_choice(cds_schema) + assert output == expected_output + + +@pytest.mark.parametrize( + "cds_schema, expected_output", + [ + ( + TEST_INPUT_CDS_SCHEMAS["geographic_extent_map"], + { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": {"type": "number"}, + "default": [1, 2, 3, 4], + }, + ), + ( + TEST_INPUT_CDS_SCHEMAS["geographic_extent_map_default_dict"], + { + "type": "array", + "minItems": 4, + "maxItems": 4, + "items": {"type": "number"}, + "default": [1, 2, 3, 4], + }, + ), + ], + ids=["default as list", "default as dict"], +) +def test_translate_geographic_extent_map(cds_schema, expected_output) -> None: + output = translators.translate_geographic_extent_map(cds_schema) + assert output == expected_output + + +@pytest.mark.parametrize( + "input_value_ids, cds_schema, expected_output", + [ + ( + ["1", "1", "1", "1"], + TEST_INPUT_CDS_SCHEMAS["geographic_extent_map"], + ["North: 1°", "West: 1°", "South: 1°", "East: 1°"], + ), + ( + [{"latitude": 10, "longitude": 10}], + TEST_INPUT_CDS_SCHEMAS["geographic_location"], + ["Latitude: 10°", "Longitude: 10°"], + ), + ( + ["val1", "val2"], + TEST_INPUT_CDS_SCHEMAS["string_list"], + ["Val1", "Val2"], + ), + ( + ["val1", "val4"], + TEST_INPUT_CDS_SCHEMAS["string_list"], + ["Val1", "val4"], + ), + ], + ids=[ + "GeographicExtentMapWidget", + "GeographicLocationWidget", + "StringListWidget with known values", + "StringListWidget with unknown value", + ], +) +def test_make_labels_from_ids(input_value_ids, cds_schema, expected_output) -> None: + output = translators.make_labels_from_ids(input_value_ids, cds_schema) + assert output == expected_output + + +@pytest.mark.parametrize( + "request_ids, cds_schema, expected_output", + [ + ( + {"key1": "val1", "key2": "val2"}, + None, + {"key1": "val1", "key2": "val2"}, + ), + ( + { + "string_list": ["val1", "val2"], + "string_choice": "val1", + "unknown_key": "unknown_value", + }, + [ + TEST_INPUT_CDS_SCHEMAS["string_list"], + TEST_INPUT_CDS_SCHEMAS["string_choice"], + ], + { + "String List": ["Val1", "Val2"], + "String Choice": ["Val1"], + "unknown_key": "unknown_value", + }, + ), + ( + {}, + [ + TEST_INPUT_CDS_SCHEMAS["string_choice"], + TEST_INPUT_CDS_SCHEMAS["exclusive_group_widget"], + TEST_INPUT_CDS_SCHEMAS["child_1"], + TEST_INPUT_CDS_SCHEMAS["child_2"], + ], + { + "String Choice": ["Val1"], + "Exclusive Group Widget": ["Child 1"], + }, + ), + ], + ids=["no cds_schema", "request with unknown key", "empty request with defaults"], +) +def test_translate_request_ids_into_labels( + request_ids, cds_schema, expected_output +) -> None: + output = translators.translate_request_ids_into_labels(request_ids, cds_schema) + assert output == expected_output def test_format_list() -> None: From e27297b4cbb4a5d0b823560b2dd7fdfdeb81139f Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Thu, 11 Sep 2025 09:28:17 +0200 Subject: [PATCH 06/13] docstring --- cads_processing_api_service/translators.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 07754c6..64029f6 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -304,6 +304,7 @@ def translate_request_ids_into_labels( def format_list( value_list: list[int | float | str], max_items_per_line: int = 1 ) -> str: + """Format a list into a string representation.""" if len(value_list) > max_items_per_line: formatted = "[\n" for i in range(0, len(value_list), max_items_per_line): From 7160a9d25bc4e41ee6b8f5e56af72efad1e3b87d Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Thu, 11 Sep 2025 10:37:17 +0200 Subject: [PATCH 07/13] docstring --- cads_processing_api_service/translators.py | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 64029f6..6959616 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -304,7 +304,20 @@ def translate_request_ids_into_labels( def format_list( value_list: list[int | float | str], max_items_per_line: int = 1 ) -> str: - """Format a list into a string representation.""" + """Format a list into a string representation. + + Parameters + ---------- + value_list : list[int | float | str] + List of values to format. + max_items_per_line : int, optional + Maximum number of items per line, by default 1. + + Returns + ------- + str + Formatted string representation of the list. + """ if len(value_list) > max_items_per_line: formatted = "[\n" for i in range(0, len(value_list), max_items_per_line): @@ -323,6 +336,20 @@ def format_request_value( request_value: int | float | str | list[int | float | str], key: str | None = None, ) -> str: + """Format a request value into a string representation. + + Parameters + ---------- + request_value : int | float | str | list[int | float | str] + Request value to format. + key : str | None, optional + Request key, by default None. + + Returns + ------- + str + Formatted string representation of the request value. + """ if isinstance(request_value, list): if key is None: formatted_request_value = format_list(request_value) From 0ed4e068cbe1abfad00c201dc89ed294950d5a66 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Thu, 11 Sep 2025 10:37:47 +0200 Subject: [PATCH 08/13] refactor unit tests --- tests/test_10_translators.py | 57 ++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/tests/test_10_translators.py b/tests/test_10_translators.py index 3dc9b4e..4f997ed 100644 --- a/tests/test_10_translators.py +++ b/tests/test_10_translators.py @@ -396,34 +396,39 @@ def test_translate_request_ids_into_labels( assert output == expected_output -def test_format_list() -> None: - value_list = ["test_value_1", "test_value_2"] - max_items_per_line = 1 - exp_output = "[\n 'test_value_1',\n 'test_value_2'\n ]" - res_output = translators.format_list(value_list, max_items_per_line) - assert res_output == exp_output - - max_items_per_line = 2 - exp_output = "['test_value_1', 'test_value_2']" - res_output = translators.format_list(value_list, max_items_per_line) - assert res_output == exp_output - - -def test_format_request_value() -> None: - test_value = "test_value" - exp_output = '"test_value"' - res_output = translators.format_request_value(test_value) - assert res_output == exp_output +@pytest.mark.parametrize( + "value_list, max_items_per_line, expected_output", + [ + ( + ["test_value_1", "test_value_2"], + 1, + "[\n 'test_value_1',\n 'test_value_2'\n ]", + ), + (["test_value_1", "test_value_2"], 2, "['test_value_1', 'test_value_2']"), + ], + ids=["max_items_per_line = 1", "max_items_per_line = 2"], +) +def test_format_list(value_list, max_items_per_line, expected_output) -> None: + output = translators.format_list(value_list, max_items_per_line) + assert output == expected_output - test_value = 1 - exp_output = "1" - res_output = translators.format_request_value(test_value) - assert res_output == exp_output - test_value = ["test_value_1", "test_value_2"] - exp_output = "[\n 'test_value_1',\n 'test_value_2'\n ]" - res_output = translators.format_request_value(test_value) - assert res_output == exp_output +@pytest.mark.parametrize( + "value, key, expected_output", + [ + ("test_value", None, '"test_value"'), + (1, None, "1"), + ( + ["test_value_1", "test_value_2"], + None, + "[\n 'test_value_1',\n 'test_value_2'\n ]", + ), + ], + ids=["string value", "integer value", "list value"], +) +def test_format_request_value(value, key, expected_output) -> None: + output = translators.format_request_value(value, key) + assert output == expected_output def test_format_api_request() -> None: From a13a17177e3800bee01db14b8a522f60f3c8ec05 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Fri, 12 Sep 2025 18:19:10 +0200 Subject: [PATCH 09/13] support dict in translate_request_ids_into_labels --- cads_processing_api_service/translators.py | 197 +++++++++++++-------- tests/test_10_translators.py | 160 ++++++++++------- 2 files changed, 217 insertions(+), 140 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 6959616..36d0107 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -159,15 +159,103 @@ def translate_cds_form( return ogc_inputs +def make_labels_from_geographic_extent_widget_ids( + input_value_ids: list[str | float], +) -> list[str]: + """Translate geographic extent input value ids into labels. + + Parameters + ---------- + input_value_ids : list[str | float] + Input value ids. + + Returns + ------- + list[str] + List of input value labels. + """ + request_labels = [ + f"{label}: {value}°" + for label, value in zip( + ["North", "West", "South", "East"], + input_value_ids, + ) + ] + return request_labels + + +def make_labels_from_geographic_location_widgetids( + input_value_ids: dict[str, str | float], +) -> list[str]: + """Translate geographic location input value ids into labels. + + Parameters + ---------- + input_value_ids : dict[str, str | float] + Input value ids. + + Returns + ------- + list[str] + List of input value labels. + """ + location = input_value_ids[0] + try: + latitude = f"{location['latitude']}°" + longitude = f"{location['longitude']}°" + except Exception as e: + logger.error( + "Error extracting latitude and longitude from geographic location", error=e + ) + latitude = longitude = "Unknown" + request_labels = [ + f"Latitude: {latitude}", + f"Longitude: {longitude}", + ] + return request_labels + + +def make_labels_from_generic_widget_ids( + input_value_ids: list[str | float], cds_input_schema: dict[str, Any] +) -> list[str]: + """Translate generic input value ids into labels. + + Parameters + ---------- + input_value_ids : list[str | float] + Input value ids. + + Returns + ------- + list[str] + List of input value labels. + """ + input_value_label = extract_labels(cds_input_schema) + request_labels = [] + for input_value_id in input_value_ids: + if input_value_id in input_value_label: + request_labels.append(input_value_label[input_value_id]) + else: + request_labels.append(input_value_id) + return request_labels + + +LABELS_GENERATORS = { + "GeographicExtentWidget": make_labels_from_geographic_extent_widget_ids, + "GeographicExtentMapWidget": make_labels_from_geographic_extent_widget_ids, + "GeographicLocationWidget": make_labels_from_geographic_location_widgetids, +} + + def make_labels_from_ids( - input_value_ids: Any, + input_value_ids: list[str | float] | dict[str, str | float], cds_input_schema: dict[str, Any], ) -> list[str]: """Translate request's input value ids into labels. Parameters ---------- - input_value_ids : Any + input_value_ids : list[str | float] | dict[str, str | float] Input value ids. cds_input_schema : dict[str, Any] CDS input schema. @@ -177,42 +265,13 @@ def make_labels_from_ids( list[str] List of input value labels. """ - if not isinstance(input_value_ids, list): - input_value_ids = [input_value_ids] - if cds_input_schema["type"] in ( - "GeographicExtentWidget", - "GeographicExtentMapWidget", - ): - request_labels = [ - f"{label}: {value}°" - for label, value in zip( - ["North", "West", "South", "East"], - input_value_ids, - ) - ] - elif cds_input_schema["type"] == "GeographicLocationWidget": - location = input_value_ids[0] - try: - latitude = f"{location['latitude']}°" - longitude = f"{location['longitude']}°" - except Exception as e: - logger.error( - "Error extracting latitude and longitude from geographic location", - error=e, - ) - latitude = longitude = "Unknown" - request_labels = [ - f"Latitude: {latitude}", - f"Longitude: {longitude}", - ] + if cds_input_schema.get("type", None) in LABELS_GENERATORS: + input_value_label_generator = LABELS_GENERATORS[cds_input_schema["type"]] + request_labels = input_value_label_generator(input_value_ids) else: - input_value_label = extract_labels(cds_input_schema) - request_labels = [] - for input_value_id in input_value_ids: - if input_value_id in input_value_label: - request_labels.append(input_value_label[input_value_id]) - else: - request_labels.append(input_value_id) + request_labels = make_labels_from_generic_widget_ids( + input_value_ids, cds_input_schema + ) return request_labels @@ -252,52 +311,34 @@ def translate_request_ids_into_labels( dict[str, Any] Request with input values translated into labels. """ - cds_form = copy.deepcopy(cds_form) if cds_form is None: - cds_form = {} - if not isinstance(cds_form, list): + return request + elif not isinstance(cds_form, list): cds_form = [cds_form] - # This will include in the labels the input keys that are not associated with - # any cds_input_schema in the cds_form - request_labels: dict[str, Any] = { - input_key_id: str(input_value_id) - for input_key_id, input_value_id in request.items() + cds_form_names_map = { + cds_input_schema["name"]: cds_input_schema for cds_input_schema in cds_form } - exclusive_group_widgets_children = [] - for cds_input_schema in cds_form: - if cds_input_schema.get("type", None) == "ExclusiveGroupWidget": - exclusive_group_widgets_children.extend(cds_input_schema["children"]) - for cds_input_schema in cds_form: - cds_input_schema_name = cds_input_schema.get("name", None) - if cds_input_schema_name in exclusive_group_widgets_children: - continue - if cds_input_schema.get("type", None) == "ExclusiveGroupWidget": - input_key_label = cds_input_schema["label"] - children = cds_input_schema["children"] - if keys_to_remove := list(set(request_labels.keys()) & set(children)): - for key_to_remove in keys_to_remove: - del request_labels[key_to_remove] - default = cds_input_schema.get("details", {}).get("default", None) - request_labels[input_key_label] = make_request_labels_group( - request, children, default, cds_form - ) + request_labels = {} + for input_key_id, input_value_ids in request.items(): + if input_key_id not in cds_form_names_map: + request_labels[input_key_id] = copy.deepcopy(input_value_ids) else: - input_key_id = cds_input_schema.get("name", None) - input_key_label = cds_input_schema.get("label", None) - if input_key_id in request_labels: - del request_labels[input_key_id] - input_value_ids = request[input_key_id] - elif default_value_ids := cds_input_schema.get("details", {}).get( - "default", None - ): - input_value_ids = default_value_ids + input_key_label = cds_form_names_map[input_key_id]["label"] + if not isinstance(input_value_ids, dict): + if not isinstance(input_value_ids, list): + input_value_ids = [input_value_ids] + input_value_labels = make_labels_from_ids( + input_value_ids, cds_form_names_map[input_key_id] + ) + request_labels[input_key_label] = ( + input_value_labels + if len(input_value_labels) > 1 + else input_value_labels[0] + ) else: - continue - if not isinstance(input_value_ids, list): - input_value_ids = [input_value_ids] - request_labels[input_key_label] = make_labels_from_ids( - input_value_ids, cds_input_schema - ) + request_labels[input_key_label] = translate_request_ids_into_labels( + input_value_ids, cds_form + ) return request_labels diff --git a/tests/test_10_translators.py b/tests/test_10_translators.py index 4f997ed..d4e0c62 100644 --- a/tests/test_10_translators.py +++ b/tests/test_10_translators.py @@ -122,44 +122,6 @@ "type": "Child2Widget", "details": {}, }, - "inclusive_group_widget_group_output_false": { - "name": "inclusive_group_widget", - "label": "Inclusive Group Widget", - "type": "InclusiveGroupWidget", - "children": ["child_3", "child_4"], - "details": {"group_output": False}, - }, - "child_3": { - "name": "child_3", - "label": "Child 3", - "type": "Child3Widget", - "details": {}, - }, - "child_4": { - "name": "child_4", - "label": "Child 4", - "type": "Child4Widget", - "details": {}, - }, - "inclusive_group_widget_group_output_true": { - "name": "inclusive_group_widget", - "label": "Inclusive Group Widget", - "type": "InclusiveGroupWidget", - "children": ["child_5", "child_6"], - "details": {"group_output": True}, - }, - "child_5": { - "name": "child_5", - "label": "Child 5", - "type": "Child5Widget", - "details": {}, - }, - "child_6": { - "name": "child_6", - "label": "Child 6", - "type": "Child6Widget", - "details": {}, - }, } @@ -353,41 +315,115 @@ def test_make_labels_from_ids(input_value_ids, cds_schema, expected_output) -> N "request_ids, cds_schema, expected_output", [ ( - {"key1": "val1", "key2": "val2"}, + {"key_1": "val_1"}, None, - {"key1": "val1", "key2": "val2"}, + {"key_1": "val_1"}, ), ( - { - "string_list": ["val1", "val2"], - "string_choice": "val1", - "unknown_key": "unknown_value", - }, + {"key_1": "val_1"}, [ - TEST_INPUT_CDS_SCHEMAS["string_list"], - TEST_INPUT_CDS_SCHEMAS["string_choice"], + { + "name": "key_1", + "label": "Key 1", + "details": {"labels": {"val_1": "Val 1"}}, + } ], - { - "String List": ["Val1", "Val2"], - "String Choice": ["Val1"], - "unknown_key": "unknown_value", - }, + {"Key 1": "Val 1"}, ), ( - {}, + {"key_1": "val_2"}, [ - TEST_INPUT_CDS_SCHEMAS["string_choice"], - TEST_INPUT_CDS_SCHEMAS["exclusive_group_widget"], - TEST_INPUT_CDS_SCHEMAS["child_1"], - TEST_INPUT_CDS_SCHEMAS["child_2"], + { + "name": "key_1", + "label": "Key 1", + "details": {"labels": {"val_1": "Val 1"}}, + } ], - { - "String Choice": ["Val1"], - "Exclusive Group Widget": ["Child 1"], - }, + {"Key 1": "val_2"}, + ), + ( + {"key_1": ["val_1", "val_2"]}, + [ + { + "name": "key_1", + "label": "Key 1", + "details": {"labels": {"val_1": "Val 1", "val_2": "Val 2"}}, + } + ], + {"Key 1": ["Val 1", "Val 2"]}, + ), + ( + {"key_1": ["val_1", "val_3"]}, + [ + { + "name": "key_1", + "label": "Key 1", + "details": {"labels": {"val_1": "Val 1", "val_2": "Val 2"}}, + } + ], + {"Key 1": ["Val 1", "val_3"]}, ), + ( + {"key_1": {"key_11": "val_1", "key_12": "val_1"}}, + [ + { + "name": "key_1", + "label": "Key 1", + }, + { + "name": "key_11", + "label": "Key 11", + "details": {"labels": {"val_1": "Val 1"}}, + }, + { + "name": "key_12", + "label": "Key 12", + "details": {"labels": {"val_1": "Val 1"}}, + }, + ], + {"Key 1": {"Key 11": "Val 1", "Key 12": "Val 1"}}, + ), + ( + {"key_1": {"key_11": "val_2", "key_12": "val_2"}}, + [ + { + "name": "key_1", + "label": "Key 1", + }, + { + "name": "key_11", + "label": "Key 11", + "details": {"labels": {"val_1": "Val 1"}}, + }, + { + "name": "key_12", + "label": "Key 12", + "details": {"labels": {"val_1": "Val 1"}}, + }, + ], + {"Key 1": {"Key 11": "val_2", "Key 12": "val_2"}}, + ), + ( + {"key_1": {"key_11": "val_1", "key_12": "val_1"}}, + [ + { + "name": "key_1", + "label": "Key 1", + }, + ], + {"Key 1": {"key_11": "val_1", "key_12": "val_1"}}, + ), + ], + ids=[ + "no cds_schema", + "single value", + "single unknown value", + "list of values", + "list with unknown value", + "dict", + "dict with unknown values", + "dict without children schema", ], - ids=["no cds_schema", "request with unknown key", "empty request with defaults"], ) def test_translate_request_ids_into_labels( request_ids, cds_schema, expected_output From 090d93cb6574bfaff51839a839d08ff54a88bcc8 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Mon, 29 Sep 2025 18:51:07 +0200 Subject: [PATCH 10/13] types --- cads_processing_api_service/translators.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 36d0107..9458f47 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -184,14 +184,14 @@ def make_labels_from_geographic_extent_widget_ids( return request_labels -def make_labels_from_geographic_location_widgetids( - input_value_ids: dict[str, str | float], +def make_labels_from_geographic_location_widget_ids( + input_value_ids: list[dict[str, str | float]], ) -> list[str]: """Translate geographic location input value ids into labels. Parameters ---------- - input_value_ids : dict[str, str | float] + input_value_ids : list[dict[str, str | float]] Input value ids. Returns @@ -216,13 +216,13 @@ def make_labels_from_geographic_location_widgetids( def make_labels_from_generic_widget_ids( - input_value_ids: list[str | float], cds_input_schema: dict[str, Any] + input_value_ids: list[str], cds_input_schema: dict[str, Any] ) -> list[str]: """Translate generic input value ids into labels. Parameters ---------- - input_value_ids : list[str | float] + input_value_ids : list[str] Input value ids. Returns @@ -243,19 +243,19 @@ def make_labels_from_generic_widget_ids( LABELS_GENERATORS = { "GeographicExtentWidget": make_labels_from_geographic_extent_widget_ids, "GeographicExtentMapWidget": make_labels_from_geographic_extent_widget_ids, - "GeographicLocationWidget": make_labels_from_geographic_location_widgetids, + "GeographicLocationWidget": make_labels_from_geographic_location_widget_ids, } def make_labels_from_ids( - input_value_ids: list[str | float] | dict[str, str | float], + input_value_ids: list[str | dict[str, str | float]], cds_input_schema: dict[str, Any], ) -> list[str]: """Translate request's input value ids into labels. Parameters ---------- - input_value_ids : list[str | float] | dict[str, str | float] + input_value_ids : list[str] Input value ids. cds_input_schema : dict[str, Any] CDS input schema. @@ -267,10 +267,10 @@ def make_labels_from_ids( """ if cds_input_schema.get("type", None) in LABELS_GENERATORS: input_value_label_generator = LABELS_GENERATORS[cds_input_schema["type"]] - request_labels = input_value_label_generator(input_value_ids) + request_labels: list[str] = input_value_label_generator(input_value_ids) # type: ignore else: request_labels = make_labels_from_generic_widget_ids( - input_value_ids, cds_input_schema + input_value_ids, cds_input_schema # type: ignore ) return request_labels From fcc53b4f48c62d6f79d144e9b1feaed31e27330a Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Mon, 29 Sep 2025 18:56:40 +0200 Subject: [PATCH 11/13] update typiing --- cads_processing_api_service/translators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 9458f47..a760489 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -374,14 +374,14 @@ def format_list( def format_request_value( - request_value: int | float | str | list[int | float | str], + request_value: int | float | str | list[int | float | str] | dict[str, Any], key: str | None = None, ) -> str: """Format a request value into a string representation. Parameters ---------- - request_value : int | float | str | list[int | float | str] + request_value : int | float | str | list[int | float | str] | dict[str, Any] Request value to format. key : str | None, optional Request key, by default None. From 3842e48fa5dff3652be0c131363fca7f1d9daf93 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Tue, 30 Sep 2025 10:11:21 +0200 Subject: [PATCH 12/13] qa --- cads_processing_api_service/translators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index a760489..83b55da 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -267,10 +267,11 @@ def make_labels_from_ids( """ if cds_input_schema.get("type", None) in LABELS_GENERATORS: input_value_label_generator = LABELS_GENERATORS[cds_input_schema["type"]] - request_labels: list[str] = input_value_label_generator(input_value_ids) # type: ignore + request_labels: list[str] = input_value_label_generator(input_value_ids) # type: ignore else: request_labels = make_labels_from_generic_widget_ids( - input_value_ids, cds_input_schema # type: ignore + input_value_ids, + cds_input_schema, # type: ignore ) return request_labels From ca3475a266431d8d3fad379291533dbf37d6d083 Mon Sep 17 00:00:00 2001 From: Marco Cucchi Date: Tue, 30 Sep 2025 12:11:27 +0200 Subject: [PATCH 13/13] types --- cads_processing_api_service/translators.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cads_processing_api_service/translators.py b/cads_processing_api_service/translators.py index 83b55da..3edd78d 100644 --- a/cads_processing_api_service/translators.py +++ b/cads_processing_api_service/translators.py @@ -216,13 +216,13 @@ def make_labels_from_geographic_location_widget_ids( def make_labels_from_generic_widget_ids( - input_value_ids: list[str], cds_input_schema: dict[str, Any] + input_value_ids: list[Any], cds_input_schema: dict[str, Any] ) -> list[str]: """Translate generic input value ids into labels. Parameters ---------- - input_value_ids : list[str] + input_value_ids : list[Any] Input value ids. Returns @@ -233,6 +233,8 @@ def make_labels_from_generic_widget_ids( input_value_label = extract_labels(cds_input_schema) request_labels = [] for input_value_id in input_value_ids: + if not isinstance(input_value_id, str): + input_value_id = str(input_value_id) if input_value_id in input_value_label: request_labels.append(input_value_label[input_value_id]) else: @@ -271,7 +273,7 @@ def make_labels_from_ids( else: request_labels = make_labels_from_generic_widget_ids( input_value_ids, - cds_input_schema, # type: ignore + cds_input_schema, ) return request_labels