From f3e84b13f81c9c69777ae906fd13f4a5d9a6e2bb Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 23 Sep 2026 15:52:16 +0200 Subject: [PATCH 1/5] fix(extract_valid_attribute_values): Handle no_value in attribute extraction --- .../routes/blueprint_routes.py | 30 +++++++++++++++++-- tests/test_routes.py | 27 +++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 4c02b6b5..6b4d1fa0 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -269,14 +269,40 @@ def extract_valid_attribute_values( return [], False if not component_attribute.is_genericable(): return [], False + nb_items = component_attribute.nb_items() + default_values = getattr(component_attribute, "default_values", None) + no_value = default_values().no_value if default_values else None + if ( + no_value is None + and attribute_name != "points" + and ( + "Point" in component_attribute.type() + or "Vector" in component_attribute.type() + ) + ): + no_value = [0.0] * nb_items + typed_value_getter = getattr(component_attribute, "value", None) + valid_values: list[float] = [] has_nan = False for element_index in range(attribute_manager.nb_elements()): value = component_attribute.generic_item_value(element_index, item_index) if value is None or math.isnan(value): has_nan = True - else: - valid_values.append(value) + continue + if no_value is not None: + if typed_value_getter is not None: + if typed_value_getter(element_index) == no_value: + has_nan = True + continue + elif value == no_value[item_index] and all( + component_attribute.generic_item_value(element_index, index) + == no_value[index] + for index in range(nb_items) + ): + has_nan = True + continue + valid_values.append(value) return valid_values, has_nan diff --git a/tests/test_routes.py b/tests/test_routes.py index 7178dd15..57cd2d70 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -2,6 +2,7 @@ import os # Third party imports +import opengeode as og from werkzeug.datastructures import FileStorage from flask.testing import FlaskClient from werkzeug.test import TestResponse @@ -13,6 +14,7 @@ from opengeodeweb_microservice.database.data import Data from opengeodeweb_microservice.database.connection import get_session from opengeodeweb_back import geode_functions, test_utils +from opengeodeweb_back.routes.blueprint_routes import extract_valid_attribute_values from opengeodeweb_back.geode_objects.geode_polygonal_surface3d import ( GeodePolygonalSurface3D, ) @@ -744,3 +746,28 @@ def get_full_data() -> test_utils.JsonData: return {"id": model_id, "component_ids": block_ids} test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_extract_valid_attribute_values_with_sentinel_no_value() -> None: + mesh = og.TriangulatedSurface3D.create() + builder = og.TriangulatedSurfaceBuilder3D.create(mesh) + vertex_0 = builder.create_point(og.Point3D([0, 0, 0])) + vertex_1 = builder.create_point(og.Point3D([1, 0, 0])) + builder.create_triangle([vertex_0, vertex_1, vertex_0]) + + attribute_manager = mesh.vertex_attribute_manager() + values_config = og.AttributeValuesDouble() + values_config.no_value = -999.0 + attribute_id = attribute_manager.create_attribute_variable_double( + "variable_double", values_config, og.AttributeProperties() + ) + attribute = attribute_manager.find_attribute_variable_double(attribute_id) + attribute.set_value(vertex_0, -999.0) + attribute.set_value(vertex_1, 42.0) + + valid_values, has_nan = extract_valid_attribute_values( + attribute_manager, "variable_double", 0 + ) + assert has_nan is True + assert valid_values == [42.0] + From cee8b53d8845acd88b196a0bc4258eeac765dce6 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:54:35 +0000 Subject: [PATCH 2/5] Apply prepare changes --- requirements.txt | 1 - tests/test_routes.py | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1f2a8398..cceba64b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,4 +66,3 @@ werkzeug==3.1.8 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.2.2 diff --git a/tests/test_routes.py b/tests/test_routes.py index 57cd2d70..762ab183 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -770,4 +770,3 @@ def test_extract_valid_attribute_values_with_sentinel_no_value() -> None: ) assert has_nan is True assert valid_values == [42.0] - From a05995b88c7ef5fa7608ac7e7d7d2d57e7547172 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 23 Sep 2026 16:06:17 +0200 Subject: [PATCH 3/5] refacto --- .../routes/blueprint_routes.py | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 6b4d1fa0..3edb1bc1 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -257,52 +257,43 @@ def extract_valid_attribute_values( attribute_name: str, item_index: int, ) -> tuple[list[float], bool]: - attribute_ids_matching_name = attribute_manager.attribute_ids_matching_name( - attribute_name - ) - if not isinstance(attribute_ids_matching_name, list): - return [], False - component_attribute = attribute_manager.find_generic_attribute( - attribute_ids_matching_name[0] - ) - if component_attribute is None: + attribute_ids = attribute_manager.attribute_ids_matching_name(attribute_name) + if not isinstance(attribute_ids, list): return [], False - if not component_attribute.is_genericable(): + attribute = attribute_manager.find_generic_attribute(attribute_ids[0]) + if attribute is None or not attribute.is_genericable(): return [], False - nb_items = component_attribute.nb_items() - default_values = getattr(component_attribute, "default_values", None) + + nb_items = attribute.nb_items() + default_values = getattr(attribute, "default_values", None) no_value = default_values().no_value if default_values else None if ( no_value is None and attribute_name != "points" - and ( - "Point" in component_attribute.type() - or "Vector" in component_attribute.type() - ) + and ("Point" in attribute.type() or "Vector" in attribute.type()) ): no_value = [0.0] * nb_items - typed_value_getter = getattr(component_attribute, "value", None) + + value_getter = getattr( + attribute, + "value", + lambda element_index: [ + attribute.generic_item_value(element_index, i) for i in range(nb_items) + ], + ) valid_values: list[float] = [] has_nan = False for element_index in range(attribute_manager.nb_elements()): - value = component_attribute.generic_item_value(element_index, item_index) - if value is None or math.isnan(value): + value = attribute.generic_item_value(element_index, item_index) + if ( + value is None + or math.isnan(value) + or (no_value is not None and value_getter(element_index) == no_value) + ): has_nan = True - continue - if no_value is not None: - if typed_value_getter is not None: - if typed_value_getter(element_index) == no_value: - has_nan = True - continue - elif value == no_value[item_index] and all( - component_attribute.generic_item_value(element_index, index) - == no_value[index] - for index in range(nb_items) - ): - has_nan = True - continue - valid_values.append(value) + else: + valid_values.append(value) return valid_values, has_nan From a2b980a33caeb5913da5d85d1da5992f143ce386 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 23 Sep 2026 16:20:24 +0200 Subject: [PATCH 4/5] fix(attributes): Imrpove validation for non-transferable attributes --- .../routes/blueprint_routes.py | 14 +++++++------ tests/test_routes.py | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 3edb1bc1..aaa18e19 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -261,16 +261,18 @@ def extract_valid_attribute_values( if not isinstance(attribute_ids, list): return [], False attribute = attribute_manager.find_generic_attribute(attribute_ids[0]) - if attribute is None or not attribute.is_genericable(): + if ( + attribute is None + or not attribute.is_genericable() + or not attribute.properties().transferable + ): return [], False nb_items = attribute.nb_items() default_values = getattr(attribute, "default_values", None) no_value = default_values().no_value if default_values else None - if ( - no_value is None - and attribute_name != "points" - and ("Point" in attribute.type() or "Vector" in attribute.type()) + if no_value is None and ( + "Point" in attribute.type() or "Vector" in attribute.type() ): no_value = [0.0] * nb_items @@ -310,7 +312,7 @@ def attributes_metadata( attribute_name = attribute.name() if attribute_name is None: continue - if not attribute.is_genericable(): + if not attribute.is_genericable() or not attribute.properties().transferable: continue nb_items = attribute.nb_items() min_values, max_values = [], [] diff --git a/tests/test_routes.py b/tests/test_routes.py index 762ab183..24e85429 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -770,3 +770,24 @@ def test_extract_valid_attribute_values_with_sentinel_no_value() -> None: ) assert has_nan is True assert valid_values == [42.0] + + +def test_extract_valid_attribute_values_with_non_transferable_attribute() -> None: + mesh = og.TriangulatedSurface3D.create() + builder = og.TriangulatedSurfaceBuilder3D.create(mesh) + vertex_0 = builder.create_point(og.Point3D([0, 0, 0])) + builder.create_triangle([vertex_0, vertex_0, vertex_0]) + + attribute_manager = mesh.vertex_attribute_manager() + properties = og.AttributeProperties() + properties.transferable = False + attribute_manager.create_attribute_variable_double( + "internal_attribute", og.AttributeValuesDouble(), properties + ) + + valid_values, has_nan = extract_valid_attribute_values( + attribute_manager, "internal_attribute", 0 + ) + assert has_nan is False + assert valid_values == [] + From 6d8515d5bd62662a3f77f87c445f0e12f6974dd6 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:20:58 +0000 Subject: [PATCH 5/5] Apply prepare changes --- tests/test_routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_routes.py b/tests/test_routes.py index 24e85429..fc04c25e 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -790,4 +790,3 @@ def test_extract_valid_attribute_values_with_non_transferable_attribute() -> Non ) assert has_nan is False assert valid_values == [] -