refactoring, the values are in an OptionValues object

This commit is contained in:
gwen 2013-02-08 11:50:22 +01:00
parent 9259a6e3f7
commit a8e6bac87f
5 changed files with 191 additions and 163 deletions

View File

@ -111,10 +111,10 @@ def test_access_with_multi_default():
s = StrOption("string", "", default=["string"], multi=True) s = StrOption("string", "", default=["string"], multi=True)
descr = OptionDescription("options", "", [s]) descr = OptionDescription("options", "", [s])
config = Config(descr) config = Config(descr)
assert config._cfgimpl_values.owners[s] == 'default' assert config._cfgimpl_values.getowner(s) == 'default'
config.string = ["foo", "bar"] config.string = ["foo", "bar"]
assert config.string == ["foo", "bar"] assert config.string == ["foo", "bar"]
assert config._cfgimpl_values.owners[s] == 'user' assert config._cfgimpl_values.getowner(s) == 'user'
#def test_attribute_access_with_multi2(): #def test_attribute_access_with_multi2():
# s = StrOption("string", "", default="string", multi=True) # s = StrOption("string", "", default="string", multi=True)

View File

@ -25,9 +25,9 @@ from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError,
AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound,
MandatoryError, MethodCallError, NoValueReturned) MandatoryError, MethodCallError, NoValueReturned)
from tiramisu.option import (OptionDescription, Option, SymLinkOption, from tiramisu.option import (OptionDescription, Option, SymLinkOption,
Multi, apply_requires) apply_requires)
from tiramisu.setting import groups, owners, Setting from tiramisu.setting import groups, owners, Setting
from tiramisu.value import OptionValues from tiramisu.value import OptionValues, Multi
# ____________________________________________________________ # ____________________________________________________________
class Config(object): class Config(object):
@ -43,20 +43,23 @@ class Config(object):
:param context: the current root config :param context: the current root config
:type context: `Config` :type context: `Config`
""" """
# main option description
self._cfgimpl_descr = descr self._cfgimpl_descr = descr
# sub option descriptions
self._cfgimpl_subconfigs = {}
self._cfgimpl_parent = parent self._cfgimpl_parent = parent
if context is None:
self._cfgimpl_context = self
else:
self._cfgimpl_context = context
if parent == None: if parent == None:
self._cfgimpl_settings = Setting() self._cfgimpl_settings = Setting()
self._cfgimpl_values = OptionValues() self._cfgimpl_values = OptionValues(self._cfgimpl_context)
else: else:
if context is None: if context is None:
raise ConfigError("cannot find a value for this config") raise ConfigError("cannot find a value for this config")
self._cfgimpl_settings = None self._cfgimpl_settings = None
self._cfgimpl_values = None self._cfgimpl_values = None
if context is None:
self._cfgimpl_context = self
else:
self._cfgimpl_context = context
"warnings are a great idea, let's make up a better use of it" "warnings are a great idea, let's make up a better use of it"
self._cfgimpl_warnings = [] self._cfgimpl_warnings = []
self._cfgimpl_toplevel = self._cfgimpl_get_toplevel() self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
@ -91,21 +94,9 @@ class Config(object):
#max len for a master/slave group #max len for a master/slave group
max_len_child = 0 max_len_child = 0
for child in self._cfgimpl_descr._children: for child in self._cfgimpl_descr._children:
if isinstance(child, Option): if isinstance(child, OptionDescription):
if child.is_multi():
childdef = Multi(copy(child.getdefault()), config=self,
opt=child)
max_len_child = max(max_len_child, len(childdef))
self._cfgimpl_context._cfgimpl_values[child] = childdef
self._cfgimpl_context._cfgimpl_values.previous_values[child] = list(childdef)
else:
childdef = child.getdefault()
self._cfgimpl_context._cfgimpl_values[child] = childdef
self._cfgimpl_context._cfgimpl_values.previous_values[child] = childdef
child.setowner(self, owners.default)
elif isinstance(child, OptionDescription):
self._validate_duplicates(child._children) self._validate_duplicates(child._children)
self._cfgimpl_context._cfgimpl_values[child] = Config(child, parent=self, self._cfgimpl_subconfigs[child] = Config(child, parent=self,
context=self._cfgimpl_context) context=self._cfgimpl_context)
# def cfgimpl_update(self): # def cfgimpl_update(self):
@ -139,7 +130,7 @@ class Config(object):
if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption: if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
self._validate(name, getattr(self._cfgimpl_descr, name)) self._validate(name, getattr(self._cfgimpl_descr, name))
self.setoption(name, value, self.setoption(name, value,
self._cfgimpl_context._cfgimpl_settings.get_owner()) self._cfgimpl_context._cfgimpl_settings.getowner())
def _validate(self, name, opt_or_descr, permissive=False): def _validate(self, name, opt_or_descr, permissive=False):
"validation for the setattr and the getattr" "validation for the setattr and the getattr"
@ -161,23 +152,6 @@ class Config(object):
" {1}".format(name, str(properties)), " {1}".format(name, str(properties)),
properties) properties)
def _is_empty(self, opt):
"convenience method to know if an option is empty"
if (not opt.is_multi() and self._cfgimpl_context._cfgimpl_values[opt] == None) or \
(opt.is_multi() and (self._cfgimpl_context._cfgimpl_values[opt] == [] or \
None in self._cfgimpl_context._cfgimpl_values[opt])):
return True
return False
def _test_mandatory(self, path, opt):
# mandatory options
mandatory = self._cfgimpl_context._cfgimpl_settings.mandatory
if opt.is_mandatory() and mandatory:
if self._is_empty(opt) and \
opt.is_empty_by_default():
raise MandatoryError("option: {0} is mandatory "
"and shall have a value".format(path))
def __getattr__(self, name): def __getattr__(self, name):
return self._getattr(name) return self._getattr(name)
@ -185,12 +159,11 @@ class Config(object):
"""fills a multi option with default and calculated values """fills a multi option with default and calculated values
""" """
# FIXME C'EST ENCORE DU N'IMPORTE QUOI # FIXME C'EST ENCORE DU N'IMPORTE QUOI
value = self._cfgimpl_context._cfgimpl_values[opt]
if not isinstance(result, list): if not isinstance(result, list):
_result = [result] _result = [result]
else: else:
_result = result _result = result
return Multi(_result, value.config, opt=value.opt) return Multi(_result, self._cfgimpl_context, opt)
def _getattr(self, name, permissive=False): def _getattr(self, name, permissive=False):
""" """
@ -211,56 +184,18 @@ class Config(object):
if type(opt_or_descr) == SymLinkOption: if type(opt_or_descr) == SymLinkOption:
rootconfig = self._cfgimpl_get_toplevel() rootconfig = self._cfgimpl_get_toplevel()
return getattr(rootconfig, opt_or_descr.path) return getattr(rootconfig, opt_or_descr.path)
if opt_or_descr not in self._cfgimpl_context._cfgimpl_values:
raise AttributeError("%s object has no attribute %s" %
(self.__class__, name))
self._validate(name, opt_or_descr, permissive) self._validate(name, opt_or_descr, permissive)
if isinstance(opt_or_descr, OptionDescription):
if opt_or_descr not in self._cfgimpl_subconfigs:
raise AttributeError("%s with name %s object has no attribute %s" %
(self.__class__, opt_or_descr._name, name))
return self._cfgimpl_subconfigs[opt_or_descr]
# special attributes # special attributes
if name.startswith('_cfgimpl_'): if name.startswith('_cfgimpl_'):
# if it were in __dict__ it would have been found already # if it were in __dict__ it would have been found already
return self.__dict__[name] return self.__dict__[name]
raise AttributeError("%s object has no attribute %s" % return self._cfgimpl_context._cfgimpl_values[opt_or_descr]
(self.__class__, name))
if not isinstance(opt_or_descr, OptionDescription):
# options with callbacks
if opt_or_descr.has_callback():
value = self._cfgimpl_context._cfgimpl_values[opt_or_descr]
if (not opt_or_descr.is_frozen() or \
not opt_or_descr.is_forced_on_freeze()) and \
not opt_or_descr.is_default_owner(self):
return value
try:
result = opt_or_descr.getcallback_value(
self._cfgimpl_get_toplevel())
except NoValueReturned, err:
pass
else:
if opt_or_descr.is_multi():
_result = self.fill_multi(opt_or_descr, result)
else:
# this result **shall not** be a list
if isinstance(result, list):
raise ConfigError('invalid calculated value returned'
' for option {0} : shall not be a list'.format(name))
_result = result
if _result != None and not opt_or_descr.validate(_result,
self._cfgimpl_context._cfgimpl_settings.validator):
raise ConfigError('invalid calculated value returned'
' for option {0}'.format(name))
self._cfgimpl_context._cfgimpl_values[opt_or_descr] = _result
opt_or_descr.setowner(self, owners.default)
# frozen and force default
if not opt_or_descr.has_callback() and opt_or_descr.is_forced_on_freeze():
value = opt_or_descr.getdefault()
if opt_or_descr.is_multi():
value = self.fill_multi(opt_or_descr, value,
use_default_multi=True,
default_multi=opt_or_descr.getdefault_multi())
self._cfgimpl_context._cfgimpl_values[opt_or_descr] = value
opt_or_descr.setowner(self, owners.default)
self._test_mandatory(name, opt_or_descr)
value = self._cfgimpl_context._cfgimpl_values[opt_or_descr]
return value
def unwrap_from_name(self, name): def unwrap_from_name(self, name):
"""convenience method to extract and Option() object from the Config() """convenience method to extract and Option() object from the Config()
@ -301,7 +236,7 @@ class Config(object):
if not isinstance(who, owners.DefaultOwner): if not isinstance(who, owners.DefaultOwner):
if type(value) != Multi: if type(value) != Multi:
if type(value) == list: if type(value) == list:
value = Multi(value, self, child) value = Multi(value, self._cfgimpl_context, child)
else: else:
raise ConfigError("invalid value for option:" raise ConfigError("invalid value for option:"
" {0} that is set to multi".format(name)) " {0} that is set to multi".format(name))
@ -339,7 +274,7 @@ class Config(object):
except Exception, e: except Exception, e:
raise e # HiddenOptionError or DisabledOptionError raise e # HiddenOptionError or DisabledOptionError
homeconfig.setoption(name, value, homeconfig.setoption(name, value,
self._cfgimpl_context._cfgimpl_settings.get_owner()) self._cfgimpl_context._cfgimpl_settings.getowner())
elif len(candidates) > 1: elif len(candidates) > 1:
raise AmbigousOptionError( raise AmbigousOptionError(
'more than one option that ends with %s' % (key, )) 'more than one option that ends with %s' % (key, ))

