attribute in Option now starts with '_'

add ValidateError
add consistancies in Option
remove byattr in find
This commit is contained in:
Emmanuel Garette 2013-04-13 22:50:55 +02:00
parent 26568dc45a
commit 9c2dcc164c
6 changed files with 281 additions and 120 deletions

View File

@ -160,9 +160,9 @@ def test_find_in_config():
assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_path('gc.dummy') assert conf.find_first(bytype=BoolOption, byname='dummy') == conf.unwrap_from_path('gc.dummy')
assert conf.find(byvalue=False, byname='dummy') == [conf.unwrap_from_path('gc.dummy')] assert conf.find(byvalue=False, byname='dummy') == [conf.unwrap_from_path('gc.dummy')]
assert conf.find_first(byvalue=False, byname='dummy') == conf.unwrap_from_path('gc.dummy') assert conf.find_first(byvalue=False, byname='dummy') == conf.unwrap_from_path('gc.dummy')
# byattrs ## byattrs
assert conf.find_first(byattrs= dict(default=2.3)) == conf.unwrap_from_path('gc.float') #assert conf.find_first(byattrs= dict(default=2.3)) == conf.unwrap_from_path('gc.float')
assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy') #assert conf.find_first(byvalue=False, byname='dummy', byattrs=dict(default=False)) == conf.unwrap_from_path('gc.dummy')
def test_does_not_find_in_config(): def test_does_not_find_in_config():
descr = make_description() descr = make_description()

View File

@ -69,7 +69,7 @@ def test_reset_with_multi():
config.unwrap_from_path("string").reset(config) config.unwrap_from_path("string").reset(config)
# assert config.string == ["string"] # assert config.string == ["string"]
assert config.cfgimpl_get_values().getowner(s) == 'default' assert config.cfgimpl_get_values().getowner(s) == 'default'
raises(ConfigError, "config.string = None") raises(ValidateError, "config.string = None")
def test_default_with_multi(): def test_default_with_multi():
"default with multi is a list" "default with multi is a list"
@ -230,7 +230,7 @@ def test_multi_with_bool():
s = BoolOption("bool", "", default=[False], multi=True) s = BoolOption("bool", "", default=[False], multi=True)
descr = OptionDescription("options", "", [s]) descr = OptionDescription("options", "", [s])
config = Config(descr) config = Config(descr)
assert descr.bool.multi == True assert descr.bool.is_multi() == True
config.bool = [True, False] config.bool = [True, False]
assert config.cfgimpl_get_values()[s] == [True, False] assert config.cfgimpl_get_values()[s] == [True, False]
assert config.bool == [True, False] assert config.bool == [True, False]
@ -239,8 +239,8 @@ def test_multi_with_bool_two():
s = BoolOption("bool", "", default=[False], multi=True) s = BoolOption("bool", "", default=[False], multi=True)
descr = OptionDescription("options", "", [s]) descr = OptionDescription("options", "", [s])
config = Config(descr) config = Config(descr)
assert descr.bool.multi == True assert descr.bool.is_multi() == True
raises(ConfigError, "config.bool = True") raises(ValidateError, "config.bool = True")
def test_choice_access_with_multi(): def test_choice_access_with_multi():
ch = ChoiceOption("t1", "", ("a", "b"), default=["a"], multi=True) ch = ChoiceOption("t1", "", ("a", "b"), default=["a"], multi=True)

View File

