diff --git a/schema/definitions/0.8.0/examples/iteration.yml b/schema/definitions/0.8.0/examples/iteration.yml new file mode 100644 index 000000000..03ed9eaaf --- /dev/null +++ b/schema/definitions/0.8.0/examples/iteration.yml @@ -0,0 +1,64 @@ +# Example metadata for an FMU Iteration object. + +$schema: https://main-fmu-schemas-dev.radix.equinor.com/schemas/0.8.0/fmu_results.json +version: "0.8.0" +source: fmu +tracklog: + - datetime: 2020-10-28T14:28:02 + user: + id: peesv + event: created + - datetime: 2020-10-28T14:46:14 + user: + id: peesv + event: updated + +class: iteration # class is the main identifier of the data type. + +fmu: # the fmu-block contains information directly related to the FMU context + model: + name: ff + revision: 21.0.0.dev + description: + - detailed description + - optional + + case: + name: MyCaseName + uuid: 8bb56d60-8758-481a-89a4-6bac8561d38e + user: + id: jriv # $USER from ERT + description: + - yet other detailed description + - optional + + context: + stage: iteration + + iteration: + id: 0 + name: iter-0 + uuid: fd6e8f2a-f298-434a-80f4-90f0f6f84688 + +access: + asset: + name: Drogon + classification: internal + +masterdata: + smda: + country: + - identifier: Norway + uuid: ad214d85-8a1d-19da-e053-c918a4889309 + discovery: + - short_identifier: DROGON + uuid: 00000000-0000-0000-0000-000000000000 # mock uuid for Drogon + field: + - identifier: DROGON + uuid: 00000000-0000-0000-0000-000000000000 # mock uuid for Drogon + coordinate_system: + identifier: ST_WGS84_UTM37N_P32637 + uuid: ad214d85-dac7-19da-e053-c918a4889309 + stratigraphic_column: + identifier: DROGON_2020 + uuid: 00000000-0000-0000-0000-000000000000 # mock uuid for Drogon diff --git a/schema/definitions/0.8.0/examples/realization.yml b/schema/definitions/0.8.0/examples/realization.yml new file mode 100644 index 000000000..d66fd9063 --- /dev/null +++ b/schema/definitions/0.8.0/examples/realization.yml @@ -0,0 +1,72 @@ +# Example metadata for an FMU Realization object. + +$schema: https://main-fmu-schemas-dev.radix.equinor.com/schemas/0.8.0/fmu_results.json +version: "0.8.0" +source: fmu +tracklog: + - datetime: 2020-10-28T14:28:02 + user: + id: peesv + event: created + - datetime: 2020-10-28T14:46:14 + user: + id: peesv + event: updated + +class: realization # class is the main identifier of the data type. + +fmu: # the fmu-block contains information directly related to the FMU context + model: + name: ff + revision: 21.0.0.dev + description: + - detailed description + - optional + + case: + name: MyCaseName + uuid: 8bb56d60-8758-481a-89a4-6bac8561d38e + user: + id: jriv # $USER from ERT + description: + - yet other detailed description + - optional + + context: + stage: realization + + iteration: + id: 0 + name: iter-0 + uuid: fd6e8f2a-f298-434a-80f4-90f0f6f84688 + + realization: + id: 0 + name: realization-0 + uuid: 8ba597f5-07e0-a789-b9ad-fd16b1966f58 + parameters: + param1: val1 + param2: val2 + +access: + asset: + name: Drogon + classification: internal + +masterdata: + smda: + country: + - identifier: Norway + uuid: ad214d85-8a1d-19da-e053-c918a4889309 + discovery: + - short_identifier: DROGON + uuid: 00000000-0000-0000-0000-000000000000 # mock uuid for Drogon + field: + - identifier: DROGON + uuid: 00000000-0000-0000-0000-000000000000 # mock uuid for Drogon + coordinate_system: + identifier: ST_WGS84_UTM37N_P32637 + uuid: ad214d85-dac7-19da-e053-c918a4889309 + stratigraphic_column: + identifier: DROGON_2020 + uuid: 00000000-0000-0000-0000-000000000000 # mock uuid for Drogon diff --git a/schema/definitions/0.8.0/schema/fmu_results.json b/schema/definitions/0.8.0/schema/fmu_results.json index b9bf169f4..d63e3c2b1 100644 --- a/schema/definitions/0.8.0/schema/fmu_results.json +++ b/schema/definitions/0.8.0/schema/fmu_results.json @@ -1128,6 +1128,60 @@ "title": "FMUContext", "type": "string" }, + "FMUIteration": { + "description": "The ``fmu`` block contains all attributes specific to FMU. The idea is that the FMU\nresults data model can be applied to data from *other* sources - in which the\nfmu-specific stuff may not make sense or be applicable.\nThis is a specialization of the FMU block for ``iteration`` objects.", + "properties": { + "case": { + "$ref": "#/$defs/Case" + }, + "context": { + "$ref": "#/$defs/IterationContext" + }, + "iteration": { + "$ref": "#/$defs/Iteration" + }, + "model": { + "$ref": "#/$defs/Model" + } + }, + "required": [ + "case", + "model", + "context", + "iteration" + ], + "title": "FMUIteration", + "type": "object" + }, + "FMURealization": { + "description": "The ``fmu`` block contains all attributes specific to FMU. The idea is that the FMU\nresults data model can be applied to data from *other* sources - in which the\nfmu-specific stuff may not make sense or be applicable.\nThis is a specialization of the FMU block for ``realization`` objects.", + "properties": { + "case": { + "$ref": "#/$defs/Case" + }, + "context": { + "$ref": "#/$defs/RealizationContext" + }, + "iteration": { + "$ref": "#/$defs/Iteration" + }, + "model": { + "$ref": "#/$defs/Model" + }, + "realization": { + "$ref": "#/$defs/Realization" + } + }, + "required": [ + "case", + "model", + "context", + "iteration", + "realization" + ], + "title": "FMURealization", + "type": "object" + }, "FaciesThicknessData": { "description": "The ``data`` block contains information about the data contained in this object.\nThis class contains metadata for facies thickness.", "properties": { @@ -3286,6 +3340,74 @@ "title": "Iteration", "type": "object" }, + "IterationContext": { + "description": "The ``fmu.context`` block contains the FMU context in which this data object\nwas produced. Here ``stage`` is required to be ``iteration``.", + "properties": { + "stage": { + "const": "iteration", + "default": "iteration", + "enum": [ + "iteration" + ], + "title": "Stage", + "type": "string" + } + }, + "title": "IterationContext", + "type": "object" + }, + "IterationMetadata": { + "description": "The FMU metadata model for an FMU Iteration.\n\nAn object representing a single Iteration of a specific case.", + "properties": { + "access": { + "$ref": "#/$defs/Access" + }, + "class": { + "const": "iteration", + "enum": [ + "iteration" + ], + "title": "metadata_class", + "type": "string" + }, + "fmu": { + "$ref": "#/$defs/FMUIteration" + }, + "masterdata": { + "$ref": "#/$defs/Masterdata" + }, + "source": { + "const": "fmu", + "enum": [ + "fmu" + ], + "title": "Source", + "type": "string" + }, + "tracklog": { + "$ref": "#/$defs/Tracklog" + }, + "version": { + "const": "0.8.0", + "enum": [ + "0.8.0" + ], + "title": "Version", + "type": "string" + } + }, + "required": [ + "class", + "masterdata", + "tracklog", + "source", + "version", + "fmu", + "access" + ], + "title": "IterationMetadata", + "type": "object" + }, "KPProductData": { "description": "The ``data`` block contains information about the data contained in this object.\nThis class contains metadata for KP products.", "properties": { @@ -6059,6 +6181,74 @@ "title": "Realization", "type": "object" }, + "RealizationContext": { + "description": "The ``fmu.context`` block contains the FMU context in which this data object\nwas produced. Here ``stage`` is required to be ``realization``.", + "properties": { + "stage": { + "const": "realization", + "default": "realization", + "enum": [ + "realization" + ], + "title": "Stage", + "type": "string" + } + }, + "title": "RealizationContext", + "type": "object" + }, + "RealizationMetadata": { + "description": "The FMU metadata model for an FMU Realization.\n\nAn object representing a single Realization of a specific Iteration.", + "properties": { + "access": { + "$ref": "#/$defs/Access" + }, + "class": { + "const": "realization", + "enum": [ + "realization" + ], + "title": "metadata_class", + "type": "string" + }, + "fmu": { + "$ref": "#/$defs/FMURealization" + }, + "masterdata": { + "$ref": "#/$defs/Masterdata" + }, + "source": { + "const": "fmu", + "enum": [ + "fmu" + ], + "title": "Source", + "type": "string" + }, + "tracklog": { + "$ref": "#/$defs/Tracklog" + }, + "version": { + "const": "0.8.0", + "enum": [ + "0.8.0" + ], + "title": "Version", + "type": "string" + } + }, + "required": [ + "class", + "masterdata", + "tracklog", + "source", + "version", + "fmu", + "access" + ], + "title": "RealizationMetadata", + "type": "object" + }, "RegionsData": { "description": "The ``data`` block contains information about the data contained in this object.\nThis class contains metadata for regions.", "properties": { @@ -9632,6 +9822,12 @@ }, { "$ref": "#/$defs/ObjectMetadata" + }, + { + "$ref": "#/$defs/RealizationMetadata" + }, + { + "$ref": "#/$defs/IterationMetadata" } ], "then": { diff --git a/src/fmu/dataio/_model/enums.py b/src/fmu/dataio/_model/enums.py index 38cb34013..e0c8f9476 100644 --- a/src/fmu/dataio/_model/enums.py +++ b/src/fmu/dataio/_model/enums.py @@ -60,6 +60,8 @@ class FMUClass(str, Enum): """The class of a data object by FMU convention or standards.""" case = "case" + realization = "realization" + iteration = "iteration" surface = "surface" table = "table" cpgrid = "cpgrid" diff --git a/src/fmu/dataio/_model/fields.py b/src/fmu/dataio/_model/fields.py index ca2ea29d1..f09bbc125 100644 --- a/src/fmu/dataio/_model/fields.py +++ b/src/fmu/dataio/_model/fields.py @@ -11,6 +11,7 @@ Any, Dict, List, + Literal, Optional, Union, ) @@ -556,6 +557,28 @@ class Context(BaseModel): See :class:`enums.FMUContext`.""" +class IterationContext(Context): + """ + The ``fmu.context`` block contains the FMU context in which this data object + was produced. Here ``stage`` is required to be ``iteration``. + """ + + stage: Literal[enums.FMUContext.iteration] = Field( + default=enums.FMUContext.iteration + ) + + +class RealizationContext(Context): + """ + The ``fmu.context`` block contains the FMU context in which this data object + was produced. Here ``stage`` is required to be ``realization``. + """ + + stage: Literal[enums.FMUContext.realization] = Field( + default=enums.FMUContext.realization + ) + + class FMUBase(BaseModel): """ The ``fmu`` block contains all attributes specific to FMU. The idea is that the FMU @@ -572,6 +595,47 @@ class FMUBase(BaseModel): See :class:`Model`.""" +class FMUIteration(FMUBase): + """ + The ``fmu`` block contains all attributes specific to FMU. The idea is that the FMU + results data model can be applied to data from *other* sources - in which the + fmu-specific stuff may not make sense or be applicable. + This is a specialization of the FMU block for ``iteration`` objects. + """ + + context: IterationContext + """The ``fmu.context`` block contains the FMU context in which this data object + was produced. See :class:`Context`. For ``iteration`` the context is ``iteration``. + """ + + iteration: Iteration + """The ``fmu.iteration`` block contains information about the iteration this data + object belongs to. See :class:`Iteration`. """ + + +class FMURealization(FMUBase): + """ + The ``fmu`` block contains all attributes specific to FMU. The idea is that the FMU + results data model can be applied to data from *other* sources - in which the + fmu-specific stuff may not make sense or be applicable. + This is a specialization of the FMU block for ``realization`` objects. + """ + + context: RealizationContext + """The ``fmu.context`` block contains the FMU context in which this data object + was produced. See :class:`Context`. For ``realization`` the context is always + ``realization``. + """ + + iteration: Iteration + """The ``fmu.iteration`` block contains information about the iteration this data + object belongs to. See :class:`Iteration`. """ + + realization: Realization + """The ``fmu.realization`` block contains information about the realization this + data object belongs to. See :class:`Realization`.""" + + class FMU(FMUBase): """ The ``fmu`` block contains all attributes specific to FMU. The idea is that the FMU diff --git a/src/fmu/dataio/_model/root.py b/src/fmu/dataio/_model/root.py index 699005df5..985fcf1a1 100644 --- a/src/fmu/dataio/_model/root.py +++ b/src/fmu/dataio/_model/root.py @@ -20,6 +20,8 @@ Display, File, FMUBase, + FMUIteration, + FMURealization, Masterdata, SsdlAccess, Tracklog, @@ -72,6 +74,42 @@ class CaseMetadata(MetadataBase): this data object. See :class:`Access`.""" +class IterationMetadata(MetadataBase): + """The FMU metadata model for an FMU Iteration. + + An object representing a single Iteration of a specific case. + """ + + class_: Literal[FMUClass.iteration] = Field(alias="class", title="metadata_class") + """The class of this metadata object. In this case, always an FMU iteration.""" + + fmu: FMUIteration + """The ``fmu`` block contains all attributes specific to FMU. + See :class:`FMU`.""" + + access: Access + """The ``access`` block contains information related to access control for + this data object. See :class:`Access`.""" + + +class RealizationMetadata(MetadataBase): + """The FMU metadata model for an FMU Realization. + + An object representing a single Realization of a specific Iteration. + """ + + class_: Literal[FMUClass.realization] = Field(alias="class", title="metadata_class") + """The class of this metadata object. In this case, always an FMU realization.""" + + fmu: FMURealization + """The ``fmu`` block contains all attributes specific to FMU. + See :class:`FMU`.""" + + access: Access + """The ``access`` block contains information related to access control for + this data object. See :class:`Access`.""" + + class ObjectMetadata(MetadataBase): """The FMU metadata model for a given data object.""" @@ -119,6 +157,8 @@ class Root( Union[ CaseMetadata, ObjectMetadata, + RealizationMetadata, + IterationMetadata, ], Field(discriminator="class_"), ] diff --git a/tests/test_schema/test_pydantic_logic.py b/tests/test_schema/test_pydantic_logic.py index af6d6894b..d7fc7fff6 100644 --- a/tests/test_schema/test_pydantic_logic.py +++ b/tests/test_schema/test_pydantic_logic.py @@ -404,3 +404,63 @@ def test_zmin_zmax_not_present_for_surfaces(metadata_examples): del example_surface["data"]["bbox"]["zmax"] model = Root.model_validate(example_surface) assert isinstance(model.root.data.root.bbox, data.BoundingBox2D) + + +def test_iteration(metadata_examples): + """Asserting validation failure when illegal contents in case example""" + + example = metadata_examples["iteration.yml"] + + # assert validation with no changes + Root.model_validate(example) + + # assert validation error when "fmu" is missing + _example = deepcopy(example) + del _example["fmu"] + + with pytest.raises(ValidationError): + Root.model_validate(_example) + + # assert validation error when "fmu.iteration" is missing + _example = deepcopy(example) + del _example["fmu"]["iteration"] + + with pytest.raises(ValidationError): + Root.model_validate(_example) + + # assert validation error when "fmu.context.stage" is not iteration + _example = deepcopy(example) + _example["fmu"]["context"]["stage"] = "case" + + with pytest.raises(ValidationError): + Root.model_validate(_example) + + +def test_realization(metadata_examples): + """Asserting validation failure when illegal contents in case example""" + + example = metadata_examples["realization.yml"] + + # assert validation with no changes + Root.model_validate(example) + + # assert validation error when "fmu" is missing + _example = deepcopy(example) + del _example["fmu"] + + with pytest.raises(ValidationError): + Root.model_validate(_example) + + # assert validation error when "fmu.realization" is missing + _example = deepcopy(example) + del _example["fmu"]["realization"] + + with pytest.raises(ValidationError): + Root.model_validate(_example) + + # assert validation error when "fmu.context.stage" is not realization + _example = deepcopy(example) + _example["fmu"]["context"]["stage"] = "iteration" + + with pytest.raises(ValidationError): + Root.model_validate(_example)