rewrite make_dict

This commit is contained in:
Emmanuel Garette 2013-04-04 11:24:00 +02:00
parent ccac34b2db
commit e9902d8ce2
5 changed files with 141 additions and 122 deletions

View File

@ -122,13 +122,13 @@ def test_make_dict():
BoolOption("a", "", default=False)]), BoolOption("a", "", default=False)]),
IntOption("int", "", default=42)]) IntOption("int", "", default=42)])
config = Config(descr) config = Config(descr)
d = make_dict(config) d = config.make_dict()
assert d == {"s1.a": False, "int": 42} assert d == {"s1.a": False, "int": 42}
config.int = 43 config.int = 43
config.s1.a = True config.s1.a = True
d = make_dict(config) d = config.make_dict()
assert d == {"s1.a": True, "int": 43} 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} assert d2 == {'a': True, 'int': 43}
#def test_delattr(): #def test_delattr():

View File

@ -46,12 +46,12 @@ def test_base_config():
'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris', 'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris',
'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine': 'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine':
'eoleng', 'general.activer_proxy_client': False} '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, result = {'serveur_ntp': [], 'mode_conteneur_actif': False,
'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None, 'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None,
'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client': 'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client':
False, 'nombre_interfaces': 1} 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(): def test_get_group_type():
descr = make_description() descr = make_description()

View File

@ -124,7 +124,6 @@ class SubConfig(object):
rootconfig = self.cfgimpl_get_context() rootconfig = self.cfgimpl_get_context()
path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt) path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt)
return getattr(rootconfig, path) return getattr(rootconfig, path)
self._validate(name, opt_or_descr, force_permissive=force_permissive) self._validate(name, opt_or_descr, force_permissive=force_permissive)
if isinstance(opt_or_descr, OptionDescription): if isinstance(opt_or_descr, OptionDescription):
children = self.cfgimpl_get_description()._children children = self.cfgimpl_get_description()._children
@ -283,23 +282,100 @@ class SubConfig(object):
return context_descr.get_path_by_opt(descr) return context_descr.get_path_by_opt(descr)
def get(self, name): 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): def find(self, bytype=None, byname=None, byvalue=None, byattrs=None):
path = self.getpath() """
return self.cfgimpl_get_context().find(bytype=bytype, byname=byname, finds a list of options recursively in the config
byvalue=byvalue,
byattrs=byattrs, :param bytype: Option class (BoolOption, StrOption, ...)
_subpath=path) :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): def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None):
path = self.getpath() """
return self.cfgimpl_get_context().find_first(bytype=bytype, finds an option recursively in the config
byname=byname,
byvalue=byvalue, :param bytype: Option class (BoolOption, StrOption, ...)
byattrs=byattrs, :param byname: filter by Option._name
_subpath=path) :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' 'there is no option that matches %s'
' or the option is hidden or disabled' % (key, )) ' or the option is hidden or disabled' % (key, ))
def get(self, name, _subpath=None): def getpath(self):
""" return 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 def _find(self, bytype, byname, byvalue, byattrs, first, ret='option',
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,
_subpath=None): _subpath=None):
""" """
convenience method for finding an option that lives only in the subtree convenience method for finding an option that lives only in the subtree
@ -436,14 +501,15 @@ class Config(SubConfig):
else: else:
continue continue
return True return True
if ret not in ('option', 'path', 'value'):
raise ValueError('unknown ret type {} for _find'.format(ret))
find_results = [] find_results = []
opts, paths = self.cfgimpl_get_description()._cache_paths opts, paths = self.cfgimpl_get_description()._cache_paths
for index in range(0, len(paths)): for index in range(0, len(paths)):
path = paths[index]
option = opts[index] option = opts[index]
if isinstance(option, OptionDescription): if isinstance(option, OptionDescription):
continue continue
path = paths[index]
if _subpath is not None and not path.startswith(_subpath + '.'): if _subpath is not None and not path.startswith(_subpath + '.'):
continue continue
if not _filter_by_name(): if not _filter_by_name():
@ -459,64 +525,21 @@ class Config(SubConfig):
value = getattr(self, path) value = getattr(self, path)
except: # a property restricts the access of the value except: # a property restricts the access of the value
continue continue
if ret == 'value':
retval = value
elif ret == 'path':
retval = path
else:
retval = option
if first: if first:
if getvalue: return retval
return value
else: else:
return option find_results.append(retval)
else:
if getvalue:
find_results.append(value)
else:
find_results.append(option)
if find_results == []: if find_results == []:
raise NotFoundError("no option found in config with these criteria") raise NotFoundError("no option found in config with these criteria")
else: else:
return find_results 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): def mandatory_warnings(config):
"""convenience function to trace Options that are mandatory and """convenience function to trace Options that are mandatory and

View File

@ -408,12 +408,9 @@ class OptionDescription(BaseInformation):
return self.get_information('doc') return self.get_information('doc')
def __getattr__(self, name): def __getattr__(self, name):
if name in self._children[0]:
return self._children[1][self._children[0].index(name)]
else:
try: try:
object.__getattr__(self, name) return self._children[1][self._children[0].index(name)]
except AttributeError: except ValueError:
raise AttributeError('unknown Option {} in OptionDescription {}' raise AttributeError('unknown Option {} in OptionDescription {}'
''.format(name, self._name)) ''.format(name, self._name))
@ -421,50 +418,49 @@ class OptionDescription(BaseInformation):
return tuple([child.getkey(getattr(config, child._name)) return tuple([child.getkey(getattr(config, child._name))
for child in self._children[1]]) 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 """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 #FIXME : cache
if currpath is None: if _currpath is None:
currpath = [] _currpath = []
paths = [] paths = []
for option in self._children[1]: for option in self._children[1]:
attr = option._name attr = option._name
if attr.startswith('_cfgimpl'):
continue
if isinstance(option, OptionDescription): if isinstance(option, OptionDescription):
if include_groups: if include_groups:
paths.append('.'.join(currpath + [attr])) paths.append('.'.join(_currpath + [attr]))
currpath.append(attr)
paths += option.getpaths(include_groups=include_groups, paths += option.getpaths(include_groups=include_groups,
currpath=currpath) _currpath=_currpath + [attr])
currpath.pop()
else: else:
paths.append('.'.join(currpath + [attr])) paths.append('.'.join(_currpath + [attr]))
return paths return paths
def build_cache(self, cache_path=None, cache_option=None, currpath=None): def getchildren(self):
if currpath is None and self._cache_paths is not None: 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 return
if currpath is None: if _currpath is None:
save = True save = True
currpath = [] _currpath = []
else: else:
save = False save = False
if cache_path is None: if cache_path is None:
cache_path = [] cache_path = [self._name]
cache_option = [] cache_option = [self]
for option in self._children[1]: for option in self._children[1]:
attr = option._name attr = option._name
if attr.startswith('_cfgimpl'): if attr.startswith('_cfgimpl'):
continue continue
cache_option.append(option) cache_option.append(option)
cache_path.append(str('.'.join(currpath + [attr]))) cache_path.append(str('.'.join(_currpath + [attr])))
if isinstance(option, OptionDescription): if isinstance(option, OptionDescription):
currpath.append(attr) _currpath.append(attr)
option.build_cache(cache_path, cache_option, currpath) option.build_cache(cache_path, cache_option, _currpath)
currpath.pop() _currpath.pop()
if save: if save:
#valid no duplicated option #valid no duplicated option
valid_child = copy(cache_option) valid_child = copy(cache_option)

