From e78ef04552340c8391b5326bd6401e212203aa19 Mon Sep 17 00:00:00 2001 From: mferrera Date: Fri, 24 May 2024 08:26:50 +0200 Subject: [PATCH] ENH: Add Provider base class --- src/fmu/dataio/_metadata.py | 43 ++++++++++++++-------- src/fmu/dataio/exceptions.py | 5 +++ src/fmu/dataio/providers/_base.py | 18 +++++++++ src/fmu/dataio/providers/_filedata.py | 4 +- src/fmu/dataio/providers/_fmu.py | 10 +++-- tests/test_units/test_fmuprovider_class.py | 24 +++++++++--- 6 files changed, 77 insertions(+), 27 deletions(-) create mode 100644 src/fmu/dataio/exceptions.py create mode 100644 src/fmu/dataio/providers/_base.py diff --git a/src/fmu/dataio/_metadata.py b/src/fmu/dataio/_metadata.py index 14664f8be..ac0a83de4 100644 --- a/src/fmu/dataio/_metadata.py +++ b/src/fmu/dataio/_metadata.py @@ -25,6 +25,7 @@ ) from .datastructure._internal import internal from .datastructure.meta import meta +from .exceptions import InvalidMetadataError from .providers._filedata import FileDataProvider from .providers._fmu import FmuProvider from .providers.objectdata._provider import objectdata_provider_factory @@ -70,21 +71,24 @@ def _get_objectdata_provider( return objdata -def _get_filedata_provider( +def _get_meta_filedata( dataio: ExportData, obj: types.Inferrable, objdata: ObjectDataProvider, fmudata: FmuProvider | None, compute_md5: bool, -) -> FileDataProvider: +) -> FileDataProvider | None: """Derive metadata for the file.""" - return FileDataProvider( - dataio=dataio, - objdata=objdata, - runpath=fmudata.get_runpath() if fmudata else None, - obj=obj, - compute_md5=compute_md5, - ) + try: + return FileDataProvider( + dataio=dataio, + objdata=objdata, + runpath=fmudata.get_runpath() if fmudata else None, + obj=obj, + compute_md5=compute_md5, + ).get_metadata() + except InvalidMetadataError: + return None def _get_meta_objectdata( @@ -97,6 +101,13 @@ def _get_meta_objectdata( ) +def _get_meta_fmu(fmudata: FmuProvider) -> internal.FMUClassMetaData | None: + try: + return fmudata.get_metadata() + except InvalidMetadataError: + return None + + def _get_meta_access(dataio: ExportData) -> meta.SsdlAccess: return meta.SsdlAccess( asset=meta.Asset( @@ -166,8 +177,6 @@ def generate_export_metadata( meta_existing = read_metadata_from_file(obj) objdata = _get_objectdata_provider(obj, dataio, meta_existing) - filedata = _get_filedata_provider(dataio, obj, objdata, fmudata, compute_md5) - masterdata = dataio.config.get("masterdata") metadata = internal.DataClassMeta( @@ -175,16 +184,18 @@ def generate_export_metadata( version=VERSION, source=SOURCE, class_=objdata.classname, - fmu=fmudata.get_metadata() if fmudata else None, + fmu=_get_meta_fmu(fmudata) if fmudata else None, masterdata=_get_meta_masterdata(masterdata) if masterdata else None, access=_get_meta_access(dataio), data=_get_meta_objectdata(objdata), - file=filedata.get_metadata(), + file=_get_meta_filedata(dataio, obj, objdata, fmudata, compute_md5), tracklog=generate_meta_tracklog(), display=_get_meta_display(dataio, objdata), - preprocessed=_get_meta_preprocessed_info(dataio) - if dataio.fmu_context == FmuContext.PREPROCESSED - else None, + preprocessed=( + _get_meta_preprocessed_info(dataio) + if dataio.fmu_context == FmuContext.PREPROCESSED + else None + ), ).model_dump(mode="json", exclude_none=True, by_alias=True) if skip_null: diff --git a/src/fmu/dataio/exceptions.py b/src/fmu/dataio/exceptions.py new file mode 100644 index 000000000..e43aa1d13 --- /dev/null +++ b/src/fmu/dataio/exceptions.py @@ -0,0 +1,5 @@ +from __future__ import annotations + + +class InvalidMetadataError(Exception): + """Raised when valid metadata cannot be generated or returned.""" diff --git a/src/fmu/dataio/providers/_base.py b/src/fmu/dataio/providers/_base.py new file mode 100644 index 000000000..26fde8f55 --- /dev/null +++ b/src/fmu/dataio/providers/_base.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod + +from pydantic import BaseModel + + +class Provider(ABC): + @abstractmethod + def get_metadata(self) -> BaseModel: + """Get the Pydantic model of the metadata that a provider is meant + to provide. + + Returns: + BaseModel: The Pydantic BaseModel representing metadata. + + Raises: + InvalidMetadataError: When the derived metadata is invalid. + """ + raise NotImplementedError diff --git a/src/fmu/dataio/providers/_filedata.py b/src/fmu/dataio/providers/_filedata.py index db959edd1..2f62ab580 100644 --- a/src/fmu/dataio/providers/_filedata.py +++ b/src/fmu/dataio/providers/_filedata.py @@ -19,6 +19,8 @@ ) from fmu.dataio.datastructure.meta import meta +from ._base import Provider + logger: Final = null_logger(__name__) if TYPE_CHECKING: @@ -34,7 +36,7 @@ class ShareFolder(str, Enum): @dataclass -class FileDataProvider: +class FileDataProvider(Provider): """Class for providing metadata for the 'files' block in fmu-dataio. Example:: diff --git a/src/fmu/dataio/providers/_fmu.py b/src/fmu/dataio/providers/_fmu.py index 63e209713..63e47fa60 100644 --- a/src/fmu/dataio/providers/_fmu.py +++ b/src/fmu/dataio/providers/_fmu.py @@ -42,6 +42,9 @@ from fmu.dataio._logging import null_logger from fmu.dataio.datastructure._internal import internal from fmu.dataio.datastructure.meta import meta +from fmu.dataio.exceptions import InvalidMetadataError + +from ._base import Provider if TYPE_CHECKING: from uuid import UUID @@ -91,7 +94,7 @@ def keyname(self) -> str: @dataclass -class FmuProvider: +class FmuProvider(Provider): """Class for getting the run environment (e.g. an ERT) and provide metadata. Args: @@ -165,13 +168,12 @@ def get_runpath(self) -> Path | None: """Return runpath for a FMU run.""" return self._runpath - def get_metadata(self) -> internal.FMUClassMetaData | None: + def get_metadata(self) -> internal.FMUClassMetaData: """Construct the metadata FMU block for an ERT forward job.""" logger.debug("Generate ERT metadata...") if self._casepath is None or self.model is None: - logger.info("Can't return metadata, missing casepath or model description") - return None + raise InvalidMetadataError("Missing casepath or model description") case_meta = self._get_fmucase_meta() diff --git a/tests/test_units/test_fmuprovider_class.py b/tests/test_units/test_fmuprovider_class.py index 50a8b24dc..40c71fad6 100644 --- a/tests/test_units/test_fmuprovider_class.py +++ b/tests/test_units/test_fmuprovider_class.py @@ -10,6 +10,7 @@ # from conftest import pretend_ert_env_run1 from fmu.dataio._definitions import FmuContext from fmu.dataio.providers._fmu import RESTART_PATH_ENVNAME, FmuEnv, FmuProvider +from fmu.dataio.exceptions import InvalidMetadataError logger = logging.getLogger(__name__) @@ -27,7 +28,10 @@ def test_fmuprovider_no_provider(): include_ertjobs=False, workflow=WORKFLOW, ) - assert myfmu.get_metadata() is None + with pytest.raises( + InvalidMetadataError, match="Missing casepath or model description" + ): + myfmu.get_metadata() def test_fmuprovider_model_info_in_metadata(fmurun_w_casemetadata): @@ -47,8 +51,11 @@ def test_fmuprovider_model_info_in_metadata(fmurun_w_casemetadata): fmu_context=FmuContext.REALIZATION, workflow=WORKFLOW, ) - meta = myfmu.get_metadata() - assert meta is None + + with pytest.raises( + InvalidMetadataError, match="Missing casepath or model description" + ): + meta = myfmu.get_metadata() def test_fmuprovider_ert_provider_guess_casemeta_path(fmurun): @@ -66,8 +73,11 @@ def test_fmuprovider_ert_provider_guess_casemeta_path(fmurun): workflow=WORKFLOW, ) - assert myfmu.get_metadata() is None assert myfmu.get_casepath() is None + with pytest.raises( + InvalidMetadataError, match="Missing casepath or model description" + ): + myfmu.get_metadata() def test_fmuprovider_ert_provider_missing_parameter_txt(fmurun_w_casemetadata): @@ -207,8 +217,10 @@ def test_fmuprovider_detect_no_case_metadata(fmurun): model=GLOBAL_CONFIG_MODEL, fmu_context=FmuContext.REALIZATION, ) - meta = myfmu.get_metadata() - assert meta is None + with pytest.raises( + InvalidMetadataError, match="Missing casepath or model description" + ): + myfmu.get_metadata() def test_fmuprovider_case_run(fmurun_prehook):