From 280f78dbb02459f513a29c70f884e8da155de442 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 8 Sep 2026 10:09:29 +0000 Subject: [PATCH] Raise `ValidationError` from the config test validators `frequenz-quantities` 1.0.2 changed its `marshmallow` optional dependency floor from `< 4` to `>= 4` (frequenz-floss/frequenz-quantities-python#107), correcting metadata that had been wrong since that library was ported to `marshmallow` 4. That cap was the only thing holding us on `marshmallow` 3, as our own requirement has allowed `< 5` since 870e8808, so CI now resolves `marshmallow` 4. `marshmallow` 4 removed support for validators that signal failure by returning `False`, deprecated in 3.24.0: Custom validators must raise a `ValidationError` for invalid values (deprecated in 3.24.0). Returning `False` is no longer supported. The configuration test dataclasses used exactly that pattern, a `lambda s: s.startswith("test")` in the field metadata. The validator is still registered and still called, only its return value is ignored, so the invalid value is accepted and `test_load_config_dataclass` and `test_new_receiver_configurations[Validation Error]` fail. Replacing the lambdas with a function that raises restores the checks. The message stays `Invalid value.`, which is what `marshmallow` 3 emitted through its `validator_failed` error key, so the expected output in `test_manager.py` is unchanged and the surrounding test cases remain consistent in asserting on `marshmallow`'s stock messages. Nothing under `src/` needed changing: the validators there are `marshmallow.validate.Range` and `OneOf`, which already raise. A raising validator also works on `marshmallow` 3, so the supported range is not narrowed, but the same silent acceptance can hit users who wrote their own boolean validators, hence the release notes entry. Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 2 ++ tests/config/test_manager.py | 15 ++++++++++++++- tests/config/test_util.py | 17 +++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 61ee6f2ad..412c6f25e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,8 @@ ## Upgrading +- Custom validators passed through a configuration dataclass field's `metadata` (as used by `load_config()` and `ConfigManager.new_receiver()`) must now raise a `marshmallow.ValidationError` to reject a value. `marshmallow` 4 removed support for validators that signal failure by returning `False`, so such validators are now ignored and the invalid value is accepted silently. This affects you as soon as `marshmallow` 4 is installed, which `frequenz-quantities` 1.0.2 and later require. + ## New Features diff --git a/tests/config/test_manager.py b/tests/config/test_manager.py index 7805eef5a..3a4341015 100644 --- a/tests/config/test_manager.py +++ b/tests/config/test_manager.py @@ -20,11 +20,24 @@ from frequenz.sdk.config._manager import _get_key +def _validate_name(value: str) -> None: + """Validate that a name starts with `test`. + + Args: + value: The name to validate. + + Raises: + marshmallow.ValidationError: If the name doesn't start with `test`. + """ + if not value.startswith("test"): + raise marshmallow.ValidationError("Invalid value.") + + @dataclass class SimpleConfig: """A simple configuration class for testing.""" - name: str = dataclasses.field(metadata={"validate": lambda s: s.startswith("test")}) + name: str = dataclasses.field(metadata={"validate": _validate_name}) value: int diff --git a/tests/config/test_util.py b/tests/config/test_util.py index 13283c165..c5beaa962 100644 --- a/tests/config/test_util.py +++ b/tests/config/test_util.py @@ -14,11 +14,24 @@ from frequenz.sdk.config._util import load_config +def _validate_name(value: str) -> None: + """Validate that a name starts with `test`. + + Args: + value: The name to validate. + + Raises: + marshmallow.ValidationError: If the name doesn't start with `test`. + """ + if not value.startswith("test"): + raise marshmallow.ValidationError("Invalid value.") + + @dataclasses.dataclass class SimpleConfig: """A simple configuration class for testing.""" - name: str = dataclasses.field(metadata={"validate": lambda s: s.startswith("test")}) + name: str = dataclasses.field(metadata={"validate": _validate_name}) value: int @@ -26,7 +39,7 @@ class SimpleConfig: class MmSimpleConfig: """A simple marshmallow_dataclass configuration class for testing.""" - name: str = dataclasses.field(metadata={"validate": lambda s: s.startswith("test")}) + name: str = dataclasses.field(metadata={"validate": _validate_name}) value: int