Source code for fmu.dataio.case

from __future__ import annotations

import copy
import uuid
import warnings
from dataclasses import dataclass, field
from pathlib import Path
from typing import Final, Optional, Union

from pydantic import ValidationError

from fmu.dataio._models.fmu_results import fields, global_configuration

from . import _utils
from ._logging import null_logger
from ._metadata import CaseMetadataExport

logger: Final = null_logger(__name__)

# ######################################################################################
# CreateCaseMetadata.
#
# The CreateCaseMetadata is used for making the case matadata prior to any other
# actions, e.g. forward jobs. However, case metadata file may already exist,
# and in that case this class should only emit a message or warning.
# ######################################################################################


[docs] @dataclass class CreateCaseMetadata: # pylint: disable=too-few-public-methods """Create metadata for an FMU Case. In ERT this is typically ran as an hook workflow in advance. Args: config: A configuration dictionary. In the standard case this is read from FMU global variables (via fmuconfig). The dictionary must contain some predefined main level keys. If config is None or the env variable FMU_GLOBAL_CONFIG pointing to a file is provided, then it will attempt to parse that file instead. rootfolder: Absolute path to the case root, including case name. casename: Name of case (experiment) caseuser: Username provided description (Optional): Description text as string or list of strings. """ config: dict rootfolder: str | Path casename: str caseuser: str description: Optional[Union[str, list]] = None _metadata: dict = field(default_factory=dict, init=False) _metafile: Path = field(default_factory=Path, init=False) _pwd: Path = field(default_factory=Path, init=False) _casepath: Path = field(default_factory=Path, init=False) def __post_init__(self) -> None: self._pwd = Path().absolute() self._casepath = Path(self.rootfolder) self._metafile = self._casepath / "share/metadata/fmu_case.yml" # For this class, the global config must be valid; hence error if not try: global_configuration.GlobalConfiguration.model_validate(self.config) except ValidationError as e: global_configuration.validation_error_warning(e) raise logger.info("Ran __post_init__ for CreateCaseMetadata") def _establish_metadata_files(self) -> bool: """Checks if the metadata files and directories are established and creates relevant directories and files if not. Returns: False if fmu_case.yml exists (not established), True if it doesn't. """ if not self._metafile.parent.exists(): self._metafile.parent.mkdir(parents=True, exist_ok=True) logger.info("Created rootpath (case) %s", self._casepath) logger.info("The requested metafile is %s", self._metafile) return not self._metafile.exists() def _case_uuid(self) -> uuid.UUID: """ Generates and persists a unique UUID for a new case. Upon creation of a new case, this UUID is stored in case metadata and written to disk, ensuring it remains constant for the case across runs and exports. It is foundational for tracking cases and embedding identifiers into file metadata. """ return uuid.uuid4() # ================================================================================== # Public methods: # ==================================================================================
[docs] def generate_metadata(self) -> dict: """Generate case metadata. Returns: A dictionary with case metadata or an empty dictionary if the metadata already exists. """ if not self._establish_metadata_files(): exists_warning = ( "The case metadata file already exists and will not be overwritten. " "To make new case metadata delete the old case or run on a different " "runpath." ) logger.warning(exists_warning) warnings.warn(exists_warning, UserWarning) return {} self._metadata = CaseMetadataExport( masterdata=fields.Masterdata.model_validate(self.config["masterdata"]), access=fields.Access.model_validate(self.config["access"]), fmu=fields.FMUBase( model=fields.Model.model_validate( self.config["model"], ), case=fields.Case( name=self.casename, uuid=self._case_uuid(), user=fields.User(id=self.caseuser), description=None, ), ), tracklog=fields.Tracklog.initialize(), description=_utils.generate_description(self.description), ).model_dump( mode="json", exclude_none=True, by_alias=True, ) return copy.deepcopy(self._metadata)
[docs] def export(self) -> str: """Export case metadata to file. Returns: Full path of metadata file. """ if self.generate_metadata(): _utils.export_metadata_file(self._metafile, self._metadata) logger.info("METAFILE %s", self._metafile) return str(self._metafile)