rewrite make_dict
authorEmmanuel Garette <egarette@cadoles.com>
Thu, 4 Apr 2013 09:24:00 +0000 (11:24 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Thu, 4 Apr 2013 09:24:00 +0000 (11:24 +0200)
test/test_config_api.py
test/test_parsing_group.py
tiramisu/config.py
tiramisu/option.py
tiramisu/value.py

index 4272389..a26f1d7 100644 (file)
@@ -122,13 +122,13 @@ def test_make_dict():
             BoolOption("a", "", default=False)]),
         IntOption("int", "", default=42)])
     config = Config(descr)
-    d = make_dict(config)
+    d = config.make_dict()
     assert d == {"s1.a": False, "int": 42}
     config.int = 43
     config.s1.a = True
-    d = make_dict(config)
+    d = config.make_dict()
     assert d == {"s1.a": True, "int": 43}
-    d2 = make_dict(config, flatten=True)
+    d2 = config.make_dict(flatten=True)
     assert d2 == {'a': True, 'int': 43}
 
 #def test_delattr():
index 09797c1..f69b67d 100644 (file)
@@ -46,12 +46,12 @@ def test_base_config():
     'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris',
     'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine':
     'eoleng', 'general.activer_proxy_client': False}
-    assert make_dict(config.creole) == result
+    assert config.creole.make_dict() == result
     result = {'serveur_ntp': [], 'mode_conteneur_actif': False,
     'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None,
     'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client':
     False, 'nombre_interfaces': 1}
-    assert make_dict(config.creole, flatten=True) == result
+    assert config.creole.make_dict(flatten=True) == result
 
 def test_get_group_type():
     descr = make_description()
index caf808d..eca1751 100644 (file)
@@ -124,7 +124,6 @@ class SubConfig(object):
             rootconfig = self.cfgimpl_get_context()
             path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt)
             return getattr(rootconfig, path)
-
         self._validate(name, opt_or_descr, force_permissive=force_permissive)
         if isinstance(opt_or_descr, OptionDescription):
             children = self.cfgimpl_get_description()._children
@@ -283,23 +282,100 @@ class SubConfig(object):
         return context_descr.get_path_by_opt(descr)
 
     def get(self, name):
-        path = self.getpath()
-        return self.cfgimpl_get_context().get(name, _subpath=path)
+        """
+        same as a `find_first()` method in a config that has identical names:
+        it returns the first item of an option named `name`
+
+        much like the attribute access way, except that
+        the search for the option is performed recursively in the whole
+        configuration tree.
+
+        :returns: option value.
+        """
+        return self.cfgimpl_get_context()._find(byname=name, bytype=None,
+                                                byvalue=None, byattrs=None,
+                                                first=True, ret='value',
+                                                _subpath=self.getpath())
 
     def find(self, bytype=None, byname=None, byvalue=None, byattrs=None):
-        path = self.getpath()
-        return self.cfgimpl_get_context().find(bytype=bytype, byname=byname,
-                                               byvalue=byvalue,
-                                               byattrs=byattrs,
-                                               _subpath=path)
+        """
+            finds a list of options recursively in the config
+
+            :param bytype: Option class (BoolOption, StrOption, ...)
+            :param byname: filter by Option._name
+            :param byvalue: filter by the option's value
+            :param byattrs: dict of option attributes (default, callback...)
+            :returns: list of matching Option objects
+        """
+        return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
+                                                byattrs, first=False,
+                                                _subpath=self.getpath())
 
     def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None):
