Skip to content

Commit aeeafc4

Browse files
committed
Improve handling of unknown fields in the configuration
If `unkown` is not specified in the `marshmallow_load_kwargs`, it will default to `marshmallow.EXCLUDE` instead of `marshmallow.RAISE`, as this is what most users will need when loading to a dataclass. But when `marshmallow.EXCLUDE` is used, a warning will be logged if there are fields in the configuration that are being excluded, so the user can still be aware of it and catch typos in the configuration file. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 6817362 commit aeeafc4

File tree

1 file changed

+69
-2
lines changed

1 file changed

+69
-2
lines changed

src/frequenz/sdk/config/_manager.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from datetime import timedelta
1010
from typing import Any, Final
1111

12+
import marshmallow
1213
from frequenz.channels import Broadcast, Receiver
1314
from frequenz.channels.experimental import WithPrevious
1415
from marshmallow import Schema, ValidationError
@@ -177,6 +178,14 @@ def new_receiver( # pylint: disable=too-many-arguments
177178
Additional arguments can be passed to [`marshmallow.Schema.load`][] using
178179
the `marshmallow_load_kwargs` keyword arguments.
179180
181+
If unspecified, the `marshmallow_load_kwargs` will have the `unknown` key set to
182+
[`marshmallow.EXCLUDE`][] (instead of the normal [`marshmallow.RAISE`][]
183+
default).
184+
185+
But when [`marshmallow.EXCLUDE`][] is used, a warning will still be logged if
186+
there are extra fields in the configuration that are excluded. This is useful,
187+
for example, to catch typos in the configuration file.
188+
180189
### Skipping superfluous updates
181190
182191
If there is a burst of configuration updates, the receiver will only receive the
@@ -220,7 +229,22 @@ def new_receiver( # pylint: disable=too-many-arguments
220229
221230
Returns:
222231
The receiver for the configuration.
232+
233+
Raises:
234+
ValueError: If the `unknown` option in `marshmallow_load_kwargs` is set to
235+
[`marshmallow.INCLUDE`][].
223236
"""
237+
marshmallow_load_kwargs = (
238+
{} if marshmallow_load_kwargs is None else marshmallow_load_kwargs.copy()
239+
)
240+
241+
if "unknown" not in marshmallow_load_kwargs:
242+
marshmallow_load_kwargs["unknown"] = marshmallow.EXCLUDE
243+
elif marshmallow_load_kwargs["unknown"] == marshmallow.INCLUDE:
244+
raise ValueError(
245+
"The 'unknown' option can't be 'INCLUDE' when loading to a dataclass"
246+
)
247+
224248
receiver = self.config_channel.new_receiver(name=f"{self}:{key}", limit=1).map(
225249
lambda config: _load_config_with_logging_and_errors(
226250
config,
@@ -286,9 +310,10 @@ def _load_config_with_logging_and_errors(
286310
_logger.debug("Configuration key %r not found, sending None", key)
287311
return None
288312

289-
loaded_config = load_config(
290-
config_class,
313+
loaded_config = _load_config(
291314
sub_config,
315+
config_class,
316+
key=key,
292317
base_schema=base_schema,
293318
marshmallow_load_kwargs=marshmallow_load_kwargs,
294319
)
@@ -356,3 +381,45 @@ def _get_key(
356381
)
357382
value = new_value
358383
return value
384+
385+
386+
def _load_config(
387+
config: Mapping[str, Any],
388+
config_class: type[DataclassT],
389+
*,
390+
key: str | Sequence[str],
391+
base_schema: type[Schema] | None = None,
392+
marshmallow_load_kwargs: dict[str, Any] | None = None,
393+
) -> DataclassT | InvalidValueForKeyError | ValidationError | None:
394+
"""Try to load a configuration and log any validation errors."""
395+
loaded_config = load_config(
396+
config_class,
397+
config,
398+
base_schema=base_schema,
399+
marshmallow_load_kwargs=marshmallow_load_kwargs,
400+
)
401+
402+
marshmallow_load_kwargs = (
403+
{} if marshmallow_load_kwargs is None else marshmallow_load_kwargs.copy()
404+
)
405+
406+
unknown = marshmallow_load_kwargs.get("unknown")
407+
if unknown == marshmallow.EXCLUDE:
408+
# When excluding unknown fields we still want to notify the user, as
409+
# this could mean there is a typo in the configuration and some value is
410+
# not being loaded as desired.
411+
marshmallow_load_kwargs["unknown"] = marshmallow.RAISE
412+
try:
413+
load_config(
414+
config_class,
415+
config,
416+
base_schema=base_schema,
417+
marshmallow_load_kwargs=marshmallow_load_kwargs,
418+
)
419+
except ValidationError as err:
420+
_logger.warning(
421+
"The configuration for key %r has extra fields that will be ignored: %s",
422+
key,
423+
err,
424+
)
425+
return loaded_config

0 commit comments

Comments
 (0)