Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions geoh5py/ui_json/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
PlainSerializer(uuid_to_string),
]

OptionalValueList = Annotated[
float | list[float] | None,
BeforeValidator(empty_string_to_none),
]


def deprecate(value, info):
"""Issue deprecation warning."""
Expand Down
143 changes: 130 additions & 13 deletions geoh5py/ui_json/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
to_uuid,
types_to_string,
)
from geoh5py.ui_json.annotations import OptionalUUIDList
from geoh5py.ui_json.annotations import OptionalUUIDList, OptionalValueList
from geoh5py.ui_json.validations.form import (
empty_string_to_none,
uuid_to_string,
Expand Down Expand Up @@ -142,6 +142,10 @@ def infer(cls, data: dict[str, Any]) -> type[BaseForm]:
k in data
for k in ["parent", "association", "dataType", "isValue", "property"]
):
if any(
k in data for k in ["allowComplement", "isComplement", "rangeLabel"]
):
return DataRangeForm
if "dataGroupType" in data:
return DataGroupForm
if "multiSelect" in data:
Expand All @@ -163,7 +167,8 @@ def infer(cls, data: dict[str, Any]) -> type[BaseForm]:
raise ValueError(f"Could not infer form from data: {data}")

@property
def json_string(self):
def json_string(self) -> str:
"""Returns the form as a json string."""
return self.model_dump_json(exclude_unset=True, by_alias=True)

def flatten(self):
Expand All @@ -177,6 +182,8 @@ def validate_data(self, params: dict[str, Any]):
class StringForm(BaseForm):
"""
String valued uijson form.

Shares documented attributes with the BaseForm.
"""

value: str = ""
Expand All @@ -186,6 +193,8 @@ class RadioLabelForm(StringForm):
"""
Radio button for two-option strings.

Shares documented attributes with the BaseForm.

The uijson dialogue will render two radio buttons with label choices. Any
form labels within the ui.json containing the string matching the original
button will be altered to the reflect the new button choice.
Expand All @@ -194,13 +203,15 @@ class RadioLabelForm(StringForm):
:param alternative_label: Label for the alternative value.
"""

original_label: str
alternate_label: str
original_label: str = ""
alternate_label: str = ""


class BoolForm(BaseForm):
"""
Boolean valued uijson form.

Shares documented attributes with the BaseForm.
"""

value: bool = True
Expand All @@ -209,6 +220,13 @@ class BoolForm(BaseForm):
class IntegerForm(BaseForm):
"""
Integer valued uijson form.

Shares documented attributes with the BaseForm.

:param min: Minimum value accepted by the rendered form in
Geoscience ANALYST.
:param max: Maximum value accepted by the rendered form in
Geoscience ANALYST.
"""

value: int = 1
Expand All @@ -219,6 +237,17 @@ class IntegerForm(BaseForm):
class FloatForm(BaseForm):
"""
Float valued uijson form.

Shares documented attributes with the BaseForm.

:param min: Minimum value accepted by the rendered form in
Geoscience ANALYST.
:param max: Maximum value accepted by the rendered form in
Geoscience ANALYST.
:param precision: Number of decimal places rendered in Geoscience
ANALYST.
:param line_edit: If True, Geoscience ANALYST will render a spin box
for adjusting the value by an increment controlled by the precision.
"""

value: float = 1.0
Expand All @@ -231,9 +260,15 @@ class FloatForm(BaseForm):
class ChoiceForm(BaseForm):
"""
Choice list uijson form.

Shares documented attributes with the BaseForm.

:param choice_list: List of valid choices for the form. The choices
are rendered in Geoscience ANALYST as a dropdown menu.

"""

value: str
value: str = ""
choice_list: list[str]

@model_validator(mode="after")
Expand All @@ -245,7 +280,16 @@ def valid_choice(self):


class MultiChoiceForm(BaseForm):
"""Multi-choice list uijson form."""
"""
Multi-choice list uijson form.

Shares documented attributes with the BaseForm.

:param choice_list: List of valid choices for the form. The choices
are rendered in Geoscience ANALYST as a multi-selection dropdown
menu.
:param multi_select: Must be True for MultiChoiceForm.
"""