-        path = self.getpath()
-        return self.cfgimpl_get_context().find_first(bytype=bytype,
-                                                     byname=byname,
-                                                     byvalue=byvalue,
-                                                     byattrs=byattrs,
-                                                     _subpath=path)
+        """
+            finds an option recursively in the config
+
+            :param bytype: Option class (BoolOption, StrOption, ...)
+            :param byname: filter by Option._name
+            :param byvalue: filter by the option's value
+            :param byattrs: dict of option attributes (default, callback...)
+            :returns: list of matching Option objects
+        """
+        return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
+                                                byattrs, first=True,
+                                                _subpath=self.getpath())
+
+    def make_dict(self, flatten=False, _currpath=None, withoption=None, withvalue=None):
+        """export the whole config into a `dict`
+        :returns: dict of Option's name (or path) and values"""
+        pathsvalues = []
+        if _currpath is None:
+            _currpath = []
+        if withoption is None and withvalue is not None:
+            raise ValueError("make_dict can't filtering with value without option")
+        if withoption is not None:
+            mypath = self.getpath()
+            for path in self.cfgimpl_get_context()._find(bytype=Option, byname=withoption,
+                                                         byvalue=withvalue, byattrs=None,
+                                                         first=False, ret='path', _subpath=mypath):
+                path = '.'.join(path.split('.')[:-1])
+                opt = self.cfgimpl_get_context().cfgimpl_get_description().get_opt_by_path(path)
+                if mypath is not None:
+                    if mypath == path:
+                        withoption = None
+                        withvalue = None
+                        break
+                    else:
+                        tmypath = mypath + '.'
+                        if not path.startswith(tmypath):
+                            raise Exception('unexpected path {}, '
+                                            'should start with {}'.format(path, mypath))
+                        path = path[len(tmypath):]
+                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
+        #withoption can be set to None below !
+        if withoption is None:
+            for opt in self.cfgimpl_get_description().getchildren():
+                path = opt._name
+                self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
+        if _currpath == []:
+            options = dict(pathsvalues)
+            return options
+        return pathsvalues
+
+    def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten):
+        if isinstance(opt, OptionDescription):
+            pathsvalues += getattr(self, path).make_dict(flatten,
+                                                         _currpath + path.split('.'))
+        else:
+            try:
+                value = self._getattr(opt._name)
+                if flatten:
+                    name = opt._name
+                else:
+                    name = '.'.join(_currpath + [opt._name])
+                pathsvalues.append((name, value))
+            except PropertiesOptionError:
+                pass  # this just a hidden or disabled option
 
 
 # ____________________________________________________________
@@ -376,21 +452,10 @@ class Config(SubConfig):
                     'there is no option that matches %s'
                     ' or the option is hidden or disabled' % (key, ))
 
-    def get(self, name, _subpath=None):
-        """
-        same as a `find_first()` method in a config that has identical names:
-        it returns the first item of an option named `name`
-
-        much like the attribute access way, except that
-        the search for the option is performed recursively in the whole
-        configuration tree.
-
-        :returns: option value.
-        """
-        return self._find(byname=name, bytype=None, byvalue=None, byattrs=None,
-                          first=True, getvalue=True, _subpath=_subpath)
+    def getpath(self):
+        return None
 
