add DynOptionDescription

This commit is contained in:
Emmanuel Garette 2014-06-19 23:22:39 +02:00
parent 888446e4c5
commit b64189f763
21 changed files with 2382 additions and 543 deletions

View file

@ -1,3 +1,9 @@
Thu Jun 19 23:20:29 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
* add DynOptionDescription:
DynOptionDescription are OptionDescription that generate auto
OptionDescription with a callback function
Sun Apr 27 10:32:40 2014 +0200 Emmanuel Garette <egarette@cadoles.com> Sun Apr 27 10:32:40 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
* behavior change in ChoiceOption: * behavior change in ChoiceOption:

View file

@ -0,0 +1,87 @@
# coding: utf-8
import autopath
from tiramisu.setting import owners
from tiramisu.option import ChoiceOption, StrOption, OptionDescription
from tiramisu.config import Config
from py.test import raises
def return_val(val):
return val
def return_list():
return ['val1', 'val2']
def return_calc_list(val):
return [val]
def test_choiceoption_function():
ch = ChoiceOption('ch', '', values=return_list)
od = OptionDescription('od', '', [ch])
cfg = Config(od)
cfg.read_write()
owner = cfg.cfgimpl_get_settings().getowner()
assert cfg.getowner(ch) == owners.default
cfg.ch = 'val1'
assert cfg.getowner(ch) == owner
del(cfg.ch)
assert cfg.getowner(ch) == owners.default
raises(ValueError, "cfg.ch='no'")
assert cfg.getowner(ch) == owners.default
def test_choiceoption_calc_function():
ch = ChoiceOption('ch', "", values=return_calc_list, values_params={'': ('val1',)})
od = OptionDescription('od', '', [ch])
cfg = Config(od)
cfg.read_write()
owner = cfg.cfgimpl_get_settings().getowner()
assert cfg.getowner(ch) == owners.default
cfg.ch = 'val1'
assert cfg.getowner(ch) == owner
del(cfg.ch)
assert cfg.getowner(ch) == owners.default
raises(ValueError, "cfg.ch='no'")
assert cfg.getowner(ch) == owners.default
def test_choiceoption_calc_opt_function():
st = StrOption('st', '', 'val1')
ch = ChoiceOption('ch', "", values=return_calc_list, values_params={'': ((st, False),)})
od = OptionDescription('od', '', [st, ch])
cfg = Config(od)
cfg.read_write()
owner = cfg.cfgimpl_get_settings().getowner()
assert cfg.getowner(ch) == owners.default
cfg.ch = 'val1'
assert cfg.getowner(ch) == owner
del(cfg.ch)
assert cfg.getowner(ch) == owners.default
raises(ValueError, "cfg.ch='no'")
assert cfg.getowner(ch) == owners.default
def test_choiceoption_calc_opt_multi_function():
st = StrOption('st', '', ['val1'], multi=True)
ch = ChoiceOption('ch', "", default_multi='val2', values=return_val, values_params={'': ((st, False),)}, multi=True)
ch2 = ChoiceOption('ch2', "", default=['val2'], values=return_val, values_params={'': ((st, False),)}, multi=True)
od = OptionDescription('od', '', [st, ch, ch2])
cfg = Config(od)
cfg.read_write()
assert cfg.ch == []
owner = cfg.cfgimpl_get_settings().getowner()
assert cfg.getowner(ch) == owners.default
raises(ValueError, "cfg.ch.append()")
cfg.ch = ['val1']
assert cfg.getowner(ch) == owner
del(cfg.ch)
assert cfg.getowner(ch) == owners.default
raises(ValueError, "cfg.ch='no'")
assert cfg.getowner(ch) == owners.default
#
raises(ValueError, "cfg.ch2")

File diff suppressed because it is too large Load diff

View file

@ -46,9 +46,12 @@ def a_func():
def test_option_valid_name(): def test_option_valid_name():
IntOption('test', '') IntOption('test', '')
raises(ValueError, 'IntOption(1, "")') raises(ValueError, 'IntOption(1, "")')
raises(ValueError, 'IntOption("1test", "")')
IntOption("test1", "")
raises(ValueError, 'IntOption("impl_test", "")') raises(ValueError, 'IntOption("impl_test", "")')
raises(ValueError, 'IntOption("_test", "")') raises(ValueError, 'IntOption("_test", "")')
raises(ValueError, 'IntOption("unwrap_from_path", "")') raises(ValueError, 'IntOption("unwrap_from_path", "")')
raises(ValueError, 'IntOption(" ", "")')
def test_option_with_callback(): def test_option_with_callback():

View file

@ -51,30 +51,6 @@ def is_config(config, **kwargs):
return 'no' return 'no'
def make_description():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
gcdummy = BoolOption('dummy', 'dummy', default=False)
objspaceoption = ChoiceOption('objspace', 'Object space',
('std', 'thunk'), 'std')
booloption = BoolOption('bool', 'Test boolean option', default=True)
intoption = IntOption('int', 'Test int option', default=0)
intoption2 = IntOption('int', 'Test int option', default=0)
floatoption = FloatOption('float', 'Test float option', default=2.3)
stroption = StrOption('str', 'Test string option', default="abc")
boolop = BoolOption('boolop', 'Test boolean option op', default=True)
wantref_option = BoolOption('wantref', 'Test requires', default=False,
requires=({'option': boolop, 'expected': True, 'action': 'hidden'},))
wantframework_option = BoolOption('wantframework', 'Test requires',
default=False,
requires=({'option': boolop, 'expected': True, 'action': 'hidden'},))
gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption, intoption2])
descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption,
wantref_option, stroption,
wantframework_option,
intoption, boolop])
return descr
def make_description_duplicates(): def make_description_duplicates():
gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref')
## dummy 1 ## dummy 1
@ -929,3 +905,26 @@ def test_callback_multi_list_params_key():
cfg = Config(maconfig) cfg = Config(maconfig)
cfg.read_write() cfg.read_write()
assert cfg.val2.val2 == ['val', 'val'] assert cfg.val2.val2 == ['val', 'val']
def test_masterslaves_callback_description():
st1 = StrOption('st1', "", multi=True)
st2 = StrOption('st2', "", multi=True, callback=return_value, callback_params={'': ((st1, False),)})
stm = OptionDescription('st1', '', [st1, st2])
stm.impl_set_group_type(groups.master)
st = OptionDescription('st', '', [stm])
od = OptionDescription('od', '', [st])
od2 = OptionDescription('od', '', [od])
cfg = Config(od2)
owner = cfg.cfgimpl_get_settings().getowner()
st1 = cfg.unwrap_from_path('od.st.st1.st1')
st2 = cfg.unwrap_from_path('od.st.st1.st2')
assert cfg.od.st.st1.st1 == []
assert cfg.od.st.st1.st2 == []
assert cfg.getowner(st1) == owners.default
assert cfg.getowner(st2) == owners.default
##
cfg.od.st.st1.st1.append('yes')
assert cfg.od.st.st1.st1 == ['yes']
assert cfg.od.st.st1.st2 == ['yes']
assert cfg.getowner(st1) == owner

View file

@ -1,9 +1,10 @@
import autopath import autopath
from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \ from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \
IntOption, OptionDescription IntOption, IPOption, NetmaskOption, StrOption, OptionDescription, \
DynOptionDescription
from tiramisu.config import Config, GroupConfig, MetaConfig from tiramisu.config import Config, GroupConfig, MetaConfig
from tiramisu.setting import owners from tiramisu.setting import groups, owners
from tiramisu.storage import delete_session from tiramisu.storage import delete_session
from tiramisu.error import ConfigError from tiramisu.error import ConfigError
from pickle import dumps, loads from pickle import dumps, loads
@ -93,10 +94,35 @@ def _diff_opt(opt1, opt2):
assert val1[key][idx] == val2[key][idx] assert val1[key][idx] == val2[key][idx]
else: else:
assert val1 == val2 assert val1 == val2
elif attr == '_master_slaves':
assert val1.master.impl_getname() == val2.master.impl_getname()
sval1 = [opt.impl_getname() for opt in val1.slaves]
sval2 = [opt.impl_getname() for opt in val2.slaves]
assert sval1 == sval2
elif attr == '_subdyn':
try:
assert val1.impl_getname() == val2.impl_getname()
except AttributeError:
assert val1 == val2
else: else:
assert val1 == val2 assert val1 == val2
def _diff_opts(opt1, opt2):
_diff_opt(opt1, opt2)
if isinstance(opt1, OptionDescription) or isinstance(opt1, DynOptionDescription):
children1 = set([opt.impl_getname() for opt in opt1._impl_getchildren(dyn=False)])
children2 = set([opt.impl_getname() for opt in opt2._impl_getchildren(dyn=False)])
diff1 = children1 - children2
diff2 = children2 - children1
if diff1 != set():
raise Exception('more attribute in opt1 {0}'.format(list(diff1)))
if diff2 != set():
raise Exception('more attribute in opt2 {0}'.format(list(diff2)))
for child in children1:
_diff_opts(opt1._getattr(child, dyn=False), opt2._getattr(child, dyn=False))
def _diff_conf(cfg1, cfg2): def _diff_conf(cfg1, cfg2):
attr1 = set(_get_slots(cfg1)) attr1 = set(_get_slots(cfg1))
attr2 = set(_get_slots(cfg2)) attr2 = set(_get_slots(cfg2))
@ -148,11 +174,7 @@ def test_diff_opt():
a = dumps(o1) a = dumps(o1)
q = loads(a) q = loads(a)
_diff_opt(o1, q) _diff_opts(o1, q)
_diff_opt(o1.o, q.o)
_diff_opt(o1.o.b, q.o.b)
_diff_opt(o1.o.u, q.o.u)
_diff_opt(o1.o.s, q.o.s)
def test_only_optiondescription(): def test_only_optiondescription():
@ -172,11 +194,7 @@ def test_diff_opt_cache():
a = dumps(o1) a = dumps(o1)
q = loads(a) q = loads(a)
_diff_opt(o1, q) _diff_opts(o1, q)
_diff_opt(o1.o, q.o)
_diff_opt(o1.o.b, q.o.b)
_diff_opt(o1.o.u, q.o.u)
_diff_opt(o1.o.s, q.o.s)
def test_diff_opt_callback(): def test_diff_opt_callback():
@ -190,11 +208,7 @@ def test_diff_opt_callback():
a = dumps(o1) a = dumps(o1)
q = loads(a) q = loads(a)
_diff_opt(o1, q) _diff_opts(o1, q)
_diff_opt(o1.o, q.o)
_diff_opt(o1.o.b, q.o.b)
_diff_opt(o1.o.b2, q.o.b2)
_diff_opt(o1.o.b3, q.o.b3)
def test_no_state_attr(): def test_no_state_attr():
@ -224,6 +238,7 @@ def test_state_config():
cfg._impl_test = True cfg._impl_test = True
a = dumps(cfg) a = dumps(cfg)
q = loads(a) q = loads(a)
_diff_opts(cfg.cfgimpl_get_description(), q.cfgimpl_get_description())
_diff_conf(cfg, q) _diff_conf(cfg, q)
try: try:
delete_session('config', '29090931') delete_session('config', '29090931')
@ -231,6 +246,35 @@ def test_state_config():
pass pass
def test_state_config2():
a = IPOption('a', '')
b = NetmaskOption('b', '')
dod1 = OptionDescription('dod1', '', [a, b])
b.impl_add_consistency('ip_netmask', a)
od1 = OptionDescription('od1', '', [dod1])
st1 = StrOption('st1', "", multi=True)
st2 = StrOption('st2', "", multi=True)
st3 = StrOption('st3', "", multi=True, callback=return_value, callback_params={'': ((st2, False),)})
stm = OptionDescription('st1', '', [st1, st2, st3])
stm.impl_set_group_type(groups.master)
st = OptionDescription('st', '', [stm])
od = OptionDescription('od', '', [st])
od2 = OptionDescription('od', '', [od, od1])
try:
cfg = Config(od2, persistent=True, session_id='29090939')
except ValueError:
cfg = Config(od2, session_id='29090939')
cfg._impl_test = True
a = dumps(cfg)
q = loads(a)
_diff_opts(cfg.cfgimpl_get_description(), q.cfgimpl_get_description())
_diff_conf(cfg, q)
try:
delete_session('config', '29090939')
except ConfigError:
pass
def test_state_properties(): def test_state_properties():
val1 = BoolOption('val1', "") val1 = BoolOption('val1', "")
maconfig = OptionDescription('rootconfig', '', [val1]) maconfig = OptionDescription('rootconfig', '', [val1])

View file

