Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
1d84b82
Rename class BaseUIJson -> UIJson
domfournier Mar 21, 2026
b62fc6b
MOve validation. Rename method
domfournier Mar 21, 2026
6572a04
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 21, 2026
b38218b
Rename tests for input file
domfournier Mar 22, 2026
b581773
remove unused class
domfournier Mar 22, 2026
8754ffc
Clean ups and rename
domfournier Mar 22, 2026
fff1fa6
Merge branch 'GEOPY-2739' of https://github.com/MiraGeoscience/geoh5p…
domfournier Mar 30, 2026
772781b
Merge branch 'GEOPY-2762' into GEOPY-2739
domfournier Mar 30, 2026
23f7bcc
Merge branch 'develop' into GEOPY-2739
domfournier Mar 31, 2026
e471548
Allow to skip validation. Update tests
domfournier Mar 31, 2026
a71409a
Change defaults nad behaviour for geoh5
domfournier Mar 31, 2026
218c23b
Merge branch 'develop' into GEOPY-2739
domfournier Apr 1, 2026
3e99e34
MOre simplifcations
domfournier Apr 1, 2026
c747227
Fix validations with new serialization
domfournier Apr 1, 2026
e52278b
Improve typing for stringify
domfournier Apr 2, 2026
13e7379
Revert to Serialization at the Field level
domfournier Apr 2, 2026
a646f9b
Fix typing
domfournier Apr 2, 2026
b103be0
accept multiple data.
Apr 3, 2026
77258b2
correct the missing children to accept list
Apr 3, 2026
7131c40
Add special case for handlling of DataRangeForm
domfournier Apr 4, 2026
ae6cdd1
Merge branch 'GEOPY-2739' into GEOPY-2739-Mat
domfournier Apr 4, 2026
ec72478
Add unit test for DataRangeForm flatten
domfournier Apr 4, 2026
fb76b2f
Add mechanics to check for dependencies enable state
domfournier Apr 4, 2026
80ccd50
Fix logic, add unitest for all cases of dependencies
domfournier Apr 5, 2026
7b12c02
Track dependency parent's name. Augment tests
domfournier Apr 5, 2026
083b993
Don't change enable state of form automatically for DataValueForm
domfournier Apr 5, 2026
933de82
Change to positive logic is_enabled instead of double negative is_dis…
domfournier Apr 5, 2026
db22f98
Update docstrings
domfournier Apr 5, 2026
a16f059
Add set_enabled to UIJson class. Refactor logic to mirror state
domfournier Apr 5, 2026
5b53e72
Add two way state enabling
domfournier Apr 5, 2026
9bb3206
Move methods around
domfournier Apr 5, 2026
6ed6dbf
Merge branch 'GEOPY-2739' into GEOPY-2739-Mat
domfournier Apr 5, 2026
ab125cd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 5, 2026
e8a7c6f
Merge pull request #870 from MiraGeoscience/GEOPY-2739-Mat
domfournier Apr 5, 2026
6a99155
Add is_optional property on form. Split group_dependencies and form_d…
domfournier Apr 6, 2026
a711a10
Fix tests
domfournier Apr 6, 2026
d4c1126
Fix docstrings, typing, and DataForm multiselect support
Copilot Apr 6, 2026
1ee6454
Remove unused _mirror_linked_state
domfournier Apr 6, 2026
5f59303
Reset version in yaml
domfournier Apr 6, 2026
b0907fc
Merge branch 'GEOPY-2739' of https://github.com/MiraGeoscience/geoh5p…
domfournier Apr 6, 2026
4fc433a
Sort methods in alphabetic order
domfournier Apr 6, 2026
96387c7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 6, 2026
876891b
Change classmethod from_dict
domfournier Apr 6, 2026
b828764
Merge branch 'GEOPY-2739' of https://github.com/MiraGeoscience/geoh5p…
domfournier Apr 6, 2026
3503943
Add set_value for DataRangeForm to mirror the flatten()
domfournier Apr 6, 2026
2f4a631
Fix set_values for is_optional, clean ups
domfournier Apr 7, 2026
a615d3d
not perfect yet....
Apr 7, 2026
07cab69
OUtift flatten and set_values for GroupMultiDataForm
domfournier Apr 7, 2026
be1b9fa
Merge pull request #871 from MiraGeoscience/GEOPY-2739-Matt-2
domfournier Apr 8, 2026
02532a5
Merge branch 'GEOPY-2739' of https://github.com/MiraGeoscience/geoh5p…
domfournier Apr 8, 2026
341651b
Switch back active only in to_params
domfournier Apr 8, 2026
270db73
Remove pylint disable
domfournier Apr 8, 2026
754db70
Simplify logic is dependencies not ordered
domfournier Apr 8, 2026
2f22c9d
Docstrings and unknown logic
domfournier Apr 8, 2026
2a77d39
Bring back logic
domfournier Apr 8, 2026
efe9286
Move dependency_type_validation inside link checkers
domfournier Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 30 additions & 32 deletions geoh5py/shared/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@
from .exceptions import Geoh5FileClosedError