@ -54,6 +54,9 @@ class SubConfig(object):
def cfgimpl_get_values(self): def cfgimpl_get_values(self):
return self._cfgimpl_context._cfgimpl_values return self._cfgimpl_context._cfgimpl_values
def cfgimpl_get_consistancies(self):
return self.cfgimpl_get_context().cfgimpl_get_description()._consistancies
def cfgimpl_get_description(self): def cfgimpl_get_description(self):
return self._cfgimpl_descr return self._cfgimpl_descr
@ -101,7 +104,8 @@ class SubConfig(object):
def __getattr__(self, name): def __getattr__(self, name):
return self._getattr(name) return self._getattr(name)
def _getattr(self, name, force_permissive=False, force_properties=None): def _getattr(self, name, force_permissive=False, force_properties=None,
validate=True):
""" """
attribute notation mechanism for accessing the value of an option attribute notation mechanism for accessing the value of an option
:param name: attribute name :param name: attribute name
@ -115,13 +119,14 @@ class SubConfig(object):
force_permissive=force_permissive, force_permissive=force_permissive,
force_properties=force_properties) force_properties=force_properties)
return homeconfig._getattr(name, force_permissive=force_permissive, return homeconfig._getattr(name, force_permissive=force_permissive,
force_properties=force_properties) force_properties=force_properties,
validate=validate)
opt_or_descr = getattr(self._cfgimpl_descr, name) opt_or_descr = getattr(self._cfgimpl_descr, name)
# symlink options # symlink options
if type(opt_or_descr) == SymLinkOption: if type(opt_or_descr) == SymLinkOption:
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 rootconfig._getattr(path, validate=validate)
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
@ -136,7 +141,8 @@ class SubConfig(object):
# if it were in __dict__ it would have been found already # if it were in __dict__ it would have been found already
object.__getattr__(self, name) object.__getattr__(self, name)
return self.cfgimpl_get_values()._getitem(opt_or_descr, return self.cfgimpl_get_values()._getitem(opt_or_descr,
force_properties=force_properties) force_properties=force_properties,
validate=validate)
def setoption(self, name, child, value): def setoption(self, name, child, value):
"""effectively modifies the value of an Option() """effectively modifies the value of an Option()
@ -279,35 +285,31 @@ class SubConfig(object):
context_descr = self.cfgimpl_get_context().cfgimpl_get_description() context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
return context_descr.get_path_by_opt(descr) return context_descr.get_path_by_opt(descr)
def find(self, bytype=None, byname=None, byvalue=None, byattrs=None, def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
type_='option'):
""" """
finds a list of options recursively in the config finds a list of options recursively in the config
:param bytype: Option class (BoolOption, StrOption, ...) :param bytype: Option class (BoolOption, StrOption, ...)
:param byname: filter by Option._name :param byname: filter by Option._name
:param byvalue: filter by the option's value :param byvalue: filter by the option's value
:param byattrs: dict of option attributes (default, callback...)
:returns: list of matching Option objects :returns: list of matching Option objects
""" """
return self.cfgimpl_get_context()._find(bytype, byname, byvalue, return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
byattrs, first=False, first=False,
type_=type_, type_=type_,
_subpath=self.getpath()) _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, type_='option'):
type_='option'):
""" """
finds an option recursively in the config finds an option recursively in the config
:param bytype: Option class (BoolOption, StrOption, ...) :param bytype: Option class (BoolOption, StrOption, ...)
:param byname: filter by Option._name :param byname: filter by Option._name
:param byvalue: filter by the option's value :param byvalue: filter by the option's value
:param byattrs: dict of option attributes (default, callback...)
:returns: list of matching Option objects :returns: list of matching Option objects
""" """
return self.cfgimpl_get_context()._find(bytype, byname, byvalue, return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
byattrs, first=True, first=True,
type_=type_, type_=type_,
_subpath=self.getpath()) _subpath=self.getpath())
@ -324,7 +326,6 @@ class SubConfig(object):
for path in self.cfgimpl_get_context()._find(bytype=Option, for path in self.cfgimpl_get_context()._find(bytype=Option,
byname=withoption, byname=withoption,
byvalue=withvalue, byvalue=withvalue,
byattrs=None,
first=False, first=False,
type_='path', type_='path',
_subpath=mypath): _subpath=mypath):
@ -436,7 +437,7 @@ class Config(SubConfig):
def getpath(self): def getpath(self):
return None return None
def _find(self, bytype, byname, byvalue, byattrs, first, type_='option', def _find(self, bytype, byname, byvalue, first, type_='option',
_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
@ -470,18 +471,17 @@ class Config(SubConfig):
return True return True
return False return False
def _filter_by_attrs(): #def _filter_by_attrs():
if byattrs is None: # if byattrs is None:
return True # return True
for key, value in byattrs.items(): # for key, val in byattrs.items():
if not hasattr(option, key): # print "----", path, key
return False # if path == key or path.endswith('.' + key):
else: # if value == val:
if getattr(option, key) != value: # return True
return False # else:
else: # return False
continue # return False
return True
if type_ not in ('option', 'path', 'value'): if type_ not in ('option', 'path', 'value'):
raise ValueError('unknown type_ type {} for _find'.format(type_)) raise ValueError('unknown type_ type {} for _find'.format(type_))
find_results = [] find_results = []
@ -497,15 +497,15 @@ class Config(SubConfig):
continue continue
if not _filter_by_value(): if not _filter_by_value():
continue continue
if not _filter_by_type():
continue
if not _filter_by_attrs():
continue
#remove option with propertyerror, ... #remove option with propertyerror, ...
try: try:
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 not _filter_by_type():
continue
#if not _filter_by_attrs():
# continue
if type_ == 'value': if type_ == 'value':
retval = value retval = value
elif type_ == 'path': elif type_ == 'path':

View File

@ -1,27 +1,53 @@
class ValidateError(Exception):
"If validation failed"
pass
class AmbigousOptionError(Exception): class AmbigousOptionError(Exception):
pass pass
class NoMatchingOptionFound(AttributeError): class NoMatchingOptionFound(AttributeError):
pass pass
class ConfigError(Exception): class ConfigError(Exception):
pass pass
class ConflictConfigError(ConfigError): class ConflictConfigError(ConfigError):
pass pass
class PropertiesOptionError(AttributeError): class PropertiesOptionError(AttributeError):
def __init__(self, msg, proptype): def __init__(self, msg, proptype):
self.proptype = proptype self.proptype = proptype
super(PropertiesOptionError, self).__init__(msg) super(PropertiesOptionError, self).__init__(msg)
class NotFoundError(Exception): class NotFoundError(Exception):
pass pass
class MethodCallError(Exception): class MethodCallError(Exception):
pass pass
class RequiresError(Exception): class RequiresError(Exception):
pass pass
class RequirementRecursionError(RequiresError): class RequirementRecursionError(RequiresError):
pass pass
class MandatoryError(Exception): class MandatoryError(Exception):
pass pass
class OptionValueError(Exception): class OptionValueError(Exception):
pass pass
class MultiTypeError(Exception): class MultiTypeError(Exception):
pass pass

View File

@ -23,8 +23,10 @@
import re import re
from copy import copy from copy import copy
from types import FunctionType from types import FunctionType
from IPy import IP
from tiramisu.error import (ConfigError, NotFoundError, ConflictConfigError, from tiramisu.error import (ConfigError, NotFoundError, ConflictConfigError,
RequiresError) RequiresError, ValidateError)
from tiramisu.setting import groups, multitypes from tiramisu.setting import groups, multitypes
name_regexp = re.compile(r'^\d+') name_regexp = re.compile(r'^\d+')
@ -44,7 +46,7 @@ def valid_name(name):
class BaseInformation(object): class BaseInformation(object):
__slots__ = ('informations') __slots__ = ('_informations')
def set_information(self, key, value): def set_information(self, key, value):
"""updates the information's attribute """updates the information's attribute
@ -53,15 +55,15 @@ class BaseInformation(object):
:param key: information's key (ex: "help", "doc" :param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string") :param value: information's value (ex: "the help string")
""" """
self.informations[key] = value self._informations[key] = value
def get_information(self, key, default=None): def get_information(self, key, default=None):
"""retrieves one information's item """retrieves one information's item
:param key: the item string (ex: "help") :param key: the item string (ex: "help")
""" """
if key in self.informations: if key in self._informations:
return self.informations[key] return self._informations[key]
elif default is not None: elif default is not None:
return default return default
else: else:
@ -74,9 +76,9 @@ class Option(BaseInformation):
Reminder: an Option object is **not** a container for the value Reminder: an Option object is **not** a container for the value
""" """
__slots__ = ('_name', '_requires', 'multi', '_validator', 'default_multi', __slots__ = ('_name', '_requires', '_multi', '_validator', '_default_multi',
'default', '_properties', 'callback', 'multitype', '_default', '_properties', '_callback', '_multitype',
'master_slaves') '_master_slaves', '_consistency')
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None, requires=None, multi=False, callback=None,
@ -103,11 +105,12 @@ class Option(BaseInformation):
if not valid_name(name): if not valid_name(name):
raise NameError("invalid name: {0} for option".format(name)) raise NameError("invalid name: {0} for option".format(name))
self._name = name self._name = name
self.informations = {} self._informations = {}
self.set_information('doc', doc) self.set_information('doc', doc)
validate_requires_arg(requires, self._name) validate_requires_arg(requires, self._name)
self._requires = requires self._requires = requires
self.multi = multi self._multi = multi
self._consistency = None
if validator is not None: if validator is not None:
if type(validator) != FunctionType: if type(validator) != FunctionType:
raise TypeError("validator must be a function") raise TypeError("validator must be a function")
@ -116,7 +119,7 @@ class Option(BaseInformation):
self._validator = (validator, validator_args) self._validator = (validator, validator_args)
else: else:
self._validator = None self._validator = None
if not self.multi and default_multi is not None: if not self._multi and default_multi is not None:
raise ConfigError("a default_multi is set whereas multi is False" raise ConfigError("a default_multi is set whereas multi is False"
" in option: {0}".format(name)) " in option: {0}".format(name))
if default_multi is not None and not self._validate(default_multi): if default_multi is not None and not self._validate(default_multi):
@ -129,27 +132,27 @@ class Option(BaseInformation):
raise ConfigError("params defined for a callback function but " raise ConfigError("params defined for a callback function but "
"no callback defined yet for option {0}".format(name)) "no callback defined yet for option {0}".format(name))
if callback is not None: if callback is not None:
self.callback = (callback, callback_params) self._callback = (callback, callback_params)
else: else:
self.callback = None self._callback = None
if self.multi: if self._multi:
if default is None: if default is None:
default = [] default = []
if not isinstance(default, list): #if not isinstance(default, list):
raise ConfigError("invalid default value {0} " # raise ValidateError("invalid default value {0} "
"for option {1} : not list type" # "for option {1} : not list type"
"".format(str(default), name)) # "".format(str(default), name))
if not self.validate(default): if not self.validate(default):
raise ConfigError("invalid default value {0} " raise ValidateError("invalid default value {0} "
"for option {1}" "for option {1}"
"".format(str(default), name)) "".format(str(default), name))
self.multitype = multitypes.default self._multitype = multitypes.default
self.default_multi = default_multi self._default_multi = default_multi
else: else:
if default is not None and not self.validate(default): if default is not None and not self.validate(default):
raise ConfigError("invalid default value {0} " raise ValidateError("invalid default value {0} "
"for option {1}".format(str(default), name)) "for option {1}".format(str(default), name))
self.default = default self._default = default
if properties is None: if properties is None:
properties = () properties = ()
if not isinstance(properties, tuple): if not isinstance(properties, tuple):
@ -157,26 +160,34 @@ class Option(BaseInformation):
' must be a tuple'.format(type(properties), self._name)) ' must be a tuple'.format(type(properties), self._name))
self._properties = properties # 'hidden', 'disabled'... self._properties = properties # 'hidden', 'disabled'...
def validate(self, value, validate=True): def validate(self, value, context=None, validate=True):
""" """
:param value: the option's value :param value: the option's value
:param validate: if true enables ``self._validator`` validation :param validate: if true enables ``self._validator`` validation
""" """
# generic calculation # generic calculation
if not self.multi: if context is not None:
cons = context.cfgimpl_get_description()
else:
cons = None
if not self._multi:
# None allows the reset of the value # None allows the reset of the value
if value is not None: if value is not None:
# customizing the validator # customizing the validator
if validate and self._validator is not None and \ if validate and self._validator is not None and \
not self._validator[0](value, **self._validator[1]): not self._validator[0](value, **self._validator[1]):
return False return False
return self._validate(value) if not self._validate(value):
return False
if cons is not None:
return cons.valid_consistency(self, value, context, None)
else: else:
if not isinstance(value, list): if not isinstance(value, list):
raise ConfigError("invalid value {0} " raise ValidateError("invalid value {0} "
"for option {1} which must be a list" "for option {1} which must be a list"
"".format(value, self._name)) "".format(value, self._name))
for val in value: for index in range(0, len(value)):
val = value[index]
# None allows the reset of the value # None allows the reset of the value
if val is not None: if val is not None:
# customizing the validator # customizing the validator
@ -185,23 +196,31 @@ class Option(BaseInformation):
return False return False
if not self._validate(val): if not self._validate(val):
return False return False
if cons is not None and not cons.valid_consistency(self, val, context, index):
return False
return True return True
def getdefault(self, default_multi=False): def getdefault(self, default_multi=False):
"accessing the default value" "accessing the default value"
if not default_multi or not self.is_multi(): if not default_multi or not self.is_multi():
return self.default return self._default
else: else:
return self.getdefault_multi() return self.getdefault_multi()
def getdefault_multi(self): def getdefault_multi(self):
"accessing the default value for a multi" "accessing the default value for a multi"
return self.default_multi return self._default_multi
def get_multitype(self):
return self._multitype
def get_master_slaves(self):
return self._master_slaves
def is_empty_by_default(self): def is_empty_by_default(self):
"no default value has been set yet" "no default value has been set yet"
if ((not self.is_multi() and self.default is None) or if ((not self.is_multi() and self._default is None) or
(self.is_multi() and (self.default == [] or None in self.default))): (self.is_multi() and (self._default == [] or None in self._default))):
return True return True
return False return False
@ -211,7 +230,7 @@ class Option(BaseInformation):
def has_callback(self): def has_callback(self):
"to know if a callback has been defined or not" "to know if a callback has been defined or not"
if self.callback is None: if self._callback is None:
return False return False
else: else:
return True return True
@ -225,11 +244,23 @@ class Option(BaseInformation):
return value return value
def is_multi(self): def is_multi(self):
return self.multi return self._multi
def cons_not_equal(self, opt, value, context, index, opts):
values = [value]
descr = context.cfgimpl_get_description()
for opt_ in opts:
if opt_ is not opt:
path = descr.get_path_by_opt(opt_)
val = context._getattr(path, validate=False)
if val in values:
return False
values.append(val)
return True
class ChoiceOption(Option): class ChoiceOption(Option):
__slots__ = ('values', 'open_values', 'opt_type') __slots__ = ('_values', '_open_values', 'opt_type')
opt_type = 'string' opt_type = 'string'
def __init__(self, name, doc, values, default=None, default_multi=None, def __init__(self, name, doc, values, default=None, default_multi=None,
@ -238,11 +269,11 @@ class ChoiceOption(Option):
validator_args=None, properties=()): validator_args=None, properties=()):
if not isinstance(values, tuple): if not isinstance(values, tuple):
raise ConfigError('values must be a tuple for {0}'.format(name)) raise ConfigError('values must be a tuple for {0}'.format(name))
self.values = values self._values = values
if open_values not in (True, False): if open_values not in (True, False):
raise ConfigError('Open_values must be a boolean for ' raise ConfigError('Open_values must be a boolean for '
'{0}'.format(name)) '{0}'.format(name))
self.open_values = open_values self._open_values = open_values
super(ChoiceOption, self).__init__(name, doc, default=default, super(ChoiceOption, self).__init__(name, doc, default=default,
default_multi=default_multi, default_multi=default_multi,
callback=callback, callback=callback,
@ -254,8 +285,8 @@ class ChoiceOption(Option):
properties=properties) properties=properties)
def _validate(self, value): def _validate(self, value):
if not self.open_values: if not self._open_values:
return value is None or value in self.values return value is None or value in self._values
else: else:
return True return True
@ -301,8 +332,9 @@ class UnicodeOption(Option):
class SymLinkOption(object): class SymLinkOption(object):
__slots__ = ('_name', 'opt') __slots__ = ('_name', 'opt', '_consistency')
opt_type = 'symlink' opt_type = 'symlink'
_consistency = None
def __init__(self, name, path, opt): def __init__(self, name, path, opt):
self._name = name self._name = name
@ -320,27 +352,112 @@ class SymLinkOption(object):
class IPOption(Option): class IPOption(Option):
__slots__ = ('opt_type') __slots__ = ('opt_type', '_only_private')
opt_type = 'ip' opt_type = 'ip'
def set_private(self):
self._only_private = True
def _validate(self, value): def _validate(self, value):
# by now the validation is nothing but a string, use IPy instead try:
return isinstance(value, str) only_private = self._only_private
except AttributeError:
only_private = False
try:
ip = IP('{0}/32'.format(value))
if only_private:
return ip.iptype() == 'PRIVATE'
return True
except ValueError:
return False
class NetworkOption(Option):
__slots__ = ('opt_type')
opt_type = 'network'
def _validate(self, value):
try:
IP(value)
return True
except ValueError:
return False
class NetmaskOption(Option): class NetmaskOption(Option):
__slots__ = ('opt_type') __slots__ = ('opt_type')
opt_type = 'netmask' opt_type = 'netmask'
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=None,
properties=None, opt_ip=None):
if opt_ip is not None and not isinstance(opt_ip, IPOption) and \
not isinstance(opt_ip, NetworkOption):
raise ValueError('opt_ip must be a IPOption not {}'.format(type(opt_ip)))
super(NetmaskOption, self).__init__(name, doc, default=default,
default_multi=default_multi,
callback=callback,
callback_params=callback_params,
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
properties=properties)
if opt_ip is None:
pass
elif isinstance(opt_ip, IPOption):
self._consistency = ('cons_ip_netmask', (self, opt_ip))
elif isinstance(opt_ip, NetworkOption):
self._consistency = ('cons_network_netmask', (self, opt_ip))
else:
raise ValueError('unknown type for opt_ip')
def _validate(self, value): def _validate(self, value):
# by now the validation is nothing but a string, use IPy instead try:
return isinstance(value, str) IP('0.0.0.0/{}'.format(value))
return True
except ValueError:
return False
def cons_network_netmask(self, opt, value, context, index, opts):
#opts must be (netmask, network) options
return self._cons_netmask(opt, value, context, index, opts, False)
def cons_ip_netmask(self, opt, value, context, index, opts):
#opts must be (netmask, ip) options
return self._cons_netmask(opt, value, context, index, opts, True)
def _cons_netmask(self, opt, value, context, index, opts, make_net):
opt_netmask, opt_ipnetwork = opts
descr = context.cfgimpl_get_description()
if opt is opt_ipnetwork:
val_ipnetwork = value
path = descr.get_path_by_opt(opt_netmask)
val_netmask = context._getattr(path, validate=False)
if opt_netmask.is_multi():
val_netmask = val_netmask[index]
if val_netmask is None:
return True
else:
val_netmask = value
path = descr.get_path_by_opt(opt_ipnetwork)
val_ipnetwork = getattr(context, path)
if opt_ipnetwork.is_multi():
val_ipnetwork = val_ipnetwork[index]
if val_ipnetwork is None:
return True
try:
IP('{}/{}'.format(val_ipnetwork, val_netmask, make_net=make_net))
return True
except ValueError:
return False
class OptionDescription(BaseInformation): class OptionDescription(BaseInformation):
"""Config's schema (organisation, group) and container of Options""" """Config's schema (organisation, group) and container of Options"""
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type', __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_properties', '_children') '_properties', '_children', '_consistencies')
def __init__(self, name, doc, children, requires=None, properties=()): def __init__(self, name, doc, children, requires=None, properties=()):
""" """
@ -350,7 +467,7 @@ class OptionDescription(BaseInformation):
if not valid_name(name): if not valid_name(name):
raise NameError("invalid name: {0} for option descr".format(name)) raise NameError("invalid name: {0} for option descr".format(name))
self._name = name self._name = name
self.informations = {} self._informations = {}
self.set_information('doc', doc) self.set_information('doc', doc)
child_names = [child._name for child in children] child_names = [child._name for child in children]
#better performance like this #better performance like this
@ -366,6 +483,7 @@ class OptionDescription(BaseInformation):
validate_requires_arg(requires, self._name) validate_requires_arg(requires, self._name)
self._requires = requires self._requires = requires
self._cache_paths = None self._cache_paths = None
self._consistencies = None
if not isinstance(properties, tuple): if not isinstance(properties, tuple):
raise ConfigError('invalid properties type {0} for {1},' raise ConfigError('invalid properties type {0} for {1},'
' must be a tuple'.format(type(properties), self._name)) ' must be a tuple'.format(type(properties), self._name))
@ -409,12 +527,13 @@ class OptionDescription(BaseInformation):
def getchildren(self): def getchildren(self):
return self._children[1] return self._children[1]
def build_cache(self, cache_path=None, cache_option=None, _currpath=None): def build_cache(self, cache_path=None, cache_option=None, _currpath=None, _consistencies=None):
if _currpath is None and self._cache_paths is not 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 = []
_consistencies = {}
else: else:
save = False save = False
if cache_path is None: if cache_path is None:
@ -426,9 +545,16 @@ class OptionDescription(BaseInformation):
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 not isinstance(option, OptionDescription):
if option._consistency is not None:
func, opts = option._consistency
for opt in opts:
if opt in _consistencies:
raise ValueError('opt {} already in consistency'.format(opt._name))
_consistencies[opt] = (func, opts)
else:
_currpath.append(attr) _currpath.append(attr)
option.build_cache(cache_path, cache_option, _currpath) option.build_cache(cache_path, cache_option, _currpath, _consistencies)
_currpath.pop() _currpath.pop()
if save: if save:
#valid no duplicated option #valid no duplicated option
@ -441,6 +567,7 @@ class OptionDescription(BaseInformation):
'{0}'.format(child)) '{0}'.format(child))
old = child old = child
self._cache_paths = (tuple(cache_option), tuple(cache_path)) self._cache_paths = (tuple(cache_option), tuple(cache_path))
self._consistencies = _consistencies
def get_opt_by_path(self, path): def get_opt_by_path(self, path):
try: try:
@ -476,24 +603,24 @@ class OptionDescription(BaseInformation):
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
raise ConfigError("master group {} shall not have " raise ConfigError("master group {} shall not have "
"a subgroup".format(self._name)) "a subgroup".format(self._name))
if not child.multi: if not child.is_multi():
raise ConfigError("not allowed option {0} in group {1}" raise ConfigError("not allowed option {0} in group {1}"
": this option is not a multi" ": this option is not a multi"
"".format(child._name, self._name)) "".format(child._name, self._name))
if child._name == self._name: if child._name == self._name:
identical_master_child_name = True identical_master_child_name = True
child.multitype = multitypes.master child._multitype = multitypes.master
master = child master = child
else: else:
slaves.append(child) slaves.append(child)
if master is None: if master is None:
raise ConfigError('master group with wrong master name for {}' raise ConfigError('master group with wrong master name for {}'
''.format(self._name)) ''.format(self._name))
master.master_slaves = tuple(slaves) master._master_slaves = tuple(slaves)
for child in self._children[1]: for child in self._children[1]:
if child != master: if child != master:
child.master_slaves = master child._master_slaves = master
child.multitype = multitypes.slave child._multitype = multitypes.slave
if not identical_master_child_name: if not identical_master_child_name:
raise ConfigError("the master group: {} has not any " raise ConfigError("the master group: {} has not any "
"master child".format(self._name)) "master child".format(self._name))
@ -503,6 +630,13 @@ class OptionDescription(BaseInformation):
def get_group_type(self): def get_group_type(self):
return self._group_type return self._group_type
def valid_consistency(self, opt, value, context, index):
cons = self._consistencies.get(opt)
if cons is not None:
func, opts = cons
return getattr(opts[0], func)(opt, value, context, index, opts)
return True
def validate_requires_arg(requires, name): def validate_requires_arg(requires, name):
"check malformed requirements" "check malformed requirements"

