From a830442e878089b7068de4358e232bd15f741fb7 Mon Sep 17 00:00:00 2001 From: JB Lovland Date: Tue, 9 Jan 2024 17:53:37 +0100 Subject: [PATCH] experimental: Build pydantic from schema --- gen.sh | 13 ++ pyproject.toml | 1 + src/fmu/dataio/model.py | 449 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100755 gen.sh create mode 100644 src/fmu/dataio/model.py diff --git a/gen.sh b/gen.sh new file mode 100755 index 000000000..2350be810 --- /dev/null +++ b/gen.sh @@ -0,0 +1,13 @@ +datamodel-codegen \ + --collapse-root-models \ + --input schema/docs/src/public/fmu_results_090.json \ + --input-file-type "jsonschema" \ + --output-model-type "pydantic_v2.BaseModel" \ + --target-python-version "3.8" \ + --use-double-quotes \ + --use-schema-description \ + --use-standard-collections \ + --use-subclass-enum \ + --use-title-as-name \ + --use-union-operator \ + --use-unique-items-as-set diff --git a/pyproject.toml b/pyproject.toml index 211d9a1f5..f90682936 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ "numpy", "pandas", "pyarrow", + "pydantic>=2.0.0", "PyYAML", "xtgeo>=2.16", ] diff --git a/src/fmu/dataio/model.py b/src/fmu/dataio/model.py new file mode 100644 index 000000000..57eaeed71 --- /dev/null +++ b/src/fmu/dataio/model.py @@ -0,0 +1,449 @@ +# generated by datamodel-codegen: +# filename: fmu_results_090.json +# timestamp: 2024-01-09T16:54:51+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import Any, Literal + +from pydantic import BaseModel, Field, RootModel, constr + + +class Generic(RootModel[Any]): + root: Any + + +class MetadataClass(str, Enum): + case = "case" + surface = "surface" + table = "table" + cpgrid = "cpgrid" + cpgrid_property = "cpgrid_property" + polygons = "polygons" + cube = "cube" + well = "well" + points = "points" + + +class Property(BaseModel): + name: str | None = Field(None, examples=["MyPropertyName"]) + attribute: str | None = Field(None, examples=["MyAttributeName"]) + is_discrete: bool | None = Field(None, examples=[True, False]) + + +class VerticalDomain(str, Enum): + depth = "depth" + time = "time" + + +class DepthReference(Enum): + msl = "msl" + sb = "sb" + rkb = "rkb" + NoneType_None = None + + +class GridModel(BaseModel): + name: str | None = Field(None, examples=["MyGrid"]) + + +class Yflip(int, Enum): + integer_0 = 0 + integer_1 = 1 + + +class Spec(BaseModel): + ncol: int | None = Field(None, examples=[281]) + nrow: int | None = Field(None, examples=[441]) + nlay: int | None = Field(None, examples=[333]) + xori: float | None = Field(None, examples=[461499.9997558594]) + yori: float | None = Field(None, examples=[461499.9997558594]) + xinc: float | None = Field(None, examples=[25]) + yflip: Yflip | None = Field(None, examples=[1]) + rotation: float | None = Field(None, examples=["30.00000000231"]) + undef: float | None = Field(None, examples=["1e+33"]) + npolys: int | None = Field( + None, + description="The number of individual polygons in the data object", + examples=[1], + ) + + +class Bbox(BaseModel): + xmin: float = Field(..., examples=[456012.5003497944]) + xmax: float = Field(..., examples=[467540.52762886323]) + ymin: float = Field(..., examples=[5926499.999511719]) + ymax: float = Field(..., examples=[5939492.128326312]) + zmin: float | None = Field(None, examples=[1244.039, None]) + zmax: float | None = Field(None, examples=[2302.683, None]) + + +class Line(BaseModel): + show: bool | None = Field(None, examples=[True]) + color: str | None = Field(None, examples=["black", "#000000"]) + + +class Points(BaseModel): + show: bool | None = Field(None, examples=[True]) + color: str | None = Field(None, examples=["black", "#000000"]) + + +class Contours(BaseModel): + show: bool | None = Field(None, examples=[True]) + color: str | None = Field( + None, + description="The color of the contour lines", + examples=["black", "#000000"], + ) + increment: float | None = Field( + None, + description="The contour increment in same values as the data", + examples=[20], + ) + + +class Fill(BaseModel): + show: bool | None = Field(None, examples=[True]) + color: str | None = Field(None, examples=["black", "#000000"]) + colormap: str | None = Field( + None, description="named reference to a colormap", examples=["gist_earth"] + ) + display_min: float | None = Field( + None, description="The low-side boundary to use for fill color", examples=[1000] + ) + display_max: float | None = Field( + None, + description="The high-side boundary to use for fill color", + examples=[1600], + ) + + +class Display(BaseModel): + name: str | None = Field(None, examples=["Top Volantis"]) + subtitle: str | None = Field(None, examples=["Some subtitle"]) + line: Line | None = None + points: Points | None = None + contours: Contours | None = None + fill: Fill | None = None + + +class Asset(BaseModel): + name: str = Field(..., examples=["Drogon"]) + + +class Access(BaseModel): + asset: Asset | None = None + + +class Workflow1(BaseModel): + reference: str | None = Field( + None, description="Reference to the part of the FMU workflow that produced this" + ) + + +class Aggregation1(BaseModel): + operation: str = Field(..., description="The aggregation performed") + realization_ids: list[int] = Field( + ..., description="Array of realization ids included in this aggregation" + ) + parameters: dict[str, Any] | None = Field( + None, description="Parameters for this realization" + ) + id: str | None = Field( + None, + description="The ID of this aggregation", + examples=["15ce3b84-766f-4c93-9050-b154861f9100"], + ) + + +class File(BaseModel): + """ + Block describing the file as the data appear in FMU context + """ + + relative_path: str = Field( + ..., + description="The file path relative to RUNPATH", + examples=["share/results/maps/volantis_gp_base--depth.gri"], + ) + absolute_path: str | None = Field( + None, + description="The absolute file path", + examples=["/abs/path/share/results/maps/volantis_gp_base--depth.gri"], + ) + checksum_md5: str = Field( + ..., + description="md5 checksum of the file or bytestring", + examples=["kjhsdfvsdlfk23knerknvk23"], + ) + + +class User(BaseModel): + id: str = Field(..., examples=["peesv"], title="User ID") + + +class Fmu(BaseModel): + """ + The FMU block records properties that are specific to FMU + """ + + model: Any + case: Any + + +class Fmu1(BaseModel): + """ + The FMU block records properties that are specific to FMU + """ + + model: Any + case: Any + iteration: Any + workflow: Any + realization: Any + + +class Fmu2(BaseModel): + """ + The FMU block records properties that are specific to FMU + """ + + model: Any + case: Any + iteration: Any + workflow: Any + aggregation: Any + + +class FMUTimeObjectItem(BaseModel): + value: str | None = Field(None, examples=["2020-10-28T14:28:02"]) + label: str | None = Field(None, examples=["Label for time stamp"]) + + +class TracklogEvent(BaseModel): + datetime: str | None = Field(None, examples=["2020-10-28T14:28:02"]) + user: User | None = None + event: str | None = Field(None, examples=["created", "updated"]) + + +class Top(BaseModel): + name: str | None = Field( + None, + description="Name of the surface. If stratigraphic, match the entry in the stratigraphic column", + examples=["VIKING GP. Top"], + ) + stratigraphic: bool | None = Field( + None, + description="True if surface represents an entity in the stratigraphic column", + examples=[True], + ) + offset: float | None = Field(None, examples=[11.2]) + + +class Base(BaseModel): + name: str | None = Field( + None, + description="Name of the surface. If stratigraphic, match the entry in the stratigraphic column", + examples=["VIKING GP. Top"], + ) + stratigraphic: bool | None = Field( + None, + description="True if surface represents an entity in the stratigraphic column", + examples=[True], + ) + offset: float | None = Field(None, examples=[11.2]) + + +class TheDataBlock(BaseModel): + name: str = Field( + ..., + description="Name of the surface. If stratigraphic, match the entry in the stratigraphic column", + examples=["VIKING GP. Top"], + ) + stratigraphic: bool = Field( + ..., + description="True if surface represents an entity in the stratigraphic column", + examples=[True], + ) + alias: list[str] | None = None + stratigraphic_alias: list[str] | None = None + offset: float | None = Field(None, examples=[11.2]) + top: Top | None = None + base: Base | None = None + content: str = Field( + ..., description="The contents of this data object", examples=["depth"] + ) + tagname: str | None = Field( + None, + description="A semi-human readable tag for internal usage and uniqueness", + examples=["ds_extract_geogrid", "ds_post_strucmod"], + ) + properties: list[Property] | None = None + format: str = Field(..., examples=["irap_binary"]) + layout: str | None = Field(None, examples=["regular"]) + unit: str | None = Field(None, examples=["m"]) + vertical_domain: VerticalDomain | None = Field(None, examples=["depth"]) + depth_reference: DepthReference | None = Field(None, examples=["msl"]) + grid_model: GridModel | None = None + spec: Spec + bbox: Bbox | None = None + time: list[FMUTimeObjectItem] | None = Field( + None, + description="List of time stamps referring to simulated time", + title="FMU time object", + ) + is_prediction: bool = Field(..., examples=[True], title="Is prediction flag") + is_observation: bool = Field(..., examples=[True], title="Is observation flag") + description: list[str] | None = None + + +class CountryItem(BaseModel): + identifier: str = Field(..., examples=["Norway"]) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + + +class DiscoveryItem(BaseModel): + short_identifier: str = Field(..., examples=["SomeDiscovery"]) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + + +class FieldItem(BaseModel): + identifier: str = Field(..., examples=["OseFax"]) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + + +class CoordinateSystem(BaseModel): + identifier: str = Field(..., examples=["ST_WGS84_UTM37N_P32637"]) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + + +class StratigraphicColumn(BaseModel): + identifier: str = Field(..., examples=["DROGON_2020"]) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + + +class Smda(BaseModel): + country: list[CountryItem] + discovery: list[DiscoveryItem] + field: list[FieldItem] + coordinate_system: CoordinateSystem + stratigraphic_column: StratigraphicColumn + + +class Masterdata(BaseModel): + smda: Smda + + +class Model1(BaseModel): + name: str | None = Field(None, examples=["Drogon"]) + revision: str | None = Field(None, examples=["21.0.0.dev"]) + description: list[str] | None = Field( + None, description="This is a free text description of the model setup" + ) + + +class Case1(BaseModel): + name: str = Field(..., description="The case name", examples=["MyCaseName"]) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + user: User = Field(..., description="The user name used in ERT") + restart_from: str | None = Field( + None, + description="A reference to another case/iteration that this iteration was restarted from", + ) + description: list[str] | None = None + + +class Iteration1(BaseModel): + name: str = Field( + ..., + description="The convential name of this iteration, e.g. iter-0 or pred", + examples=["iter-0"], + ) + id: int = Field( + ..., + description="The internal identification of this iteration, e.g. the iteration number", + examples=[0], + ) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + + +class Realization1(BaseModel): + name: str = Field( + ..., + description="The convential name of this iteration, e.g. iter-0 or pred", + examples=["iter-0"], + ) + id: int = Field( + ..., + description="The unique number of this realization as used in FMU", + examples=[33], + ) + uuid: constr( + pattern=r"^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$" + ) = Field(..., examples=["ad214d85-8a1d-19da-e053-c918a4889309"]) + parameters: dict[str, Any] | None = Field( + None, description="Parameters for this realization" + ) + jobs: dict[str, Any] | None = Field( + None, + description="Content directly taken from the ERT jobs.json file for this realization", + ) + + +class Fmu3(BaseModel): + model: Model1 | None = None + workflow: Workflow1 | None = None + case: Case1 | None = None + iteration: Iteration1 | None = None + realization: Realization1 | None = None + aggregation: Aggregation1 | None = None + + +class CaseModel(BaseModel): + source: Literal["fmu"] = Field(..., description="Data source (FMU)") + version: Literal["0.9.0"] = Field( + ..., examples=["1.2.3"], title="FMU results metadata version" + ) + data: TheDataBlock | None = None + tracklog: list[TracklogEvent] + access: Access + masterdata: Masterdata + class_: MetadataClass = Field(..., alias="class") + fmu: Fmu = Field( + ..., description="The FMU block records properties that are specific to FMU" + ) + + +class DataObject(BaseModel): + source: Literal["fmu"] = Field(..., description="Data source (FMU)") + version: Literal["0.9.0"] = Field( + ..., examples=["1.2.3"], title="FMU results metadata version" + ) + data: TheDataBlock + tracklog: list[TracklogEvent] + access: Access + masterdata: Masterdata + class_: MetadataClass = Field(..., alias="class") + fmu: Fmu1 | Fmu2 = Field( + ..., description="The FMU block records properties that are specific to FMU" + ) + file: File + + +class ModelModel(RootModel[CaseModel | DataObject]): + root: CaseModel | DataObject