UidOrNumeric = UUID | float | int | None
StringOrNumeric = str | float | int
# pylint: disable=too-many-lines

if TYPE_CHECKING:
Expand Down Expand Up @@ -837,7 +835,9 @@ def map_attributes(object_, **kwargs):
set_attributes(object_, **values)


def stringify(values: dict[str, Any]) -> dict[str, Any]:
def stringify(
values: Any | dict[str, Any],
) -> dict[str, str] | dict[str, list[str]] | str | list[str]:
"""
Convert all values in a dictionary to string.

Expand All @@ -846,11 +846,13 @@ def stringify(values: dict[str, Any]) -> dict[str, Any]:
:return: Dictionary of string values.
"""
mappers = [
type2uuid,
entity2uuid,
nan2str,
inf2str,
as_str_if_uuid,
none2str,
enum_name_to_str,
workspace2path,
path2str,
]
Expand Down Expand Up @@ -889,31 +891,6 @@ def to_tuple(value: Any) -> tuple:
return (value,)


class SetDict(dict):
def __init__(self, **kwargs):
kwargs = {k: self.make_set(v) for k, v in kwargs.items()}
super().__init__(kwargs)

def make_set(self, value):
if isinstance(value, (set, tuple, list)):
value = set(value)
else:
value = {value}
return value

def __setitem__(self, key, value):
value = self.make_set(value)
super().__setitem__(key, value)

def update(self, value: dict, **kwargs) -> None: # type: ignore
for key, val in value.items():
val = self.make_set(val)
if key in self:
val = self[key].union(val)
value[key] = val
super().update(value, **kwargs)


def inf2str(value): # map np.inf to "inf"
if not isinstance(value, (int, float)):
return value
Expand Down Expand Up @@ -944,8 +921,15 @@ def nan2str(value):
return value


def str2none(value):
if value == "":
def str2none(value: Any) -> Any:
"""
Convert an empty string or zero UUID string to None.

:param value: Value to convert.

:return: None if value is an empty string or zero UUID, original value otherwise.
"""
if value in ("", "{00000000-0000-0000-0000-000000000000}"):
return None
return value

