Skip to content

Commit

Permalink
feat: Raise error for unknown keywords (#632)
Browse files Browse the repository at this point in the history
refs: #622
  • Loading branch information
MRVermeulenDeltares authored Jun 7, 2024
1 parent cc138a2 commit b7dc73a
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 269 deletions.
8 changes: 8 additions & 0 deletions hydrolib/core/dflowfm/crosssection/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from hydrolib.core.dflowfm.ini.util import (
LocationValidationConfiguration,
LocationValidationFieldNames,
UnknownKeywordErrorManager,
get_enum_validator,
get_from_subclass_defaults,
get_split_string_on_delimiter_validator,
Expand Down Expand Up @@ -71,6 +72,13 @@ class Comments(INIBasedModel.Comments):
type: str = Field(alias="type")
thalweg: Optional[float]

@classmethod
def _get_unknown_keyword_error_manager(cls) -> Optional[UnknownKeywordErrorManager]:
"""
The CrossSectionDefinition does not currently support raising an error on unknown keywords.
"""
return None

def _get_identifier(self, data: dict) -> Optional[str]:
return data.get("id")

Expand Down
8 changes: 8 additions & 0 deletions hydrolib/core/dflowfm/ext/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from hydrolib.core.dflowfm.ini.serializer import INISerializerConfig
from hydrolib.core.dflowfm.ini.util import (
LocationValidationConfiguration,
UnknownKeywordErrorManager,
get_enum_validator,
get_split_string_on_delimiter_validator,
make_list_validator,
Expand Down Expand Up @@ -249,6 +250,13 @@ class Comments(INIBasedModel.Comments):

comments: Comments = Comments()

@classmethod
def _get_unknown_keyword_error_manager(cls) -> Optional[UnknownKeywordErrorManager]:
"""
The Meteo does not currently support raising an error on unknown keywords.
"""
return None

_disk_only_file_model_should_not_be_none = (
validator_set_default_disk_only_file_model_when_none()
)
Expand Down
27 changes: 25 additions & 2 deletions hydrolib/core/dflowfm/ini/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
INISerializerConfig,
write_ini,
)
from .util import make_list_validator
from .util import UnknownKeywordErrorManager, make_list_validator

logger = logging.getLogger(__name__)

Expand All @@ -45,7 +45,7 @@ class INIBasedModel(BaseModel, ABC):
descriptions for all data fields.
"""

_header: str
_header: str = ""
_file_path_style_converter = FilePathStyleConverter()
_scientific_notation_regex = compile(
r"([\d.]+)([dD])([+-]?\d{1,3})"
Expand All @@ -55,6 +55,10 @@ class Config:
extra = Extra.ignore
arbitrary_types_allowed = False

@classmethod
def _get_unknown_keyword_error_manager(cls) -> Optional[UnknownKeywordErrorManager]:
return UnknownKeywordErrorManager()

@classmethod
def _supports_comments(cls):
return True
Expand Down Expand Up @@ -103,6 +107,18 @@ class Config:

comments: Optional[Comments] = Comments()

@root_validator(pre=True)
def _validate_unknown_keywords(cls, values):
unknown_keyword_error_manager = cls._get_unknown_keyword_error_manager()
if unknown_keyword_error_manager:
unknown_keyword_error_manager.raise_error_for_unknown_keywords(
values,
cls._header,
cls.__fields__,
cls._exclude_fields(),
)
return values

@root_validator(pre=True)
def _skip_nones_and_set_header(cls, values):
"""Drop None fields for known fields."""
Expand Down Expand Up @@ -220,6 +236,13 @@ class DataBlockINIBasedModel(INIBasedModel):

_make_lists = make_list_validator("datablock")

@classmethod
def _get_unknown_keyword_error_manager(cls) -> Optional[UnknownKeywordErrorManager]:
"""
The DataBlockINIBasedModel does not need to raise an error on unknown keywords.
"""
return None

def _to_section(
self,
config: DataBlockINIBasedSerializerConfig,
Expand Down
55 changes: 53 additions & 2 deletions hydrolib/core/dflowfm/ini/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from datetime import datetime
from enum import Enum
from operator import eq
from typing import Any, Callable, Dict, List, Optional, Type
from typing import Any, Callable, Dict, List, Optional, Set, Type

from pydantic.v1.class_validators import root_validator, validator
from pydantic.v1.class_validators import validator
from pydantic.v1.fields import ModelField
from pydantic.v1.main import BaseModel

Expand Down Expand Up @@ -622,3 +622,54 @@ def rename_keys_for_backwards_compatibility(
break

return values


class UnknownKeywordErrorManager:
"""
Error manager for unknown keys.
Detects unknown keys and manages the Error to the user.
"""

def raise_error_for_unknown_keywords(
self,
data: Dict[str, Any],
section_header: str,
fields: Dict[str, ModelField],
excluded_fields: Set[str],
) -> None:
"""
Notify the user of unknown keywords.
Args:
data (Dict[str, Any]) : Input data containing all properties which are checked on unknown keywords.
section_header (str) : Header of the section in which unknown keys might be detected.
fields (Dict[str, ModelField]) : Known fields of the section.
excluded_fields (Set[str]) : Fields which should be excluded from the check for unknown keywords.
"""
unknown_keywords = self._get_all_unknown_keywords(data, fields, excluded_fields)

if len(unknown_keywords) == 0:
return

raise ValueError(
f"Unknown keywords are detected in section: '{section_header}', '{unknown_keywords}'"
)

def _get_all_unknown_keywords(
self, data: Dict[str, Any], fields: Dict[str, ModelField], excluded_fields: Set
) -> List[str]:
list_of_unknown_keywords = []
for name in data:
if self._is_unknown_keyword(name, fields, excluded_fields):
list_of_unknown_keywords.append(name)

return list_of_unknown_keywords

def _is_unknown_keyword(
self, name: str, fields: Dict[str, ModelField], excluded_fields: Set
):
for model_field in fields.values():
if name == model_field.name or name == model_field.alias:
return False

return name not in excluded_fields
8 changes: 8 additions & 0 deletions hydrolib/core/dflowfm/storagenode/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from hydrolib.core.dflowfm.ini.models import INIBasedModel, INIGeneral, INIModel
from hydrolib.core.dflowfm.ini.util import (
UnknownKeywordErrorManager,
get_enum_validator,
get_split_string_on_delimiter_validator,
make_list_validator,
Expand Down Expand Up @@ -162,6 +163,13 @@ class Comments(INIBasedModel.Comments):
Interpolation.linear.value, alias="interpolate"
)

@classmethod
def _get_unknown_keyword_error_manager(cls) -> Optional[UnknownKeywordErrorManager]:
"""
The StorageNode does not currently support raising an error on unknown keywords.
"""
return None

_interpolation_validator = get_enum_validator("interpolate", enum=Interpolation)
_nodetype_validator = get_enum_validator("nodetype", enum=NodeType)
_storagetype_validator = get_enum_validator("storagetype", enum=StorageType)
Expand Down
8 changes: 8 additions & 0 deletions hydrolib/core/dflowfm/structure/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from hydrolib.core.dflowfm.friction.models import FrictionType
from hydrolib.core.dflowfm.ini.models import INIBasedModel, INIGeneral, INIModel
from hydrolib.core.dflowfm.ini.util import (
UnknownKeywordErrorManager,
get_enum_validator,
get_from_subclass_defaults,
get_split_string_on_delimiter_validator,
Expand Down Expand Up @@ -83,6 +84,13 @@ class Comments(INIBasedModel.Comments):
_loc_branch_fields = {"branchid", "chainage"}
_loc_all_fields = _loc_coord_fields | _loc_branch_fields

@classmethod
def _get_unknown_keyword_error_manager(cls) -> Optional[UnknownKeywordErrorManager]:
"""
The Structure does not currently support raising an error on unknown keywords.
"""
return None

_split_to_list = get_split_string_on_delimiter_validator(
"xcoordinates", "ycoordinates"
)
Expand Down
Loading

0 comments on commit b7dc73a

Please sign in to comment.