From e9902d8ce2ac4f29bbd1dcfd4037d15ac9ae98b5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 4 Apr 2013 11:24:00 +0200 Subject: [PATCH] rewrite make_dict --- test/test_config_api.py | 6 +- test/test_parsing_group.py | 4 +- tiramisu/config.py | 187 +++++++++++++++++++++---------------- tiramisu/option.py | 52 +++++------ tiramisu/value.py | 14 +-- 5 files changed, 141 insertions(+), 122 deletions(-) diff --git a/test/test_config_api.py b/test/test_config_api.py index 4272389..a26f1d7 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -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(): diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 09797c1..f69b67d 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -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() diff --git a/tiramisu/config.py b/tiramisu/config.py index caf808d..eca1751 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -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` + def getpath(self): + return None - 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 _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 first: - if getvalue: - return value - else: - return option + if ret == 'value': + retval = value + elif ret == 'path': + retval = path else: - if getvalue: - find_results.append(value) - else: - find_results.append(option) + retval = option + if first: + return retval + else: + 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 diff --git a/tiramisu/option.py b/tiramisu/option.py index 3710d6d..5b9b222 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -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) diff --git a/tiramisu/value.py b/tiramisu/value.py index 9f9e4d6..d24d8d8 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -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)