Expand Down Expand Up @@ -1443,12 +1427,26 @@ def map_to_class(
return class_map


def enum_name_to_str(value: Enum) -> str:
def enum_name_to_str(value: Any | Enum) -> Any | str:
"""
Convert enum name to capitalized string.

:param value: Enum value to convert.

:return: Capitalized string.
"""
return value.name.capitalize()
if isinstance(value, Enum):
return value.name.capitalize()

return value


def type2uuid(value: Any) -> Any | UUID:
"""
Convert an Entity type to its default uuid.

:param value: An entity type or any.
"""
if isinstance(value, type) and hasattr(value, "default_type_uid"):
return value.default_type_uid()
return value
6 changes: 0 additions & 6 deletions geoh5py/shared/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,6 @@ def to_class(
return out


def types_to_string(types: list) -> list[str] | str:
if len(types) > 1:
return [f"{{{k.default_type_uid()!s}}}" for k in types]
return f"{{{types[0].default_type_uid()!s}}}"


class BaseValidator(ABC):
"""Concrete base class for validators."""

Expand Down
2 changes: 1 addition & 1 deletion geoh5py/ui_json/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
from .utils import monitored_directory_copy
from .validation import InputValidation
from .forms import BaseForm
from .ui_json import BaseUIJson
from .ui_json import UIJson
46 changes: 29 additions & 17 deletions geoh5py/ui_json/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@
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, none2str, str2none, stringify
from geoh5py.shared.utils import (
enum_name_to_str,
none2str,
str2none,
stringify,
workspace2path,
)
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

Expand All @@ -47,55 +52,61 @@ def deprecate(value, info):
return value


Deprecated = Annotated[
Any,
Field(exclude=True),
BeforeValidator(deprecate),
]

AssociationOptions = Annotated[
DataAssociationEnum,
PlainSerializer(enum_name_to_str),
PlainSerializer(enum_name_to_str, when_used="json"),
]

DataTypeOptions = Annotated[
DataTypeEnum,
PlainSerializer(enum_name_to_str),
]


Deprecated = Annotated[
Any,
Field(exclude=True),
BeforeValidator(deprecate),
PlainSerializer(enum_name_to_str, when_used="json"),
]

GroupTypes = Annotated[
list[type[Group]],
BeforeValidator(to_class),
BeforeValidator(to_type_uid_or_class),
BeforeValidator(to_list),
PlainSerializer(types_to_string, when_used="json"),
PlainSerializer(stringify, 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"),
PlainSerializer(stringify, when_used="json"),
]

OptionalPath = Annotated[
Path | None,
BeforeValidator(str2none),
PlainSerializer(none2str),
BeforeValidator(workspace2path),
PlainSerializer(none2str, when_used="json"),
]

OptionalString = Annotated[
str | None,
BeforeValidator(str2none),
PlainSerializer(none2str, when_used="json"),
]

OptionalUUID = Annotated[
UUID | None,
BeforeValidator(optional_uuid_mapper),
PlainSerializer(stringify),
PlainSerializer(stringify, when_used="json"),
]

OptionalUUIDList = Annotated[
list[UUID] | None,
list[UUID] | UUID | None,
BeforeValidator(optional_uuid_mapper),
PlainSerializer(stringify),
PlainSerializer(stringify, when_used="json"),
]

OptionalValueList = Annotated[
Expand All @@ -107,4 +118,5 @@ def deprecate(value, info):
list[Path],
BeforeValidator(to_path),
BeforeValidator(to_list),
PlainSerializer(stringify, when_used="json"),
]
95 changes: 66 additions & 29 deletions geoh5py/ui_json/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,18 @@ def flatten(self):
"""Returns the data for the form."""
return self.value

def validate_data(self, params: dict[str, Any]):
"""Validate the form data."""

def set_value(self, value: Any):
"""Set the form value."""
"""
Set the form value.
"""
self.value = value

if "optional" in self.model_fields_set:
self.enabled = self.value is not None
@property
def is_optional(self) -> bool:
"""
Whether the field is optional or not.
"""
return self.optional or self.group_optional or len(self.dependency) > 0


class StringForm(BaseForm):
Expand Down Expand Up @@ -472,9 +475,12 @@ class DataForm(DataFormMixin, BaseForm):
Geoh5py uijson form for data associated with an object.

Shares documented attributes with the BaseForm and DataFormMixin.

When ``multiselect`` is ``True`` the value may be a list of UUIDs; otherwise
a single UUID or ``None`` is expected.
"""

value: OptionalUUID
value: OptionalUUIDList


class DataGroupForm(DataForm):
Expand Down Expand Up @@ -536,6 +542,27 @@ def to_list(cls, value: str | list[str]) -> str | list[str]:
raise TypeError(f"'value' must be a list of strings; got '{type(value)}'")
return value

def flatten(self) -> dict:
"""Returns the property, data and is_complement values for the form."""
return {
"group_value": self.group_value,
"value": self.value,
}

def set_value(self, value: Any):
"""
Set the form value.
"""
if isinstance(value, dict):
for key, val in value.items():
setattr(self, key, val)

if isinstance(value, list | str):
self.value = value

if isinstance(value, UUID | None):
self.group_value = value


class DataOrValueForm(DataFormMixin, BaseForm):
"""
Expand Down Expand Up @@ -571,20 +598,22 @@ def flatten(self) -> UUID | float | int | None:
return self.value

def set_value(self, value: Any):
"""Set the form value."""
"""
Set the form value.

Either a Numeric value or a UUID, in which case it will be assigned
to the `property` field and the `is_value` field will be set to False.
"""
try:
self.value = value
self.is_value = True
except ValidationError:
if value is not None:
if value is None:
self.is_value = True
self.property = value
self.is_value = False
else:
self.is_value = True
self.property = None

if "optional" in self.model_fields_set:
self.enabled = value is not None
self.property = value
self.is_value = False


class MultiSelectDataForm(DataFormMixin, BaseForm):
Expand All @@ -607,20 +636,6 @@ def only_multi_select(cls, value: bool) -> bool:
raise ValueError("MultiSelectForm must have multi_select: True.")
return value

@field_validator("value", mode="before")
@classmethod
def to_list(cls, value: str | list[str]) -> list[str]:
"""
Validate that value is a list, converting it if it's a string.

:param value: The value to validate.

:return: A list of strings representing the value.
"""
if not isinstance(value, list):
value = [value]
return value


class DataRangeForm(DataFormMixin, BaseForm):
"""
Expand All @@ -645,6 +660,28 @@ class DataRangeForm(DataFormMixin, BaseForm):
allow_complement: bool = False
is_complement: bool = False

def flatten(self) -> dict:
"""Returns the property, data and is_complement values for the form."""
return {
"is_complement": self.is_complement,
"property": self.property,
"value": self.value,
}

def set_value(self, value: Any):
"""
Set the form value.
"""
if isinstance(value, dict):
for key, val in value.items():
setattr(self, key, val)

if isinstance(value, list):
self.value = value

if isinstance(value, UUID | None):
self.property = value


def all_subclasses(type_object: type[BaseForm]) -> list[type[BaseForm]]:
"""Recursively find all subclasses of input type object."""
Expand Down
Loading
Loading