@ -136,22 +136,32 @@ def carry_out_calculation(option, config, callback, callback_params,
""" """
tcparams = {} tcparams = {}
# if callback_params has a callback, launch several time calculate() # if callback_params has a callback, launch several time calculate()
one_is_multi = False master_slave = False
# multi's option should have same value for all option # multi's option should have same value for all option
len_multi = None len_multi = None
try:
if option._is_subdyn():
tcparams[''] = [(option.impl_getsuffix(), False)]
except AttributeError:
pass
for key, callbacks in callback_params.items(): for key, callbacks in callback_params.items():
for callbk in callbacks: for callbk in callbacks:
if isinstance(callbk, tuple): if isinstance(callbk, tuple):
if config is None: if config is undefined:
raise ContextError() raise ContextError() # pragma: optional cover
if len(callbk) == 1: if len(callbk) == 1: # pragma: optional cover
tcparams.setdefault(key, []).append((config, False)) tcparams.setdefault(key, []).append((config, False))
else: else:
# callbk is something link (opt, True|False) # callbk is something link (opt, True|False)
opt, force_permissive = callbk opt, force_permissive = callbk
path = config.cfgimpl_get_description( if opt._is_subdyn():
).impl_get_path_by_opt(opt) root = '.'.join(option.impl_getpath(config).split('.')[:-1])
name = opt.impl_getname() + option.impl_getsuffix()
path = root + '.' + name
opt = opt._impl_to_dyn(name, path)
else:
path = config.cfgimpl_get_description(
).impl_get_path_by_opt(opt)
# get value # get value
try: try:
value = config.getattr(path, force_permissive=True, value = config.getattr(path, force_permissive=True,
@ -159,7 +169,7 @@ def carry_out_calculation(option, config, callback, callback_params,
# convert to list, not modifie this multi # convert to list, not modifie this multi
if value.__class__.__name__ == 'Multi': if value.__class__.__name__ == 'Multi':
value = list(value) value = list(value)
except PropertiesOptionError as err: except PropertiesOptionError as err: # pragma: optional cover
if force_permissive: if force_permissive:
continue continue
raise ConfigError(_('unable to carry out a calculation' raise ConfigError(_('unable to carry out a calculation'
@ -171,7 +181,7 @@ def carry_out_calculation(option, config, callback, callback_params,
if opt.impl_is_master_slaves() and \ if opt.impl_is_master_slaves() and \
opt.impl_get_master_slaves().in_same_group(option): opt.impl_get_master_slaves().in_same_group(option):
len_multi = len(value) len_multi = len(value)
one_is_multi = True master_slave = True
is_multi = True is_multi = True
else: else:
is_multi = False is_multi = False
@ -183,9 +193,11 @@ def carry_out_calculation(option, config, callback, callback_params,
# if one value is a multi, launch several time calculate # if one value is a multi, launch several time calculate
# if index is set, return a value # if index is set, return a value
# if no index, return a list # if no index, return a list
if one_is_multi: if master_slave:
ret = [] ret = []
if index is not undefined: if index is not undefined:
# for example if append master and get a no default slave without
# getting master
range_ = [index] range_ = [index]
else: else:
range_ = range(len_multi) range_ = range(len_multi)

View file

@ -21,7 +21,8 @@
"options handler global entry point" "options handler global entry point"
import weakref import weakref
from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.option import OptionDescription, Option, SymLinkOption from tiramisu.option import OptionDescription, Option, SymLinkOption, \
DynSymLinkOption
from tiramisu.setting import groups, Settings, default_encoding, undefined from tiramisu.setting import groups, Settings, default_encoding, undefined
from tiramisu.storage import get_storages, get_storage, set_storage, \ from tiramisu.storage import get_storages, get_storage, set_storage, \
_impl_getstate_setting _impl_getstate_setting
@ -47,12 +48,18 @@ class SubConfig(object):
:type subpath: `str` with the path name :type subpath: `str` with the path name
""" """
# main option description # main option description
if descr is not None and not isinstance(descr, OptionDescription): error = False
try:
if descr is not None and not descr.impl_is_optiondescription(): # pragma: optional cover
error = True
except AttributeError:
error = True
if error:
raise TypeError(_('descr must be an optiondescription, not {0}' raise TypeError(_('descr must be an optiondescription, not {0}'
).format(type(descr))) ).format(type(descr)))
self._impl_descr = descr self._impl_descr = descr
# sub option descriptions # sub option descriptions
if not isinstance(context, weakref.ReferenceType): if not isinstance(context, weakref.ReferenceType): # pragma: optional cover
raise ValueError('context must be a Weakref') raise ValueError('context must be a Weakref')
self._impl_context = context self._impl_context = context
self._impl_path = subpath self._impl_path = subpath
@ -60,7 +67,7 @@ class SubConfig(object):
def cfgimpl_reset_cache(self, only_expired=False, only=('values', def cfgimpl_reset_cache(self, only_expired=False, only=('values',
'settings')): 'settings')):
"remove cache (in context)" "remove cache (in context)"
self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only) self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only) # pragma: optional cover
def cfgimpl_get_home_by_path(self, path, force_permissive=False): def cfgimpl_get_home_by_path(self, path, force_permissive=False):
""":returns: tuple (config, name)""" """:returns: tuple (config, name)"""
@ -93,13 +100,15 @@ class SubConfig(object):
def __iter__(self): def __iter__(self):
"""Pythonesque way of parsing group's ordered options. """Pythonesque way of parsing group's ordered options.
iteration only on Options (not OptionDescriptions)""" iteration only on Options (not OptionDescriptions)"""
for child in self.cfgimpl_get_description().impl_getchildren(): for child in self.cfgimpl_get_description()._impl_getchildren(
if not isinstance(child, OptionDescription): context=self._cfgimpl_get_context()):
if not child.impl_is_optiondescription():
try: try:
yield child.impl_getname(), getattr(self, child.impl_getname()) name = child.impl_getname()
except GeneratorExit: yield name, getattr(self, name)
except GeneratorExit: # pragma: optional cover
raise StopIteration raise StopIteration
except PropertiesOptionError: except PropertiesOptionError: # pragma: optional cover
pass # option with properties pass # option with properties
def iter_all(self, force_permissive=False): def iter_all(self, force_permissive=False):
@ -108,10 +117,10 @@ class SubConfig(object):
for child in self.cfgimpl_get_description().impl_getchildren(): for child in self.cfgimpl_get_description().impl_getchildren():
try: try:
yield child.impl_getname(), self.getattr(child.impl_getname(), yield child.impl_getname(), self.getattr(child.impl_getname(),
force_permissive=force_permissive) force_permissive=force_permissive)
except GeneratorExit: except GeneratorExit: # pragma: optional cover
raise StopIteration raise StopIteration
except PropertiesOptionError: except PropertiesOptionError: # pragma: optional cover
pass # option with properties pass # option with properties
def iter_groups(self, group_type=None, force_permissive=False): def iter_groups(self, group_type=None, force_permissive=False):
@ -124,19 +133,20 @@ class SubConfig(object):
`setting.groups` `setting.groups`
""" """
if group_type is not None and not isinstance(group_type, if group_type is not None and not isinstance(group_type,
groups.GroupType): groups.GroupType): # pragma: optional cover
raise TypeError(_("unknown group_type: {0}").format(group_type)) raise TypeError(_("unknown group_type: {0}").format(group_type))
for child in self.cfgimpl_get_description().impl_getchildren(): for child in self.cfgimpl_get_description()._impl_getchildren(
if isinstance(child, OptionDescription): context=self._cfgimpl_get_context()):
if child.impl_is_optiondescription():
try: try:
if group_type is None or (group_type is not None and if group_type is None or (group_type is not None and
child.impl_get_group_type() child.impl_get_group_type()
== group_type): == group_type):
yield child.impl_getname(), self.getattr(child.impl_getname(), name = child.impl_getname()
force_permissive=force_permissive) yield name, self.getattr(name, force_permissive=force_permissive)
except GeneratorExit: except GeneratorExit: # pragma: optional cover
raise StopIteration raise StopIteration
except PropertiesOptionError: except PropertiesOptionError: # pragma: optional cover
pass pass
# ______________________________________________________________________ # ______________________________________________________________________
@ -148,7 +158,7 @@ class SubConfig(object):
for name, value in self: for name, value in self:
try: try:
lines.append("{0} = {1}".format(name, value)) lines.append("{0} = {1}".format(name, value))
except UnicodeEncodeError: except UnicodeEncodeError: # pragma: optional cover
lines.append("{0} = {1}".format(name, lines.append("{0} = {1}".format(name,
value.encode(default_encoding))) value.encode(default_encoding)))
return '\n'.join(lines) return '\n'.join(lines)
@ -162,12 +172,12 @@ class SubConfig(object):
old `SubConfig`, `Values`, `Multi` or `Settings`) old `SubConfig`, `Values`, `Multi` or `Settings`)
""" """
context = self._impl_context() context = self._impl_context()
if context is None: if context is None: # pragma: optional cover
raise ConfigError(_('the context does not exist anymore')) raise ConfigError(_('the context does not exist anymore'))
return context return context
def cfgimpl_get_description(self): def cfgimpl_get_description(self):
if self._impl_descr is None: if self._impl_descr is None: # pragma: optional cover
raise ConfigError(_('no option description found for this config' raise ConfigError(_('no option description found for this config'
' (may be GroupConfig)')) ' (may be GroupConfig)'))
else: else:
@ -183,43 +193,50 @@ class SubConfig(object):
# attribute methods # attribute methods
def __setattr__(self, name, value): def __setattr__(self, name, value):
"attribute notation mechanism for the setting of the value of an option" "attribute notation mechanism for the setting of the value of an option"
if name.startswith('_impl_'):
object.__setattr__(self, name, value)
return
self._setattr(name, value) self._setattr(name, value)
def _setattr(self, name, value, force_permissive=False): def _setattr(self, name, value, force_permissive=False):
if '.' in name: if name.startswith('_impl_'):
object.__setattr__(self, name, value)
return
if '.' in name: # pragma: optional cover
homeconfig, name = self.cfgimpl_get_home_by_path(name) homeconfig, name = self.cfgimpl_get_home_by_path(name)
return homeconfig.__setattr__(name, value) return homeconfig._setattr(name, value, force_permissive)
child = getattr(self.cfgimpl_get_description(), name) context = self._cfgimpl_get_context()
child = self.cfgimpl_get_description().__getattr__(name,
context=context)
if isinstance(child, OptionDescription): if isinstance(child, OptionDescription):
raise TypeError(_("can't assign to an OptionDescription")) raise TypeError(_("can't assign to an OptionDescription")) # pragma: optional cover
elif not isinstance(child, SymLinkOption): elif isinstance(child, SymLinkOption) and \
if self._impl_path is None: not isinstance(child, DynSymLinkOption): # pragma: no dynoptiondescription cover
path = name
else:
path = self._impl_path + '.' + name
self.cfgimpl_get_values().setitem(child, value, path,
force_permissive=force_permissive)
else:
context = self._cfgimpl_get_context()
path = context.cfgimpl_get_description().impl_get_path_by_opt( path = context.cfgimpl_get_description().impl_get_path_by_opt(
child._opt) child._opt)
context._setattr(path, value, force_permissive=force_permissive) context._setattr(path, value, force_permissive=force_permissive)
else:
subpath = self._get_subpath(name)
self.cfgimpl_get_values().setitem(child, value, subpath,
force_permissive=force_permissive)
def __delattr__(self, name): def __delattr__(self, name):
child = getattr(self.cfgimpl_get_description(), name) context = self._cfgimpl_get_context()
child = self.cfgimpl_get_description().__getattr__(name, context)
self.cfgimpl_get_values().__delitem__(child) self.cfgimpl_get_values().__delitem__(child)
def __getattr__(self, name): def __getattr__(self, name):
return self.getattr(name) return self.getattr(name)
def _getattr(self, name, force_permissive=False, validate=True): def _getattr(self, name, force_permissive=False, validate=True): # pragma: optional cover
"""use getattr instead of _getattr """use getattr instead of _getattr
""" """
return self.getattr(name, force_permissive, validate) return self.getattr(name, force_permissive, validate)
def _get_subpath(self, name):
if self._impl_path is None:
subpath = name
else:
subpath = self._impl_path + '.' + name
return subpath
def getattr(self, name, force_permissive=False, validate=True): def getattr(self, name, force_permissive=False, validate=True):
""" """
attribute notation mechanism for accessing the value of an option attribute notation mechanism for accessing the value of an option
@ -234,21 +251,20 @@ class SubConfig(object):
name, force_permissive=force_permissive) name, force_permissive=force_permissive)
return homeconfig.getattr(name, force_permissive=force_permissive, return homeconfig.getattr(name, force_permissive=force_permissive,
validate=validate) validate=validate)
opt_or_descr = self.cfgimpl_get_description().__getattr__(name) context = self._cfgimpl_get_context()
if self._impl_path is None: opt_or_descr = self.cfgimpl_get_description().__getattr__(name, context=context)
subpath = name subpath = self._get_subpath(name)
else: if isinstance(opt_or_descr, DynSymLinkOption):
subpath = self._impl_path + '.' + name return self.cfgimpl_get_values()._get_cached_item(
# symlink options opt_or_descr, path=subpath,
#FIXME a gerer plutot dans l'option ca ... validate=validate,
#FIXME je n'en sais rien en fait ... :/ force_permissive=force_permissive)
if isinstance(opt_or_descr, SymLinkOption): elif isinstance(opt_or_descr, SymLinkOption): # pragma: no dynoptiondescription cover
context = self._cfgimpl_get_context()
path = context.cfgimpl_get_description().impl_get_path_by_opt( path = context.cfgimpl_get_description().impl_get_path_by_opt(
opt_or_descr._opt) opt_or_descr._opt)
return context.getattr(path, validate=validate, return context.getattr(path, validate=validate,
force_permissive=force_permissive) force_permissive=force_permissive)
elif isinstance(opt_or_descr, OptionDescription): elif opt_or_descr.impl_is_optiondescription():
self.cfgimpl_get_settings().validate_properties( self.cfgimpl_get_settings().validate_properties(
opt_or_descr, True, False, path=subpath, opt_or_descr, True, False, path=subpath,
force_permissive=force_permissive) force_permissive=force_permissive)
@ -272,7 +288,7 @@ class SubConfig(object):
return self._cfgimpl_get_context()._find(bytype, byname, byvalue, return self._cfgimpl_get_context()._find(bytype, byname, byvalue,
first=False, first=False,
type_=type_, type_=type_,
_subpath=self.cfgimpl_get_path(), _subpath=self.cfgimpl_get_path(False),
check_properties=check_properties, check_properties=check_properties,
force_permissive=force_permissive) force_permissive=force_permissive)
@ -289,7 +305,7 @@ class SubConfig(object):
""" """
return self._cfgimpl_get_context()._find( return self._cfgimpl_get_context()._find(
bytype, byname, byvalue, first=True, type_=type_, bytype, byname, byvalue, first=True, type_=type_,
_subpath=self.cfgimpl_get_path(), display_error=display_error, _subpath=self.cfgimpl_get_path(False), display_error=display_error,
check_properties=check_properties, check_properties=check_properties,
force_permissive=force_permissive) force_permissive=force_permissive)
@ -311,11 +327,11 @@ class SubConfig(object):
return byvalue in value return byvalue in value
else: else:
return value == byvalue return value == byvalue
except PropertiesOptionError: # a property is a restriction # a property is a restriction upon the access of the value
# upon the access of the value except PropertiesOptionError: # pragma: optional cover
return False return False
if type_ not in ('option', 'path', 'value'): if type_ not in ('option', 'path', 'value'): # pragma: optional cover
raise ValueError(_('unknown type_ type {0}' raise ValueError(_('unknown type_ type {0}'
'for _find').format(type_)) 'for _find').format(type_))
find_results = [] find_results = []
@ -324,7 +340,7 @@ class SubConfig(object):
# and so on # and so on
only_first = first is True and byvalue is None and check_properties is None only_first = first is True and byvalue is None and check_properties is None
options = self.cfgimpl_get_description().impl_get_options_paths( options = self.cfgimpl_get_description().impl_get_options_paths(
bytype, byname, _subpath, only_first) bytype, byname, _subpath, only_first, self._cfgimpl_get_context())
for path, option in options: for path, option in options:
if not _filter_by_value(): if not _filter_by_value():
continue continue
@ -333,7 +349,7 @@ class SubConfig(object):
try: try:
value = self.getattr(path, value = self.getattr(path,
force_permissive=force_permissive) force_permissive=force_permissive)
except PropertiesOptionError: except PropertiesOptionError: # pragma: optional cover
# a property restricts the access of the value # a property restricts the access of the value
continue continue
if type_ == 'value': if type_ == 'value':
@ -349,7 +365,7 @@ class SubConfig(object):
return self._find_return_results(find_results, display_error) return self._find_return_results(find_results, display_error)
def _find_return_results(self, find_results, display_error): def _find_return_results(self, find_results, display_error):
if find_results == []: if find_results == []: # pragma: optional cover
if display_error: if display_error:
raise AttributeError(_("no option found in config" raise AttributeError(_("no option found in config"
" with these criteria")) " with these criteria"))
@ -400,21 +416,18 @@ class SubConfig(object):
pathsvalues = [] pathsvalues = []
if _currpath is None: if _currpath is None:
_currpath = [] _currpath = []
if withoption is None and withvalue is not undefined: if withoption is None and withvalue is not undefined: # pragma: optional cover
raise ValueError(_("make_dict can't filtering with value without " raise ValueError(_("make_dict can't filtering with value without "
"option")) "option"))
if withoption is not None: if withoption is not None:
mypath = self.cfgimpl_get_path() context = self._cfgimpl_get_context()
for path in self._cfgimpl_get_context()._find(bytype=None, for path in context._find(bytype=None, byname=withoption,
byname=withoption, byvalue=withvalue, first=False,
byvalue=withvalue, type_='path', _subpath=self.cfgimpl_get_path(False),
first=False, force_permissive=force_permissive):
type_='path',
_subpath=mypath,
force_permissive=force_permissive):
path = '.'.join(path.split('.')[:-1]) path = '.'.join(path.split('.')[:-1])
opt = self._cfgimpl_get_context().cfgimpl_get_description( opt = context.unwrap_from_path(path, force_permissive=True)
).impl_get_opt_by_path(path) mypath = self.cfgimpl_get_path()
if mypath is not None: if mypath is not None:
if mypath == path: if mypath == path:
withoption = None withoption = None
@ -422,7 +435,7 @@ class SubConfig(object):
break break
else: else:
tmypath = mypath + '.' tmypath = mypath + '.'
if not path.startswith(tmypath): if not path.startswith(tmypath): # pragma: optional cover
raise AttributeError(_('unexpected path {0}, ' raise AttributeError(_('unexpected path {0}, '
'should start with {1}' 'should start with {1}'
'').format(path, mypath)) '').format(path, mypath))
@ -443,7 +456,7 @@ class SubConfig(object):
def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten, def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten,
force_permissive=False): force_permissive=False):
try: try:
if isinstance(opt, OptionDescription): if opt.impl_is_optiondescription():
pathsvalues += self.getattr(path, pathsvalues += self.getattr(path,
force_permissive=force_permissive).make_dict( force_permissive=force_permissive).make_dict(
flatten, flatten,
@ -457,13 +470,15 @@ class SubConfig(object):
else: else:
name = '.'.join(_currpath + [opt.impl_getname()]) name = '.'.join(_currpath + [opt.impl_getname()])
pathsvalues.append((name, value)) pathsvalues.append((name, value))
except PropertiesOptionError: except PropertiesOptionError: # pragma: optional cover
pass pass
def cfgimpl_get_path(self): def cfgimpl_get_path(self, dyn=True):
descr = self.cfgimpl_get_description() descr = self.cfgimpl_get_description()
context_descr = self._cfgimpl_get_context().cfgimpl_get_description() if not dyn and descr.impl_is_dynoptiondescription():
return context_descr.impl_get_path_by_opt(descr) context_descr = self._cfgimpl_get_context().cfgimpl_get_description()
return context_descr.impl_get_path_by_opt(descr._opt)
return self._impl_path
class _CommonConfig(SubConfig): class _CommonConfig(SubConfig):
@ -488,7 +503,7 @@ class _CommonConfig(SubConfig):
"""convenience method to retrieve an option's owner """convenience method to retrieve an option's owner
from the config itself from the config itself
""" """
if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption): if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption): # pragma: optional cover
raise TypeError(_('opt in getowner must be an option not {0}' raise TypeError(_('opt in getowner must be an option not {0}'
'').format(type(opt))) '').format(type(opt)))
return self.cfgimpl_get_values().getowner(opt, return self.cfgimpl_get_values().getowner(opt,
@ -501,13 +516,14 @@ class _CommonConfig(SubConfig):
:returns: Option() :returns: Option()
""" """
context = self._cfgimpl_get_context()
if '.' in path: if '.' in path:
homeconfig, path = self.cfgimpl_get_home_by_path( homeconfig, path = self.cfgimpl_get_home_by_path(
path, force_permissive=force_permissive) path, force_permissive=force_permissive)
return getattr(homeconfig.cfgimpl_get_description(), path) return homeconfig.cfgimpl_get_description().__getattr__(path, context=context)
return getattr(self.cfgimpl_get_description(), path) return self.cfgimpl_get_description().__getattr__(path, context=context)
def cfgimpl_get_path(self): def cfgimpl_get_path(self, dyn=True):
return None return None
def cfgimpl_get_meta(self): def cfgimpl_get_meta(self):
@ -533,7 +549,7 @@ class _CommonConfig(SubConfig):
# ----- state # ----- state
def __getstate__(self): def __getstate__(self):
if self._impl_meta is not None: if self._impl_meta is not None:
raise ConfigError(_('cannot serialize Config with MetaConfig')) raise ConfigError(_('cannot serialize Config with MetaConfig')) # pragma: optional cover
slots = set() slots = set()
for subclass in self.__class__.__mro__: for subclass in self.__class__.__mro__:
if subclass is not object: if subclass is not object:
@ -543,12 +559,12 @@ class _CommonConfig(SubConfig):
for slot in slots: for slot in slots:
try: try:
state[slot] = getattr(self, slot) state[slot] = getattr(self, slot)
except AttributeError: except AttributeError: # pragma: optional cover
pass pass
storage = self._impl_values._p_._storage storage = self._impl_values._p_._storage
if not storage.serializable: if not storage.serializable:
raise ConfigError(_('this storage is not serialisable, could be a ' raise ConfigError(_('this storage is not serialisable, could be a '
'none persistent storage')) 'none persistent storage')) # pragma: optional cover
state['_storage'] = {'session_id': storage.session_id, state['_storage'] = {'session_id': storage.session_id,
'persistent': storage.persistent} 'persistent': storage.persistent}
state['_impl_setting'] = _impl_getstate_setting() state['_impl_setting'] = _impl_getstate_setting()
@ -719,6 +735,6 @@ class MetaConfig(GroupConfig):
super(MetaConfig, self).__init__(children, session_id, persistent, descr) super(MetaConfig, self).__init__(children, session_id, persistent, descr)
def mandatory_warnings(config): def mandatory_warnings(config): # pragma: optional cover
#only for retro-compatibility #only for retro-compatibility
return config.cfgimpl_get_values().mandatory_warnings() return config.cfgimpl_get_values().mandatory_warnings()

View file

@ -65,7 +65,7 @@ class ConstError(TypeError):
#Warning #Warning
class ValueWarning(UserWarning): class ValueWarning(UserWarning): # pragma: optional cover
"""Option could warn user and not raise ValueError. """Option could warn user and not raise ValueError.
Example: Example:

View file

@ -43,9 +43,9 @@ languages += DEFAULT_LANG
mo_location = LOCALE_DIR mo_location = LOCALE_DIR
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3: # pragma: optional cover
gettext.install(True, localedir=None) gettext.install(True, localedir=None)
else: else: # pragma: optional cover
gettext.install(True, localedir=None, unicode=1) gettext.install(True, localedir=None, unicode=1)
gettext.find(APP_NAME, mo_location) gettext.find(APP_NAME, mo_location)
gettext.textdomain(APP_NAME) gettext.textdomain(APP_NAME)

View file

@ -1,6 +1,7 @@
from .masterslave import MasterSlaves from .masterslave import MasterSlaves
from .optiondescription import OptionDescription from .optiondescription import OptionDescription, DynOptionDescription, \
from .baseoption import Option, SymLinkOption, submulti SynDynOptionDescription
from .baseoption import Option, SymLinkOption, DynSymLinkOption, submulti
from .option import (ChoiceOption, BoolOption, IntOption, FloatOption, from .option import (ChoiceOption, BoolOption, IntOption, FloatOption,
StrOption, UnicodeOption, IPOption, PortOption, StrOption, UnicodeOption, IPOption, PortOption,
NetworkOption, NetmaskOption, BroadcastOption, NetworkOption, NetmaskOption, BroadcastOption,
@ -8,9 +9,10 @@ from .option import (ChoiceOption, BoolOption, IntOption, FloatOption,
FilenameOption) FilenameOption)
__all__ = ('MasterSlaves', 'OptionDescription', 'Option', 'SymLinkOption', __all__ = ('MasterSlaves', 'OptionDescription', 'DynOptionDescription',
'ChoiceOption', 'BoolOption', 'IntOption', 'FloatOption', 'SynDynOptionDescription', 'Option', 'SymLinkOption',
'StrOption', 'UnicodeOption', 'IPOption', 'PortOption', 'DynSymLinkOption', 'ChoiceOption', 'BoolOption',
'NetworkOption', 'NetmaskOption', 'BroadcastOption', 'IntOption', 'FloatOption', 'StrOption', 'UnicodeOption',
'DomainnameOption', 'EmailOption', 'URLOption', 'UsernameOption', 'IPOption', 'PortOption', 'NetworkOption', 'NetmaskOption',
'FilenameOption', 'submulti') 'BroadcastOption', 'DomainnameOption', 'EmailOption', 'URLOption',
'UsernameOption', 'FilenameOption', 'submulti')

View file

@ -40,7 +40,8 @@ class SubMulti(object):
submulti = SubMulti() submulti = SubMulti()
name_regexp = re.compile(r'^\d+') allowed_character = '[a-z\d\-_]'
name_regexp = re.compile(r'^[a-z]{0}*$'.format(allowed_character))
forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
'make_dict', 'unwrap_from_path', 'read_only', 'make_dict', 'unwrap_from_path', 'read_only',
'read_write', 'getowner', 'set_contexts') 'read_write', 'getowner', 'set_contexts')
@ -48,51 +49,51 @@ forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
def valid_name(name): def valid_name(name):
"an option's name is a str and does not start with 'impl' or 'cfgimpl'" "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
if not isinstance(name, str): if not isinstance(name, str): # pragma: optional cover
return False return False
if re.match(name_regexp, name) is None and not name.startswith('_') \ if re.match(name_regexp, name) is not None and not name.startswith('_') \
and name not in forbidden_names \ and name not in forbidden_names \
and not name.startswith('impl_') \ and not name.startswith('impl_') \
and not name.startswith('cfgimpl_'): and not name.startswith('cfgimpl_'):
return True return True
else: else: # pragma: optional cover
return False return False
def validate_callback(callback, callback_params, type_): def validate_callback(callback, callback_params, type_):
if type(callback) != FunctionType: if type(callback) != FunctionType: # pragma: optional cover
raise ValueError(_('{0} must be a function').format(type_)) raise ValueError(_('{0} must be a function').format(type_))
if callback_params is not None: if callback_params is not None:
if not isinstance(callback_params, dict): if not isinstance(callback_params, dict): # pragma: optional cover
raise ValueError(_('{0}_params must be a dict').format(type_)) raise ValueError(_('{0}_params must be a dict').format(type_))
for key, callbacks in callback_params.items(): for key, callbacks in callback_params.items():
if key != '' and len(callbacks) != 1: if key != '' and len(callbacks) != 1: # pragma: optional cover
raise ValueError(_("{0}_params with key {1} mustn't have " raise ValueError(_("{0}_params with key {1} mustn't have "
"length different to 1").format(type_, "length different to 1").format(type_,
key)) key))
if not isinstance(callbacks, tuple): if not isinstance(callbacks, tuple): # pragma: optional cover
raise ValueError(_('{0}_params must be tuple for key "{1}"' raise ValueError(_('{0}_params must be tuple for key "{1}"'
).format(type_, key)) ).format(type_, key))
for callbk in callbacks: for callbk in callbacks:
if isinstance(callbk, tuple): if isinstance(callbk, tuple):
if len(callbk) == 1: if len(callbk) == 1:
if callbk != (None,): if callbk != (None,): # pragma: optional cover
raise ValueError(_('{0}_params with length of ' raise ValueError(_('{0}_params with length of '
'tuple as 1 must only have ' 'tuple as 1 must only have '
'None as first value')) 'None as first value'))
elif len(callbk) != 2: elif len(callbk) != 2: # pragma: optional cover
raise ValueError(_('{0}_params must only have 1 or 2 ' raise ValueError(_('{0}_params must only have 1 or 2 '
'as length')) 'as length'))
else: else:
option, force_permissive = callbk option, force_permissive = callbk
if type_ == 'validator' and not force_permissive: if type_ == 'validator' and not force_permissive: # pragma: optional cover
raise ValueError(_('validator not support tuple')) raise ValueError(_('validator not support tuple'))
if not isinstance(option, Option) and not \ if not isinstance(option, Option) and not \
isinstance(option, SymLinkOption): isinstance(option, SymLinkOption): # pragma: optional cover
raise ValueError(_('{0}_params must have an option' raise ValueError(_('{0}_params must have an option'
' not a {0} for first argument' ' not a {0} for first argument'
).format(type_, type(option))) ).format(type_, type(option)))
if force_permissive not in [True, False]: if force_permissive not in [True, False]: # pragma: optional cover
raise ValueError(_('{0}_params must have a boolean' raise ValueError(_('{0}_params must have a boolean'
' not a {0} for second argument' ' not a {0} for second argument'
).format(type_, type( ).format(type_, type(
@ -104,11 +105,23 @@ def validate_callback(callback, callback_params, type_):
class Base(StorageBase): class Base(StorageBase):
__slots__ = tuple() __slots__ = tuple()
def impl_set_callback(self, callback, callback_params=None):
if callback is None and callback_params is not None: # pragma: optional cover
raise ValueError(_("params defined for a callback function but "
"no callback defined"
" yet for option {0}").format(
self.impl_getname()))
self._validate_callback(callback, callback_params)
if callback is not None:
validate_callback(callback, callback_params, 'callback')
self._callback = callback
self._callback_params = callback_params
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,
callback_params=None, validator=None, validator_params=None, callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False): properties=None, warnings_only=False):
if not valid_name(name): if not valid_name(name): # pragma: optional cover
raise ValueError(_("invalid name: {0} for option").format(name)) raise ValueError(_("invalid name: {0} for option").format(name))
self._name = name self._name = name
self._readonly = False self._readonly = False
@ -120,13 +133,13 @@ class Base(StorageBase):
else: else:
self._calc_properties = frozenset() self._calc_properties = frozenset()
self._requires = [] self._requires = []
if not multi and default_multi is not None: if not multi and default_multi is not None: # pragma: optional cover
raise ValueError(_("a default_multi is set whereas multi is False" raise ValueError(_("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: if default_multi is not None:
try: try:
self._validate(default_multi) self._validate(default_multi)
except ValueError as err: except ValueError as err: # pragma: optional cover
raise ValueError(_("invalid default_multi value {0} " raise ValueError(_("invalid default_multi value {0} "
"for option {1}: {2}").format( "for option {1}: {2}").format(
str(default_multi), name, err)) str(default_multi), name, err))
@ -135,16 +148,9 @@ class Base(StorageBase):
if default is None: if default is None:
default = [] default = []
self._default_multi = default_multi self._default_multi = default_multi
if callback is not None and ((not multi and (default is not None or
default_multi is not None))
or (multi and (default != [] or
default_multi is not None))
):
raise ValueError(_("default value not allowed if option: {0} "
"is calculated").format(name))
if properties is None: if properties is None:
properties = tuple() properties = tuple()
if not isinstance(properties, tuple): if not isinstance(properties, tuple): # pragma: optional cover
raise TypeError(_('invalid properties type {0} for {1},' raise TypeError(_('invalid properties type {0} for {1},'
' must be a tuple').format( ' must be a tuple').format(
type(properties), type(properties),
@ -154,16 +160,7 @@ class Base(StorageBase):
self._validator = validator self._validator = validator
if validator_params is not None: if validator_params is not None:
self._validator_params = validator_params self._validator_params = validator_params
if callback is None and callback_params is not None: if self._calc_properties != frozenset([]) and properties is not tuple(): # pragma: optional cover
raise ValueError(_("params defined for a callback function but "
"no callback defined"
" yet for option {0}").format(name))
if callback is not None:
validate_callback(callback, callback_params, 'callback')
self._callback = callback
if callback_params is not None:
self._callback_params = callback_params
if self._calc_properties != frozenset([]) and properties is not tuple():
set_forbidden_properties = self._calc_properties & set(properties) set_forbidden_properties = self._calc_properties & set(properties)
if set_forbidden_properties != frozenset(): if set_forbidden_properties != frozenset():
raise ValueError('conflict: properties already set in ' raise ValueError('conflict: properties already set in '
@ -173,12 +170,23 @@ class Base(StorageBase):
self._default = [] self._default = []
else: else:
self._default = default self._default = default
if callback is not False:
self.impl_set_callback(callback, callback_params)
self._properties = properties self._properties = properties
self._warnings_only = warnings_only self._warnings_only = warnings_only
ret = super(Base, self).__init__() ret = super(Base, self).__init__()
self.impl_validate(self._default) self.impl_validate(self._default)
return ret return ret
def impl_is_optiondescription(self):
return self.__class__.__name__ in ['OptionDescription',
'DynOptionDescription',
'SynDynOptionDescription']
def impl_is_dynoptiondescription(self):
return self.__class__.__name__ in ['DynOptionDescription',
'SynDynOptionDescription']
class BaseOption(Base): class BaseOption(Base):
"""This abstract base class stands for attribute access """This abstract base class stands for attribute access
@ -204,9 +212,9 @@ class BaseOption(Base):
""" """
if key in self._informations: if key in self._informations:
return self._informations[key] return self._informations[key]
elif default is not undefined: elif default is not undefined: # pragma: optional cover
return default return default
else: else: # pragma: optional cover
raise ValueError(_("information's item not found: {0}").format( raise ValueError(_("information's item not found: {0}").format(
key)) key))
@ -246,6 +254,52 @@ class BaseOption(Base):
else: else:
self._state_requires = new_value self._state_requires = new_value
def _impl_convert_callback(self, descr, load=False):
if self.__class__.__name__ == 'OptionDescription' or \
isinstance(self, SymLinkOption):
return
if not load and self._callback is None:
self._state_callback = None
self._state_callback_params = None
elif load and self._state_callback is None:
self._callback = None
self._callback_params = None
del(self._state_callback)
del(self._state_callback_params)
else:
if load:
callback = self._state_callback
callback_params = self._state_callback_params
else:
callback = self._callback
callback_params = self._callback_params
self._state_callback_params = None
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
vls = []
for value in values:
if isinstance(value, tuple):
if load:
value = (descr.impl_get_opt_by_path(value[0]),
value[1])
else:
value = (descr.impl_get_path_by_opt(value[0]),
value[1])
vls.append(value)
cllbck_prms[key] = tuple(vls)
else:
cllbck_prms = None
if load:
del(self._state_callback)
del(self._state_callback_params)
self._callback = callback
self._callback_params = cllbck_prms
else:
self._state_callback = callback
self._state_callback_params = cllbck_prms
# serialize # serialize
def _impl_getstate(self, descr): def _impl_getstate(self, descr):
"""the under the hood stuff that need to be done """the under the hood stuff that need to be done
@ -270,7 +324,7 @@ class BaseOption(Base):
""" """
try: try:
self._stated self._stated
except AttributeError: except AttributeError: # pragma: optional cover
raise SystemError(_('cannot serialize Option, ' raise SystemError(_('cannot serialize Option, '
'only in OptionDescription')) 'only in OptionDescription'))
slots = set() slots = set()
@ -311,7 +365,7 @@ class BaseOption(Base):
self._readonly = self._state_readonly self._readonly = self._state_readonly
del(self._state_readonly) del(self._state_readonly)
del(self._stated) del(self._stated)
except AttributeError: except AttributeError: # pragma: optional cover
pass pass
def __setstate__(self, state): def __setstate__(self, state):
@ -350,7 +404,7 @@ class BaseOption(Base):
pass pass
elif name != '_readonly': elif name != '_readonly':
is_readonly = self.impl_is_readonly() is_readonly = self.impl_is_readonly()
if is_readonly: if is_readonly: # pragma: optional cover
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is" raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format( " read-only").format(
self.__class__.__name__, self.__class__.__name__,
@ -369,6 +423,12 @@ class BaseOption(Base):
def impl_getname(self): def impl_getname(self):
return self._name return self._name
def impl_getpath(self, context):
return context.cfgimpl_get_description().impl_get_path_by_opt(self)
def impl_get_callback(self):
return self._callback, self._callback_params
class OnlyOption(BaseOption): class OnlyOption(BaseOption):
__slots__ = tuple() __slots__ = tuple()
@ -440,25 +500,31 @@ class Option(OnlyOption):
:param warnings_only: specific raise error for warning :param warnings_only: specific raise error for warning
:type warnings_only: `boolean` :type warnings_only: `boolean`
""" """
if context is not None: if context is not undefined:
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
all_cons_vals = [] all_cons_vals = []
for opt in all_cons_opts: for opt in all_cons_opts:
#get value #get value
if option == opt: if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
option == opt:
opt_value = value opt_value = value
else: else:
#if context, calculate value, otherwise get default value #if context, calculate value, otherwise get default value
if context is not None: if context is not undefined:
opt_value = context.getattr( if isinstance(opt, DynSymLinkOption):
descr.impl_get_path_by_opt(opt), validate=False, path = opt.impl_getpath(context)
force_permissive=True) else:
path = descr.impl_get_path_by_opt(opt)
opt_value = context.getattr(path, validate=False,
force_permissive=True)
else: else:
opt_value = opt.impl_getdefault() opt_value = opt.impl_getdefault()
#append value #append value
if not self.impl_is_multi() or option == opt: if not self.impl_is_multi() or (isinstance(opt, DynSymLinkOption)
and option._dyn == opt._dyn) or \
option == opt:
all_cons_vals.append(opt_value) all_cons_vals.append(opt_value)
elif self.impl_is_submulti(): elif self.impl_is_submulti():
try: try:
@ -476,8 +542,9 @@ class Option(OnlyOption):
return return
getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only) getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
def impl_validate(self, value, context=None, validate=True, def impl_validate(self, value, context=undefined, validate=True,
force_index=None, force_submulti_index=None): force_index=None, force_submulti_index=None,
current_opt=undefined):
""" """
:param value: the option's value :param value: the option's value
:param context: Config's context :param context: Config's context
@ -493,6 +560,8 @@ class Option(OnlyOption):
""" """
if not validate: if not validate:
return return
if current_opt is undefined:
current_opt = self
def val_validator(val): def val_validator(val):
if self._validator is not None: if self._validator is not None:
@ -520,7 +589,7 @@ class Option(OnlyOption):
# option validation # option validation
try: try:
self._validate(_value, context) self._validate(_value, context)
except ValueError as err: except ValueError as err: # pragma: optional cover
log.debug('do_validation: value: {0}, index: {1}, ' log.debug('do_validation: value: {0}, index: {1}, '
'submulti_index: {2}'.format(_value, _index, 'submulti_index: {2}'.format(_value, _index,
submulti_index), submulti_index),
@ -533,9 +602,9 @@ class Option(OnlyOption):
# valid with self._validator # valid with self._validator
val_validator(_value) val_validator(_value)
# if not context launch consistency validation # if not context launch consistency validation
if context is not None: if context is not undefined:
descr._valid_consistency(self, _value, context, _index, descr._valid_consistency(current_opt, _value, context,
submulti_index) _index, submulti_index)
self._second_level_validation(_value, self._warnings_only) self._second_level_validation(_value, self._warnings_only)
except ValueError as error: except ValueError as error:
log.debug(_('do_validation for {0}: error in value').format( log.debug(_('do_validation for {0}: error in value').format(
@ -550,9 +619,9 @@ class Option(OnlyOption):
if error is None and warning is None: if error is None and warning is None:
try: try:
# if context launch consistency validation # if context launch consistency validation
if context is not None: if context is not undefined:
descr._valid_consistency(self, _value, context, _index, descr._valid_consistency(current_opt, _value, context,
submulti_index) _index, submulti_index)
except ValueError as error: except ValueError as error:
log.debug(_('do_validation for {0}: error in consistency').format( log.debug(_('do_validation for {0}: error in consistency').format(
self.impl_getname()), exc_info=True) self.impl_getname()), exc_info=True)
@ -572,14 +641,14 @@ class Option(OnlyOption):
self._name, error)) self._name, error))
# generic calculation # generic calculation
if context is not None: if context is not undefined:
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
if not self.impl_is_multi(): if not self.impl_is_multi():
do_validation(value, None, None) do_validation(value, None, None)
elif force_index is not None: elif force_index is not None:
if self.impl_is_submulti() and force_submulti_index is None: if self.impl_is_submulti() and force_submulti_index is None:
if not isinstance(value, list): if not isinstance(value, list): # pragma: optional cover
raise ValueError(_("invalid value {0} for option {1} which" raise ValueError(_("invalid value {0} for option {1} which"
" must be a list").format( " must be a list").format(
value, self.impl_getname())) value, self.impl_getname()))
@ -588,13 +657,13 @@ class Option(OnlyOption):
else: else:
do_validation(value, force_index, force_submulti_index) do_validation(value, force_index, force_submulti_index)
else: else:
if not isinstance(value, list): if not isinstance(value, list): # pragma: optional cover
raise ValueError(_("invalid value {0} for option {1} which " raise ValueError(_("invalid value {0} for option {1} which "
"must be a list").format(value, "must be a list").format(value,
self.impl_getname())) self.impl_getname()))
for idx, val in enumerate(value): for idx, val in enumerate(value):
if self.impl_is_submulti() and force_submulti_index is None: if self.impl_is_submulti() and force_submulti_index is None:
if not isinstance(val, list): if not isinstance(val, list): # pragma: optional cover
raise ValueError(_("invalid value {0} for option {1} " raise ValueError(_("invalid value {0} for option {1} "
"which must be a list of list" "which must be a list of list"
"").format(value, "").format(value,
@ -651,9 +720,6 @@ class Option(OnlyOption):
else: else:
return True return True
def impl_get_callback(self):
return self._callback, self._callback_params
#def impl_getkey(self, value): #def impl_getkey(self, value):
# return value # return value
@ -673,18 +739,33 @@ class Option(OnlyOption):
:type other_opts: `list` of `tiramisu.option.Option` :type other_opts: `list` of `tiramisu.option.Option`
:param params: extra params (only warnings_only are allowed) :param params: extra params (only warnings_only are allowed)
""" """
if self.impl_is_readonly(): if self.impl_is_readonly(): # pragma: optional cover
raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is" raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
" read-only").format( " read-only").format(
self.__class__.__name__, self.__class__.__name__,
self._name)) self._name))
warnings_only = params.get('warnings_only', False) warnings_only = params.get('warnings_only', False)
if self._is_subdyn():
dynod = self._subdyn
else:
dynod = None
for opt in other_opts: for opt in other_opts:
if not isinstance(opt, Option): if not isinstance(opt, Option): # pragma: optional cover
raise ConfigError(_('consistency must be set with an option')) raise ConfigError(_('consistency must be set with an option'))
if self is opt: if opt._is_subdyn():
if dynod is None:
raise ConfigError(_('almost one option in consistency is '
'in a dynoptiondescription but not all'))
if dynod != opt._subdyn:
raise ConfigError(_('option in consistency must be in same'
' dynoptiondescription'))
dynod = opt._subdyn
elif dynod is not None:
raise ConfigError(_('almost one option in consistency is in a '
'dynoptiondescription but not all'))
if self is opt: # pragma: optional cover
raise ConfigError(_('cannot add consistency with itself')) raise ConfigError(_('cannot add consistency with itself'))
if self.impl_is_multi() != opt.impl_is_multi(): if self.impl_is_multi() != opt.impl_is_multi(): # pragma: optional cover
raise ConfigError(_('every options in consistency must be ' raise ConfigError(_('every options in consistency must be '
'multi or none')) 'multi or none'))
func = '_cons_{0}'.format(func) func = '_cons_{0}'.format(func)
@ -694,7 +775,7 @@ class Option(OnlyOption):
if self.impl_is_multi(): if self.impl_is_multi():
for idx, val in enumerate(value): for idx, val in enumerate(value):
if not self.impl_is_submulti(): if not self.impl_is_submulti():
self._launch_consistency(func, self, val, None, idx, self._launch_consistency(func, self, val, undefined, idx,
None, all_cons_opts, None, all_cons_opts,
warnings_only) warnings_only)
else: else:
@ -704,7 +785,7 @@ class Option(OnlyOption):
all_cons_opts, all_cons_opts,
warnings_only) warnings_only)
else: else:
self._launch_consistency(func, self, value, None, None, self._launch_consistency(func, self, value, undefined, None,
None, all_cons_opts, warnings_only) None, all_cons_opts, warnings_only)
self._add_consistency(func, all_cons_opts, params) self._add_consistency(func, all_cons_opts, params)
self.impl_validate(self.impl_getdefault()) self.impl_validate(self.impl_getdefault())
@ -720,49 +801,6 @@ class Option(OnlyOption):
raise ValueError(msg.format(opts[idx_inf].impl_getname(), raise ValueError(msg.format(opts[idx_inf].impl_getname(),
opts[idx_inf + idx_sup + 1].impl_getname())) opts[idx_inf + idx_sup + 1].impl_getname()))
def _impl_convert_callbacks(self, descr, load=False):
if not load and self._callback is None:
self._state_callback = None
self._state_callback_params = None
elif load and self._state_callback is None:
self._callback = None
self._callback_params = None
del(self._state_callback)
del(self._state_callback_params)
else:
if load:
callback = self._state_callback
callback_params = self._state_callback_params
else:
callback = self._callback
callback_params = self._callback_params
self._state_callback_params = None
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
vls = []
for value in values:
if isinstance(value, tuple):
if load:
value = (descr.impl_get_opt_by_path(value[0]),
value[1])
else:
value = (descr.impl_get_path_by_opt(value[0]),
value[1])
vls.append(value)
cllbck_prms[key] = tuple(vls)
else:
cllbck_prms = None
if load:
del(self._state_callback)
del(self._state_callback_params)
self._callback = callback
self._callback_params = cllbck_prms
else:
self._state_callback = callback
self._state_callback_params = cllbck_prms
# serialize/unserialize # serialize/unserialize
def _impl_convert_consistencies(self, descr, load=False): def _impl_convert_consistencies(self, descr, load=False):
"""during serialization process, many things have to be done. """during serialization process, many things have to be done.
@ -801,6 +839,24 @@ class Option(OnlyOption):
def _second_level_validation(self, value, warnings_only): def _second_level_validation(self, value, warnings_only):
pass pass
def _impl_to_dyn(self, name, path):
return DynSymLinkOption(name, self, dyn=path)
def _validate_callback(self, callback, callback_params):
try:
default_multi = self._default_multi
except AttributeError:
default_multi = None
if callback is not None and ((not self._multi and
(self._default is not None or
default_multi is not None))
or (self._multi and
(self._default != [] or
default_multi is not None))
): # pragma: optional cover
raise ValueError(_("default value not allowed if option: {0} "
"is calculated").format(self.impl_getname()))
def validate_requires_arg(requires, name): def validate_requires_arg(requires, name):
"""check malformed requirements """check malformed requirements
@ -819,60 +875,60 @@ def validate_requires_arg(requires, name):
# start parsing all requires given by user (has dict) # start parsing all requires given by user (has dict)
# transforme it to a tuple # transforme it to a tuple
for require in requires: for require in requires:
if not type(require) == dict: if not type(require) == dict: # pragma: optional cover
raise ValueError(_("malformed requirements type for option:" raise ValueError(_("malformed requirements type for option:"
" {0}, must be a dict").format(name)) " {0}, must be a dict").format(name))
valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive', valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
'same_action') 'same_action')
unknown_keys = frozenset(require.keys()) - frozenset(valid_keys) unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
if unknown_keys != frozenset(): if unknown_keys != frozenset(): # pragma: optional cover
raise ValueError('malformed requirements for option: {0}' raise ValueError(_('malformed requirements for option: {0}'
' unknown keys {1}, must only ' ' unknown keys {1}, must only '
'{2}'.format(name, '{2}').format(name,
unknown_keys, unknown_keys,
valid_keys)) valid_keys))
# prepare all attributes # prepare all attributes
try: try:
option = require['option'] option = require['option']
expected = require['expected'] expected = require['expected']
action = require['action'] action = require['action']
except KeyError: except KeyError: # pragma: optional cover
raise ValueError(_("malformed requirements for option: {0}" raise ValueError(_("malformed requirements for option: {0}"
" require must have option, expected and" " require must have option, expected and"
" action keys").format(name)) " action keys").format(name))
if action == 'force_store_value': if action == 'force_store_value': # pragma: optional cover
raise ValueError(_("malformed requirements for option: {0}" raise ValueError(_("malformed requirements for option: {0}"
" action cannot be force_store_value" " action cannot be force_store_value"
).format(name)) ).format(name))
inverse = require.get('inverse', False) inverse = require.get('inverse', False)
if inverse not in [True, False]: if inverse not in [True, False]: # pragma: optional cover
raise ValueError(_('malformed requirements for option: {0}' raise ValueError(_('malformed requirements for option: {0}'
' inverse must be boolean')) ' inverse must be boolean'))
transitive = require.get('transitive', True) transitive = require.get('transitive', True)
if transitive not in [True, False]: if transitive not in [True, False]: # pragma: optional cover
raise ValueError(_('malformed requirements for option: {0}' raise ValueError(_('malformed requirements for option: {0}'
' transitive must be boolean')) ' transitive must be boolean'))
same_action = require.get('same_action', True) same_action = require.get('same_action', True)
if same_action not in [True, False]: if same_action not in [True, False]: # pragma: optional cover
raise ValueError(_('malformed requirements for option: {0}' raise ValueError(_('malformed requirements for option: {0}'
' same_action must be boolean')) ' same_action must be boolean'))
if not isinstance(option, Option): if not isinstance(option, Option): # pragma: optional cover
raise ValueError(_('malformed requirements ' raise ValueError(_('malformed requirements '
'must be an option in option {0}').format(name)) 'must be an option in option {0}').format(name))
if option.impl_is_multi(): if option.impl_is_multi(): # pragma: optional cover
raise ValueError(_('malformed requirements option {0} ' raise ValueError(_('malformed requirements option {0} '
'must not be a multi for {1}').format( 'must not be a multi for {1}').format(
option.impl_getname(), name)) option.impl_getname(), name))
if expected is not None: if expected is not None:
try: try:
option._validate(expected) option._validate(expected)
except ValueError as err: except ValueError as err: # pragma: optional cover
raise ValueError(_('malformed requirements second argument ' raise ValueError(_('malformed requirements second argument '
'must be valid for option {0}' 'must be valid for option {0}'
': {1}').format(name, err)) ': {1}').format(name, err))
if action in config_action: if action in config_action:
if inverse != config_action[action]: if inverse != config_action[action]: # pragma: optional cover
raise ValueError(_("inconsistency in action types" raise ValueError(_("inconsistency in action types"
" for option: {0}" " for option: {0}"
" action: {1}").format(name, action)) " action: {1}").format(name, action))
@ -897,23 +953,20 @@ def validate_requires_arg(requires, name):
class SymLinkOption(OnlyOption): class SymLinkOption(OnlyOption):
#FIXME : et avec sqlalchemy ca marche vraiment ?
__slots__ = ('_opt', '_state_opt') __slots__ = ('_opt', '_state_opt')
#not return _opt consistencies
#_consistencies = None
def __init__(self, name, opt): def __init__(self, name, opt):
self._name = name self._name = name
if not isinstance(opt, Option): if not isinstance(opt, Option): # pragma: optional cover
raise ValueError(_('malformed symlinkoption ' raise ValueError(_('malformed symlinkoption '
'must be an option ' 'must be an option '
'for symlink {0}').format(name)) 'for symlink {0}').format(name))
self._opt = opt self._opt = opt
self._readonly = True self._readonly = True
return super(Base, self).__init__() super(Base, self).__init__()
def __getattr__(self, name): def __getattr__(self, name, context=undefined):
if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'): if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath'):
return object.__getattr__(self, name) return object.__getattr__(self, name)
else: else:
return getattr(self._opt, name) return getattr(self._opt, name)
@ -929,3 +982,29 @@ class SymLinkOption(OnlyOption):
def impl_get_information(self, key, default=undefined): def impl_get_information(self, key, default=undefined):
return self._opt.impl_get_information(key, default) return self._opt.impl_get_information(key, default)
class DynSymLinkOption(SymLinkOption):
__slots__ = ('_dyn',)
def __init__(self, name, opt, dyn):
self._dyn = dyn
super(DynSymLinkOption, self).__init__(name, opt)
def impl_getsuffix(self):
return self._dyn.split('.')[-1][len(self._opt.impl_getname()):]
def impl_getpath(self, context):
path = self._opt.impl_getpath(context)
base_path = '.'.join(path.split('.')[:-2])
if self.impl_is_master_slaves() and base_path is not '':
base_path = base_path + self.impl_getsuffix()
if base_path == '':
return self._dyn
else:
return base_path + '.' + self._dyn
def impl_validate(self, value, context=undefined, validate=True,
force_index=None, force_submulti_index=None):
return self._opt.impl_validate(value, context, validate, force_index,
force_submulti_index, current_opt=self)

View file

@ -22,63 +22,92 @@
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.setting import log, undefined from tiramisu.setting import log, undefined
from tiramisu.error import SlaveError, ConfigError from tiramisu.error import SlaveError, ConfigError
from .baseoption import SymLinkOption, Option from .baseoption import DynSymLinkOption, SymLinkOption, Option
class MasterSlaves(object): class MasterSlaves(object):
__slots__ = ('master', 'slaves') __slots__ = ('master', 'slaves')
def __init__(self, name, childs): def __init__(self, name, childs, validate=True):
#if master (same name has group) is set #if master (same name has group) is set
#for collect all slaves #for collect all slaves
self.master = None self.master = None
slaves = [] slaves = []
for child in childs: for child in childs:
if isinstance(child, SymLinkOption): if isinstance(child, SymLinkOption): # pragma: optional cover
raise ValueError(_("master group {0} shall not have " raise ValueError(_("master group {0} shall not have "
"a symlinkoption").format(name)) "a symlinkoption").format(name))
if not isinstance(child, Option): if not isinstance(child, Option): # pragma: optional cover
raise ValueError(_("master group {0} shall not have " raise ValueError(_("master group {0} shall not have "
"a subgroup").format(name)) "a subgroup").format(name))
if not child.impl_is_multi(): if not child.impl_is_multi(): # pragma: optional cover
raise ValueError(_("not allowed option {0} " raise ValueError(_("not allowed option {0} "
"in group {1}" "in group {1}"
": this option is not a multi" ": this option is not a multi"
"").format(child._name, name)) "").format(child.impl_getname(), name))
if child._name == name: if child.impl_getname() == name:
self.master = child self.master = child
else: else:
slaves.append(child) slaves.append(child)
if self.master is None: if self.master is None: # pragma: optional cover
raise ValueError(_('master group with wrong' raise ValueError(_('master group with wrong'
' master name for {0}' ' master name for {0}'
).format(name)) ).format(name))
callback, callback_params = self.master.impl_get_callback() if validate:
if callback is not None and callback_params is not None: callback, callback_params = self.master.impl_get_callback()
for key, callbacks in callback_params.items(): if callback is not None and callback_params is not None: # pragma: optional cover
for callbk in callbacks: for key, callbacks in callback_params.items():
if isinstance(callbk, tuple): for callbk in callbacks:
if callbk[0] in slaves: if isinstance(callbk, tuple):
raise ValueError(_("callback of master's option shall " if callbk[0] in slaves:
"not refered a slave's ones")) raise ValueError(_("callback of master's option shall "
"not refered a slave's ones"))
#everything is ok, store references #everything is ok, store references
self.slaves = tuple(slaves) self.slaves = tuple(slaves)
for child in childs: for child in childs:
child._master_slaves = self child._master_slaves = self
def is_master(self, opt): def is_master(self, opt):
return opt == self.master return opt == self.master or (isinstance(opt, DynSymLinkOption) and
opt._opt == self.master)
def getmaster(self, opt):
if isinstance(opt, DynSymLinkOption):
suffix = opt.impl_getsuffix()
name = self.master.impl_getname() + suffix
base_path = opt._dyn.split('.')[0] + '.'
path = base_path + name
master = self.master._impl_to_dyn(name, path)
else: # pragma: no dynoptiondescription cover
master = self.master
return master
def getslaves(self, opt):
if isinstance(opt, DynSymLinkOption):
for slave in self.slaves:
suffix = opt.impl_getsuffix()
name = slave.impl_getname() + suffix
base_path = opt._dyn.split('.')[0] + '.'
path = base_path + name
yield slave._impl_to_dyn(name, path)
else: # pragma: no dynoptiondescription cover
for slave in self.slaves:
yield slave
def in_same_group(self, opt): def in_same_group(self, opt):
return opt == self.master or opt in self.slaves if isinstance(opt, DynSymLinkOption):
return opt._opt == self.master or opt._opt in self.slaves
else: # pragma: no dynoptiondescription cover
return opt == self.master or opt in self.slaves
def reset(self, values): def reset(self, opt, values):
for slave in self.slaves: #FIXME pas de opt ???
for slave in self.getslaves(opt):
values.reset(slave) values.reset(slave)
def pop(self, values, index): def pop(self, opt, values, index):
#FIXME pas test de meta ... #FIXME pas test de meta ...
for slave in self.slaves: for slave in self.getslaves(opt):
if not values.is_default_owner(slave, validate_properties=False, if not values.is_default_owner(slave, validate_properties=False,
validate_meta=False): validate_meta=False):
values._get_cached_item(slave, validate=False, values._get_cached_item(slave, validate=False,
@ -89,7 +118,7 @@ class MasterSlaves(object):
def getitem(self, values, opt, path, validate, force_permissive, def getitem(self, values, opt, path, validate, force_permissive,
force_properties, validate_properties, slave_path=undefined, force_properties, validate_properties, slave_path=undefined,
slave_value=undefined): slave_value=undefined):
if opt == self.master: if self.is_master(opt):
return self._getmaster(values, opt, path, validate, return self._getmaster(values, opt, path, validate,
force_permissive, force_properties, force_permissive, force_properties,
validate_properties, slave_path, validate_properties, slave_path,
@ -108,9 +137,9 @@ class MasterSlaves(object):
validate_properties) validate_properties)
if validate is True: if validate is True:
masterlen = len(value) masterlen = len(value)
for slave in self.slaves: for slave in self.getslaves(opt):
try: try:
slave_path = values._get_opt_path(slave) slave_path = slave.impl_getpath(values._getcontext())
if c_slave_path == slave_path: if c_slave_path == slave_path:
slave_value = c_slave_value slave_value = c_slave_value
else: else:
@ -121,8 +150,8 @@ class MasterSlaves(object):
None, False, None, False,
None) # not undefined None) # not undefined
slavelen = len(slave_value) slavelen = len(slave_value)
self.validate_slave_length(masterlen, slavelen, slave._name) self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
except ConfigError: except ConfigError: # pragma: optional cover
pass pass
return value return value
@ -136,10 +165,10 @@ class MasterSlaves(object):
return self.get_slave_value(values, opt, value, validate, validate_properties) return self.get_slave_value(values, opt, value, validate, validate_properties)
def setitem(self, values, opt, value, path): def setitem(self, values, opt, value, path):
if opt == self.master: if self.is_master(opt):
masterlen = len(value) masterlen = len(value)
for slave in self.slaves: for slave in self.getslaves(opt):
slave_path = values._get_opt_path(slave) slave_path = slave.impl_getpath(values._getcontext())
slave_value = values._get_validated_value(slave, slave_value = values._get_validated_value(slave,
slave_path, slave_path,
False, False,
@ -147,24 +176,28 @@ class MasterSlaves(object):
None, False, None, False,
None) # not undefined None) # not undefined
slavelen = len(slave_value) slavelen = len(slave_value)
self.validate_slave_length(masterlen, slavelen, slave._name) self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
else: else:
self.validate_slave_length(self.get_length(values), len(value), self.validate_slave_length(self.get_length(values, opt,
opt._name, setitem=True) slave_path=path), len(value),
opt.impl_getname(), opt, setitem=True)
def get_length(self, values, validate=True, slave_path=undefined, def get_length(self, values, opt, validate=True, slave_path=undefined,
slave_value=undefined): slave_value=undefined):
masterp = values._get_opt_path(self.master) """get master len with slave option"""
return len(self.getitem(values, self.master, masterp, validate, False, masterp = self.getmaster(opt).impl_getpath(values._getcontext())
if slave_value is undefined:
slave_path = undefined
return len(self.getitem(values, self.getmaster(opt), masterp, validate, False,
None, True, slave_path, slave_value)) None, True, slave_path, slave_value))
def validate_slave_length(self, masterlen, valuelen, name, setitem=False): def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False):
if valuelen > masterlen or (valuelen < masterlen and setitem): if valuelen > masterlen or (valuelen < masterlen and setitem): # pragma: optional cover
log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, ' log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
'setitem: {2}'.format(masterlen, valuelen, setitem)) 'setitem: {2}'.format(masterlen, valuelen, setitem))
raise SlaveError(_("invalid len for the slave: {0}" raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format( " which has {1} as master").format(
name, self.master._name)) name, self.getmaster(opt).impl_getname()))
def get_slave_value(self, values, opt, value, validate=True, def get_slave_value(self, values, opt, value, validate=True,
validate_properties=True): validate_properties=True):
@ -190,11 +223,11 @@ class MasterSlaves(object):
list is greater than master: raise SlaveError list is greater than master: raise SlaveError
""" """
#if slave, had values until master's one #if slave, had values until master's one
path = values._get_opt_path(opt) path = opt.impl_getpath(values._getcontext())
masterlen = self.get_length(values, validate, path, value) masterlen = self.get_length(values, opt, validate, path, value)
valuelen = len(value) valuelen = len(value)
if validate: if validate:
self.validate_slave_length(masterlen, valuelen, opt._name) self.validate_slave_length(masterlen, valuelen, opt.impl_getname(), opt)
if valuelen < masterlen: if valuelen < masterlen:
for num in range(0, masterlen - valuelen): for num in range(0, masterlen - valuelen):
index = valuelen + num index = valuelen + num

View file

@ -23,7 +23,7 @@ import re
import sys import sys
from IPy import IP from IPy import IP
from types import FunctionType from types import FunctionType
from tiramisu.setting import log from tiramisu.setting import log, undefined
from tiramisu.error import ConfigError, ContextError from tiramisu.error import ConfigError, ContextError
from tiramisu.i18n import _ from tiramisu.i18n import _
@ -48,7 +48,7 @@ class ChoiceOption(Option):
""" """
if isinstance(values, FunctionType): if isinstance(values, FunctionType):
validate_callback(values, values_params, 'values') validate_callback(values, values_params, 'values')
elif not isinstance(values, tuple): elif not isinstance(values, tuple): # pragma: optional cover
raise TypeError(_('values must be a tuple or a function for {0}' raise TypeError(_('values must be a tuple or a function for {0}'
).format(name)) ).format(name))
self._extra = {'_choice_values': values, self._extra = {'_choice_values': values,
@ -74,20 +74,20 @@ class ChoiceOption(Option):
values = carry_out_calculation(self, config=context, values = carry_out_calculation(self, config=context,
callback=values, callback=values,
callback_params=values_params) callback_params=values_params)
if not isinstance(values, list): if not isinstance(values, list): # pragma: optional cover
raise ConfigError(_('calculated values for {0} is not a list' raise ConfigError(_('calculated values for {0} is not a list'
'').format(self.impl_getname())) '').format(self.impl_getname()))
return values return values
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
try: try:
values = self.impl_get_values(context) values = self.impl_get_values(context)
if not value in values: if not value in values: # pragma: optional cover
raise ValueError(_('value {0} is not permitted, ' raise ValueError(_('value {0} is not permitted, '
'only {1} is allowed' 'only {1} is allowed'
'').format(value, '').format(value,
values)) values))
except ContextError: except ContextError: # pragma: optional cover
log.debug('ChoiceOption validation, disabled because no context') log.debug('ChoiceOption validation, disabled because no context')
@ -95,39 +95,39 @@ class BoolOption(Option):
"represents a choice between ``True`` and ``False``" "represents a choice between ``True`` and ``False``"
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if not isinstance(value, bool): if not isinstance(value, bool):
raise ValueError(_('invalid boolean')) raise ValueError(_('invalid boolean')) # pragma: optional cover
class IntOption(Option): class IntOption(Option):
"represents a choice of an integer" "represents a choice of an integer"
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if not isinstance(value, int): if not isinstance(value, int):
raise ValueError(_('invalid integer')) raise ValueError(_('invalid integer')) # pragma: optional cover
class FloatOption(Option): class FloatOption(Option):
"represents a choice of a floating point number" "represents a choice of a floating point number"
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if not isinstance(value, float): if not isinstance(value, float):
raise ValueError(_('invalid float')) raise ValueError(_('invalid float')) # pragma: optional cover
class StrOption(Option): class StrOption(Option):
"represents the choice of a string" "represents the choice of a string"
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if not isinstance(value, str): if not isinstance(value, str):
raise ValueError(_('invalid string')) raise ValueError(_('invalid string')) # pragma: optional cover
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3: # pragma: optional cover
#UnicodeOption is same as StrOption in python 3+ #UnicodeOption is same as StrOption in python 3+
class UnicodeOption(StrOption): class UnicodeOption(StrOption):
__slots__ = tuple() __slots__ = tuple()
@ -138,9 +138,9 @@ else:
__slots__ = tuple() __slots__ = tuple()
_empty = u'' _empty = u''
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if not isinstance(value, unicode): if not isinstance(value, unicode):
raise ValueError(_('invalid unicode')) raise ValueError(_('invalid unicode')) # pragma: optional cover
class IPOption(Option): class IPOption(Option):
@ -165,31 +165,31 @@ class IPOption(Option):
properties=properties, properties=properties,
warnings_only=warnings_only) warnings_only=warnings_only)
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
# sometimes an ip term starts with a zero # sometimes an ip term starts with a zero
# but this does not fit in some case, for example bind does not like it # but this does not fit in some case, for example bind does not like it
try: try:
for val in value.split('.'): for val in value.split('.'):
if val.startswith("0") and len(val) > 1: if val.startswith("0") and len(val) > 1:
raise ValueError(_('invalid IP')) raise ValueError(_('invalid IP')) # pragma: optional cover
except AttributeError: except AttributeError: # pragma: optional cover
#if integer for example #if integer for example
raise ValueError(_('invalid IP')) raise ValueError(_('invalid IP'))
# 'standard' validation # 'standard' validation
try: try:
IP('{0}/32'.format(value)) IP('{0}/32'.format(value))
except ValueError: except ValueError: # pragma: optional cover
raise ValueError(_('invalid IP')) raise ValueError(_('invalid IP'))
def _second_level_validation(self, value, warnings_only): def _second_level_validation(self, value, warnings_only):
ip = IP('{0}/32'.format(value)) ip = IP('{0}/32'.format(value))
if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED': if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED': # pragma: optional cover
if warnings_only: if warnings_only:
msg = _("IP is in reserved class") msg = _("IP is in reserved class")
else: else:
msg = _("invalid IP, mustn't be in reserved class") msg = _("invalid IP, mustn't be in reserved class")
raise ValueError(msg) raise ValueError(msg)
if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE': if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE': # pragma: optional cover
if warnings_only: if warnings_only:
msg = _("IP is not in private class") msg = _("IP is not in private class")
else: else:
@ -198,11 +198,11 @@ class IPOption(Option):
def _cons_in_network(self, opts, vals, warnings_only): def _cons_in_network(self, opts, vals, warnings_only):
if len(vals) != 3: if len(vals) != 3:
raise ConfigError(_('invalid len for vals')) raise ConfigError(_('invalid len for vals')) # pragma: optional cover
if None in vals: if None in vals:
return return
ip, network, netmask = vals ip, network, netmask = vals
if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): # pragma: optional cover
if warnings_only: if warnings_only:
msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}' msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}'
' ({5})') ' ({5})')
@ -247,12 +247,12 @@ class PortOption(Option):
elif not allowed: elif not allowed:
is_finally = True is_finally = True
elif allowed and is_finally: elif allowed and is_finally:
raise ValueError(_('inconsistency in allowed range')) raise ValueError(_('inconsistency in allowed range')) # pragma: optional cover
if allowed: if allowed:
extra['_max_value'] = ports_max[index] extra['_max_value'] = ports_max[index]
if extra['_max_value'] is None: if extra['_max_value'] is None:
raise ValueError(_('max value is empty')) raise ValueError(_('max value is empty')) # pragma: optional cover
self._extra = extra self._extra = extra
super(PortOption, self).__init__(name, doc, default=default, super(PortOption, self).__init__(name, doc, default=default,
@ -266,8 +266,8 @@ class PortOption(Option):
properties=properties, properties=properties,
warnings_only=warnings_only) warnings_only=warnings_only)
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if self._extra['_allow_range'] and ":" in str(value): if self._extra['_allow_range'] and ":" in str(value): # pragma: optional cover
value = str(value).split(':') value = str(value).split(':')
if len(value) != 2: if len(value) != 2:
raise ValueError(_('invalid port, range must have two values ' raise ValueError(_('invalid port, range must have two values '
@ -281,9 +281,9 @@ class PortOption(Option):
for val in value: for val in value:
try: try:
val = int(val) val = int(val)
except ValueError: except ValueError: # pragma: optional cover
raise ValueError(_('invalid port')) raise ValueError(_('invalid port'))
if not self._extra['_min_value'] <= val <= self._extra['_max_value']: if not self._extra['_min_value'] <= val <= self._extra['_max_value']: # pragma: optional cover
raise ValueError(_('invalid port, must be an between {0} ' raise ValueError(_('invalid port, must be an between {0} '
'and {1}').format(self._extra['_min_value'], 'and {1}').format(self._extra['_min_value'],
self._extra['_max_value'])) self._extra['_max_value']))
@ -293,15 +293,15 @@ class NetworkOption(Option):
"represents the choice of a network" "represents the choice of a network"
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
try: try:
IP(value) IP(value)
except ValueError: except ValueError: # pragma: optional cover
raise ValueError(_('invalid network address')) raise ValueError(_('invalid network address'))
def _second_level_validation(self, value, warnings_only): def _second_level_validation(self, value, warnings_only):
ip = IP(value) ip = IP(value)
if ip.iptype() == 'RESERVED': if ip.iptype() == 'RESERVED': # pragma: optional cover
if warnings_only: if warnings_only:
msg = _("network address is in reserved class") msg = _("network address is in reserved class")
else: else:
@ -313,10 +313,10 @@ class NetmaskOption(Option):
"represents the choice of a netmask" "represents the choice of a netmask"
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
try: try:
IP('0.0.0.0/{0}'.format(value)) IP('0.0.0.0/{0}'.format(value))
except ValueError: except ValueError: # pragma: optional cover
raise ValueError(_('invalid netmask address')) raise ValueError(_('invalid netmask address'))
def _cons_network_netmask(self, opts, vals, warnings_only): def _cons_network_netmask(self, opts, vals, warnings_only):
@ -334,7 +334,7 @@ class NetmaskOption(Option):
def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net, def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net,
warnings_only): warnings_only):
if len(opts) != 2: if len(opts) != 2:
raise ConfigError(_('invalid len for opts')) raise ConfigError(_('invalid len for opts')) # pragma: optional cover
msg = None msg = None
try: try:
ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
@ -347,14 +347,14 @@ class NetmaskOption(Option):
except ValueError: except ValueError:
pass pass
else: else:
if make_net: if make_net: # pragma: optional cover
msg = _("invalid IP {0} ({1}) with netmask {2}," msg = _("invalid IP {0} ({1}) with netmask {2},"
" this IP is a network") " this IP is a network")
except ValueError: except ValueError: # pragma: optional cover
if not make_net: if not make_net:
msg = _('invalid network {0} ({1}) with netmask {2}') msg = _('invalid network {0} ({1}) with netmask {2}')
if msg is not None: if msg is not None: # pragma: optional cover
raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(), raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
val_netmask)) val_netmask))
@ -362,15 +362,15 @@ class NetmaskOption(Option):
class BroadcastOption(Option): class BroadcastOption(Option):
__slots__ = tuple() __slots__ = tuple()
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
try: try:
IP('{0}/32'.format(value)) IP('{0}/32'.format(value))
except ValueError: except ValueError: # pragma: optional cover
raise ValueError(_('invalid broadcast address')) raise ValueError(_('invalid broadcast address'))
def _cons_broadcast(self, opts, vals, warnings_only): def _cons_broadcast(self, opts, vals, warnings_only):
if len(vals) != 3: if len(vals) != 3:
raise ConfigError(_('invalid len for vals')) raise ConfigError(_('invalid len for vals')) # pragma: optional cover
if None in vals: if None in vals:
return return
broadcast, network, netmask = vals broadcast, network, netmask = vals
@ -378,7 +378,7 @@ class BroadcastOption(Option):
raise ValueError(_('invalid broadcast {0} ({1}) with network {2} ' raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
'({3}) and netmask {4} ({5})').format( '({3}) and netmask {4} ({5})').format(
broadcast, opts[0].impl_getname(), network, broadcast, opts[0].impl_getname(), network,
opts[1].impl_getname(), netmask, opts[2].impl_getname())) opts[1].impl_getname(), netmask, opts[2].impl_getname())) # pragma: optional cover
class DomainnameOption(Option): class DomainnameOption(Option):
@ -396,12 +396,12 @@ class DomainnameOption(Option):
properties=None, allow_ip=False, type_='domainname', properties=None, allow_ip=False, type_='domainname',
warnings_only=False, allow_without_dot=False): warnings_only=False, allow_without_dot=False):
if type_ not in ['netbios', 'hostname', 'domainname']: if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_)) raise ValueError(_('unknown type_ {0} for hostname').format(type_)) # pragma: optional cover
self._extra = {'_dom_type': type_} self._extra = {'_dom_type': type_}
if allow_ip not in [True, False]: if allow_ip not in [True, False]:
raise ValueError(_('allow_ip must be a boolean')) raise ValueError(_('allow_ip must be a boolean')) # pragma: optional cover
if allow_without_dot not in [True, False]: if allow_without_dot not in [True, False]:
raise ValueError(_('allow_without_dot must be a boolean')) raise ValueError(_('allow_without_dot must be a boolean')) # pragma: optional cover
self._extra['_allow_ip'] = allow_ip self._extra['_allow_ip'] = allow_ip
self._extra['_allow_without_dot'] = allow_without_dot self._extra['_allow_without_dot'] = allow_without_dot
end = '' end = ''
@ -410,17 +410,17 @@ class DomainnameOption(Option):
if self._extra['_dom_type'] != 'netbios': if self._extra['_dom_type'] != 'netbios':
allow_number = '\d' allow_number = '\d'
else: else:
allow_number = '' allow_number = '' # pragma: optional cover
if self._extra['_dom_type'] == 'netbios': if self._extra['_dom_type'] == 'netbios':
length = 14 length = 14 # pragma: optional cover
elif self._extra['_dom_type'] == 'hostname': elif self._extra['_dom_type'] == 'hostname':
length = 62 length = 62 # pragma: optional cover
elif self._extra['_dom_type'] == 'domainname': elif self._extra['_dom_type'] == 'domainname':
length = 62 length = 62
if allow_without_dot is False: if allow_without_dot is False:
extrachar_mandatory = '\.' extrachar_mandatory = '\.'
else: else:
extrachar = '\.' extrachar = '\.' # pragma: optional cover
end = '+[a-z]*' end = '+[a-z]*'
self._extra['_domain_re'] = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$' self._extra['_domain_re'] = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
''.format(allow_number, extrachar, length, ''.format(allow_number, extrachar, length,
@ -436,37 +436,37 @@ class DomainnameOption(Option):
properties=properties, properties=properties,
warnings_only=warnings_only) warnings_only=warnings_only)
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
if self._extra['_allow_ip'] is True: if self._extra['_allow_ip'] is True: # pragma: optional cover
try: try:
IP('{0}/32'.format(value)) IP('{0}/32'.format(value))
return return
except ValueError: except ValueError:
pass pass
if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \ if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \
'.' not in value: '.' not in value: # pragma: optional cover
raise ValueError(_("invalid domainname, must have dot")) raise ValueError(_("invalid domainname, must have dot"))
if len(value) > 255: if len(value) > 255:
raise ValueError(_("invalid domainname's length (max 255)")) raise ValueError(_("invalid domainname's length (max 255)")) # pragma: optional cover
if len(value) < 2: if len(value) < 2:
raise ValueError(_("invalid domainname's length (min 2)")) raise ValueError(_("invalid domainname's length (min 2)")) # pragma: optional cover
if not self._extra['_domain_re'].search(value): if not self._extra['_domain_re'].search(value):
raise ValueError(_('invalid domainname')) raise ValueError(_('invalid domainname')) # pragma: optional cover
class EmailOption(DomainnameOption): class EmailOption(DomainnameOption):
__slots__ = tuple() __slots__ = tuple()
username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$") username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
splitted = value.split('@', 1) splitted = value.split('@', 1)
try: try:
username, domain = splitted username, domain = splitted
except ValueError: except ValueError: # pragma: optional cover
raise ValueError(_('invalid email address, must contains one @' raise ValueError(_('invalid email address, must contains one @'
)) ))
if not self.username_re.search(username): if not self.username_re.search(username):
raise ValueError(_('invalid username in email address')) raise ValueError(_('invalid username in email address')) # pragma: optional cover
super(EmailOption, self)._validate(domain) super(EmailOption, self)._validate(domain)
@ -475,9 +475,9 @@ class URLOption(DomainnameOption):
proto_re = re.compile(r'(http|https)://') proto_re = re.compile(r'(http|https)://')
path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
match = self.proto_re.search(value) match = self.proto_re.search(value)
if not match: if not match: # pragma: optional cover
raise ValueError(_('invalid url, must start with http:// or ' raise ValueError(_('invalid url, must start with http:// or '
'https://')) 'https://'))
value = value[len(match.group(0)):] value = value[len(match.group(0)):]
@ -493,17 +493,17 @@ class URLOption(DomainnameOption):
try: try:
domain, port = splitted domain, port = splitted
except ValueError: except ValueError: # pragma: optional cover
domain = splitted[0] domain = splitted[0]
port = 0 port = 0
if not 0 <= int(port) <= 65535: if not 0 <= int(port) <= 65535:
raise ValueError(_('invalid url, port must be an between 0 and ' raise ValueError(_('invalid url, port must be an between 0 and '
'65536')) '65536')) # pragma: optional cover
# validate domainname # validate domainname
super(URLOption, self)._validate(domain) super(URLOption, self)._validate(domain)
# validate file # validate file
if files is not None and files != '' and not self.path_re.search(files): if files is not None and files != '' and not self.path_re.search(files):
raise ValueError(_('invalid url, must ends with filename')) raise ValueError(_('invalid url, must ends with filename')) # pragma: optional cover
class UsernameOption(Option): class UsernameOption(Option):
@ -511,17 +511,17 @@ class UsernameOption(Option):
#regexp build with 'man 8 adduser' informations #regexp build with 'man 8 adduser' informations
username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
match = self.username_re.search(value) match = self.username_re.search(value)
if not match: if not match:
raise ValueError(_('invalid username')) raise ValueError(_('invalid username')) # pragma: optional cover
class FilenameOption(Option): class FilenameOption(Option):
__slots__ = tuple() __slots__ = tuple()
path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
def _validate(self, value, context=None): def _validate(self, value, context=undefined):
match = self.path_re.search(value) match = self.path_re.search(value)
if not match: if not match:
raise ValueError(_('invalid filename')) raise ValueError(_('invalid filename')) # pragma: optional cover