View File

@ -18,7 +18,7 @@
# #
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.error import MandatoryError, MultiTypeError, \ from tiramisu.error import MandatoryError, MultiTypeError, \
ConfigError # , OptionValueError ConfigError, ValidateError
from tiramisu.setting import owners, multitypes from tiramisu.setting import owners, multitypes
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
@ -44,8 +44,8 @@ class Values(object):
if opt.is_multi(): if opt.is_multi():
value = Multi(value, self.context, opt) value = Multi(value, self.context, opt)
#if slave, had values until master's one #if slave, had values until master's one
if opt.multitype == multitypes.slave: if opt.get_multitype() == multitypes.slave:
masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.master_slaves) masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.get_master_slaves())
mastervalue = getattr(self.context, masterpath) mastervalue = getattr(self.context, masterpath)
masterlen = len(mastervalue) masterlen = len(mastervalue)
if len(value) > masterlen: if len(value) > masterlen:
@ -118,7 +118,7 @@ class Values(object):
def __getitem__(self, opt): def __getitem__(self, opt):
return self._getitem(opt) return self._getitem(opt)
def _getitem(self, opt, force_properties=None): def _getitem(self, opt, force_properties=None, validate=True):
# options with callbacks # options with callbacks
value = self._get_value(opt) value = self._get_value(opt)
setting = self.context.cfgimpl_get_settings() setting = self.context.cfgimpl_get_settings()
@ -130,13 +130,11 @@ class Values(object):
if not self.is_default_owner(opt) and ( if not self.is_default_owner(opt) and (
not is_frozen or (is_frozen and not is_frozen or (is_frozen and
not setting.has_property('force_default_on_freeze', opt, False))): not setting.has_property('force_default_on_freeze', opt, False))):
return value pass
else:
value = self._getcallback_value(opt) value = self._getcallback_value(opt)
if opt.is_multi(): if opt.is_multi():
value = self.fill_multi(opt, value) 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 #suppress value if already set
self.reset(opt) self.reset(opt)
# frozen and force default # frozen and force default
@ -145,15 +143,18 @@ class Values(object):
if opt.is_multi(): if opt.is_multi():
value = self.fill_multi(opt, value) value = self.fill_multi(opt, value)
self._test_mandatory(opt, value, force_properties) self._test_mandatory(opt, value, force_properties)
if validate and not opt.validate(value, self.context, setting.has_property('validator')):
raise ValidateError('invalid calculated value returned'
' for option {0}: {1}'.format(opt._name, value))
return value return value
def __setitem__(self, opt, value): def __setitem__(self, opt, value):
if not opt.validate(value, if not opt.validate(value, self.context,
self.context.cfgimpl_get_settings().has_property('validator')): self.context.cfgimpl_get_settings().has_property('validator')):
raise ConfigError('invalid value {}' raise ValidateError('invalid value {}'
' for option {}'.format(value, opt._name)) ' for option {}'.format(value, opt._name))
if opt.is_multi(): if opt.is_multi():
if opt.multitype == multitypes.master: if opt.get_multitype() == multitypes.master:
masterlen = len(value) masterlen = len(value)
for slave in opt.master_slaves: for slave in opt.master_slaves:
value_slave = self._get_value(slave) value_slave = self._get_value(slave)
@ -165,7 +166,7 @@ class Values(object):
for num in range(0, masterlen - len(value_slave)): for num in range(0, masterlen - len(value_slave)):
value_slave.append(slave.getdefault_multi(), force=True) value_slave.append(slave.getdefault_multi(), force=True)
elif opt.multitype == multitypes.slave: elif opt.get_multitype() == multitypes.slave:
if len(self._get_value(opt.master_slaves)) != len(value): if len(self._get_value(opt.master_slaves)) != len(value):
raise MultiTypeError("invalid len for the slave: {0}" raise MultiTypeError("invalid len for the slave: {0}"
" which has {1} as master".format( " which has {1} as master".format(
@ -231,11 +232,11 @@ class Multi(list):
only if the option is a master only if the option is a master
""" """
if not force: if not force:
if self.opt.multitype == multitypes.slave: if self.opt.get_multitype() == multitypes.slave:
raise MultiTypeError("cannot append a value on a multi option {0}" raise MultiTypeError("cannot append a value on a multi option {0}"
" which is a slave".format(self.opt._name)) " which is a slave".format(self.opt._name))
elif self.opt.multitype == multitypes.master: elif self.opt.get_multitype() == multitypes.master:
for slave in self.opt.master_slaves: for slave in self.opt.get_master_slaves():
self.context.cfgimpl_get_values()[slave].append(slave.getdefault_multi(), force=True) self.context.cfgimpl_get_values()[slave].append(slave.getdefault_multi(), force=True)
self._validate(value) self._validate(value)
self.context.cfgimpl_get_values().setitem(self.opt, self) self.context.cfgimpl_get_values().setitem(self.opt, self)
@ -259,7 +260,7 @@ class Multi(list):
raise MultiTypeError("cannot append a value on a multi option {0}" raise MultiTypeError("cannot append a value on a multi option {0}"
" which is a slave".format(self.opt._name)) " which is a slave".format(self.opt._name))
elif self.opt.multitype == multitypes.master: elif self.opt.multitype == multitypes.master:
for slave in self.opt.master_slaves: for slave in self.opt.get_master_slaves():
self.context.cfgimpl_get_values()[slave].pop(key, force=True) self.context.cfgimpl_get_values()[slave].pop(key, force=True)
self.context.cfgimpl_get_values().setitem(self.opt, self) self.context.cfgimpl_get_values().setitem(self.opt, self)
return super(Multi, self).pop(key) return super(Multi, self).pop(key)