diff --git a/test/test_config_api.py b/test/test_config_api.py index a26f1d7..26589e4 100644 --- a/test/test_config_api.py +++ b/test/test_config_api.py @@ -108,7 +108,7 @@ def test_getpaths_with_hidden(): def test_str(): descr = make_description() c = Config(descr) - print c # does not crash + c # does not crash #def test_dir(): # descr = make_description() diff --git a/tiramisu/config.py b/tiramisu/config.py index d0b870c..306f69b 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -23,8 +23,7 @@ #from inspect import getmembers, ismethod from tiramisu.error import (PropertiesOptionError, NotFoundError, AmbigousOptionError, NoMatchingOptionFound, MandatoryError) -from tiramisu.option import (OptionDescription, Option, SymLinkOption, - apply_requires) +from tiramisu.option import OptionDescription, Option, SymLinkOption from tiramisu.setting import groups, Setting from tiramisu.value import Values @@ -78,7 +77,6 @@ class SubConfig(object): def _validate(self, name, opt_or_descr, force_permissive=False): "validation for the setattr and the getattr" - apply_requires(opt_or_descr, self) if not isinstance(opt_or_descr, Option) and \ not isinstance(opt_or_descr, OptionDescription): raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr))) @@ -87,7 +85,7 @@ class SubConfig(object): properties = properties - set(['mandatory', 'frozen']) set_properties = set(self.cfgimpl_get_settings().get_properties()) properties = properties & set_properties - if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive'): + if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive', is_apply_req=False): properties = properties - set(self.cfgimpl_get_settings().get_permissive()) properties = properties - set(self.cfgimpl_get_settings().get_permissive(opt_or_descr)) properties = list(properties) @@ -306,9 +304,13 @@ class SubConfig(object): 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): + for path in self.cfgimpl_get_context()._find(bytype=Option, + byname=withoption, + byvalue=withvalue, + byattrs=None, + first=False, + type_='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: @@ -362,7 +364,7 @@ class Config(SubConfig): :param context: the current root config :type context: `Config` """ - self._cfgimpl_settings = Setting() + self._cfgimpl_settings = Setting(self) self._cfgimpl_values = Values(self) super(Config, self).__init__(descr, self) # , slots) self._cfgimpl_build_all_paths() @@ -390,6 +392,7 @@ class Config(SubConfig): :param kwargs: dict of name strings to values. """ + #opts, paths = self.cfgimpl_get_description()._cache_paths all_paths = [p.split(".") for p in self.getpaths(allpaths=True)] for key, value in kwargs.iteritems(): key_p = key.split('.') diff --git a/tiramisu/error.py b/tiramisu/error.py index fae89a7..3d82c65 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -21,8 +21,6 @@ class RequirementRecursionError(RequiresError): pass class MandatoryError(Exception): pass -class NoValueReturned(Exception): - pass class OptionValueError(Exception): pass class MultiTypeError(Exception): diff --git a/tiramisu/option.py b/tiramisu/option.py index 04f0c10..cedea5e 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -24,10 +24,8 @@ import re from copy import copy from types import FunctionType from tiramisu.error import (ConfigError, NotFoundError, ConflictConfigError, - RequiresError, RequirementRecursionError, - PropertiesOptionError) -from tiramisu.autolib import carry_out_calculation -from tiramisu.setting import groups, multitypes + RequiresError) +from tiramisu.setting import groups, multitypes, apply_requires name_regexp = re.compile(r'^\d+') @@ -84,6 +82,7 @@ class Option(BaseInformation): requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_args=None, properties=None): + #FIXME : validation de callback et callback_params !!! """ :param name: the option's name :param doc: the option's description @@ -109,10 +108,11 @@ class Option(BaseInformation): validate_requires_arg(requires, self._name) self._requires = requires self.multi = multi - #self._validator_args = None if validator is not None: if type(validator) != FunctionType: raise TypeError("validator must be a function") + if validator_args is None: + validator_args = {} self._validator = (validator, validator_args) else: self._validator = None @@ -139,14 +139,14 @@ class Option(BaseInformation): raise ConfigError("invalid default value {0} " "for option {1} : not list type" "".format(str(default), name)) - if not self.validate(default, False): + if not self.validate(default): raise ConfigError("invalid default value {0} " "for option {1}" "".format(str(default), name)) self.multitype = multitypes.default self.default_multi = default_multi else: - if default is not None and not self.validate(default, False): + if default is not None and not self.validate(default): raise ConfigError("invalid default value {0} " "for option {1}".format(str(default), name)) self.default = default @@ -216,14 +216,6 @@ class Option(BaseInformation): else: return True - def getcallback_value(self, config): - callback, callback_params = self.callback - if callback_params is None: - callback_params = {} - return carry_out_calculation(self._name, config=config, - callback=callback, - callback_params=callback_params) - def reset(self, config): """resets the default value and owner """ @@ -558,54 +550,3 @@ def validate_requires_arg(requires, name): " action: {1}".format(name, action)) else: config_action[action] = inverse - - -def apply_requires(opt, config): - "carries out the jit (just in time requirements between options" - def build_actions(requires): - "action are hide, show, enable, disable..." - trigger_actions = {} - for require in requires: - action = require[2] - trigger_actions.setdefault(action, []).append(require) - return trigger_actions - #for symlink - if hasattr(opt, '_requires') and opt._requires is not None: - # filters the callbacks - setting = config.cfgimpl_get_settings() - trigger_actions = build_actions(opt._requires) - optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt) - for requires in trigger_actions.values(): - matches = False - for require in requires: - if len(require) == 3: - path, expected, action = require - inverse = False - elif len(require) == 4: - path, expected, action, inverse = require - if path.startswith(optpath): - raise RequirementRecursionError("malformed requirements " - "imbrication detected for option: '{0}' " - "with requirement on: '{1}'".format(optpath, path)) - try: - value = config.cfgimpl_get_context()._getattr(path, force_permissive=True) - except PropertiesOptionError, err: - properties = err.proptype - raise NotFoundError("option '{0}' has requirement's property error: " - "{1} {2}".format(opt._name, path, properties)) - except Exception, err: - raise NotFoundError("required option not found: " - "{0}".format(path)) - if value == expected: - if inverse: - setting.del_property(action, opt) - else: - setting.add_property(action, opt) - matches = True - #FIXME optimisation : fait un double break non ? voire un return - # no requirement has been triggered, then just reverse the action - if not matches: - if inverse: - setting.add_property(action, opt) - else: - setting.del_property(action, opt) diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 964ec81..c3ed7c6 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -21,6 +21,9 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ +from tiramisu.error import (RequirementRecursionError, PropertiesOptionError, + NotFoundError) + class _const: """convenient class that emulates a module @@ -136,9 +139,9 @@ populate_multitypes() #____________________________________________________________ class Setting(object): "``Config()``'s configuration options" - __slots__ = ('properties', 'permissives', 'owner') + __slots__ = ('properties', 'permissives', 'owner', 'context') - def __init__(self): + def __init__(self, context): # properties attribute: the name of a property enables this property # key is None for global properties self.properties = {None: []} # ['hidden', 'disabled', 'mandatory', 'frozen', 'validator']} @@ -146,24 +149,27 @@ class Setting(object): self.permissives = {} # generic owner self.owner = owners.user + self.context = context #____________________________________________________________ # properties methods - def has_properties(self, opt=None): + def has_properties(self, opt=None, is_apply_req=True): "has properties means the Config's properties attribute is not empty" - return bool(len(self.get_properties(opt))) + return bool(len(self.get_properties(opt, is_apply_req))) - def get_properties(self, opt=None): + def get_properties(self, opt=None, is_apply_req=True): if opt is None: default = [] else: + if is_apply_req: + apply_requires(opt, self.context) default = list(opt._properties) return self.properties.get(opt, default) - def has_property(self, propname, opt=None): + def has_property(self, propname, opt=None, is_apply_req=True): """has property propname in the Config's properties attribute :param property: string wich is the name of the property""" - return propname in self.get_properties(opt) + return propname in self.get_properties(opt, is_apply_req) def enable_property(self, propname): "puts property propname in the Config's properties attribute" @@ -192,14 +198,14 @@ class Setting(object): else: self.properties[opt] = properties - def add_property(self, propname, opt): - properties = self.get_properties(opt) + def add_property(self, propname, opt, is_apply_req=True): + properties = self.get_properties(opt, is_apply_req) if not propname in properties: properties.append(propname) self.set_properties(properties, opt) - def del_property(self, propname, opt): - properties = self.get_properties(opt) + def del_property(self, propname, opt, is_apply_req=True): + properties = self.get_properties(opt, is_apply_req) if propname in properties: properties.remove(propname) self.set_properties(properties, opt) @@ -241,5 +247,56 @@ class Setting(object): self.enable_property('hidden') self.enable_property('disabled') self.disable_property('mandatory') - self.disable_property('validator') + self.enable_property('validator') self.disable_property('permissive') + + +def apply_requires(opt, config): + "carries out the jit (just in time requirements between options" + def build_actions(requires): + "action are hide, show, enable, disable..." + trigger_actions = {} + for require in requires: + action = require[2] + trigger_actions.setdefault(action, []).append(require) + return trigger_actions + #for symlink + if hasattr(opt, '_requires') and opt._requires is not None: + # filters the callbacks + setting = config.cfgimpl_get_settings() + trigger_actions = build_actions(opt._requires) + optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt) + for requires in trigger_actions.values(): + matches = False + for require in requires: + if len(require) == 3: + path, expected, action = require + inverse = False + elif len(require) == 4: + path, expected, action, inverse = require + if path == optpath or path.startswith(optpath + '.'): + raise RequirementRecursionError("malformed requirements " + "imbrication detected for option: '{0}' " + "with requirement on: '{1}'".format(optpath, path)) + try: + value = config.cfgimpl_get_context()._getattr(path, force_permissive=True) + except PropertiesOptionError, err: + properties = err.proptype + raise NotFoundError("option '{0}' has requirement's property error: " + "{1} {2}".format(opt._name, path, properties)) + except AttributeError: + raise NotFoundError("required option not found: " + "{0}".format(path)) + if value == expected: + if inverse: + setting.del_property(action, opt, False) + else: + setting.add_property(action, opt, False) + matches = True + #FIXME optimisation : fait un double break non ? voire un return + # no requirement has been triggered, then just reverse the action + if not matches: + if inverse: + setting.add_property(action, opt, False) + else: + setting.del_property(action, opt, False) diff --git a/tiramisu/value.py b/tiramisu/value.py index d24d8d8..9fc9a95 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -17,9 +17,10 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ -from tiramisu.error import NoValueReturned, MandatoryError, MultiTypeError, \ +from tiramisu.error import MandatoryError, MultiTypeError, \ ConfigError # , OptionValueError from tiramisu.setting import owners, multitypes +from tiramisu.autolib import carry_out_calculation class Values(object): @@ -36,28 +37,35 @@ class Values(object): self.context = context def _get_value(self, opt): - "special case for the multis: they never return None" + "return value or default value if not set" + #if no value if opt not in self.values: + value = opt.getdefault() if opt.is_multi(): - value = Multi(opt.getdefault(), self.context, opt) + value = Multi(value, self.context, opt) + #if slave, had values until master's one if opt.multitype == multitypes.slave: - masterpath = self.context._cfgimpl_descr.get_path_by_opt(opt.master_slaves) + masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.master_slaves) mastervalue = getattr(self.context, masterpath) masterlen = len(mastervalue) + if len(value) > masterlen: + raise MultiTypeError("invalid len for the slave: {0}" + " which has {1} as master".format( + opt._name, masterpath)) if len(value) < masterlen: for num in range(0, masterlen - len(value)): - value.append(None, force=True) - else: - value = opt.getdefault() - - return value - return self.values[opt][1] + value.append(opt.getdefault_multi(), force=True) + #FIXME si inferieur ?? + else: + #if value + value = self.values[opt][1] + return value def reset(self, opt): if opt in self.values: del(self.values[opt]) - def _is_empty(self, opt, value=None): + def _is_empty(self, opt, value): "convenience method to know if an option is empty" #FIXME: buggy ? #if value is not None: @@ -68,22 +76,6 @@ class Values(object): return True return False - def is_empty(self, opt): - #FIXME that not empty ... just no value! - if opt not in self.values: - return True - value = self.values[opt][1] - if not opt.is_multi(): - if self._get_value(opt) is None: - return True - return False - else: - value = list(value) - for val in value: - if val is not None: - return False - return True - def _test_mandatory(self, opt, value, force_properties=None): setting = self.context.cfgimpl_get_settings() if force_properties is None: @@ -92,8 +84,7 @@ class Values(object): set_mandatory = ('mandatory' in force_properties or setting.has_property('mandatory')) if setting.has_property('mandatory', opt) and set_mandatory: - if self._is_empty(opt, value) and \ - opt.is_empty_by_default(): + if self._is_empty(opt, value) and opt.is_empty_by_default(): raise MandatoryError("option: {0} is mandatory " "and shall have a value".format(opt._name)) #empty value @@ -114,9 +105,16 @@ class Values(object): _result = [result] else: _result = result - #multitype = self._get_multitype(opt) return Multi(_result, self.context, opt) # , multitype) + def _getcallback_value(self, opt): + callback, callback_params = opt.callback + if callback_params is None: + callback_params = {} + return carry_out_calculation(opt._name, config=self.context, + callback=callback, + callback_params=callback_params) + def __getitem__(self, opt): return self._getitem(opt) @@ -124,32 +122,25 @@ class Values(object): # options with callbacks value = self._get_value(opt) setting = self.context.cfgimpl_get_settings() + is_frozen = setting.has_property('frozen', opt) if opt.has_callback(): - 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): + #if value is set and : + # - not frozen + # - frozen and not force_default_on_freeze + if not self.is_default_owner(opt) and ( + not is_frozen or (is_frozen and + not setting.has_property('force_default_on_freeze', opt))): return value - try: - result = opt.getcallback_value(self.context) - except NoValueReturned: - pass - else: - if opt.is_multi(): - value = self.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(opt._name)) - value = result - if value is not None and \ - not opt.validate(value, setting.has_property('validator')): - raise ConfigError('invalid calculated value returned' - ' for option {0}'.format(opt._name)) + value = self._getcallback_value(opt) + if opt.is_multi(): + value = self.fill_multi(opt, value) + if not opt.validate(value, setting.has_property('validator')): + raise ConfigError('invalid calculated value returned' + ' for option {0}: {1}'.format(opt._name, value)) + #suppress value if already set + self.reset(opt) # frozen and force default - elif setting.has_property('force_default_on_freeze', opt): + elif is_frozen and setting.has_property('force_default_on_freeze', opt): value = opt.getdefault() if opt.is_multi(): value = self.fill_multi(opt, value) @@ -168,7 +159,7 @@ class Values(object): slave._name, opt._name)) elif len(value_slave) < masterlen: for num in range(0, masterlen - len(value_slave)): - value_slave.append(None, force=True) + value_slave.append(slave.getdefault_multi(), force=True) elif opt.multitype == multitypes.slave: if len(self._get_value(opt.master_slaves)) != len(value): @@ -241,7 +232,7 @@ class Multi(list): " which is a slave".format(self.opt._name)) elif self.opt.multitype == multitypes.master: for slave in self.opt.master_slaves: - self.context.cfgimpl_get_values()[slave].append(None, force=True) + self.context.cfgimpl_get_values()[slave].append(slave.getdefault_multi(), force=True) self._validate(value) self.context.cfgimpl_get_values().setitem(self.opt, self) super(Multi, self).append(value)