View file

@ -19,16 +19,21 @@
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from copy import copy from copy import copy
import re
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.setting import groups # , log from tiramisu.setting import groups, undefined # , log
from .baseoption import BaseOption from .baseoption import BaseOption, DynSymLinkOption, SymLinkOption, \
allowed_character
from . import MasterSlaves from . import MasterSlaves
from tiramisu.error import ConfigError, ConflictError, ValueWarning from tiramisu.error import ConfigError, ConflictError, ValueWarning
from tiramisu.storage import get_storages_option from tiramisu.storage import get_storages_option
from tiramisu.autolib import carry_out_calculation
StorageOptionDescription = get_storages_option('optiondescription') StorageOptionDescription = get_storages_option('optiondescription')
name_regexp = re.compile(r'^{0}*$'.format(allowed_character))
class OptionDescription(BaseOption, StorageOptionDescription): class OptionDescription(BaseOption, StorageOptionDescription):
@ -42,16 +47,31 @@ class OptionDescription(BaseOption, StorageOptionDescription):
:param children: a list of options (including optiondescriptions) :param children: a list of options (including optiondescriptions)
""" """
super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties) super(OptionDescription, self).__init__(name, doc=doc,
child_names = [child.impl_getname() for child in children] requires=requires,
properties=properties,
callback=False)
child_names = []
dynopt_names = []
for child in children:
name = child.impl_getname()
child_names.append(name)
if isinstance(child, DynOptionDescription):
dynopt_names.append(name)
#better performance like this #better performance like this
valid_child = copy(child_names) valid_child = copy(child_names)
valid_child.sort() valid_child.sort()
old = None old = None
for child in valid_child: for child in valid_child:
if child == old: if child == old: # pragma: optional cover
raise ConflictError(_('duplicate option name: ' raise ConflictError(_('duplicate option name: '
'{0}').format(child)) '{0}').format(child))
if dynopt_names:
for dynopt in dynopt_names:
if child != dynopt and child.startswith(dynopt):
raise ConflictError(_('option must not start as '
'dynoptiondescription'))
old = child old = child
self._add_children(child_names, children) self._add_children(child_names, children)
self._cache_paths = None self._cache_paths = None
@ -74,19 +94,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
"""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)
""" """
if _currpath is None: return _impl_getpaths(self, include_groups, _currpath)
_currpath = []
paths = []
for option in self.impl_getchildren():
attr = option.impl_getname()
if isinstance(option, OptionDescription):
if include_groups:
paths.append('.'.join(_currpath + [attr]))
paths += option.impl_getpaths(include_groups=include_groups,
_currpath=_currpath + [attr])
else:
paths.append('.'.join(_currpath + [attr]))
return paths
def impl_build_cache_consistency(self, _consistencies=None, cache_option=None): def impl_build_cache_consistency(self, _consistencies=None, cache_option=None):
#FIXME cache_option ! #FIXME cache_option !
@ -96,7 +104,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
cache_option = [] cache_option = []
else: else:
init = False init = False
for option in self.impl_getchildren(): for option in self._impl_getchildren(dyn=False):
cache_option.append(option._get_id()) cache_option.append(option._get_id())
if not isinstance(option, OptionDescription): if not isinstance(option, OptionDescription):
for func, all_cons_opts, params in option._get_consistencies(): for func, all_cons_opts, params in option._get_consistencies():
@ -111,7 +119,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
self._cache_consistencies = {} self._cache_consistencies = {}
for opt, cons in _consistencies.items(): for opt, cons in _consistencies.items():
#FIXME dans le cache ... #FIXME dans le cache ...
if opt._get_id() not in cache_option: if opt._get_id() not in cache_option: # pragma: optional cover
raise ConfigError(_('consistency with option {0} ' raise ConfigError(_('consistency with option {0} '
'which is not in Config').format( 'which is not in Config').format(
opt.impl_getname())) opt.impl_getname()))
@ -125,13 +133,13 @@ class OptionDescription(BaseOption, StorageOptionDescription):
cache_option = [] cache_option = []
else: else:
init = False init = False
for option in self.impl_getchildren(): for option in self._impl_getchildren(dyn=False):
#FIXME specifique id for sqlalchemy? #FIXME specifique id for sqlalchemy?
#FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes) #FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes)
#if option.id is None: #if option.id is None:
# raise SystemError(_("an option's id should not be None " # raise SystemError(_("an option's id should not be None "
# "for {0}").format(option.impl_getname())) # "for {0}").format(option.impl_getname()))
if option._get_id() in cache_option: if option._get_id() in cache_option: # pragma: optional cover
raise ConflictError(_('duplicate option: {0}').format(option)) raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option._get_id()) cache_option.append(option._get_id())
option._readonly = True option._readonly = True
@ -147,7 +155,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
:param group_type: an instance of `GroupType` or `MasterGroupType` :param group_type: an instance of `GroupType` or `MasterGroupType`
that lives in `setting.groups` that lives in `setting.groups`
""" """
if self._group_type != groups.default: if self._group_type != groups.default: # pragma: optional cover
raise TypeError(_('cannot change group_type if already set ' raise TypeError(_('cannot change group_type if already set '
'(old {0}, new {1})').format(self._group_type, '(old {0}, new {1})').format(self._group_type,
group_type)) group_type))
@ -155,7 +163,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
self._group_type = group_type self._group_type = group_type
if isinstance(group_type, groups.MasterGroupType): if isinstance(group_type, groups.MasterGroupType):
MasterSlaves(self.impl_getname(), self.impl_getchildren()) MasterSlaves(self.impl_getname(), self.impl_getchildren())
else: else: # pragma: optional cover
raise ValueError(_('group_type: {0}' raise ValueError(_('group_type: {0}'
' not allowed').format(group_type)) ' not allowed').format(group_type))
@ -166,18 +174,29 @@ class OptionDescription(BaseOption, StorageOptionDescription):
if self._cache_consistencies is None: if self._cache_consistencies is None:
return True return True
#consistencies is something like [('_cons_not_equal', (opt1, opt2))] #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
consistencies = self._cache_consistencies.get(option) if isinstance(option, DynSymLinkOption):
consistencies = self._cache_consistencies.get(option._opt)
else:
consistencies = self._cache_consistencies.get(option)
if consistencies is not None: if consistencies is not None:
for func, all_cons_opts, params in consistencies: for func, all_cons_opts, params in consistencies:
warnings_only = params.get('warnings_only', False) warnings_only = params.get('warnings_only', False)
#all_cons_opts[0] is the option where func is set #all_cons_opts[0] is the option where func is set
if isinstance(option, DynSymLinkOption):
subpath = '.'.join(option._dyn.split('.')[:-1])
namelen = len(option._opt.impl_getname())
suffix = option.impl_getname()[namelen:]
opts = []
for opt in all_cons_opts:
name = opt.impl_getname() + suffix
path = subpath + '.' + name
opts.append(opt._impl_to_dyn(name, path))
else:
opts = all_cons_opts
try: try:
all_cons_opts[0]._launch_consistency(func, option, opts[0]._launch_consistency(func, option, value, context,
value, index, submulti_idx, opts,
context, index, warnings_only)
submulti_idx,
all_cons_opts,
warnings_only)
except ValueError as err: except ValueError as err:
if warnings_only: if warnings_only:
raise ValueWarning(err.message, option) raise ValueWarning(err.message, option)
@ -189,12 +208,13 @@ class OptionDescription(BaseOption, StorageOptionDescription):
:param descr: parent :class:`tiramisu.option.OptionDescription` :param descr: parent :class:`tiramisu.option.OptionDescription`
""" """
if descr is None: if descr is None:
self.impl_build_cache_consistency() #FIXME faut le desactiver ?
#self.impl_build_cache_consistency()
self.impl_build_cache_option() self.impl_build_cache_option()
descr = self descr = self
super(OptionDescription, self)._impl_getstate(descr) super(OptionDescription, self)._impl_getstate(descr)
self._state_group_type = str(self._group_type) self._state_group_type = str(self._group_type)
for option in self.impl_getchildren(): for option in self._impl_getchildren():
option._impl_getstate(descr) option._impl_getstate(descr)
def __getstate__(self): def __getstate__(self):
@ -224,9 +244,12 @@ class OptionDescription(BaseOption, StorageOptionDescription):
self.impl_build_cache_option() self.impl_build_cache_option()
descr = self descr = self
self._group_type = getattr(groups, self._state_group_type) self._group_type = getattr(groups, self._state_group_type)
if isinstance(self._group_type, groups.MasterGroupType):
MasterSlaves(self.impl_getname(), self.impl_getchildren(),
validate=False)
del(self._state_group_type) del(self._state_group_type)
super(OptionDescription, self)._impl_setstate(descr) super(OptionDescription, self)._impl_setstate(descr)
for option in self.impl_getchildren(): for option in self._impl_getchildren(dyn=False):
option._impl_setstate(descr) option._impl_setstate(descr)
def __setstate__(self, state): def __setstate__(self, state):
@ -235,3 +258,129 @@ class OptionDescription(BaseOption, StorageOptionDescription):
self._stated self._stated
except AttributeError: except AttributeError:
self._impl_setstate() self._impl_setstate()
def _impl_get_suffixes(self, context):
callback, callback_params = self.impl_get_callback()
if callback_params is None:
callback_params = {}
values = carry_out_calculation(self, config=context,
callback=callback,
callback_params=callback_params)
if len(values) > len(set(values)):
raise ConfigError(_('DynOptionDescription callback return not uniq value'))
for val in values:
if not isinstance(val, str) or re.match(name_regexp, val) is None:
raise ValueError(_("invalid suffix: {0} for option").format(val))
return values
def _impl_search_dynchild(self, name=undefined, context=undefined):
ret = []
for child in self._impl_st_getchildren():
cname = child.impl_getname()
if isinstance(child, DynOptionDescription) and \
(name is undefined or name.startswith(cname)):
path = cname
for value in child._impl_get_suffixes(context):
if name is undefined:
ret.append(SynDynOptionDescription(child, cname + value, path + value, value))
elif name == cname + value:
return SynDynOptionDescription(child, name, path + value, value)
return ret
def _impl_get_dynchild(self, child, suffix):
name = child.impl_getname() + suffix
path = self._name + suffix + '.' + name
if isinstance(child, OptionDescription):
return SynDynOptionDescription(child, name, path, suffix)
else:
return child._impl_to_dyn(name, path)
def _impl_getchildren(self, dyn=True, context=undefined):
for child in self._impl_st_getchildren():
cname = child._name
if dyn and child.impl_is_dynoptiondescription():
path = cname
for value in child._impl_get_suffixes(context):
yield SynDynOptionDescription(child,
cname + value,
path + value, value)
else:
yield child
def impl_getchildren(self):
return list(self._impl_getchildren())
class DynOptionDescription(OptionDescription):
def __init__(self, name, doc, children, requires=None, properties=None,
callback=None, callback_params=None):
for child in children:
if isinstance(child, OptionDescription):
if child.impl_get_group_type() != groups.master:
raise ConfigError(_('cannot set optiondescription in an '
'dynoptiondescription'))
for chld in child._impl_getchildren():
chld._subdyn = self
if isinstance(child, SymLinkOption):
raise ConfigError(_('cannot set symlinkoption in an '
'dynoptiondescription'))
child._subdyn = self
super(DynOptionDescription, self).__init__(name, doc, children,
requires, properties)
self.impl_set_callback(callback, callback_params)
def _validate_callback(self, callback, callback_params):
if callback is None:
raise ConfigError(_('callback is mandatory for dynoptiondescription'))
class SynDynOptionDescription(object):
__slots__ = ('_opt', '_name', '_path', '_suffix')
def __init__(self, opt, name, path, suffix):
self._opt = opt
self._name = name
self._path = path
self._suffix = suffix
def __getattr__(self, name, context=undefined):
if name in dir(self._opt):
return getattr(self._opt, name)
return self._opt._getattr(name, self._name, self._suffix, context)
def impl_getname(self):
return self._name
def _impl_getchildren(self, dyn=True, context=undefined):
children = []
for child in self._opt._impl_getchildren():
children.append(self._opt._impl_get_dynchild(child, self._suffix))
return children
def impl_getchildren(self):
return self._impl_getchildren()
def impl_getpath(self, context):
return self._path
def impl_getpaths(self, include_groups=False, _currpath=None):
return _impl_getpaths(self, include_groups, _currpath)
def _impl_getpaths(klass, include_groups, _currpath):
"""returns a list of all paths in klass, recursively
_currpath should not be provided (helps with recursion)
"""
if _currpath is None:
_currpath = []
paths = []
for option in klass._impl_getchildren():
attr = option.impl_getname()
if option.impl_is_optiondescription():
if include_groups:
paths.append('.'.join(_currpath + [attr]))
paths += option.impl_getpaths(include_groups=include_groups,
_currpath=_currpath + [attr])
else:
paths.append('.'.join(_currpath + [attr]))
return paths

View file

@ -114,11 +114,11 @@ class _NameSpace(object):
""" """
def __setattr__(self, name, value): def __setattr__(self, name, value):
if name in self.__dict__: if name in self.__dict__: # pragma: optional cover
raise ConstError(_("can't rebind {0}").format(name)) raise ConstError(_("can't rebind {0}").format(name))
self.__dict__[name] = value self.__dict__[name] = value
def __delattr__(self, name): def __delattr__(self, name): # pragma: optional cover
if name in self.__dict__: if name in self.__dict__:
raise ConstError(_("can't unbind {0}").format(name)) raise ConstError(_("can't unbind {0}").format(name))
raise ValueError(name) raise ValueError(name)
@ -248,7 +248,7 @@ class Property(object):
:type propname: string :type propname: string
""" """
if self._opt is not None and self._opt.impl_getrequires() is not None \ if self._opt is not None and self._opt.impl_getrequires() is not None \
and propname in self._opt._calc_properties: and propname in self._opt._calc_properties: # pragma: optional cover
raise ValueError(_('cannot append {0} property for option {1}: ' raise ValueError(_('cannot append {0} property for option {1}: '
'this property is calculated').format( 'this property is calculated').format(
propname, self._opt.impl_getname())) propname, self._opt.impl_getname()))
@ -315,7 +315,7 @@ class Settings(object):
old `SubConfig`, `Values`, `Multi` or `Settings`) old `SubConfig`, `Values`, `Multi` or `Settings`)
""" """
context = self.context() context = self.context()
if context is None: if context is None: # pragma: optional cover
raise ConfigError(_('the context does not exist anymore')) raise ConfigError(_('the context does not exist anymore'))
return context return context
@ -329,24 +329,24 @@ class Settings(object):
return str(list(self._getproperties())) return str(list(self._getproperties()))
def __getitem__(self, opt): def __getitem__(self, opt):
path = self._get_path_by_opt(opt) path = opt.impl_getpath(self._getcontext())
return self._getitem(opt, path) return self._getitem(opt, path)
def _getitem(self, opt, path): def _getitem(self, opt, path):
return Property(self, self._getproperties(opt, path), opt, path) return Property(self, self._getproperties(opt, path), opt, path)
def __setitem__(self, opt, value): def __setitem__(self, opt, value): # pragma: optional cover
raise ValueError('you should only append/remove properties') raise ValueError('you should only append/remove properties')
def reset(self, opt=None, _path=None, all_properties=False): def reset(self, opt=None, _path=None, all_properties=False):
if all_properties and (_path or opt): if all_properties and (_path or opt): # pragma: optional cover
raise ValueError(_('opt and all_properties must not be set ' raise ValueError(_('opt and all_properties must not be set '
'together in reset')) 'together in reset'))
if all_properties: if all_properties:
self._p_.reset_all_properties() self._p_.reset_all_properties()
else: else:
if opt is not None and _path is None: if opt is not None and _path is None:
_path = self._get_path_by_opt(opt) _path = opt.impl_getpath(self._getcontext())
self._p_.reset_properties(_path) self._p_.reset_properties(_path)
self._getcontext().cfgimpl_reset_cache() self._getcontext().cfgimpl_reset_cache()
@ -357,7 +357,7 @@ class Settings(object):
if opt is None: if opt is None:
props = copy(self._p_.getproperties(path, default_properties)) props = copy(self._p_.getproperties(path, default_properties))
else: else:
if path is None: if path is None: # pragma: optional cover
raise ValueError(_('if opt is not None, path should not be' raise ValueError(_('if opt is not None, path should not be'
' None in _getproperties')) ' None in _getproperties'))
ntime = None ntime = None
@ -491,15 +491,15 @@ class Settings(object):
instead of passing a :class:`tiramisu.option.Option()` object. instead of passing a :class:`tiramisu.option.Option()` object.
""" """
if opt is not None and path is None: if opt is not None and path is None:
path = self._get_path_by_opt(opt) path = opt.impl_getpath(self._getcontext())
if not isinstance(permissive, tuple): if not isinstance(permissive, tuple): # pragma: optional cover
raise TypeError(_('permissive must be a tuple')) raise TypeError(_('permissive must be a tuple'))
self._p_.setpermissive(path, permissive) self._p_.setpermissive(path, permissive)
#____________________________________________________________ #____________________________________________________________
def setowner(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): # pragma: optional cover
raise TypeError(_("invalid generic owner {0}").format(str(owner))) raise TypeError(_("invalid generic owner {0}").format(str(owner)))
self._owner = owner self._owner = owner
@ -586,8 +586,8 @@ class Settings(object):
for require in requires: for require in requires:
option, expected, action, inverse, \ option, expected, action, inverse, \
transitive, same_action = require transitive, same_action = require
reqpath = self._get_path_by_opt(option) reqpath = option.impl_getpath(context)
if reqpath == path or reqpath.startswith(path + '.'): if reqpath == path or reqpath.startswith(path + '.'): # pragma: optional cover
raise RequirementError(_("malformed requirements " raise RequirementError(_("malformed requirements "
"imbrication detected for option:" "imbrication detected for option:"
" '{0}' with requirement on: " " '{0}' with requirement on: "
@ -598,7 +598,7 @@ class Settings(object):
if not transitive: if not transitive:
continue continue
properties = err.proptype properties = err.proptype
if same_action and action not in properties: if same_action and action not in properties: # pragma: optional cover
raise RequirementError(_("option '{0}' has " raise RequirementError(_("option '{0}' has "
"requirement's property " "requirement's property "
"error: " "error: "
@ -616,14 +616,6 @@ class Settings(object):
break break
return calc_properties return calc_properties
def _get_path_by_opt(self, opt):
"""just a wrapper to get path in optiondescription's cache
:param opt: `Option`'s object
:returns: path
"""
return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt)
def get_modified_properties(self): def get_modified_properties(self):
return self._p_.get_modified_properties() return self._p_.get_modified_properties()

View file

@ -40,7 +40,7 @@ class StorageType(object):
storage_type = None storage_type = None
mod = None mod = None
def set(self, name): def set(self, name): # pragma: optional cover
if self.storage_type is not None: if self.storage_type is not None:
if self.storage_type == name: if self.storage_type == name:
return return
@ -63,7 +63,7 @@ storage_type = StorageType()
storage_option_type = StorageType() storage_option_type = StorageType()
def set_storage(type_, name, **kwargs): def set_storage(type_, name, **kwargs): # pragma: optional cover
"""Change storage's configuration """Change storage's configuration
:params name: is the storage name. If storage is already set, cannot :params name: is the storage name. If storage is already set, cannot
@ -95,7 +95,7 @@ def _impl_getstate_setting():
return state return state
def get_storage(type_, session_id, persistent, test): def get_storage(type_, session_id, persistent, test): # pragma: optional cover
"""all used when __setstate__ a Config """all used when __setstate__ a Config
""" """
if type_ == 'option': if type_ == 'option':
@ -123,7 +123,7 @@ def get_storages_option(type_):
return imp.OptionDescription return imp.OptionDescription
def list_sessions(type_): def list_sessions(type_): # pragma: optional cover
"""List all available session (persistent or not persistent) """List all available session (persistent or not persistent)
""" """
if type_ == 'option': if type_ == 'option':
@ -132,7 +132,7 @@ def list_sessions(type_):
return storage_type.get().list_sessions() return storage_type.get().list_sessions()
def delete_session(type_, session_id): def delete_session(type_, session_id): # pragma: optional cover
"""Delete a selected session, be careful, you can deleted a session """Delete a selected session, be careful, you can deleted a session
use by an other instance use by an other instance
:params session_id: id of session to delete :params session_id: id of session to delete