View File

@ -27,6 +27,7 @@ from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError,
PropertiesOptionError) PropertiesOptionError)
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
from tiramisu.setting import groups, owners from tiramisu.setting import groups, owners
from tiramisu.value import Multi
requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')] requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')]
@ -37,62 +38,6 @@ for act1, act2 in requires_actions:
reverse_actions[act1] = act2 reverse_actions[act1] = act2
reverse_actions[act2] = act1 reverse_actions[act2] = act1
# ____________________________________________________________ # ____________________________________________________________
# multi types
class Multi(list):
"""multi options values container
that support item notation for the values of multi options"""
def __init__(self, lst, config, opt):
"""
:param lst: the Multi wraps a list value
:param config: the parent config
:param opt: the option object that have this Multi value
"""
self.config = config
self.opt = opt
super(Multi, self).__init__(lst)
def __setitem__(self, key, value):
self._setvalue(value, key,
who=self.config._cfgimpl_context._cfgimpl_settings.get_owner())
def append(self, value):
"""the list value can be updated (appened)
only if the option is a master
"""
self._setvalue(value,
who=self.config._cfgimpl_context._cfgimpl_settings.get_owner())
def _setvalue(self, value, key=None, who=None):
if value != None:
if not self.opt._validate(value):
raise ConfigError("invalid value {0} "
"for option {1}".format(str(value), self.opt._name))
oldvalue = list(self)
if key is None:
super(Multi, self).append(value)
else:
super(Multi, self).__setitem__(key, value)
if who != None:
if not isinstance(who, owners.Owner):
raise TypeError("invalid owner {0} for the value {1}".format(
str(who), str(value)))
self.opt.setowner(self.config, getattr(owners, who))
self.config._cfgimpl_context._cfgimpl_values.previous_values[self.opt] = oldvalue
def pop(self, key):
"""the list value can be updated (poped)
only if the option is a master
:param key: index of the element to pop
:return: the requested element
"""
self.opt.setowner(self.config,
self.config._cfgimpl_context._cfgimpl_settings.get_owner())
self.config._cfgimpl_context._cfgimpl_values.previous_values[self.opt] = list(self)
return super(Multi, self).pop(key)
# ____________________________________________________________
# #
class Option(HiddenBaseType, DisabledBaseType): class Option(HiddenBaseType, DisabledBaseType):
""" """
@ -267,7 +212,7 @@ class Option(HiddenBaseType, DisabledBaseType):
def getowner(self, config): def getowner(self, config):
"config *must* be only the **parent** config (not the toplevel config)" "config *must* be only the **parent** config (not the toplevel config)"
return config._cfgimpl_context._cfgimpl_values.owners[self] return config._cfgimpl_context._cfgimpl_values.getowner(self)
def reset(self, config): def reset(self, config):
"""resets the default value and owner """resets the default value and owner
@ -297,13 +242,14 @@ class Option(HiddenBaseType, DisabledBaseType):
if not self.is_multi() and value == '': if not self.is_multi() and value == '':
value = None value = None
if self.is_multi() and '' in value: if self.is_multi() and '' in value:
value = Multi([{'': None}.get(i, i) for i in value], config, self) value = Multi([{'': None}.get(i, i) for i in value],
config._cfgimpl_context, self)
if config._cfgimpl_context._cfgimpl_settings.is_mandatory() \ if config._cfgimpl_context._cfgimpl_settings.is_mandatory() \
and ((self.is_multi() and value == []) or \ and ((self.is_multi() and value == []) or \
(not self.is_multi() and value is None)): (not self.is_multi() and value is None)):
raise MandatoryError('cannot change the value to %s for ' raise MandatoryError('cannot change the value to %s for '
'option %s' % (value, name)) 'option %s' % (value, name))
if self not in config._cfgimpl_context._cfgimpl_values: if self not in config._cfgimpl_descr._children:
raise AttributeError('unknown option %s' % (name)) raise AttributeError('unknown option %s' % (name))
if config._cfgimpl_context._cfgimpl_settings.is_frozen_for_everything(): if config._cfgimpl_context._cfgimpl_settings.is_frozen_for_everything():
@ -315,10 +261,10 @@ class Option(HiddenBaseType, DisabledBaseType):
raise TypeError('cannot change the value to %s for ' raise TypeError('cannot change the value to %s for '
'option %s this option is frozen' % (str(value), name)) 'option %s this option is frozen' % (str(value), name))
apply_requires(self, config) apply_requires(self, config)
if type(config._cfgimpl_context._cfgimpl_values[self]) == Multi: # if type(config._cfgimpl_context._cfgimpl_values[self]) == Multi:
config._cfgimpl_context._cfgimpl_values.previous_values[self] = list(config._cfgimpl_context._cfgimpl_values[self]) # config._cfgimpl_context._cfgimpl_values.previous_values[self] = list(config._cfgimpl_context._cfgimpl_values[self])
else: # else:
config._cfgimpl_context._cfgimpl_values.previous_values[self] = config._cfgimpl_context._cfgimpl_values[self] # config._cfgimpl_context._cfgimpl_values.previous_values[self] = config._cfgimpl_context._cfgimpl_values[self]
config._cfgimpl_context._cfgimpl_values[self] = value config._cfgimpl_context._cfgimpl_values[self] = value
def getkey(self, value): def getkey(self, value):

