diff --git a/geoh5py/data/__init__.py b/geoh5py/data/__init__.py index e369bcac2..db5272bd8 100644 --- a/geoh5py/data/__init__.py +++ b/geoh5py/data/__init__.py @@ -86,3 +86,18 @@ def from_primitive_type(cls, primitive_type: PrimitiveTypeEnum) -> type: :return: The data type. """ return DataTypeEnum[primitive_type.name].value + + @classmethod + def _missing_(cls, value) -> DataTypeEnum: + """ + Allows for case-insensitive matching of enum members. + + For example, "Integer" will match "INTEGER". + + :param value: The value to match against the enum members. + """ + if isinstance(value, str): + normalized = value.upper() + if normalized in cls.__members__: + return cls[normalized] + return super()._missing_(value) diff --git a/geoh5py/data/data_association_enum.py b/geoh5py/data/data_association_enum.py index 84e0987c0..1cbec080d 100644 --- a/geoh5py/data/data_association_enum.py +++ b/geoh5py/data/data_association_enum.py @@ -37,3 +37,18 @@ class DataAssociationEnum(Enum): FACE = 4 GROUP = 5 DEPTH = 6 + + @classmethod + def _missing_(cls, value) -> DataAssociationEnum: + """ + Allows for case-insensitive matching of enum members. + + For example, "Cell" will match "CELL". + + :param value: The value to match against the enum members. + """ + if isinstance(value, str): + normalized = value.upper() + if normalized in cls.__members__: + return cls[normalized] + return super()._missing_(value) diff --git a/geoh5py/shared/utils.py b/geoh5py/shared/utils.py index cec533ea6..1310342b0 100644 --- a/geoh5py/shared/utils.py +++ b/geoh5py/shared/utils.py @@ -39,6 +39,8 @@ from .exceptions import Geoh5FileClosedError +UidOrNumeric = UUID | float | int | None +StringOrNumeric = str | float | int # pylint: disable=too-many-lines if TYPE_CHECKING: @@ -1439,3 +1441,14 @@ def map_to_class( class_map[identifier] = member return class_map + + +def enum_name_to_str(value: Enum) -> str: + """ + Convert enum name to capitalized string. + + :param value: Enum value to convert. + + :return: Capitalized string. + """ + return value.name.capitalize() diff --git a/geoh5py/shared/validators.py b/geoh5py/shared/validators.py index 0cd7ecdb0..7efe98e2f 100644 --- a/geoh5py/shared/validators.py +++ b/geoh5py/shared/validators.py @@ -145,13 +145,6 @@ def to_class( return out -def none_to_empty_string(value): - """None transforms to empty string for serialization.""" - if value is None: - return "" - return value - - def types_to_string(types: list) -> list[str] | str: if len(types) > 1: return [f"{{{k.default_type_uid()!s}}}" for k in types] diff --git a/geoh5py/ui_json/annotations.py b/geoh5py/ui_json/annotations.py index 33f9c982e..9de8c97b0 100644 --- a/geoh5py/ui_json/annotations.py +++ b/geoh5py/ui_json/annotations.py @@ -18,27 +18,28 @@ # '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' import logging +from pathlib import Path from typing import Annotated, Any from uuid import UUID from pydantic import BeforeValidator, Field, PlainSerializer -from geoh5py.ui_json.validations.form import empty_string_to_none, uuid_to_string +from geoh5py.data import DataAssociationEnum, DataTypeEnum +from geoh5py.groups import Group +from geoh5py.objects import ObjectBase +from geoh5py.shared.utils import enum_name_to_str, stringify +from geoh5py.shared.validators import ( + to_class, + to_list, + to_path, + to_type_uid_or_class, + types_to_string, +) +from geoh5py.ui_json.utils import optional_uuid_mapper logger = logging.getLogger(__name__) -OptionalUUIDList = Annotated[ - list[UUID] | None, # pylint: disable=unsupported-binary-operation - BeforeValidator(empty_string_to_none), - PlainSerializer(uuid_to_string), -] - -OptionalValueList = Annotated[ - float | list[float] | None, - BeforeValidator(empty_string_to_none), -] - def deprecate(value, info): """Issue deprecation warning.""" @@ -46,8 +47,58 @@ def deprecate(value, info): return value +AssociationOptions = Annotated[ + DataAssociationEnum, + PlainSerializer(enum_name_to_str), +] + +DataTypeOptions = Annotated[ + DataTypeEnum, + PlainSerializer(enum_name_to_str), +] + + Deprecated = Annotated[ Any, Field(exclude=True), BeforeValidator(deprecate), ] + +GroupTypes = Annotated[ + list[type[Group]], + BeforeValidator(to_class), + BeforeValidator(to_type_uid_or_class), + BeforeValidator(to_list), + PlainSerializer(types_to_string, when_used="json"), +] + +MeshTypes = Annotated[ + list[type[ObjectBase]], + BeforeValidator(to_class), + BeforeValidator(to_type_uid_or_class), + BeforeValidator(to_list), + PlainSerializer(types_to_string, when_used="json"), +] + +OptionalUUID = Annotated[ + UUID | None, + BeforeValidator(optional_uuid_mapper), + PlainSerializer(stringify), +] + +OptionalUUIDList = Annotated[ + list[UUID] | None, + BeforeValidator(optional_uuid_mapper), + PlainSerializer(stringify), +] + +OptionalValueList = Annotated[ + float | list[float] | None, + BeforeValidator(optional_uuid_mapper), +] + +PathList = Annotated[ + list[Path], + BeforeValidator(to_path), + BeforeValidator(to_list), +] diff --git a/geoh5py/ui_json/forms.py b/geoh5py/ui_json/forms.py index a0b327c32..6897b3f66 100644 --- a/geoh5py/ui_json/forms.py +++ b/geoh5py/ui_json/forms.py @@ -22,14 +22,13 @@ from enum import Enum from pathlib import Path -from typing import Annotated, Any +from typing import Any from uuid import UUID import numpy as np from pydantic import ( BaseModel, ConfigDict, - PlainSerializer, TypeAdapter, ValidationError, field_serializer, @@ -37,23 +36,17 @@ model_validator, ) from pydantic.alias_generators import to_camel, to_snake -from pydantic.functional_validators import BeforeValidator - -from geoh5py.data import DataAssociationEnum, DataTypeEnum -from geoh5py.groups import Group, GroupTypeEnum -from geoh5py.objects import ObjectBase -from geoh5py.shared.validators import ( - to_class, - to_list, - to_path, - to_type_uid_or_class, - types_to_string, -) -from geoh5py.ui_json.annotations import OptionalUUIDList, OptionalValueList -from geoh5py.ui_json.validations.form import ( - empty_string_to_none, - uuid_to_string, - uuid_to_string_or_numeric, + +from geoh5py.groups import GroupTypeEnum +from geoh5py.ui_json.annotations import ( + AssociationOptions, + DataTypeOptions, + GroupTypes, + MeshTypes, + OptionalUUID, + OptionalUUIDList, + OptionalValueList, + PathList, ) @@ -304,13 +297,6 @@ def valid_choice(self): return self -PathList = Annotated[ - list[Path], - BeforeValidator(to_path), - BeforeValidator(to_list), -] - - class FileForm(BaseForm): """ File path uijson form. @@ -428,21 +414,6 @@ def force_file_description(cls, _): return ["Directory"] -MeshTypes = Annotated[ - list[type[ObjectBase]], - BeforeValidator(to_class), - BeforeValidator(to_type_uid_or_class), - BeforeValidator(to_list), - PlainSerializer(types_to_string, when_used="json"), -] - -OptionalUUID = Annotated[ - UUID | None, # pylint: disable=unsupported-binary-operation - BeforeValidator(empty_string_to_none), - PlainSerializer(uuid_to_string), -] - - class ObjectForm(BaseForm): """ Geoh5py object uijson form. @@ -457,15 +428,6 @@ class ObjectForm(BaseForm): mesh_type: MeshTypes -GroupTypes = Annotated[ - list[type[Group]], - BeforeValidator(to_class), - BeforeValidator(to_type_uid_or_class), - BeforeValidator(to_list), - PlainSerializer(types_to_string, when_used="json"), -] - - class GroupForm(BaseForm): """ Geoh5py group uijson form. @@ -480,23 +442,6 @@ class GroupForm(BaseForm): group_type: GroupTypes -Association = Enum( # type: ignore - "Association", - [(k.name, k.name.capitalize()) for k in DataAssociationEnum], - type=str, -) - -DataType = Enum( # type: ignore - "DataType", [(k.name, k.name.capitalize()) for k in DataTypeEnum], type=str -) - -UUIDOrNumber = Annotated[ - UUID | float | int | None, # pylint: disable=unsupported-binary-operation - BeforeValidator(empty_string_to_none), - PlainSerializer(uuid_to_string_or_numeric), -] - - class DataFormMixin(BaseModel): """ Mixin class to add common attributes a series of data classes. @@ -513,8 +458,8 @@ class DataFormMixin(BaseModel): """ parent: str - association: Association | list[Association] - data_type: DataType | list[DataType] + association: AssociationOptions | list[AssociationOptions] + data_type: DataTypeOptions | list[DataTypeOptions] class DataForm(DataFormMixin, BaseForm): @@ -562,7 +507,7 @@ class GroupMultiDataForm(BaseForm): group_type: GroupTypes group_value: OptionalUUID - data_type: DataType | list[DataType] + data_type: DataTypeOptions | list[DataTypeOptions] value: str | list[str] multi_select: bool = True diff --git a/geoh5py/ui_json/ui_json.py b/geoh5py/ui_json/ui_json.py index 30a4fdf89..17dc3bfd7 100644 --- a/geoh5py/ui_json/ui_json.py +++ b/geoh5py/ui_json/ui_json.py @@ -38,19 +38,23 @@ from geoh5py import Workspace from geoh5py.groups import PropertyGroup, UIJsonGroup from geoh5py.shared import Entity -from geoh5py.shared.utils import fetch_active_workspace, str2uuid, stringify -from geoh5py.shared.validators import none_to_empty_string +from geoh5py.shared.utils import ( + fetch_active_workspace, + none2str, + str2none, + str2uuid, + stringify, +) from geoh5py.ui_json.forms import BaseForm -from geoh5py.ui_json.validations import ErrorPool, UIJsonError, get_validations -from geoh5py.ui_json.validations.form import empty_string_to_none +from geoh5py.ui_json.validation import ErrorPool, UIJsonError, get_validations logger = logging.getLogger(__name__) OptionalPath = Annotated[ - Path | None, # pylint: disable=unsupported-binary-operation - BeforeValidator(empty_string_to_none), - PlainSerializer(none_to_empty_string), + Path | None, + BeforeValidator(str2none), + PlainSerializer(none2str), ] diff --git a/geoh5py/ui_json/utils.py b/geoh5py/ui_json/utils.py index e6c4b601a..64d24fe3a 100644 --- a/geoh5py/ui_json/utils.py +++ b/geoh5py/ui_json/utils.py @@ -31,7 +31,12 @@ from geoh5py import Workspace from geoh5py.groups import ContainerGroup, Group from geoh5py.objects import ObjectBase -from geoh5py.shared.utils import fetch_active_workspace +from geoh5py.shared.utils import ( + dict_mapper, + entity2uuid, + fetch_active_workspace, + str2none, +) logger = getLogger(__name__) @@ -531,3 +536,13 @@ def monitored_directory_copy( move(working_path / temp_geoh5, directory_path / temp_geoh5, copy) return str(directory_path / temp_geoh5) + + +def optional_uuid_mapper(value: Any): + """ + Take values and convert them into UUID or None (or list of). + + :param value: Either a string of entity, or list of. + :return: UUID, Nont or list of. + """ + return dict_mapper(value, [str2none, entity2uuid]) diff --git a/geoh5py/ui_json/validation.py b/geoh5py/ui_json/validation.py index b971df522..956579528 100644 --- a/geoh5py/ui_json/validation.py +++ b/geoh5py/ui_json/validation.py @@ -20,12 +20,15 @@ from __future__ import annotations +from collections.abc import Callable from copy import deepcopy from typing import Any, cast from uuid import UUID from warnings import warn +from geoh5py.data import Data from geoh5py.groups import PropertyGroup +from geoh5py.objects import ObjectBase from geoh5py.shared import Entity from geoh5py.shared.exceptions import RequiredValidationError from geoh5py.shared.validators import ( @@ -315,3 +318,114 @@ def __call__(self, data, *args): "InputValidators can only be called with dictionary of data or " "(key, value) pair." ) + + +## Validation utility for UIJson class ## +class UIJsonError(Exception): + """Exception raised for errors in the UIJson object.""" + + def __init__(self, message: str): + super().__init__(message) + + +class ErrorPool: # pylint: disable=too-few-public-methods + """ + Stores validation errors for all UIJson members. + + :param errors: Dictionary mapping parameter names to lists of + exceptions encountered during validation. + """ + + def __init__(self, errors: dict[str, list[Exception]]): + self.pool = errors + + def _format_error_message(self): + """Format the error message for the UIJsonError.""" + + msg = "" + for key, errors in self.pool.items(): + if errors: + msg += f"\t{key}:\n" + for i, error in enumerate(errors): + msg += f"\t\t{i}. {error}\n" + + return msg + + def throw(self): + """Raise the UIJsonError with detailed list of errors per parameter.""" + + message = self._format_error_message() + if message: + message = "Collected UIJson errors:\n" + message + raise UIJsonError(message) + + +def dependency_type_validation( + name: str, data: dict[str, Any], json_dict: dict[str, Any] +): + """ + Validate that the dependency for is optional or bool type. + + :param name: Name of the form + :param data: Input data with known validations. + :param json_dict: A dict representation of the UIJson object. + """ + + dependency = json_dict[name]["dependency"] + dependency_form = json_dict[dependency] + if "optional" not in dependency_form and not isinstance(data[dependency], bool): + raise UIJsonError( + f"Dependency {dependency} must be either optional or of boolean type." + ) + + +def get_validations(form_keys: list[str]) -> list[Callable]: + """ + Get callable validations based on identifying form keys. + + :param form_keys: List of form keys. + + :return: List of callable validations. + """ + return [VALIDATIONS_MAP[k] for k in form_keys if k in VALIDATIONS_MAP] + + +def mesh_type_validation(name: str, data: dict[str, Any], json_dict: dict[str, Any]): + """ + Validate that value is one of the provided mesh types. + + :param name: Name of the form + :param data: Input data with known validations. + :param json_dict: A dict representation of the UIJson object. + """ + + mesh_types = json_dict[name]["mesh_type"] + obj = data[name] + if not isinstance(obj, tuple(mesh_types)): + raise UIJsonError(f"Object's mesh type must be one of {mesh_types}.") + + +def parent_validation(name: str, data: dict[str, Any], json_dict: dict[str, Any]): + """ + Validate that the data is a child of the parent object. + + :param name: Name of the form + :param data: Input data with known validations. + :param json_dict: A dict representation of the UIJson object. + """ + + form = json_dict[name] + child = data[name] + parent = data[form["parent"]] + + if not isinstance(parent, ObjectBase) or ( + isinstance(child, Data) and parent.get_entity(child.uid)[0] is None + ): + raise UIJsonError(f"{name} data is not a child of {form['parent']}.") + + +VALIDATIONS_MAP = { + "dependency": dependency_type_validation, + "mesh_type": mesh_type_validation, + "parent": parent_validation, +} diff --git a/geoh5py/ui_json/validations/__init__.py b/geoh5py/ui_json/validations/__init__.py deleted file mode 100644 index 315aea7c9..000000000 --- a/geoh5py/ui_json/validations/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -# Copyright (c) 2020-2026 Mira Geoscience Ltd. ' -# ' -# This file is part of geoh5py. ' -# ' -# geoh5py is free software: you can redistribute it and/or modify ' -# it under the terms of the GNU Lesser General Public License as published by ' -# the Free Software Foundation, either version 3 of the License, or ' -# (at your option) any later version. ' -# ' -# geoh5py is distributed in the hope that it will be useful, ' -# but WITHOUT ANY WARRANTY; without even the implied warranty of ' -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' -# GNU Lesser General Public License for more details. ' -# ' -# You should have received a copy of the GNU Lesser General Public License ' -# along with geoh5py. If not, see . ' -# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' - -from collections.abc import Callable - -from .uijson import ( - ErrorPool, - UIJsonError, - dependency_type_validation, - mesh_type_validation, - parent_validation, -) - - -VALIDATIONS_MAP = { - "dependency": dependency_type_validation, - "mesh_type": mesh_type_validation, - "parent": parent_validation, -} - - -def get_validations(form_keys: list[str]) -> list[Callable]: - """Returns a list of callable validations based on identifying form keys.""" - return [VALIDATIONS_MAP[k] for k in form_keys if k in VALIDATIONS_MAP] diff --git a/geoh5py/ui_json/validations/form.py b/geoh5py/ui_json/validations/form.py deleted file mode 100644 index 734ba3a6b..000000000 --- a/geoh5py/ui_json/validations/form.py +++ /dev/null @@ -1,61 +0,0 @@ -# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -# Copyright (c) 2020-2026 Mira Geoscience Ltd. ' -# ' -# This file is part of geoh5py. ' -# ' -# geoh5py is free software: you can redistribute it and/or modify ' -# it under the terms of the GNU Lesser General Public License as published by ' -# the Free Software Foundation, either version 3 of the License, or ' -# (at your option) any later version. ' -# ' -# geoh5py is distributed in the hope that it will be useful, ' -# but WITHOUT ANY WARRANTY; without even the implied warranty of ' -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' -# GNU Lesser General Public License for more details. ' -# ' -# You should have received a copy of the GNU Lesser General Public License ' -# along with geoh5py. If not, see . ' -# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' - -from uuid import UUID - - -UidOrNumeric = UUID | float | int | None -StringOrNumeric = str | float | int - - -def uuid_to_string(value: UUID | list[UUID] | None) -> str | list[str]: - """Serialize UUID(s) as a string.""" - - def convert(value: UUID | None) -> str: - if value is None: - return "" - if isinstance(value, UUID): - return f"{{{value!s}}}" - return value - - if isinstance(value, list): - return [convert(v) for v in value] - return convert(value) - - -def empty_string_to_none(value): - """Promote empty string to uid, and pass all other values.""" - if value == "": - return None - return value - - -def uuid_to_string_or_numeric( - value: UidOrNumeric | list[UidOrNumeric], -) -> StringOrNumeric | list[StringOrNumeric]: - def convert(value: UidOrNumeric) -> StringOrNumeric: - if value is None: - return "" - if isinstance(value, UUID): - return f"{{{value}}}" - return value - - if isinstance(value, list): - return [convert(v) for v in value] - return convert(value) diff --git a/geoh5py/ui_json/validations/uijson.py b/geoh5py/ui_json/validations/uijson.py deleted file mode 100644 index f0e61e927..000000000 --- a/geoh5py/ui_json/validations/uijson.py +++ /dev/null @@ -1,90 +0,0 @@ -# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -# Copyright (c) 2020-2026 Mira Geoscience Ltd. ' -# ' -# This file is part of geoh5py. ' -# ' -# geoh5py is free software: you can redistribute it and/or modify ' -# it under the terms of the GNU Lesser General Public License as published by ' -# the Free Software Foundation, either version 3 of the License, or ' -# (at your option) any later version. ' -# ' -# geoh5py is distributed in the hope that it will be useful, ' -# but WITHOUT ANY WARRANTY; without even the implied warranty of ' -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ' -# GNU Lesser General Public License for more details. ' -# ' -# You should have received a copy of the GNU Lesser General Public License ' -# along with geoh5py. If not, see . ' -# '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' - -from typing import Any - -from geoh5py.data import Data -from geoh5py.objects import ObjectBase - - -class UIJsonError(Exception): - """Exception raised for errors in the UIJson object.""" - - def __init__(self, message: str): - super().__init__(message) - - -class ErrorPool: # pylint: disable=too-few-public-methods - """Stores validation errors for all UIJson members.""" - - def __init__(self, errors: dict[str, list[Exception]]): - self.pool = errors - - def _format_error_message(self): - """Format the error message for the UIJsonError.""" - - msg = "" - for key, errors in self.pool.items(): - if errors: - msg += f"\t{key}:\n" - for i, error in enumerate(errors): - msg += f"\t\t{i}. {error}\n" - - return msg - - def throw(self): - """Raise the UIJsonError with detailed list of errors per parameter.""" - - message = self._format_error_message() - if message: - message = "Collected UIJson errors:\n" + message - raise UIJsonError(message) - - -def dependency_type_validation(name: str, data: dict[str, Any], params: dict[str, Any]): - """Dependency is optional or bool type.""" - - dependency = params[name]["dependency"] - dependency_form = params[dependency] - if "optional" not in dependency_form and not isinstance(data[dependency], bool): - raise UIJsonError( - f"Dependency {dependency} must be either optional or of boolean type." - ) - - -def mesh_type_validation(name: str, data: dict[str, Any], params: dict[str, Any]): - """Promoted value is one of the provided mesh types.""" - - mesh_types = params[name]["mesh_type"] - obj = data[name] - if not isinstance(obj, tuple(mesh_types)): - raise UIJsonError(f"Object's mesh type must be one of {mesh_types}.") - - -def parent_validation(name: str, data: dict[str, Any], params: dict[str, Any]): - """Data is a child of the parent object.""" - - form = params[name] - child = data[name] - parent = data[form["parent"]] - - if not isinstance(parent, ObjectBase) or ( - isinstance(child, Data) and parent.get_entity(child.uid)[0] is None - ): - raise UIJsonError(f"{name} data is not a child of {form['parent']}.") diff --git a/tests/ui_json/forms_test.py b/tests/ui_json/forms_test.py index fae8010f7..b22384973 100644 --- a/tests/ui_json/forms_test.py +++ b/tests/ui_json/forms_test.py @@ -27,10 +27,10 @@ from pydantic import BaseModel, ValidationError from geoh5py import Workspace +from geoh5py.data import DataAssociationEnum, DataTypeEnum from geoh5py.groups import GroupTypeEnum, PropertyGroup from geoh5py.objects import Curve, DrapeModel, Points, Surface from geoh5py.ui_json.forms import ( - Association, BaseForm, BoolForm, ChoiceForm, @@ -38,7 +38,6 @@ DataGroupForm, DataOrValueForm, DataRangeForm, - DataType, DirectoryForm, FileForm, FloatForm, @@ -430,6 +429,10 @@ def test_object_form_mesh_type_as_classes(tmp_path): assert isinstance(ws.get_entity(form.value)[0], tuple(form.mesh_type)) + form_entity = ObjectForm(label="name", value=points, mesh_type=[Points]) + + assert form.value == form_entity.value + def test_object_form_empty_string_handling(): form = ObjectForm(label="name", value="", mesh_type=[Points, Surface]) @@ -448,8 +451,8 @@ def test_data_form(): assert form.label == "name" assert form.value == uuid.UUID(data_uid) assert form.parent == "my_param" - assert form.association == "Vertex" - assert form.data_type == "Float" + assert form.association.name == "VERTEX" + assert form.data_type.name == "FLOAT" form = DataForm( label="name", @@ -458,8 +461,8 @@ def test_data_form(): association=["Vertex", "Cell"], data_type=["Float", "Integer"], ) - assert form.association == [Association.VERTEX, Association.CELL] - assert form.data_type == [DataType.FLOAT, DataType.INTEGER] + assert form.association == [DataAssociationEnum.VERTEX, DataAssociationEnum.CELL] + assert form.data_type == [DataTypeEnum.FLOAT, DataTypeEnum.INTEGER] def test_data_group_form(): @@ -476,8 +479,8 @@ def test_data_group_form(): assert form.value == uuid.UUID(group_uid) assert form.data_group_type == GroupTypeEnum.STRIKEDIP assert form.parent == "Da-da" - assert form.association == [Association.VERTEX, Association.CELL] - assert form.data_type == [DataType.FLOAT, DataType.INTEGER] + assert form.association == [DataAssociationEnum.VERTEX, DataAssociationEnum.CELL] + assert form.data_type == [DataTypeEnum.FLOAT, DataTypeEnum.INTEGER] def test_data_or_value_form(): @@ -494,8 +497,8 @@ def test_data_or_value_form(): assert form.label == "name" assert form.value == 0.0 assert form.parent == "my_param" - assert form.association == "Vertex" - assert form.data_type == "Float" + assert form.association.name == "VERTEX" + assert form.data_type.name == "FLOAT" assert not form.is_value assert form.property == uuid.UUID(data_uid) @@ -528,8 +531,8 @@ def test_multichoice_data_form(): assert form.label == "name" assert form.value == [uuid.UUID(data_uid_1)] assert form.parent == "my_param" - assert form.association == "Vertex" - assert form.data_type == "Float" + assert form.association.name == "VERTEX" + assert form.data_type.name == "FLOAT" form = MultiSelectDataForm( label="name", @@ -541,6 +544,26 @@ def test_multichoice_data_form(): ) assert form.value == [uuid.UUID(data_uid_1), uuid.UUID(data_uid_2)] + ws = Workspace() + obj = Points.create(ws, vertices=np.random.randn(100, 3)) + data_list = obj.add_data( + {f"data{i}": {"values": np.random.randn(100)} for i in range(5)} + ) + + form = MultiSelectDataForm( + label="name", + value=data_list, + parent="my_param", + association="Vertex", + data_type="Float", + multi_select=True, + ) + + assert len(data_list) == len(form.value) + assert all( + data.uid == val for data, val in zip(data_list, form.value, strict=False) + ) + def test_multichoice_data_form_serialization(): data_uid_1 = f"{{{uuid.uuid4()!s}}}" @@ -597,8 +620,8 @@ def test_data_range_form(): assert form.property == uuid.UUID(data_uid) assert form.value == [0.0, 1.0] assert form.parent == "my_param" - assert form.association == "Vertex" - assert form.data_type == "Float" + assert form.association.name == "VERTEX" + assert form.data_type.name == "FLOAT" assert form.range_label == "value range" @@ -862,7 +885,7 @@ def test_multi_data_group_form(): assert form.label == "name" assert form.value == [data_uid_1, data_uid_2] assert form.group_value == uuid.UUID(group_uid) - assert form.data_type == [DataType.FLOAT, DataType.INTEGER] + assert form.data_type == [DataTypeEnum.FLOAT, DataTypeEnum.INTEGER] assert form.multi_select assert form.tooltip == ["some ", "tooltip ", "text"] diff --git a/tests/ui_json/uijson_test.py b/tests/ui_json/uijson_test.py index 383e54a58..cd47c5ea9 100644 --- a/tests/ui_json/uijson_test.py +++ b/tests/ui_json/uijson_test.py @@ -43,7 +43,7 @@ StringForm, ) from geoh5py.ui_json.ui_json import BaseUIJson -from geoh5py.ui_json.validations import UIJsonError +from geoh5py.ui_json.validation import UIJsonError @pytest.fixture @@ -547,6 +547,11 @@ def test_unknown_uijson(tmp_path): assert "my_group_optional_parameter" not in params assert "my_grouped_parameter" not in params + re_loaded = BaseUIJson.read(tmp_path / "test_copy.ui.json") + + for name in uijson.model_fields_set: + assert getattr(re_loaded, name) == getattr(uijson, name) + def test_str_and_repr(tmp_path): Workspace.create(tmp_path / "test.geoh5")