From fb7536bf0404c5bfe5637529c9450f11ca44c1ea Mon Sep 17 00:00:00 2001 From: M Platypus Date: Wed, 29 Jul 2026 01:44:35 -0400 Subject: [PATCH] fix: deep-merge nested dicts in ConfigDict constructor's *args path The constructor merged positional-arg dicts into the base input with a shallow dict.update(), so a partial override of a nested key (e.g. training.native_amp) wholesale-replaced the entire nested dict, silently dropping sibling defaults (training.a). Adds a _deep_merge helper and uses it in the *args path, matching the recursive-merge behavior ConfigDict.update() already has. Co-Authored-By: Claude Sonnet 5 --- tinyml-modelmaker/tests/test_config_dict.py | 6 ++++++ .../tinyml_modelmaker/utils/config_dict.py | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tinyml-modelmaker/tests/test_config_dict.py b/tinyml-modelmaker/tests/test_config_dict.py index 1372f544..2255d786 100644 --- a/tinyml-modelmaker/tests/test_config_dict.py +++ b/tinyml-modelmaker/tests/test_config_dict.py @@ -44,3 +44,9 @@ def test_none_input(self): cfg = ConfigDict(None) # Should create an empty config without error assert isinstance(cfg, ConfigDict) + + def test_constructor_args_deep_merge_nested_dict(self): + default = dict(training=dict(a=1, b=2)) + user = dict(training=dict(b=99)) + cfg = ConfigDict(default, user) + assert dict(cfg.training) == {"a": 1, "b": 99} diff --git a/tinyml-modelmaker/tinyml_modelmaker/utils/config_dict.py b/tinyml-modelmaker/tinyml_modelmaker/utils/config_dict.py index 953fa23d..d40e4cf2 100644 --- a/tinyml-modelmaker/tinyml_modelmaker/utils/config_dict.py +++ b/tinyml-modelmaker/tinyml_modelmaker/utils/config_dict.py @@ -57,7 +57,7 @@ def __init__(self, input=None, *args, **kwargs): # override the entries with args for value in args: if isinstance(value, (dict, ConfigDict)): - input_dict.update(value) + self._deep_merge(input_dict, value) # # # override the entries with kwargs @@ -95,6 +95,17 @@ def __setstate__(self, state): def _initialize(self): pass + @staticmethod + def _deep_merge(target, source): + for key, value in source.items(): + if key in target and isinstance(target[key], (dict, ConfigDict)) and isinstance(value, (dict, ConfigDict)): + ConfigDict._deep_merge(target[key], value) + else: + target[key] = value + # + # + return target + def _parse_include_files(self, include_files, include_base_path): input_dict = {} include_files = list(include_files)