View file

@ -18,7 +18,8 @@
# #
# ____________________________________________________________ # ____________________________________________________________
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.setting import groups from tiramisu.setting import groups, undefined
from tiramisu.error import ConfigError
#____________________________________________________________ #____________________________________________________________
@ -32,9 +33,14 @@ class Base(object):
'_default', '_default_multi', '_state_callback', '_callback', '_default', '_default_multi', '_state_callback', '_callback',
'_state_callback_params', '_callback_params', '_multitype', '_state_callback_params', '_callback_params', '_multitype',
'_consistencies', '_warnings_only', '_master_slaves', '_consistencies', '_warnings_only', '_master_slaves',
'_state_consistencies', '_extra', '__weakref__') '_state_consistencies', '_extra', '_subdyn', '__weakref__',
'_state_master_slaves')
def __init__(self): def __init__(self):
try:
self._subdyn
except AttributeError:
self._subdyn = False
try: try:
self._consistencies self._consistencies
except AttributeError: except AttributeError:
@ -65,6 +71,12 @@ class Base(object):
def _get_id(self): def _get_id(self):
return id(self) return id(self)
def _is_subdyn(self):
try:
return self._subdyn is not False
except AttributeError:
return False
class OptionDescription(Base): class OptionDescription(Base):
__slots__ = ('_children', '_cache_paths', '_cache_consistencies', __slots__ = ('_children', '_cache_paths', '_cache_consistencies',
@ -82,20 +94,21 @@ class OptionDescription(Base):
def impl_get_opt_by_path(self, path): def impl_get_opt_by_path(self, path):
try: try:
return self._cache_paths[0][self._cache_paths[1].index(path)] return self._cache_paths[0][self._cache_paths[1].index(path)]
except ValueError: except ValueError: # pragma: optional cover
raise AttributeError(_('no option for path {0}').format(path)) raise AttributeError(_('no option for path {0}').format(path))
def impl_get_path_by_opt(self, opt): def impl_get_path_by_opt(self, opt):
try: try:
return self._cache_paths[1][self._cache_paths[0].index(opt)] return self._cache_paths[1][self._cache_paths[0].index(opt)]
except ValueError: except ValueError: # pragma: optional cover
raise AttributeError(_('no option {0} found').format(opt)) raise AttributeError(_('no option {0} found').format(opt))
def impl_get_group_type(self): def impl_get_group_type(self): # pragma: optional cover
return getattr(groups, self._group_type) return getattr(groups, self._group_type)
def impl_build_cache_option(self, _currpath=None, cache_path=None, cache_option=None): def impl_build_cache_option(self, _currpath=None, cache_path=None,
if _currpath is None and self._cache_paths is not None: cache_option=None):
if _currpath is None and self._cache_paths is not None: # pragma: optional cover
# cache already set # cache already set
return return
if _currpath is None: if _currpath is None:
@ -106,11 +119,12 @@ class OptionDescription(Base):
if cache_path is None: if cache_path is None:
cache_path = [] cache_path = []
cache_option = [] cache_option = []
for option in self.impl_getchildren(): for option in self._impl_getchildren(dyn=False):
attr = option._name attr = option._name
path = str('.'.join(_currpath + [attr]))
cache_option.append(option) cache_option.append(option)
cache_path.append(str('.'.join(_currpath + [attr]))) cache_path.append(path)
if option.__class__.__name__ == 'OptionDescription': if option.impl_is_optiondescription():
_currpath.append(attr) _currpath.append(attr)
option.impl_build_cache_option(_currpath, cache_path, option.impl_build_cache_option(_currpath, cache_path,
cache_option) cache_option)
@ -118,52 +132,141 @@ class OptionDescription(Base):
if save: if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path)) self._cache_paths = (tuple(cache_option), tuple(cache_path))
def impl_get_options_paths(self, bytype, byname, _subpath, only_first): def impl_get_options_paths(self, bytype, byname, _subpath, only_first, context):
def _filter_by_name():
if byname is None or path == byname or \
path.endswith('.' + byname):
return True
return False
def _filter_by_type():
if bytype is None:
return True
if isinstance(option, bytype):
return True
return False
find_results = [] find_results = []
def _rebuild_dynpath(path, suffix, dynopt):
found = False
spath = path.split('.')
for length in xrange(1, len(spath)):
subpath = '.'.join(spath[0:length])
subopt = self.impl_get_opt_by_path(subpath)
if dynopt == subopt:
found = True
break
if not found:
#FIXME
raise ConfigError(_('hu?'))
subpath = subpath + suffix
for slength in xrange(length, len(spath)):
subpath = subpath + '.' + spath[slength] + suffix
return subpath
def _filter_by_name(path, option):
name = option.impl_getname()
if option._is_subdyn():
if byname.startswith(name):
found = False
for suffix in option._subdyn._impl_get_suffixes(
context):
if byname == name + suffix:
found = True
path = _rebuild_dynpath(path, suffix,
option._subdyn)
option = option._impl_to_dyn(
name + suffix, path)
break
if not found:
return False
else:
if not byname == name:
return False
find_results.append((path, option))
return True
def _filter_by_type(path, option):
if isinstance(option, bytype):
#if byname is not None, check option byname in _filter_by_name
#not here
if byname is None:
if option._is_subdyn():
name = option.impl_getname()
for suffix in option._subdyn._impl_get_suffixes(
context):
spath = _rebuild_dynpath(path, suffix,
option._subdyn)
find_results.append((spath, option._impl_to_dyn(
name + suffix, spath)))
else:
find_results.append((path, option))
return True
return False
def _filter(path, option):
if bytype is not None:
retval = _filter_by_type(path, option)
if byname is None:
return retval
if byname is not None:
return _filter_by_name(path, option)
opts, paths = self._cache_paths opts, paths = self._cache_paths
for index in range(0, len(paths)): for index in range(0, len(paths)):
option = opts[index] option = opts[index]
if option.__class__.__name__ == 'OptionDescription': if option.impl_is_optiondescription():
continue continue
path = paths[index] 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 bytype == byname is None:
continue if option._is_subdyn():
if not _filter_by_type(): name = option.impl_getname()
continue for suffix in option._subdyn._impl_get_suffixes(
retval = (path, option) context):
spath = _rebuild_dynpath(path, suffix,
option._subdyn)
find_results.append((spath, option._impl_to_dyn(
name + suffix, spath)))
else:
find_results.append((path, option))
else:
if _filter(path, option) is False:
continue
if only_first: if only_first:
return retval return find_results[0]
find_results.append(retval)
return find_results return find_results
def impl_getchildren(self): def _impl_st_getchildren(self):
return self._children[1] return self._children[1]
def __getattr__(self, name): def __getattr__(self, name, context=undefined):
if name == '_name': if name == '_name':
return object.__getattribute__(self, name) return object.__getattribute__(self, name)
try: return self._getattr(name, context=context)
if name == '_readonly':
raise AttributeError("{0} instance has no attribute " def _getattr(self, name, dyn_od=undefined, suffix=undefined,
"'_readonly'".format( context=undefined, dyn=True):
self.__class__.__name__)) error = False
return self._children[1][self._children[0].index(name)] if suffix is not undefined:
except ValueError: try:
if undefined in [dyn_od, suffix, context]: # pragma: optional cover
raise ConfigError(_("dyn_od, suffix and context needed if "
"it's a dyn option"))
if name.endswith(suffix):
oname = name[:-len(suffix)]
child = self._children[1][self._children[0].index(oname)]
return self._impl_get_dynchild(child, suffix)
else:
error = True
except ValueError: # pragma: optional cover
error = True
else:
try: # pragma: optional cover
if name == '_readonly':
raise AttributeError(_("{0} instance has no attribute "
"'_readonly'").format(
self.__class__.__name__))
child = self._children[1][self._children[0].index(name)]
if dyn and child.impl_is_dynoptiondescription():
error = True
else:
return child
except ValueError:
child = self._impl_search_dynchild(name, context=context)
if child != []:
return child
error = True
if error:
raise AttributeError(_('unknown Option {0} ' raise AttributeError(_('unknown Option {0} '
'in OptionDescription {1}' 'in OptionDescription {1}'
'').format(name, self._name)) '').format(name, self._name))

