diff --git a/pyutilib/misc/config.py b/pyutilib/misc/config.py index d7da10ee..6dec9223 100644 --- a/pyutilib/misc/config.py +++ b/pyutilib/misc/config.py @@ -35,7 +35,7 @@ def dump(x, **args): __all__ = ('ConfigBlock', 'ConfigList', 'ConfigValue') -logger = logging.getLogger('pyutilib.misc') +logger = logging.getLogger('pyutilib.misc.config') def _munge_name(name, space_to_dash=True): @@ -171,26 +171,70 @@ def __setstate__(self, state): # of setting self.__dict__[key] = val. object.__setattr__(self, key, val) - def __call__(self, value=NoArgument): - ans = self.__class__() - ans._default = self.value() - ans._domain = self._domain - ans._description = self._description - ans._doc = self._doc - ans._visibility = self._visibility - if self.__class__ is ConfigBlock: - ans._implicit_declaration = self._implicit_declaration - ans._implicit_domain = self._implicit_domain + def __call__(self, value=NoArgument, default=NoArgument, domain=NoArgument, + description=NoArgument, doc=NoArgument, visibility=NoArgument, + implicit=NoArgument, implicit_domain=NoArgument, + preserve_implicit=False): + # We will pass through overriding arguments to the constructor. + # This way if the constructor does special processing of any of + # the arguments (like implicit_domain), we don't have to repeat + # that code here. Unfortunately, it means we need to do a bit + # of logic to be sure we only pass through appropriate + # arguments. + kwds = {} + kwds['description'] = ( self._description + if description is ConfigBase.NoArgument else + description ) + kwds['doc'] = ( self._doc + if doc is ConfigBase.NoArgument else + doc ) + kwds['visibility'] = ( self._visibility + if visibility is ConfigBase.NoArgument else + visibility ) + if isinstance(self, ConfigBlock): + kwds['implicit'] = ( self._implicit_declaration + if implicit is ConfigBase.NoArgument else + implicit ) + kwds['implicit_domain'] = ( + self._implicit_domain + if implicit_domain is ConfigBase.NoArgument else + implicit_domain ) + if domain is not ConfigBase.NoArgument: + logger.warn("domain ignored by __call__(): " + "class is a ConfigBlock" % (type(self),)) + if default is not ConfigBase.NoArgument: + logger.warn("default ignored by __call__(): " + "class is a ConfigBlock" % (type(self),)) + else: + kwds['default'] = ( self.value() + if default is ConfigBase.NoArgument else + default ) + kwds['domain'] = ( self._domain + if domain is ConfigBase.NoArgument else + domain ) + if implicit is not ConfigBase.NoArgument: + logger.warn("implicit ignored by __call__(): " + "class %s is not a ConfigBlock" % (type(self),)) + if implicit_domain is not ConfigBase.NoArgument: + logger.warn("implicit_domain ignored by __call__(): " + "class %s is not a ConfigBlock" % (type(self),)) + + # Copy over any other object-specific information (mostly Block + # definitions) + ans = self.__class__(**kwds) + if isinstance(self, ConfigBlock): for k in self._decl_order: - if k in self._declared: + if preserve_implicit or k in self._declared: v = self._data[k] - ans._data[k] = _tmp = v() + ans._data[k] = _tmp = v(preserve_implicit=preserve_implicit) ans._decl_order.append(k) - ans._declared.add(k) + if k in self._declared: + ans._declared.add(k) _tmp._parent = ans _tmp._name = v._name else: ans.reset() + # ... and set the value, if appropriate if value is not ConfigBase.NoArgument: ans.set_value(value) return ans @@ -221,7 +265,10 @@ def _cast(self, value): return value if self._domain is not None: try: - return self._domain(value) + if value is not ConfigBase.NoArgument: + return self._domain(value) + else: + return self._domain() except: err = exc_info()[1] if hasattr(self._domain, '__name__'): diff --git a/pyutilib/misc/tests/test_config.py b/pyutilib/misc/tests/test_config.py index b13d18f0..e71d6b96 100644 --- a/pyutilib/misc/tests/test_config.py +++ b/pyutilib/misc/tests/test_config.py @@ -144,8 +144,8 @@ def setUp(self): } # Utility method for generating and validating a template description - def _validateTemplate(self, reference_template, **kwds): - test = self.config.generate_yaml_template(**kwds) + def _validateTemplate(self, config, reference_template, **kwds): + test = config.generate_yaml_template(**kwds) width = kwds.get('width', 80) indent = kwds.get('indent_spacing', 2) sys.stdout.write(test) @@ -183,7 +183,7 @@ def test_template_default(self): max pipes: 2 # Maximum number of pipes to close response time: 60.0 # Time [min] between detection and closing valves """ - self._validateTemplate(reference_template) + self._validateTemplate(self.config, reference_template) def test_template_3space(self): reference_template = """# Basic configuration for Flushing models @@ -215,7 +215,8 @@ def test_template_3space(self): response time: 60.0 # Time [min] between detection and closing # valves """ - self._validateTemplate(reference_template, indent_spacing=3) + self._validateTemplate(self.config, reference_template, + indent_spacing=3) def test_template_4space(self): reference_template = """# Basic configuration for Flushing models @@ -247,7 +248,8 @@ def test_template_4space(self): response time: 60.0 # Time [min] between detection and closing # valves """ - self._validateTemplate(reference_template, indent_spacing=4) + self._validateTemplate(self.config, reference_template, + indent_spacing=4) def test_template_3space_narrow(self): reference_template = """# Basic configuration for Flushing models @@ -280,7 +282,8 @@ def test_template_3space_narrow(self): response time: 60.0 # Time [min] between detection and closing # valves """ - self._validateTemplate(reference_template, indent_spacing=3, width=72) + self._validateTemplate(self.config, reference_template, + indent_spacing=3, width=72) def test_display_default(self): reference = """network: @@ -1507,5 +1510,60 @@ def test_set_value(self): self.assertEqual(config.a_c, 20) self.assertEqual(config.a_d_e, 30) + def test_call_options(self): + config = ConfigBlock(description="base description", + doc="base doc", + visibility=1, + implicit=True) + config.declare("a", ConfigValue(domain=int, doc="a doc", default=1)) + config.declare("b", config.get("a")(2)) + config.declare("c", config.get("a")(domain=float, doc="c doc")) + config.d = 0 + config.e = ConfigBlock(implicit=True) + config.e.a = 0 + + reference_template = """# base description +""" + self._validateTemplate(config, reference_template) + reference_template = """# base description +a: 1 +b: 2 +c: 1.0 +d: 0 +e: + a: 0 +""" + self._validateTemplate(config, reference_template, visibility=1) + + # Preserving implicit values should leave the copy the same as + # the original + implicit_copy = config(preserve_implicit=True) + self._validateTemplate(config, reference_template, visibility=1) + + # Simple copies strip out the implicitly-declared values + reference_template = """# base description +a: 1 +b: 2 +c: 1.0 +""" + simple_copy = config() + self._validateTemplate(simple_copy, reference_template, visibility=1) + self.assertEqual(simple_copy._doc, "base doc") + self.assertEqual(simple_copy._description, "base description") + self.assertEqual(simple_copy._visibility, 1) + + mod_copy = config(description="new description", + doc="new doc", + visibility=0) + reference_template = """# new description +a: 1 +b: 2 +c: 1.0 +""" + self._validateTemplate(mod_copy, reference_template, visibility=0) + self.assertEqual(mod_copy._doc, "new doc") + self.assertEqual(mod_copy._description, "new description") + self.assertEqual(mod_copy._visibility, 0) + if __name__ == "__main__": unittest.main()