View File

@ -123,13 +123,13 @@ class Values(object):
def _getitem(self, opt, force_properties=None): def _getitem(self, opt, force_properties=None):
# options with callbacks # options with callbacks
value = self._get_value(opt) value = self._get_value(opt)
if opt.has_callback():
setting = self.context.cfgimpl_get_settings() setting = self.context.cfgimpl_get_settings()
if (not setting.has_property('frozen', opt) or if opt.has_callback():
(setting.has_property('frozen', opt) and is_frozen = setting.has_property('frozen', opt)
if (not is_frozen or (is_frozen and
not setting.has_property('force_default_on_freeze', opt) not setting.has_property('force_default_on_freeze', opt)
)) and not self.context.cfgimpl_get_values().is_default_owner(opt): )) and not self.context.cfgimpl_get_values().is_default_owner(opt):
return self._get_value(opt) return value
try: try:
result = opt.getcallback_value(self.context) result = opt.getcallback_value(self.context)
except NoValueReturned: except NoValueReturned:
@ -149,7 +149,7 @@ class Values(object):
raise ConfigError('invalid calculated value returned' raise ConfigError('invalid calculated value returned'
' for option {0}'.format(opt._name)) ' for option {0}'.format(opt._name))
# frozen and force default # 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() value = opt.getdefault()
if opt.is_multi(): if opt.is_multi():
value = self.fill_multi(opt, value) value = self.fill_multi(opt, value)