value: list[str]
choice_list: list[str]
Expand All @@ -260,7 +304,7 @@ def only_multi_select(cls, value):

@field_validator("value", mode="before")
@classmethod
def to_list(cls, value):
def to_list(cls, value: str | list[str]) -> list[str]:
if not isinstance(value, list):
value = [value]
return value
Expand All @@ -286,7 +330,16 @@ def valid_choice(self):

class FileForm(BaseForm):
"""
File path uijson form
File path uijson form.

Shares documented attributes with the BaseForm.

:param file_description: List of file descriptions for each file type.
:param file_type: List of file extensions (without the dot) for each file type.
:param file_multi: If True, Geoscience ANALYST will allow multi-selection of
files.
:param directory_only: If True, Geoscience ANALYST will restrict selecitons
to directories only.
"""

value: PathList
Expand All @@ -296,7 +349,7 @@ class FileForm(BaseForm):
directory_only: bool = False

@field_serializer("value", when_used="json")
def to_string(self, value):
def to_string(self, value: list[Path]) -> str:
return ";".join([str(path) for path in value])

@field_validator("value")
Expand Down Expand Up @@ -360,6 +413,11 @@ def directory_file_type(cls, data):
class ObjectForm(BaseForm):
"""
Geoh5py object uijson form.

Shares documented attributes with the BaseForm.

:param mesh_type: List of object types that restricts the options in the
Geoscience ANALYST ui.json dropdown.
"""

value: OptionalUUID
Expand All @@ -378,6 +436,11 @@ class ObjectForm(BaseForm):
class GroupForm(BaseForm):
"""
Geoh5py group uijson form.

Shares documented attributes with the BaseForm.

:param group_type: List of group types that restricts the options in the
Geoscience ANALYST ui.json dropdown.
"""

value: OptionalUUID
Expand All @@ -402,7 +465,19 @@ class GroupForm(BaseForm):


class DataFormMixin(BaseModel):
"""Mixin class to add common attributes a series of data classes."""
"""
Mixin class to add common attributes a series of data classes.

Shares documented attributes with the BaseForm.

:param parent: The name of the parameter in the ui.json that contains
the data to select from.
:param association: The data association, eg: 'Cell', 'Face', 'Vertex'
of a grid object, that filters the options in the Geoscience ANALYST
ui.json dropdown.
:param data_type: The data type, eg: 'Integer', 'Float', that filters
the options in the Geoscience ANALYST ui.json dropdown.
"""

parent: str
association: Association | list[Association]
Expand All @@ -412,6 +487,8 @@ class DataFormMixin(BaseModel):
class DataForm(DataFormMixin, BaseForm):
"""
Geoh5py uijson form for data associated with an object.

Shares documented attributes with the BaseForm and DataFormMixin.
"""

value: OptionalUUID
Expand All @@ -420,6 +497,11 @@ class DataForm(DataFormMixin, BaseForm):
class DataGroupForm(DataForm):
"""
Geoh5py uijson form for grouped data associated with an object.

Shares documented attributes with the BaseForm.

:param data_group_type: The group type, eg: 'Multi-Element', '3d Vector'
that filters the groups available in the Geoscience ANALYST ui.json.
"""

data_group_type: GroupTypeEnum | list[GroupTypeEnum]
Expand All @@ -428,6 +510,10 @@ class DataGroupForm(DataForm):
class DataOrValueForm(DataFormMixin, BaseForm):
"""
Geoh5py uijson data form that also accepts a single value.

Shares documented attributes with the BaseForm and DataFormMixin.

:param is_value: If True, the value field is used to provide a scalar value.
"""

value: UUIDOrNumber
Expand Down Expand Up @@ -456,7 +542,7 @@ def property_if_not_is_value(self):
raise ValueError("A property must be provided if is_value is used.")
return self

