Source code for geoh5py.ui_json.ui_json

#  Copyright (c) 2024 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 <https://www.gnu.org/licenses/>.

from __future__ import annotations

import json
from pathlib import Path
from typing import Any
from uuid import UUID

from pydantic import BaseModel, ConfigDict, field_validator

from geoh5py import Workspace
from geoh5py.shared.utils import fetch_active_workspace
from geoh5py.ui_json.forms import BaseForm
from geoh5py.ui_json.validations import ErrorPool, UIJsonError, get_validations


[docs] class BaseUIJson(BaseModel): """ Base class for storing ui.json data on disk. :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. """ model_config = ConfigDict(arbitrary_types_allowed=True) title: str geoh5: Path | None run_command: str monitoring_directory: Path conda_environment: str workspace_geoh5: Path
[docs] @field_validator("workspace_geoh5", mode="after") @classmethod def current_directory_if_workspace_doesnt_exist(cls, path): if not path.exists(): return Path() return path
[docs] @field_validator("geoh5", mode="after") @classmethod def workspace_path_exists(cls, path: Path): if not path.exists(): raise FileNotFoundError(f"geoh5 path {path} does not exist.") return path
[docs] @classmethod def read(cls, path: Path): """Create a UIJson object from file.""" path = path.resolve() if not path.exists(): raise FileNotFoundError(f"File {path} does not exist.") if "".join(path.suffixes[-2:]) != ".ui.json": raise ValueError(f"File {path} is not a .ui.json file.") with open(path, encoding="utf-8") as file: kwargs = json.load(file) return cls(**kwargs)
[docs] def to_params(self, workspace: Workspace | None = None) -> dict[str, Any]: """ Promote, flatten and validate parameter/values dictionary. :param workspace: Workspace to fetch entities from. Used for passing active workspaces to avoid closing and flushing data. """ with fetch_active_workspace(workspace or Workspace(self.geoh5)) as geoh5: if geoh5 is None: raise ValueError("Workspace cannot be None.") data = {} errors: dict[str, Any] = {k: [] for k in self.model_fields_set} for field in self.model_fields_set: if field == "geoh5": data[field] = geoh5 continue value = getattr(self, field) value = value.flatten() if isinstance(value, BaseForm) else value if isinstance(value, UUID): value = self._object_or_catch(geoh5, value) if isinstance(value, UIJsonError): errors[field].append(value) data[field] = value self.validate_data(data, errors) return data
[docs] def validate_data( self, params: dict[str, Any] | None = None, errors: dict[str, Any] | None = None ): """ Validate the UIJson data. :param params: Promoted and flattened parameters/values dictionary. The params dictionary will be generated from the model values if not provided. :param errors: Optionally pass existing errors. Primarily for the to_params method. :raises UIJsonError: If any validations fail. """ if params is None: self.to_params() return if errors is None: errors = {k: [] for k in params} ui_json = self.model_dump(exclude_unset=True) for field in self.model_fields_set: form = ui_json[field] validations = get_validations(list(form) if isinstance(form, dict) else []) for validation in validations: try: validation(field, params, ui_json) except UIJsonError as e: errors[field].append(e) ErrorPool(errors).throw()
def _object_or_catch( self, workspace: Workspace, uuid: UUID, ): """ Returns an object if it exists in the workspace or an error if not. :param workspace: Workspace to fetch entities from. :param uuid: UUID of the object to fetch. """ obj = workspace.get_entity(uuid) if obj[0] is not None: return obj[0] return UIJsonError(f"Workspace does not contain an entity with uid: {uuid}.")