Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Version checks to all relevant models. #180

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion hydrolib/core/io/bc/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from hydrolib.core.io.ini.parser import Parser, ParserConfig
from hydrolib.core.io.ini.serializer import SerializerConfig, write_ini
from hydrolib.core.io.ini.util import get_enum_validator, get_from_subclass_defaults
from hydrolib.core.utils import FMVersion

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -196,7 +197,7 @@ class Constant(ForcingBase):


class ForcingGeneral(INIGeneral):
fileversion: str = Field("1.01", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("1.1.0"), alias="fileVersion")
filetype: Literal["boundConds"] = Field("boundConds", alias="fileType")


Expand Down
5 changes: 3 additions & 2 deletions hydrolib/core/io/crosssection/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
make_list_length_root_validator,
make_list_validator,
)
from hydrolib.core.utils import FMVersion

logger = logging.getLogger(__name__)

Expand All @@ -30,14 +31,14 @@
class CrossDefGeneral(INIGeneral):
"""The crosssection definition file's `[General]` section with file meta data."""

fileversion: str = Field("3.00", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("3.0.0"), alias="fileVersion")
filetype: Literal["crossDef"] = Field("crossDef", alias="fileType")


class CrossLocGeneral(INIGeneral):
"""The crosssection location file's `[General]` section with file meta data."""

fileversion: str = Field("3.00", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("1.1.0"), alias="fileVersion")
filetype: Literal["crossLoc"] = Field("crossLoc", alias="fileType")


Expand Down
9 changes: 7 additions & 2 deletions hydrolib/core/io/dimr/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
from typing import Callable, List, Literal, Optional, Type, Union

from pydantic import Field, validator
from semantic_version import Version as SemVersion

from hydrolib.core import __version__
from hydrolib.core.basemodel import BaseModel, FileModel
from hydrolib.core.io.dimr.parser import DIMRParser
from hydrolib.core.io.dimr.serializer import DIMRSerializer
from hydrolib.core.io.fnm.models import RainfallRunoffModel
from hydrolib.core.io.mdu.models import FMModel
from hydrolib.core.utils import to_list
from hydrolib.core.utils import DIMRVersion, get_version_validator, to_list


class KeyValuePair(BaseModel):
Expand Down Expand Up @@ -103,10 +104,14 @@ class Documentation(BaseModel):
creationDate: The creation date of the DIMR file.
"""

fileVersion: str = "1.3"
fileVersion: DIMRVersion = DIMRVersion.coerce("1.2")
createdBy: str = f"hydrolib-core {__version__}"
creationDate: datetime = Field(default_factory=datetime.utcnow)

_version_validator = get_version_validator(
"fileVersion",
)


class GlobalSettings(BaseModel):
"""
Expand Down
4 changes: 2 additions & 2 deletions hydrolib/core/io/ext/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
get_split_string_on_delimiter_validator,
make_list_validator,
)
from hydrolib.core.utils import str_is_empty_or_none
from hydrolib.core.utils import FMVersion, str_is_empty_or_none


class Boundary(INIBasedModel):
Expand Down Expand Up @@ -234,7 +234,7 @@ class ExtGeneral(INIGeneral):
"""The external forcing file's `[General]` section with file meta data."""

_header: Literal["General"] = "General"
fileversion: str = Field("2.01", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("2.1.0"), alias="fileVersion")
filetype: Literal["extForce"] = Field("extForce", alias="fileType")


Expand Down
3 changes: 2 additions & 1 deletion hydrolib/core/io/friction/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from hydrolib.core.io.ini.models import INIBasedModel, INIGeneral, INIModel
from hydrolib.core.io.ini.util import get_split_string_on_delimiter_validator
from hydrolib.core.utils import FMVersion

from ..ini.util import make_list_validator

Expand All @@ -32,7 +33,7 @@ class Comments(INIBasedModel.Comments):

comments: Comments = Comments()
_header: Literal["General"] = "General"
fileversion: str = Field("3.01", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("3.0.0"), alias="fileVersion")
filetype: Literal["roughness"] = Field("roughness", alias="fileType")
frictionvaluesfile: Optional[Path] = Field(alias="frictionValuesFile")

Expand Down
8 changes: 6 additions & 2 deletions hydrolib/core/io/ini/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from hydrolib.core import __version__ as version
from hydrolib.core.basemodel import BaseModel, FileModel
from hydrolib.core.utils import FMVersion, get_version_validator

from .io_models import CommentBlock, Document, Property, Section
from .parser import Parser
Expand All @@ -33,7 +34,6 @@ class INIBasedModel(BaseModel, ABC):

class Config:
extra = Extra.allow
arbitrary_types_allowed = False

@classmethod
def _supports_comments(cls):
Expand Down Expand Up @@ -137,9 +137,13 @@ def _to_section(self) -> Section:

class INIGeneral(INIBasedModel):
_header: Literal["General"] = "General"
fileversion: str = Field("3.00", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("3.0.0"), alias="fileVersion")
filetype: str = Field(alias="fileType")

_version_validator = get_version_validator(
"fileversion",
)

@classmethod
def _supports_comments(cls):
return True
Expand Down
3 changes: 2 additions & 1 deletion hydrolib/core/io/mdu/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
from hydrolib.core.io.polyfile.models import PolyFile
from hydrolib.core.io.structure.models import StructureModel
from hydrolib.core.io.xyz.models import XYZModel
from hydrolib.core.utils import FMVersion