View file

@ -29,11 +29,11 @@ setting = Setting()
_list_sessions = [] _list_sessions = []
def list_sessions(): def list_sessions(): # pragma: optional cover
return _list_sessions return _list_sessions
def delete_session(session_id): def delete_session(session_id): # pragma: optional cover
raise ConfigError(_('dictionary storage cannot delete session')) raise ConfigError(_('dictionary storage cannot delete session'))
@ -44,9 +44,9 @@ class Storage(object):
serializable = True serializable = True
def __init__(self, session_id, persistent, test=False): def __init__(self, session_id, persistent, test=False):
if not test and session_id in _list_sessions: if not test and session_id in _list_sessions: # pragma: optional cover
raise ValueError(_('session already used')) raise ValueError(_('session already used'))
if persistent: if persistent: # pragma: optional cover
raise ValueError(_('a dictionary cannot be persistent')) raise ValueError(_('a dictionary cannot be persistent'))
self.session_id = session_id self.session_id = session_id
self.persistent = persistent self.persistent = persistent
@ -55,5 +55,5 @@ class Storage(object):
def __del__(self): def __del__(self):
try: try:
_list_sessions.remove(self.session_id) _list_sessions.remove(self.session_id)
except AttributeError: except AttributeError: # pragma: optional cover
pass pass