-    def _find(self, bytype, byname, byvalue, byattrs, first, getvalue=False,
+    def _find(self, bytype, byname, byvalue, byattrs, first, ret='option',
               _subpath=None):
         """
         convenience method for finding an option that lives only in the subtree
@@ -436,14 +501,15 @@ class Config(SubConfig):
                     else:
                         continue
             return True
-
+        if ret not in ('option', 'path', 'value'):
+            raise ValueError('unknown ret type {} for _find'.format(ret))
         find_results = []
         opts, paths = self.cfgimpl_get_description()._cache_paths
         for index in range(0, len(paths)):
-            path = paths[index]
             option = opts[index]
             if isinstance(option, OptionDescription):
                 continue
+            path = paths[index]
             if _subpath is not None and not path.startswith(_subpath + '.'):
                 continue
             if not _filter_by_name():
@@ -459,64 +525,21 @@ class Config(SubConfig):
                 value = getattr(self, path)
             except:  # a property restricts the access of the value
                 continue
+            if ret == 'value':
+                retval = value
+            elif ret == 'path':
+                retval = path
+            else:
+                retval = option
             if first:
-                if getvalue:
-                    return value
-                else:
-                    return option
+                return retval
             else:
-                if getvalue:
-                    find_results.append(value)
-                else:
-                    find_results.append(option)
+                find_results.append(retval)
         if find_results == []:
             raise NotFoundError("no option found in config with these criteria")
         else:
             return find_results
 
-    def find(self, bytype=None, byname=None, byvalue=None, byattrs=None, _subpath=None):
-        """
-            finds a list of options recursively in the config
-
-            :param bytype: Option class (BoolOption, StrOption, ...)
-            :param byname: filter by Option._name
-            :param byvalue: filter by the option's value
-            :param byattrs: dict of option attributes (default, callback...)
-            :returns: list of matching Option objects
-        """
-        return self._find(bytype, byname, byvalue, byattrs, first=False, _subpath=_subpath)
-
-    def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None, _subpath=None):
-        """
-            finds an option recursively in the config
-
-            :param bytype: Option class (BoolOption, StrOption, ...)
-            :param byname: filter by Option._name
-            :param byvalue: filter by the option's value
-            :param byattrs: dict of option attributes (default, callback...)
-            :returns: list of matching Option objects
-        """
-        return self._find(bytype, byname, byvalue, byattrs, first=True, _subpath=_subpath)
-
-
-def make_dict(config, flatten=False):
-    """export the whole config into a `dict`
-    :returns: dict of Option's name (or path) and values"""
-    paths = config.getpaths()
-    pathsvalues = []
-    for path in paths:
-        if flatten:
-            pathname = path.split('.')[-1]
-        else:
-            pathname = path
-        try:
-            value = getattr(config, path)
-            pathsvalues.append((pathname, value))
-        except:
-            pass  # this just a hidden or disabled option
-    options = dict(pathsvalues)
-    return options
-
 
 def mandatory_warnings(config):
     """convenience function to trace Options that are mandatory and
index 3710d6d..5b9b222 100644 (file)
@@ -408,63 +408,59 @@ class OptionDescription(BaseInformation):
         return self.get_information('doc')
 
     def __getattr__(self, name):
-        if name in self._children[0]:
+        try:
             return self._children[1][self._children[0].index(name)]
-        else:
-            try:
-                object.__getattr__(self, name)
-            except AttributeError:
-                raise AttributeError('unknown Option {} in OptionDescription {}'
-                                     ''.format(name, self._name))
+        except ValueError:
+            raise AttributeError('unknown Option {} in OptionDescription {}'
+                                 ''.format(name, self._name))
 
     def getkey(self, config):
         return tuple([child.getkey(getattr(config, child._name))
                       for child in self._children[1]])
 
-    def getpaths(self, include_groups=False, currpath=None):
+    def getpaths(self, include_groups=False, _currpath=None):
         """returns a list of all paths in self, recursively
-           currpath should not be provided (helps with recursion)
+           _currpath should not be provided (helps with recursion)
         """
         #FIXME : cache
-        if currpath is None:
-            currpath = []
+        if _currpath is None:
+            _currpath = []
         paths = []
         for option in self._children[1]:
             attr = option._name
-            if attr.startswith('_cfgimpl'):
-                continue
             if isinstance(option, OptionDescription):
                 if include_groups:
-                    paths.append('.'.join(currpath + [attr]))
-                currpath.append(attr)
+                    paths.append('.'.join(_currpath + [attr]))
                 paths += option.getpaths(include_groups=include_groups,
-                                         currpath=currpath)
-                currpath.pop()
+                                         _currpath=_currpath + [attr])
             else:
-                paths.append('.'.join(currpath + [attr]))
+                paths.append('.'.join(_currpath + [attr]))
         return paths
 
-    def build_cache(self, cache_path=None, cache_option=None, currpath=None):
-        if currpath is None and self._cache_paths is not None:
+    def getchildren(self):
+        return self._children[1]
+
+    def build_cache(self, cache_path=None, cache_option=None, _currpath=None):
+        if _currpath is None and self._cache_paths is not None:
             return
-        if currpath is None:
+        if _currpath is None:
             save = True
-            currpath = []
+            _currpath = []
         else:
             save = False
         if cache_path is None:
-            cache_path = []
-            cache_option = []
+            cache_path = [self._name]
+            cache_option = [self]
         for option in self._children[1]:
             attr = option._name
             if attr.startswith('_cfgimpl'):
                 continue
             cache_option.append(option)
-            cache_path.append(str('.'.join(currpath + [attr])))
+            cache_path.append(str('.'.join(_currpath + [attr])))
             if isinstance(option, OptionDescription):
-                currpath.append(attr)
-                option.build_cache(cache_path, cache_option, currpath)
-                currpath.pop()
+                _currpath.append(attr)
+                option.build_cache(cache_path, cache_option, _currpath)
+                _currpath.pop()
         if save:
             #valid no duplicated option
             valid_child = copy(cache_option)
index 9f9e4d6..d24d8d8 100644 (file)
@@ -123,13 +123,13 @@ class Values(object):
     def _getitem(self, opt, force_properties=None):
         # options with callbacks
         value = self._get_value(opt)
+        setting = self.context.cfgimpl_get_settings()
         if opt.has_callback():
-            setting = self.context.cfgimpl_get_settings()
-            if (not setting.has_property('frozen', opt) or
-                (setting.has_property('frozen', opt) and
-                 not setting.has_property('force_default_on_freeze', opt)
-                 )) and not self.context.cfgimpl_get_values().is_default_owner(opt):
-                return self._get_value(opt)
+            is_frozen = setting.has_property('frozen', opt)
+            if (not is_frozen or (is_frozen and
+                                  not setting.has_property('force_default_on_freeze', opt)
+                                  )) and not self.context.cfgimpl_get_values().is_default_owner(opt):
+                return value
             try:
                 result = opt.getcallback_value(self.context)
             except NoValueReturned:
@@ -149,7 +149,7 @@ class Values(object):
                     raise ConfigError('invalid calculated value returned'
                                       ' for option {0}'.format(opt._name))
         # frozen and force default
-        if not opt.has_callback() and self.context.cfgimpl_get_settings().has_property('force_default_on_freeze', opt):
+        elif setting.has_property('force_default_on_freeze', opt):
             value = opt.getdefault()
             if opt.is_multi():
                 value = self.fill_multi(opt, value)