Skip to content

Commit

Permalink
feat: (622) Update unknown keyword manager to raise an error instead …
Browse files Browse the repository at this point in the history
…of a message.
  • Loading branch information
MRVermeulenDeltares committed May 10, 2024
1 parent 060539c commit 790dfe4
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 203 deletions.
17 changes: 3 additions & 14 deletions hydrolib/core/dflowfm/ini/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
INISerializerConfig,
write_ini,
)
from .util import UnknownKeyNotificationManager, make_list_validator
from .util import UnknownKeywordErrorManager, make_list_validator

logger = logging.getLogger(__name__)

Expand All @@ -46,32 +46,21 @@ class INIBasedModel(BaseModel, ABC):

_header: str = ""
_file_path_style_converter = FilePathStyleConverter()
_unknown_key_notification_manager = UnknownKeyNotificationManager()
_unknown_keyword_error_manager = UnknownKeywordErrorManager()

class Config:
extra = Extra.ignore
arbitrary_types_allowed = False

def __init__(self, **data):
super().__init__(**data)
self._unknown_key_notification_manager.notify_unknown_keywords(
self._unknown_keyword_error_manager.raise_error_for_unknown_keywords(
data,
self._header,
self.__fields__,
self._exclude_fields(),
self.Config.extra,
)

def __setattr__(self, name, value):
self._unknown_key_notification_manager.notify_unknown_keyword(
name,
self._header,
self.__fields__,
self._exclude_fields(),
self.Config.extra,
)
super().__setattr__(name, value)

@classmethod
def _supports_comments(cls):
return True
Expand Down
69 changes: 6 additions & 63 deletions hydrolib/core/dflowfm/ini/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,19 +625,18 @@ def rename_keys_for_backwards_compatibility(
return values


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

def notify_unknown_keywords(
def raise_error_for_unknown_keywords(
self,
data: Dict[str, Any],
section_header: str,
fields: Dict[str, Any],
excluded_fields: Set,
config_extra: Extra,
):
"""
Notify the user of unknown keywords.
Expand All @@ -647,37 +646,13 @@ def notify_unknown_keywords(
section_header (str) : Header of the section in which unknown keys might be detected.
fields (Dict[str, Any]) : Known fields of the section.
excluded_fields (Set) : Fields which should be excluded from the check for unknown keywords.
config_extra (Extra) : Setting which determines if unknown keywords are allowed or dropped.
"""
unknown_keywords = self._get_all_unknown_keywords(data, fields, excluded_fields)

if len(unknown_keywords) == 0:
return

self._print_list_of_unknown_keywords(
section_header, config_extra, unknown_keywords
)

def notify_unknown_keyword(
self,
name: str,
section_header: str,
fields: Dict[str, Any],
excluded_fields: Set,
config_extra: Extra,
):
"""
Notify the user of a unknown keyword.
Args:
name (str) : Keyword which is checked if it is an unknown keyword.
section_header (str) : Header of the section in which unknown keys might be detected.
fields (Dict[str, Any]) : Known fields of the section.
excluded_fields (Set) : Fields which should be excluded from the check for unknown keywords.
config_extra (Extra) : Setting which determines if unknown keywords are allowed or dropped.
"""
if self._is_unknown_keyword(name, fields, excluded_fields):
self._print_single_unknown_keyword(name, section_header, config_extra)

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, Any], excluded_fields: Set
Expand All @@ -693,35 +668,3 @@ def _is_unknown_keyword(
self, name: str, fields: Dict[str, Any], excluded_fields: Set
):
return name not in fields and name not in excluded_fields

def _print_list_of_unknown_keywords(
self,
section_header: str,
config_extra: Extra,
list_of_unknown_keywords: List[str],
):
if config_extra == Extra.allow:
print(
f"Unknown keywords are detected in '{section_header}', these keywords will be kept in memory but will have no validation:"
)
else:
print(
f"Unknown keywords are detected in '{section_header}', these keywords will be dropped:"
)

for name in list_of_unknown_keywords:
print(name)

print()

def _print_single_unknown_keyword(
self, name: str, section_header: str, config_extra: Extra
):
if config_extra == Extra.allow:
print(
f"Unknown keyword detected in '{section_header}', '{name}', keyword will be kept in memory but will have no validation."
)
else:
print(
f"Unknown keyword detected in '{section_header}', '{name}', keyword will be dropped."
)
146 changes: 20 additions & 126 deletions tests/dflowfm/ini/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from hydrolib.core.dflowfm.ini.util import (
LocationValidationConfiguration,
LocationValidationFieldNames,
UnknownKeyNotificationManager,
UnknownKeywordErrorManager,
get_from_subclass_defaults,
get_type_based_on_subclass_default_value,
rename_keys_for_backwards_compatibility,
Expand Down Expand Up @@ -359,131 +359,25 @@ class GrandChildWithDefaultProperty(WithoutDefaultProperty):
name: Literal["GrandChildWithDefaultProperty"] = "GrandChildWithDefaultProperty"


class TestUnknownKeyNotificationManager:
class TestUnknownKeywordErrorManager:

section_header = "section header"
fields = {}
excluded_fields = set()
name = "keyname"
second_name = "second_other"

@pytest.fixture
def setup(self):
self.section_header = "section header"
self.fields = {}
self.excluded_fields = set()
self.name = "keyname"
self.second_name = "second_other"

@pytest.mark.parametrize(
"config_extra, expected_message",
[
pytest.param(
Extra.allow,
f"Unknown keyword detected in '{section_header}', '{name}', keyword will be kept in memory but will have no validation.",
),
pytest.param(
Extra.ignore,
f"Unknown keyword detected in '{section_header}', '{name}', keyword will be dropped.",
),
pytest.param(
Extra.forbid,
f"Unknown keyword detected in '{section_header}', '{name}', keyword will be dropped.",
),
],
)
def test_unknown_keyword_given_and_unknown_keyword_gives_message_with_keyword_kept(
self, config_extra, expected_message, capsys, setup
):
uknm = UnknownKeyNotificationManager()

uknm.notify_unknown_keyword(
self.name,
self.section_header,
self.fields,
self.excluded_fields,
config_extra,
)
captured = capsys.readouterr()

assert expected_message in captured.out

@pytest.mark.parametrize(
"config_extra",
[
pytest.param(Extra.allow),
pytest.param(Extra.ignore),
pytest.param(Extra.forbid),
],
)
def test_unknown_keyword_given_no_unknown_keys_gives_no_message(
self, config_extra, capsys, setup
):
uknm = UnknownKeyNotificationManager()
self.fields[self.name] = 1

uknm.notify_unknown_keyword(
self.name,
self.section_header,
self.fields,
self.excluded_fields,
config_extra,
)
captured = capsys.readouterr()

assert len(captured.out) == 0

@pytest.mark.parametrize(
"config_extra, expected_message",
[
pytest.param(
Extra.allow,
f"Unknown keywords are detected in '{section_header}', these keywords will be kept in memory but will have no validation:",
),
pytest.param(
Extra.ignore,
f"Unknown keywords are detected in '{section_header}', these keywords will be dropped:",
),
pytest.param(
Extra.forbid,
f"Unknown keywords are detected in '{section_header}', these keywords will be dropped:",
),
],
)
def test_unknown_keywords_given_and_unknown_keywords_gives_message_with_keyword_kept(
self, config_extra, expected_message, capsys, setup
def test_unknown_keywords_given_when_notify_unknown_keywords_gives_error_with_unknown_keywords(
self
):
uknm = UnknownKeyNotificationManager()
data = {self.name: 1, self.second_name: 2}

uknm.notify_unknown_keywords(
data, self.section_header, self.fields, self.excluded_fields, config_extra
)
captured = capsys.readouterr()

assert expected_message in captured.out
assert self.name in captured.out
assert self.second_name in captured.out

@pytest.mark.parametrize(
"config_extra",
[
pytest.param(Extra.allow),
pytest.param(Extra.ignore),
pytest.param(Extra.forbid),
],
)
def test_unknown_keywords_given_no_unknown_keys_gives_no_message(
self, config_extra, capsys, setup
):
uknm = UnknownKeyNotificationManager()
data = {}
data[self.name] = 1
self.fields[self.name] = 1

uknm.notify_unknown_keywords(
data, self.section_header, self.fields, self.excluded_fields, config_extra
)
captured = capsys.readouterr()
section_header = "section header"
fields = {}
excluded_fields = set()
name = "keyname"
second_name = "second_other"

ukem = UnknownKeywordErrorManager()
data = {name: 1, second_name: 2}

expected_message = f"Unknown keywords are detected in section: '{section_header}', '{[name, second_name]}'"

with pytest.raises(ValueError) as exc_err:
ukem.raise_error_for_unknown_keywords(
data, section_header, fields, excluded_fields
)

assert len(captured.out) == 0
assert expected_message in str(exc_err.value)

0 comments on commit 790dfe4

Please sign in to comment.