View file

@ -88,5 +88,5 @@ class Values(Cache):
""" """
if key in self._informations: if key in self._informations:
return self._informations[key] return self._informations[key]
else: else: # pragma: optional cover
raise ValueError("not found") raise ValueError("not found")

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"takes care of the option's values and multi values" "takes care of the option's values and multi values"
# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) # Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software: you can redistribute it and/or modify it # This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the # under the terms of the GNU Lesser General Public License as published by the
@ -22,7 +22,7 @@ from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError
from tiramisu.setting import owners, expires_time, undefined from tiramisu.setting import owners, expires_time, undefined
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.option import SymLinkOption, OptionDescription from tiramisu.option import SymLinkOption, DynSymLinkOption
class Values(object): class Values(object):
@ -50,7 +50,7 @@ class Values(object):
old `SubConfig`, `Values`, `Multi` or `Settings`) old `SubConfig`, `Values`, `Multi` or `Settings`)
""" """
context = self.context() context = self.context()
if context is None: if context is None: # pragma: optional cover
raise ConfigError(_('the context does not exist anymore')) raise ConfigError(_('the context does not exist anymore'))
return context return context
@ -60,9 +60,11 @@ class Values(object):
:param opt: the `option.Option()` object :param opt: the `option.Option()` object
:returns: the option's value (or the default value if not set) :returns: the option's value (or the default value if not set)
""" """
if opt.impl_is_optiondescription(): # pragma: optional cover
raise ValueError(_('optiondescription has no value'))
setting = self._getcontext().cfgimpl_get_settings() setting = self._getcontext().cfgimpl_get_settings()
force_default = 'frozen' in setting[opt] and \ force_default = 'frozen' in setting._getitem(opt, path) and \
'force_default_on_freeze' in setting[opt] 'force_default_on_freeze' in setting._getitem(opt, path)
if not is_default and not force_default: if not is_default and not force_default:
value = self._p_.getvalue(path) value = self._p_.getvalue(path)
if index is not undefined: if index is not undefined:
@ -101,7 +103,7 @@ class Values(object):
#FIXME : problème de longueur si meta + slave #FIXME : problème de longueur si meta + slave
#doit passer de meta à pas meta #doit passer de meta à pas meta
#en plus il faut gérer la longueur avec les meta ! #en plus il faut gérer la longueur avec les meta !
#FIXME SymlinkOption #FIXME SymLinkOption
value = meta.cfgimpl_get_values()[opt] value = meta.cfgimpl_get_values()[opt]
if isinstance(value, Multi): if isinstance(value, Multi):
if index is not undefined: if index is not undefined:
@ -136,7 +138,7 @@ class Values(object):
:param opt: the `option.Option()` object :param opt: the `option.Option()` object
""" """
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
return self._contains(path) return self._contains(path)
def _contains(self, path): def _contains(self, path):
@ -148,7 +150,7 @@ class Values(object):
def reset(self, opt, path=None): def reset(self, opt, path=None):
if path is None: if path is None:
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
if self._p_.hasvalue(path): if self._p_.hasvalue(path):
context = self._getcontext() context = self._getcontext()
setting = context.cfgimpl_get_settings() setting = context.cfgimpl_get_settings()
@ -156,7 +158,7 @@ class Values(object):
context, 'validator' in setting) context, 'validator' in setting)
context.cfgimpl_reset_cache() context.cfgimpl_reset_cache()
if opt.impl_is_master_slaves('master'): if opt.impl_is_master_slaves('master'):
opt.impl_get_master_slaves().reset(self) opt.impl_get_master_slaves().reset(opt, self)
self._p_.resetvalue(path) self._p_.resetvalue(path)
def _isempty(self, opt, value): def _isempty(self, opt, value):
@ -186,7 +188,7 @@ class Values(object):
force_permissive=False, force_properties=None, force_permissive=False, force_properties=None,
validate_properties=True): validate_properties=True):
if path is None: if path is None:
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
ntime = None ntime = None
setting = self._getcontext().cfgimpl_get_settings() setting = self._getcontext().cfgimpl_get_settings()
if 'cache' in setting and self._p_.hascache(path): if 'cache' in setting and self._p_.hascache(path):
@ -275,13 +277,13 @@ class Values(object):
force_submulti_index = None force_submulti_index = None
else: else:
force_submulti_index = submulti_index force_submulti_index = submulti_index
opt.impl_validate(value, context, 'validator' in setting, opt.impl_validate(value, context, 'validator' in setting,
force_index=force_index, force_index=force_index,
force_submulti_index=force_submulti_index) force_submulti_index=force_submulti_index)
#FIXME pas de test avec les metas ... #FIXME pas de test avec les metas ...
#FIXME et les symlinkoption ... #FIXME et les symlinkoption ...
if is_default and 'force_store_value' in setting[opt]: if is_default and 'force_store_value' in setting._getitem(opt,
path):
if isinstance(value, Multi): if isinstance(value, Multi):
item = list(value) item = list(value)
else: else:
@ -297,7 +299,7 @@ class Values(object):
raise config_error raise config_error
return value return value
def __setitem__(self, opt, value): def __setitem__(self, opt, value): # pragma: optional cover
raise ValueError(_('you should only set value with config')) raise ValueError(_('you should only set value with config'))
def setitem(self, opt, value, path, force_permissive=False, def setitem(self, opt, value, path, force_permissive=False,
@ -342,9 +344,10 @@ class Values(object):
was present was present
:returns: a `setting.owners.Owner` object :returns: a `setting.owners.Owner` object
""" """
if isinstance(opt, SymLinkOption): if isinstance(opt, SymLinkOption) and \
not isinstance(opt, DynSymLinkOption):
opt = opt._opt opt = opt._opt
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
return self._getowner(opt, path, force_permissive=force_permissive) return self._getowner(opt, path, force_permissive=force_permissive)
def _getowner(self, opt, path, validate_properties=True, def _getowner(self, opt, path, validate_properties=True,
@ -365,14 +368,14 @@ class Values(object):
:param opt: the `option.Option` object :param opt: the `option.Option` object
:param owner: a valid owner, that is a `setting.owners.Owner` object :param owner: a valid owner, that is a `setting.owners.Owner` object
""" """
if not isinstance(owner, owners.Owner): if not isinstance(owner, owners.Owner): # pragma: optional cover
raise TypeError(_("invalid generic owner {0}").format(str(owner))) raise TypeError(_("invalid generic owner {0}").format(str(owner)))
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
self._setowner(opt, path, owner) self._setowner(opt, path, owner)
def _setowner(self, opt, path, owner): def _setowner(self, opt, path, owner):
if self._getowner(opt, path) == owners.default: if self._getowner(opt, path) == owners.default: # pragma: optional cover
raise ConfigError(_('no value for {0} cannot change owner to {1}' raise ConfigError(_('no value for {0} cannot change owner to {1}'
'').format(path, owner)) '').format(path, owner))
self._p_.setowner(path, owner) self._p_.setowner(path, owner)
@ -384,7 +387,7 @@ class Values(object):
(not the toplevel config) (not the toplevel config)
:return: boolean :return: boolean
""" """
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
return self._is_default_owner(opt, path, return self._is_default_owner(opt, path,
validate_properties=validate_properties, validate_properties=validate_properties,
validate_meta=validate_meta) validate_meta=validate_meta)
@ -403,16 +406,6 @@ class Values(object):
else: else:
self._p_.reset_all_cache() self._p_.reset_all_cache()
def _get_opt_path(self, opt):
"""
retrieve the option's path in the config
:param opt: the `option.Option` object
:returns: a string with points like "gc.dummy.my_option"
"""
return self._getcontext().cfgimpl_get_description(
).impl_get_path_by_opt(opt)
# information # information
def set_information(self, key, value): def set_information(self, key, value):
"""updates the information's attribute """updates the information's attribute
@ -429,7 +422,7 @@ class Values(object):
""" """
try: try:
return self._p_.get_information(key) return self._p_.get_information(key)
except ValueError: except ValueError: # pragma: optional cover
if default is not undefined: if default is not undefined:
return default return default
else: else:
@ -445,22 +438,26 @@ class Values(object):
""" """
def _mandatory_warnings(description): def _mandatory_warnings(description):
#if value in cache, properties are not calculated #if value in cache, properties are not calculated
for opt in description.impl_getchildren(): _ret = []
if isinstance(opt, OptionDescription): for opt in description._impl_getchildren(
_mandatory_warnings(opt) context=self._getcontext()):
elif isinstance(opt, SymLinkOption): if opt.impl_is_optiondescription():
_ret.extend(_mandatory_warnings(opt))
elif isinstance(opt, SymLinkOption) and \
not isinstance(opt, DynSymLinkOption):
pass pass
else: else:
path = self._get_opt_path(opt) path = opt.impl_getpath(self._getcontext())
try: try:
self._get_cached_item(opt, path=path, self._get_cached_item(opt, path=path,
force_properties=frozenset(('mandatory',))) force_properties=frozenset(('mandatory',)))
except PropertiesOptionError as err: except PropertiesOptionError as err:
if err.proptype == ['mandatory']: if err.proptype == ['mandatory']:
yield path _ret.append(path)
return _ret
self.reset_cache(False) self.reset_cache(False)
descr = self._getcontext().cfgimpl_get_description() descr = self._getcontext().cfgimpl_get_description()
ret = list(_mandatory_warnings(descr)) ret = _mandatory_warnings(descr)
self.reset_cache(False) self.reset_cache(False)
return ret return ret
@ -507,12 +504,12 @@ class Multi(list):
""" """
if value is None: if value is None:
value = [] value = []
if not opt.impl_is_submulti() and isinstance(value, Multi): if not opt.impl_is_submulti() and isinstance(value, Multi): # pragma: optional cover
raise ValueError(_('{0} is already a Multi ').format( raise ValueError(_('{0} is already a Multi ').format(
opt.impl_getname())) opt.impl_getname()))
self.opt = opt self.opt = opt
self.path = path self.path = path
if not isinstance(context, weakref.ReferenceType): if not isinstance(context, weakref.ReferenceType): # pragma: optional cover
raise ValueError('context must be a Weakref') raise ValueError('context must be a Weakref')
self.context = context self.context = context
if not isinstance(value, list): if not isinstance(value, list):
@ -542,7 +539,7 @@ class Multi(list):
old `SubConfig`, `Values`, `Multi` or `Settings`) old `SubConfig`, `Values`, `Multi` or `Settings`)
""" """
context = self.context() context = self.context()
if context is None: if context is None: # pragma: optional cover
raise ConfigError(_('the context does not exist anymore')) raise ConfigError(_('the context does not exist anymore'))
return context return context
@ -574,7 +571,7 @@ 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.impl_is_master_slaves('slave'): if self.opt.impl_is_master_slaves('slave'): # pragma: optional cover
raise SlaveError(_("cannot append a value on a multi option {0}" raise SlaveError(_("cannot append a value on a multi option {0}"
" which is a slave").format(self.opt.impl_getname())) " which is a slave").format(self.opt.impl_getname()))
index = self.__len__() index = self.__len__()
@ -650,12 +647,12 @@ class Multi(list):
""" """
context = self._getcontext() context = self._getcontext()
if not force: if not force:
if self.opt.impl_is_master_slaves('slave'): if self.opt.impl_is_master_slaves('slave'): # pragma: optional cover
raise SlaveError(_("cannot pop a value on a multi option {0}" raise SlaveError(_("cannot pop a value on a multi option {0}"
" which is a slave").format(self.opt.impl_getname())) " which is a slave").format(self.opt.impl_getname()))
if self.opt.impl_is_master_slaves('master'): if self.opt.impl_is_master_slaves('master'):
self.opt.impl_get_master_slaves().pop( self.opt.impl_get_master_slaves().pop(self.opt,
context.cfgimpl_get_values(), index) context.cfgimpl_get_values(), index)
#set value without valid properties #set value without valid properties
ret = super(Multi, self).pop(index) ret = super(Multi, self).pop(index)
self._store(force) self._store(force)