diff --git a/pyomo/common/config.py b/pyomo/common/config.py index 94e81531aa1..82f0966036b 100644 --- a/pyomo/common/config.py +++ b/pyomo/common/config.py @@ -1600,7 +1600,13 @@ def __call__( # ... and set the value, if appropriate if value is not NOTSET: - ans.set_value(value) + # Change in response to Pyomo/pyomo#3721 + # If setting a ConfigDict from a (nonempty) dict during __call__, + # do not mark the container itself as user-set; only the children. + if isinstance(ans, ConfigDict) and isinstance(value, dict): + ans.set_value(value, _mark_container_userSet=False) + else: + ans.set_value(value) return ans def name(self, fully_qualified=False): @@ -2580,7 +2586,7 @@ def value(self, accessValue=True): self._userAccessed = True return {cfg._name: cfg.value(accessValue) for cfg in self._data.values()} - def set_value(self, value, skip_implicit=False): + def set_value(self, value, skip_implicit=False, _mark_container_userSet=True): if value is None: return self if isinstance(value, str): @@ -2631,7 +2637,9 @@ def set_value(self, value, skip_implicit=False): self.reset() self.set_value(_old_data) raise - self._userSet = True + # Change in response to Pyomo/pyomo#3721 + if _mark_container_userSet: + self._userSet = True return self def reset(self): diff --git a/pyomo/common/tests/test_config.py b/pyomo/common/tests/test_config.py index eda594dfd0e..ea1a461bed4 100644 --- a/pyomo/common/tests/test_config.py +++ b/pyomo/common/tests/test_config.py @@ -1599,6 +1599,24 @@ def test_UserValues_declare_subBlock(self): test = '\n'.join(x.name(True) for x in self.config.user_values()) self.assertEqual(test, "") + def test_userValues_call_nonempty(self): + # See bug report in Pyomo/pyomo#3721 + default = ConfigDict() + default.declare("filename", ConfigValue(default=None, domain=str)) + cfg = default(value={"filename": "example.txt"}) + names = [x.name(True) for x in cfg.user_values()] + self.assertEqual(names, ["filename"]) + self.assertTrue(all(x is not cfg for x in cfg.user_values())) + + def test_userValues_call_empty_then_set(self): + # See bug report in Pyomo/pyomo#3721 + default = ConfigDict() + default.declare("filename", ConfigValue(default=None, domain=str)) + cfg = default({}) + cfg["filename"] = "example.txt" + names = [x.name(True) for x in cfg.user_values()] + self.assertEqual(names, ["filename"]) + @unittest.skipIf(not yaml_available, "Test requires PyYAML") def test_parseDisplayAndValue_default(self): test = _display(self.config)