class General(INIGeneral):
_header: Literal["General"] = "General"
program: str = Field("D-Flow FM", alias="program")
version: str = Field("1.2.94.66079M", alias="version")
filetype: Literal["modelDef"] = Field("modelDef", alias="fileType")
fileversion: str = Field("1.09", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("1.9.0"), alias="fileVersion")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the attempt, yet I find it reads quite confusing.

autostart: bool = Field(False, alias="autoStart")
pathsrelativetoparent: bool = Field(False, alias="pathsRelativeToParent")

Expand Down
4 changes: 2 additions & 2 deletions hydrolib/core/io/structure/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
get_from_subclass_defaults,
get_split_string_on_delimiter_validator,
)
from hydrolib.core.utils import str_is_empty_or_none
from hydrolib.core.utils import FMVersion, str_is_empty_or_none

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -855,7 +855,7 @@ class StructureGeneral(INIGeneral):
"""`[General]` section with structure file metadata."""

_header: Literal["General"] = "General"
fileversion: str = Field("3.00", alias="fileVersion")
fileversion: FMVersion = Field(FMVersion("2.0.0"), alias="fileVersion")
filetype: Literal["structure"] = Field("structure", alias="fileType")


Expand Down
55 changes: 55 additions & 0 deletions hydrolib/core/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Any, List, Optional

from pydantic import Field, validator
from semantic_version import Version as SemVer


def example(a: float, b: float = 1.0) -> float:
"""[summary]
Expand Down Expand Up @@ -69,3 +72,55 @@ def get_substring_between(source: str, start: str, end: str) -> Optional[str]:
return None

return source[index_start:index_end]


class HVersion(SemVer):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstrings for the purpose of these classes HVersion, etc., and subroutines such as coerce() would help understanding a lot.

@classmethod
def coerce(cls, *args, **kwargs):
version = super().coerce(*args, **kwargs)
return cls(**version.__dict__)


class FMVersion(HVersion):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite like this FM-related class here, inside a "neutral" utils namespace.
Also it suggest that this is "the" FM version, whereas it is only the numbering schema, so consider renaming this class to: class FMStyleVersion, or DHydroStyleVersion, or ZeroPrefixedVersion.

def __str__(self) -> str:
"""Add zero to minor version if it is less than 10."""
version = f"{self.major}"
if self.minor is not None:
version = f"{version}.{self.minor:02d}"

return version


class DIMRVersion(HVersion):
def __str__(self) -> str:
"""Strip patch version."""
version = f"{self.major}"
if self.minor is not None:
version = f"{version}.{self.minor}"

return version


def get_version_validator(*field_name: str):
"""Get a validator to check the Version number."""

def check_version(cls, v: Any, field: Field):
"""Validate (semantic) version numbers."""

if isinstance(v, str):
version = field.default.__class__.coerce(v)
elif isinstance(v, (FMVersion, DIMRVersion)):
version = v
elif v is None:
return field.default
else:
raise ValueError(f"Invalid version specified: {v}")

if version < field.default or version >= field.default.next_major():
raise ValueError(
f"Input with version {v} isn't a version support by HYDROLIB-core, which supports >={field.default}"
)

return version

return validator(*field_name, allow_reuse=True, pre=True)(check_version)
14 changes: 13 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ numpy = "^1.21"
pydantic = {extras = ["dotenv"], version = "^1.8"}
lxml = "^4.6"
meshkernel = "^1.0.0"
semantic-version = "^2.8.5"

[tool.poetry.dev-dependencies]
pytest = "^6.2"
Expand Down
2 changes: 1 addition & 1 deletion tests/data/reference/dimr/test_serialize.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<dimrConfig xmlns="http://schemas.deltares.nl/dimr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.deltares.nl/dimr http://content.oss.deltares.nl/schemas/dimr-1.3.xsd">
<documentation>
<fileVersion>1.3</fileVersion>
<fileVersion>1.2</fileVersion>
<createdBy>hydrolib-core 0.1.5</createdBy>
<creationDate>2020-03-17T10:02:49.4520672Z</creationDate>
</documentation>
Expand Down
2 changes: 1 addition & 1 deletion tests/data/reference/model/test_dimr_model_save.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<dimrConfig xmlns="http://schemas.deltares.nl/dimr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.deltares.nl/dimr http://content.oss.deltares.nl/schemas/dimr-1.3.xsd">
<documentation>
<fileVersion>1.3</fileVersion>
<fileVersion>1.2</fileVersion>
<createdBy>hydrolib-core 0.1.5</createdBy>
<creationDate>2021-07-29 12:45:00</creationDate>
</documentation>
Expand Down
4 changes: 2 additions & 2 deletions tests/io/test_dimr.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_parse_returns_correct_data():
result = DIMRParser.parse(test_file)

documentation = result["documentation"]
assert documentation["fileVersion"] == "1.2"
assert str(documentation["fileVersion"]) == "1.2"
assert documentation["createdBy"] == "Deltares, Coupling Team"
assert documentation["creationDate"] == "2020-03-17T10:02:49.4520672Z"

Expand Down Expand Up @@ -75,7 +75,7 @@ def test_serialize():

data = {
"documentation": {
"fileVersion": "1.3",
"fileVersion": "1.2",
"createdBy": f"hydrolib-core {__version__}",
"creationDate": "2020-03-17T10:02:49.4520672Z",
},
Expand Down