def flatten(self):
def flatten(self) -> UUID | float | int | None:
"""Returns the data for the form."""
if (
"is_value" in self.model_fields_set # pylint: disable=unsupported-membership-test
Expand All @@ -467,14 +553,21 @@ def flatten(self):


class MultiSelectDataForm(DataFormMixin, BaseForm):
"""Geoh5py uijson data form with multi-selection."""
"""
Geoh5py uijson data form with multi-selection.

Shares documented attributes with the BaseForm and DataFormMixin.

:param multi_select: Must be True for MultiSelectDataForm.
"""

value: OptionalUUIDList
multi_select: bool = True

@field_validator("multi_select", mode="before")
@classmethod
def only_multi_select(cls, value):
def only_multi_select(cls, value: bool) -> bool:
"""Validate that multi_select is True."""
if not value:
raise ValueError("MultiSelectForm must have multi_select: True.")
return value
Expand All @@ -485,3 +578,27 @@ def to_list(cls, value):
if not isinstance(value, list):
value = [value]
return value


class DataRangeForm(DataFormMixin, BaseForm):
"""
Geoh5py data range uijson form.

Shares documented attributes with the BaseForm and the DataFormMixin.

:param value: The value can be a single float or a list of two floats.
Geoscience ANALYST will estimate a range on load if a single float
is provided, but will always return a list.
:param property: The UUID of the property to which the range applies.
:param range_label: Label for the range.
:param allow_complement: If True, the complement option will be available
in Geoscience ANALYST as a checkbox.
:param is_complement: If True, the range slider in Geoscience ANALYST will
be inverted and the implied selection is outside of the range provided.
"""

value: OptionalValueList
property: OptionalUUID
range_label: str
allow_complement: bool = False
is_complement: bool = False
19 changes: 14 additions & 5 deletions geoh5py/ui_json/ui_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Annotated, Any
from uuid import UUID
Expand All @@ -44,6 +45,8 @@
from geoh5py.ui_json.validations.form import empty_string_to_none


logger = logging.getLogger(__name__)

OptionalPath = Annotated[
Path | None, # pylint: disable=unsupported-binary-operation
BeforeValidator(empty_string_to_none),
Expand All @@ -55,13 +58,12 @@ class BaseUIJson(BaseModel):
"""
Base class for storing ui.json data on disk.

:param version: Version of the application.
:params title: Title of the application.
:params geoh5: Path to the geoh5 file.
:params run_command: Command to run the application.
:params run_command_boolean: Boolean to run the command.
:params monitoring_directory: Directory to monitor for changes.
:params conda_environment: Conda environment to run the application.
:params conda_environment_boolean: Boolean to run the conda environment.
:params workspace_geoh5: Path to the workspace geoh5 file.
"""

Expand Down Expand Up @@ -130,7 +132,11 @@ def read(cls, path: str | Path) -> BaseUIJson:
if name in BaseUIJson.model_fields:
continue
if isinstance(value, dict):
fields[name] = (BaseForm.infer(value), ...)
form_type = BaseForm.infer(value)
logger.info(
"Parameter: %s interpreted as a %s.", name, form_type.__name__
)
fields[name] = (form_type, ...)
else:
fields[name] = (type(value), ...)

Expand Down Expand Up @@ -161,7 +167,7 @@ def get_groups(self) -> dict[str, list[str]]:
"""
Returns grouped forms.

:returns: Dictionary of group names and the parameters belonging to each
:returns: Group names and the parameters belonging to each
group.
"""
groups: dict[str, list[str]] = {}
Expand All @@ -180,7 +186,7 @@ def is_disabled(self, field: str) -> bool:
Checks if a field is disabled based on form status.

:param field: Field name to check.
:returns: True if the field is disabled by it's own enabled status or
:returns: True if the field is disabled by its own enabled status or
the groups enabled status, False otherwise.
"""

Expand Down Expand Up @@ -208,6 +214,9 @@ def flatten(self, skip_disabled=False, active_only=False) -> dict[str, Any]:
Chooses between value/property in data forms depending on the is_value
field.

:param skip_disabled: If True, skips fields with 'enabled' set to False.
:param active_only: If True, skips fields that have not been explicitly set.

:return: Flattened dictionary of key/value pairs.
"""
data = {}
Expand Down
Loading