View File

@ -197,11 +197,11 @@ class Setting():
"freeze flag at Config level" "freeze flag at Config level"
return self.frozen return self.frozen
def set_owner(self, owner): def setowner(self, owner):
":param owner: sets the default value for owner at the Config level" ":param owner: sets the default value for owner at the Config level"
if not isinstance(owner, owners.Owner): if not isinstance(owner, owners.Owner):
raise TypeError("invalid generic owner {0}".format(str(owner))) raise TypeError("invalid generic owner {0}".format(str(owner)))
self.owner = owner self.owner = owner
def get_owner(self): def getowner(self):
return self.owner return self.owner

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"takes care of the option's values" "takes care of the option's values and multi values"
# Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
@ -20,19 +20,166 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.error import NoValueReturned, MandatoryError
from tiramisu.setting import owners
class OptionValues(object): class OptionValues(object):
def __init__(self): def __init__(self, context):
self.owners = {} self.owners = {}
"Config's root indeed is in charge of the `Option()`'s values" "Config's root indeed is in charge of the `Option()`'s values"
self.values = {} self.values = {}
self.previous_values = {} self.previous_values = {}
self.context = context
def __getitem__(self, opt_or_descr): def _get_value(self, opt):
return self.values[opt_or_descr] "special case for the multis: they never return None"
if opt.is_multi():
if opt not in self.values:
# FIXME : default value for a multi, we shall work on groups
return Multi(opt.getdefault(), self.context, opt)
else:
if opt not in self.values:
return opt.getdefault()
return self.values[opt]
def __setitem__(self, opt_or_descr, value): def _is_empty(self, opt):
self.values[opt_or_descr] = value "convenience method to know if an option is empty"
if (not opt.is_multi() and self._get_value(opt) == None) or \
(opt.is_multi() and (self._get_value(opt) == [] or \
None in self._get_value(opt))):
return True
return False
def __contains__(self, opt_or_descr): def _test_mandatory(self, opt):
return opt_or_descr in self.values # mandatory options
mandatory = self.context._cfgimpl_settings.mandatory
if opt.is_mandatory() and mandatory:
if self._is_empty(opt) and \
opt.is_empty_by_default():
raise MandatoryError("option: {0} is mandatory "
"and shall have a value".format(opt._name))
def fill_multi(self, opt, result, use_default_multi=False, default_multi=None):
"""fills a multi option with default and calculated values
"""
value = self._get_value(opt)
if not isinstance(result, list):
_result = [result]
else:
_result = result
return Multi(_result, self.context, opt)
def __getitem__(self, opt):
# options with callbacks
if opt.has_callback():
if (not opt.is_frozen() or \
not opt.is_forced_on_freeze()) and \
not opt.is_default_owner(self):
return self._get_value(opt)
try:
result = opt.getcallback_value(
self.context)
except NoValueReturned, err:
pass
else:
if opt.is_multi():
#FIXME revoir les multis
_result = fill_multi(opt, result)
else:
# this result **shall not** be a list
if isinstance(result, list):
raise ConfigError('invalid calculated value returned'
' for option {0} : shall not be a list'.format(name))
_result = result
if _result != None and not opt.validate(_result,
self.context._cfgimpl_settings.validator):
raise ConfigError('invalid calculated value returned'
' for option {0}'.format(name))
self.values[opt] = _result
self.owners[opt] = owners.default
# frozen and force default
if not opt.has_callback() and opt.is_forced_on_freeze():
value = opt.getdefault()
if opt.is_multi():
#FIXME le fill_multi
value = self.fill_multi(opt, value,
use_default_multi=True,
default_multi=opt.getdefault_multi())
self.values[opt] = value
self.owners[opt] = owners.default
self._test_mandatory(opt)
value = self._get_value(opt)
return value
def __setitem__(self, opt, value):
if opt in self.values:
old_value = self.values[opt]
else:
old_value = None
if type(old_value) == Multi:
self.previous_values[opt] = list(value)
else:
self.previous_values[opt] = value
self.values[opt] = value
def __contains__(self, opt):
return opt in self.values
#____________________________________________________________
def setowner(self, opt, owner):
pass
def getowner(self, opt):
return self.owners.get(opt, owners.default)
# ____________________________________________________________
# multi types
class Multi(list):
"""multi options values container
that support item notation for the values of multi options"""
def __init__(self, lst, context, opt):
"""
:param lst: the Multi wraps a list value
:param context: the context has the settings and the values
:param opt: the option object that have this Multi value
"""
self.settings = context._cfgimpl_settings
self.opt = opt
self.values = context._cfgimpl_values
super(Multi, self).__init__(lst)
def __setitem__(self, key, value):
self._setvalue(value, key, who=self.settings.getowner())
def append(self, value):
"""the list value can be updated (appened)
only if the option is a master
"""
self._setvalue(value, who=self.settings.getowner(self.opt))
def _setvalue(self, value, key=None, who=None):
if value != None:
if not self.opt._validate(value):
raise ConfigError("invalid value {0} "
"for option {1}".format(str(value), self.opt._name))
oldvalue = list(self)
if key is None:
super(Multi, self).append(value)
else:
super(Multi, self).__setitem__(key, value)
if who != None:
if not isinstance(who, owners.Owner):
raise TypeError("invalid owner {0} for the value {1}".format(
str(who), str(value)))
self.values.setowner(self.opt, getattr(owners, who))
self.values.previous_values[self.opt] = oldvalue
def pop(self, key):
"""the list value can be updated (poped)
only if the option is a master
:param key: index of the element to pop
:return: the requested element
"""
self.values.setowner(opt, self.settings.get_owner())
self.values.previous_values[self.opt] = list(self)
return super(Multi, self).pop(key)