From cd4d3527c79f5b6935540d14038540a05f4444de Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sat, 12 Apr 2014 11:53:58 +0200 Subject: [PATCH] split tiramisu/option.py and add MasterSlaves object --- ChangeLog | 10 + test/test_option_calculation.py | 169 +++- test/test_option_consistency.py | 2 +- test/test_requires.py | 24 + tiramisu/autolib.py | 44 +- tiramisu/config.py | 5 +- tiramisu/option/__init__.py | 16 + tiramisu/{option.py => option/baseoption.py} | 948 ++----------------- tiramisu/option/masterslave.py | 187 ++++ tiramisu/option/option.py | 525 ++++++++++ tiramisu/option/optiondescription.py | 263 +++++ tiramisu/setting.py | 25 +- tiramisu/value.py | 436 ++++----- 13 files changed, 1478 insertions(+), 1176 deletions(-) create mode 100644 ChangeLog create mode 100644 tiramisu/option/__init__.py rename tiramisu/{option.py => option/baseoption.py} (51%) create mode 100644 tiramisu/option/masterslave.py create mode 100644 tiramisu/option/option.py create mode 100644 tiramisu/option/optiondescription.py diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..23913bc --- /dev/null +++ b/ChangeLog @@ -0,0 +1,10 @@ +Sat Apr 12 11:37:27 CEST 2014 Emmanuel Garette + + * behavior change in master/slave part of code: + if slave has a default value greater than master's one, it's raise + SlaveError, didn't try to reduce the slave's length + * tiramisu/option.py: split into tiramisu/option directory + * tiramisu/option/masterslave.py: master/slaves have no a special + object MasterSlaves for all code related to master/slaves options + * tiramisu/option/masterslave.py: master and slaves values (length, + consistency, ...) are now check every time diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index de9c282..567f854 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -1,8 +1,8 @@ import autopath from py.test import raises -from tiramisu.setting import groups from tiramisu.config import Config +from tiramisu.setting import groups, owners from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ StrOption, OptionDescription, SymLinkOption from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError @@ -430,6 +430,17 @@ def test_callback_multi_list_extend(): assert cfg.val1 == ['1', '2', '3', '4', '5'] +def test_callback_multi_callback(): + val1 = StrOption('val1', "", multi=True, callback=return_val) + interface1 = OptionDescription('val1', '', [val1]) + maconfig = OptionDescription('rootconfig', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1.val1 == ['val'] + cfg.val1.val1.append() + assert cfg.val1.val1 == ['val', 'val'] + + def test_callback_master_and_slaves_master(): val1 = StrOption('val1', "", multi=True, callback=return_val) val2 = StrOption('val2', "", multi=True) @@ -541,8 +552,6 @@ def test_callback_master_and_slaves_slave_cal(): cfg.val3 = ['val1'] assert cfg.val1.val1 == ['val1'] assert cfg.val1.val2 == ['val'] - assert cfg.val1.val1 == ['val1'] - assert cfg.val1.val2 == ['val'] del(cfg.val1.val1) cfg.val1.val2 = ['val'] cfg.val3 = ['val1', 'val2'] @@ -571,8 +580,8 @@ def test_callback_master_and_slaves_slave_cal2(): assert cfg.val1.val1 == ['val', 'val'] assert cfg.val1.val2 == ['val2', 'val2'] cfg.val3.pop(1) -# # cannot remove slave's value because master is calculated -# # so raise + # cannot remove slave's value because master is calculated + # so raise raises(SlaveError, "cfg.val1.val1") raises(SlaveError, "cfg.val1.val2") cfg.val3 = ['val', 'val'] @@ -585,6 +594,88 @@ def test_callback_master_and_slaves_slave_cal2(): assert cfg.val1.val2 == ['val2', 'val2'] +def test_callback_master_and_slaves_master_disabled(): + #properties must be transitive + val1 = StrOption('val1', "", multi=True, properties=('disabled',)) + val2 = StrOption('val2', "", multi=True) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + raises(PropertiesOptionError, "cfg.val1.val1") + raises(PropertiesOptionError, "cfg.val1.val1.append('yes')") + raises(PropertiesOptionError, "cfg.val1.val2") + + +def test_callback_master_and_slaves_master_callback_disabled(): + val0 = StrOption('val0', "", multi=True, properties=('disabled',)) + val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val0, False),)}) + val2 = StrOption('val2', "", multi=True) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, val0]) + cfg = Config(maconfig) + cfg.read_write() + raises(ConfigError, "cfg.val1.val1") + raises(ConfigError, "cfg.val1.val2") + cfg.cfgimpl_get_settings().remove('disabled') + cfg.val1.val1 = [] + cfg.cfgimpl_get_settings().append('disabled') + assert cfg.val1.val1 == [] + assert cfg.val1.val2 == [] + + +def test_callback_master_and_slaves_slave_disabled(): + val1 = StrOption('val1', "", multi=True) + val2 = StrOption('val2', "", multi=True, properties=('disabled',)) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1.val1 == [] + raises(PropertiesOptionError, "cfg.val1.val2") + cfg.val1.val1.append('yes') + assert cfg.val1.val1 == ['yes'] + cfg.cfgimpl_get_settings().remove('disabled') + assert cfg.val1.val2 == [None] + cfg.val1.val2 = ['no'] + cfg.val1.val1.append('yes2') + cfg.val1.val1.append('yes3') + cfg.val1.val2[2] = 'no1' + assert cfg.val1.val2 == ['no', None, 'no1'] + cfg.cfgimpl_get_settings().append('disabled') + cfg.val1.val1.pop(0) + assert cfg.val1.val1 == ['yes2', 'yes3'] + cfg.cfgimpl_get_settings().remove('disabled') + assert cfg.val1.val2 == [None, 'no1'] + + +def test_callback_master_and_slaves_slave_callback_disabled(): + val0 = StrOption('val0', "", multi=True, properties=('disabled',)) + val1 = StrOption('val1', "", multi=True) + val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val0, False),)}) + interface1 = OptionDescription('val1', '', [val1, val2]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('rootconfig', '', [interface1, val0]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.val1.val1 == [] + raises(ConfigError, "cfg.val1.val2") + cfg.val1.val1.append('yes') + assert cfg.val1.val1 == ['yes'] + cfg.cfgimpl_get_settings().remove('disabled') + assert cfg.val1.val2 == [None] + cfg.val1.val2 = ['no'] + cfg.val1.val1.append('yes1') + cfg.val1.val2[1] = 'no1' + cfg.cfgimpl_get_settings().append('disabled') + cfg.val1.val1.pop(0) + assert cfg.val1.val1 == ['yes1'] + assert cfg.val1.val2 == ['no1'] + + def test_callback_master_and_slaves_slave_list(): val1 = StrOption('val1', "", multi=True) val2 = StrOption('val2', "", multi=True, callback=return_list) @@ -593,20 +684,20 @@ def test_callback_master_and_slaves_slave_list(): maconfig = OptionDescription('rootconfig', '', [interface1]) cfg = Config(maconfig) cfg.read_write() - assert cfg.val1.val2 == [] + #len is equal to 2 for slave and 0 for master + raises(SlaveError, "cfg.val1.val2") cfg.val1.val1 = ['val1', 'val2'] assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val2 == ['val', 'val'] - cfg.val1.val1 = ['val1'] #wrong len - raises(SlaveError, 'cfg.val1.val2') + raises(SlaveError, "cfg.val1.val1 = ['val1']") def test_callback_master_and_slaves_value(): + val4 = StrOption('val4', '', multi=True, default=['val10', 'val11']) val1 = StrOption('val1', "", multi=True) val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)}) val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)}) - val4 = StrOption('val4', '', multi=True, default=['val10', 'val11']) val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)}) val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)}) interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6]) @@ -614,27 +705,24 @@ def test_callback_master_and_slaves_value(): maconfig = OptionDescription('rootconfig', '', [interface1, val4]) cfg = Config(maconfig) cfg.read_write() - assert cfg.val1.val1 == [] - assert cfg.val1.val2 == [] - assert cfg.val1.val3 == [] - assert cfg.val1.val5 == [] - assert cfg.val1.val6 == [] + cfg.val4 == ['val10', 'val11'] + raises(SlaveError, "cfg.val1.val1") + raises(SlaveError, "cfg.val1.val2") + raises(SlaveError, "cfg.val1.val3") + raises(SlaveError, "cfg.val1.val5") + raises(SlaveError, "cfg.val1.val6") # - cfg.val1.val1 = ['val1'] - assert cfg.val1.val1 == ['val1'] - assert cfg.val1.val2 == ['val1'] - assert cfg.val1.val3 == ['yes'] - assert cfg.val1.val5 == ['val10'] - assert cfg.val1.val6 == ['val10'] + #default calculation has greater length + raises(SlaveError, "cfg.val1.val1 = ['val1']") # - cfg.val1.val1.append('val2') + cfg.val1.val1 = ['val1', 'val2'] assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val2 == ['val1', 'val2'] assert cfg.val1.val3 == ['yes', 'yes'] assert cfg.val1.val5 == ['val10', 'val11'] assert cfg.val1.val6 == ['val10', 'val11'] # - cfg.val1.val1 = ['val1', 'val2', 'val3'] + cfg.val1.val1.append('val3') assert cfg.val1.val1 == ['val1', 'val2', 'val3'] assert cfg.val1.val2 == ['val1', 'val2', 'val3'] assert cfg.val1.val3 == ['yes', 'yes', 'yes'] @@ -705,9 +793,16 @@ def test_callback_master_and_other_master_slave(): assert cfg.val4.val6 == ['no', None] cfg.val1.val1 = ['yes', 'yes', 'yes'] cfg.val1.val2 = ['no', 'no', 'no'] - assert cfg.val4.val4 == ['val10', 'val11'] - assert cfg.val4.val5 == ['yes', 'yes'] - assert cfg.val4.val6 == ['no', 'no'] + raises(SlaveError, "cfg.val4.val4") + raises(SlaveError, "cfg.val4.val5") + raises(SlaveError, "cfg.val4.val6") + cfg.val4.getattr('val4', validate=False).append('val12') + assert cfg.val4.val4 == ['val10', 'val11', 'val12'] + assert cfg.val4.val5 == ['yes', 'yes', 'yes'] + assert cfg.val4.val6 == ['no', 'no', 'no'] + + +#FIXME: slave est un symlink def test_callback_different_type(): @@ -757,6 +852,19 @@ def test_callback_two_disabled(): raises(PropertiesOptionError, 'cfg.od2.opt2') +def test_callback_two_disabled2(): + opt1 = BoolOption('opt1', '', properties=('hidden',)) + opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('hidden',)) + od1 = OptionDescription('od1', '', [opt1]) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + cfg = Config(maconfig) + cfg.read_write() + cfg.cfgimpl_get_settings().setpermissive(('hidden',)) + raises(PropertiesOptionError, 'cfg.od2.opt2') + assert cfg.getowner(opt2, force_permissive=True) == owners.default + + def test_callback_calculating_disabled(): opt1 = BoolOption('opt1', '', properties=('disabled',)) opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}) @@ -779,6 +887,17 @@ def test_callback_calculating_mandatory(): raises(ConfigError, 'cfg.od2.opt2') +def test_callback_calculating_mandatory_multi(): + opt1 = BoolOption('opt1', '', multi=True, properties=('disabled',)) + opt2 = BoolOption('opt2', '', multi=True, callback=return_value, callback_params={'': ((opt1, False),)}, properties=('mandatory',)) + od1 = OptionDescription('od1', '', [opt1]) + od2 = OptionDescription('od2', '', [opt2]) + maconfig = OptionDescription('rootconfig', '', [od1, od2]) + cfg = Config(maconfig) + cfg.read_only() + raises(ConfigError, 'cfg.od2.opt2') + + def test_callback_two_disabled_multi(): opt1 = BoolOption('opt1', '', properties=('disabled',)) opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True) diff --git a/test/test_option_consistency.py b/test/test_option_consistency.py index edb4f4a..5d2f9da 100644 --- a/test/test_option_consistency.py +++ b/test/test_option_consistency.py @@ -321,7 +321,7 @@ def test_consistency_broadcast_error(): c.impl_add_consistency('broadcast', a) c = Config(od) c.a = ['192.168.1.0'] - c.b = ['255.255.255.0'] + raises(ConfigError, "c.b = ['255.255.255.0']") raises(ConfigError, "c.c = ['192.168.1.255']") diff --git a/test/test_requires.py b/test/test_requires.py index ff9c2a6..c6d1032 100644 --- a/test/test_requires.py +++ b/test/test_requires.py @@ -242,6 +242,30 @@ def test_requires_transitive(): assert props == ['disabled'] +def test_requires_transitive_owner(): + a = BoolOption('activate_service', '', True) + b = BoolOption('activate_service_web', '', True, + requires=[{'option': a, 'expected': False, 'action': 'disabled'}]) + + d = IPOption('ip_address_service_web', '', + requires=[{'option': b, 'expected': False, 'action': 'disabled'}]) + od = OptionDescription('service', '', [a, b, d]) + c = Config(od) + c.read_write() + c.activate_service + c.activate_service_web + c.ip_address_service_web + #no more default value + c.ip_address_service_web = '1.1.1.1' + c.activate_service = False + props = [] + try: + c.ip_address_service_web + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + def test_requires_transitive_bis(): a = BoolOption('activate_service', '', True) abis = BoolOption('activate_service_bis', '', True) diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 92b86c5..6d8ed6e 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -19,13 +19,13 @@ # ____________________________________________________________ "enables us to carry out a calculation and return an option's value" from tiramisu.error import PropertiesOptionError, ConfigError -from tiramisu.setting import multitypes from tiramisu.i18n import _ +from tiramisu.setting import undefined # ____________________________________________________________ def carry_out_calculation(option, config, callback, callback_params, - index=None, max_len=None): + index=undefined): """a function that carries out a calculation for an option's value :param name: the option @@ -38,8 +38,6 @@ def carry_out_calculation(option, config, callback, callback_params, :type callback_params: dict :param index: if an option is multi, only calculates the nth value :type index: int - :param max_len: max length for a multi - :type max_len: int The callback_params is a dict. Key is used to build args (if key is '') and kwargs (otherwise). Values are tuple of: @@ -129,7 +127,7 @@ def carry_out_calculation(option, config, callback, callback_params, * if callback_params={'value': ((opt1, False), (opt2, False))} => raises ValueError() - If index is not None, return a value, otherwise return: + If index is not undefined, return a value, otherwise return: * a list if one parameters have multi option * a value otherwise @@ -154,15 +152,8 @@ def carry_out_calculation(option, config, callback, callback_params, ).impl_get_path_by_opt(opt) # get value try: - if option.impl_is_multi() and \ - (option.impl_get_multitype() == multitypes.master or - (option.impl_get_multitype() == multitypes.slave and - option.impl_get_master_slaves() != opt)): - validate = True - else: - validate = False value = config.getattr(path, force_permissive=True, - validate=validate) + validate=False) # convert to list, not modifie this multi if value.__class__.__name__ == 'Multi': value = list(value) @@ -175,20 +166,13 @@ def carry_out_calculation(option, config, callback, callback_params, err.proptype, option._name)) - is_multi = False - if opt.impl_is_multi(): - #opt is master, search if option is a slave - if opt.impl_get_multitype() == multitypes.master: - if option in opt.impl_get_master_slaves(): - is_multi = True - #opt is slave, search if option is an other slaves - elif opt.impl_get_multitype() == multitypes.slave: - if option in opt.impl_get_master_slaves( - ).impl_get_master_slaves(): - is_multi = True - if is_multi: + if opt.impl_is_master_slaves() and \ + opt.impl_get_master_slaves().in_same_group(option): len_multi = len(value) one_is_multi = True + is_multi = True + else: + is_multi = False tcparams.setdefault(key, []).append((value, is_multi)) else: # callbk is a value and not a multi @@ -199,7 +183,7 @@ def carry_out_calculation(option, config, callback, callback_params, # if no index, return a list if one_is_multi: ret = [] - if index is not None: + if index is not undefined: range_ = [index] else: range_ = range(len_multi) @@ -218,7 +202,7 @@ def carry_out_calculation(option, config, callback, callback_params, else: kwargs[key] = val calc = calculate(callback, args, kwargs) - if index is not None: + if index is not undefined: ret = calc else: ret.append(calc) @@ -237,11 +221,7 @@ def carry_out_calculation(option, config, callback, callback_params, kwargs[key] = couple[0] ret = calculate(callback, args, kwargs) if callback_params != {}: - if isinstance(ret, list) and max_len: - ret = ret[:max_len] - if len(ret) < max_len: - ret = ret + [None] * (max_len - len(ret)) - if isinstance(ret, list) and index is not None: + if isinstance(ret, list) and index is not undefined: if len(ret) < index + 1: ret = None else: diff --git a/tiramisu/config.py b/tiramisu/config.py index 8356aaa..04c9cb9 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -228,6 +228,7 @@ class SubConfig(object): :return: option's value if name is an option name, OptionDescription otherwise """ + #FIXME force_properties vraiment utile maintenant ? # attribute access by passing a path, # for instance getattr(self, "creole.general.family.adresse_ip_eth0") if '.' in name: @@ -243,6 +244,8 @@ class SubConfig(object): else: subpath = self._impl_path + '.' + name # symlink options + #FIXME a gerer plutot dans l'option ca ... + #FIXME je n'en sais rien en fait ... :/ if isinstance(opt_or_descr, SymLinkOption): context = self._cfgimpl_get_context() path = context.cfgimpl_get_description().impl_get_path_by_opt( @@ -257,7 +260,7 @@ class SubConfig(object): force_properties=force_properties) return SubConfig(opt_or_descr, self._impl_context, subpath) else: - return self.cfgimpl_get_values().getitem( + return self.cfgimpl_get_values()._get_cached_item( opt_or_descr, path=subpath, validate=validate, force_properties=force_properties, diff --git a/tiramisu/option/__init__.py b/tiramisu/option/__init__.py new file mode 100644 index 0000000..1a325f8 --- /dev/null +++ b/tiramisu/option/__init__.py @@ -0,0 +1,16 @@ +from .masterslave import MasterSlaves +from .optiondescription import OptionDescription +from .baseoption import Option, SymLinkOption +from .option import (ChoiceOption, BoolOption, IntOption, FloatOption, + StrOption, UnicodeOption, IPOption, PortOption, + NetworkOption, NetmaskOption, BroadcastOption, + DomainnameOption, EmailOption, URLOption, UsernameOption, + FilenameOption) + + +__all__ = (MasterSlaves, OptionDescription, Option, SymLinkOption, + ChoiceOption, BoolOption, IntOption, FloatOption, + StrOption, UnicodeOption, IPOption, PortOption, + NetworkOption, NetmaskOption, BroadcastOption, + DomainnameOption, EmailOption, URLOption, UsernameOption, + FilenameOption) diff --git a/tiramisu/option.py b/tiramisu/option/baseoption.py similarity index 51% rename from tiramisu/option.py rename to tiramisu/option/baseoption.py index 820908e..1e62e8f 100644 --- a/tiramisu/option.py +++ b/tiramisu/option/baseoption.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -"option types and option description" -# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors) # # 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 @@ -20,16 +19,14 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ import re -import sys from copy import copy, deepcopy from types import FunctionType -from IPy import IP import warnings -from tiramisu.error import ConfigError, ConflictError, ValueWarning -from tiramisu.setting import groups, multitypes, log from tiramisu.i18n import _ +from tiramisu.setting import log from tiramisu.autolib import carry_out_calculation +from tiramisu.error import ConfigError, ValueWarning name_regexp = re.compile(r'^\d+') forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', @@ -48,6 +45,46 @@ def valid_name(name): return True else: return False + + +def validate_callback(callback, callback_params, type_): + if type(callback) != FunctionType: + raise ValueError(_('{0} must be a function').format(type_)) + if callback_params is not None: + if not isinstance(callback_params, dict): + raise ValueError(_('{0}_params must be a dict').format(type_)) + for key, callbacks in callback_params.items(): + if key != '' and len(callbacks) != 1: + raise ValueError(_("{0}_params with key {1} mustn't have " + "length different to 1").format(type_, + key)) + if not isinstance(callbacks, tuple): + raise ValueError(_('{0}_params must be tuple for key "{1}"' + ).format(type_, key)) + for callbk in callbacks: + if isinstance(callbk, tuple): + if len(callbk) == 1: + if callbk != (None,): + raise ValueError(_('{0}_params with length of ' + 'tuple as 1 must only have ' + 'None as first value')) + elif len(callbk) != 2: + raise ValueError(_('{0}_params must only have 1 or 2 ' + 'as length')) + else: + option, force_permissive = callbk + if type_ == 'validator' and not force_permissive: + raise ValueError(_('validator not support tuple')) + if not isinstance(option, Option) and not \ + isinstance(option, SymLinkOption): + raise ValueError(_('{0}_params must have an option' + ' not a {0} for first argument' + ).format(type_, type(option))) + if force_permissive not in [True, False]: + raise ValueError(_('{0}_params must have a boolean' + ' not a {0} for second argument' + ).format(type_, type( + force_permissive))) #____________________________________________________________ # @@ -264,9 +301,9 @@ class Option(BaseOption): Reminder: an Option object is **not** a container for the value. """ __slots__ = ('_multi', '_validator', '_default_multi', '_default', - '_state_callback', '_callback', '_multitype', - '_consistencies', '_warnings_only', '_master_slaves', - '_state_consistencies', '__weakref__') + '_state_callback', '_callback', '_consistencies', + '_warnings_only', '_master_slaves', '_state_consistencies', + '__weakref__') _empty = '' def __init__(self, name, doc, default=None, default_multi=None, @@ -327,7 +364,6 @@ class Option(BaseOption): if self._multi: if default is None: default = [] - self._multitype = multitypes.default self._default_multi = default_multi self._warnings_only = warnings_only self.impl_validate(default) @@ -390,9 +426,9 @@ class Option(BaseOption): :type context: :class:`tiramisu.config.Config` :param validate: if true enables ``self._validator`` validation :type validate: boolean - :param force_no_multi: if multi, value has to be a list - not if force_no_multi is True - :type force_no_multi: boolean + :param force_index: if multi, value has to be a list + not if force_index is not None + :type force_index: integer """ if not validate: return @@ -421,6 +457,8 @@ class Option(BaseOption): try: self._validate(_value) except ValueError as err: + log.debug('do_validation: value: {0} index: {1}'.format( + _value, _index), exc_info=True) raise ValueError(_('invalid value for option {0}: {1}' '').format(self._name, err)) error = None @@ -476,14 +514,28 @@ class Option(BaseOption): def impl_getdefault(self): "accessing the default value" + if isinstance(self._default, list): + return copy(self._default) return self._default def impl_getdefault_multi(self): "accessing the default value for a multi" return self._default_multi - def impl_get_multitype(self): - return self._multitype + def impl_is_master_slaves(self, type_='both'): + """FIXME + """ + try: + self._master_slaves + if type_ in ('both', 'master') and \ + self._master_slaves.is_master(self): + return True + if type_ in ('both', 'slave') and \ + not self._master_slaves.is_master(self): + return True + except: + pass + return False def impl_get_master_slaves(self): return self._master_slaves @@ -632,805 +684,6 @@ class Option(BaseOption): pass -class ChoiceOption(Option): - """represents a choice out of several objects. - - The option can also have the value ``None`` - """ - - __slots__ = ('_values', '_open_values') - _opt_type = 'string' - - def __init__(self, name, doc, values, default=None, default_multi=None, - requires=None, multi=False, callback=None, - callback_params=None, open_values=False, validator=None, - validator_params=None, properties=None, warnings_only=False): - """ - :param values: is a list of values the option can possibly take - """ - if not isinstance(values, tuple): - raise TypeError(_('values must be a tuple for {0}').format(name)) - self._values = values - if open_values not in (True, False): - raise TypeError(_('open_values must be a boolean for ' - '{0}').format(name)) - self._open_values = open_values - super(ChoiceOption, self).__init__(name, doc, default=default, - default_multi=default_multi, - callback=callback, - callback_params=callback_params, - requires=requires, - multi=multi, - validator=validator, - validator_params=validator_params, - properties=properties, - warnings_only=warnings_only) - - def impl_get_values(self): - return self._values - - def impl_is_openvalues(self): - return self._open_values - - def _validate(self, value): - if not self.impl_is_openvalues() and not value in self.impl_get_values(): - raise ValueError(_('value {0} is not permitted, ' - 'only {1} is allowed' - '').format(value, self._values)) - - -class BoolOption(Option): - "represents a choice between ``True`` and ``False``" - __slots__ = tuple() - _opt_type = 'bool' - - def _validate(self, value): - if not isinstance(value, bool): - raise ValueError(_('invalid boolean')) - - -class IntOption(Option): - "represents a choice of an integer" - __slots__ = tuple() - _opt_type = 'int' - - def _validate(self, value): - if not isinstance(value, int): - raise ValueError(_('invalid integer')) - - -class FloatOption(Option): - "represents a choice of a floating point number" - __slots__ = tuple() - _opt_type = 'float' - - def _validate(self, value): - if not isinstance(value, float): - raise ValueError(_('invalid float')) - - -class StrOption(Option): - "represents the choice of a string" - __slots__ = tuple() - _opt_type = 'string' - - def _validate(self, value): - if not isinstance(value, str): - raise ValueError(_('invalid string')) - - -if sys.version_info[0] >= 3: - #UnicodeOption is same as StrOption in python 3+ - class UnicodeOption(StrOption): - __slots__ = tuple() - pass -else: - class UnicodeOption(Option): - "represents the choice of a unicode string" - __slots__ = tuple() - _opt_type = 'unicode' - _empty = u'' - - def _validate(self, value): - if not isinstance(value, unicode): - raise ValueError(_('invalid unicode')) - - -class SymLinkOption(BaseOption): - __slots__ = ('_name', '_opt', '_state_opt') - _opt_type = 'symlink' - #not return _opt consistencies - _consistencies = None - - def __init__(self, name, opt): - self._name = name - if not isinstance(opt, Option): - raise ValueError(_('malformed symlinkoption ' - 'must be an option ' - 'for symlink {0}').format(name)) - self._opt = opt - self._readonly = True - - def __getattr__(self, name): - if name in ('_name', '_opt', '_opt_type', '_readonly'): - return object.__getattr__(self, name) - else: - return getattr(self._opt, name) - - def _impl_getstate(self, descr): - super(SymLinkOption, self)._impl_getstate(descr) - self._state_opt = descr.impl_get_path_by_opt(self._opt) - - def _impl_setstate(self, descr): - self._opt = descr.impl_get_opt_by_path(self._state_opt) - del(self._state_opt) - super(SymLinkOption, self)._impl_setstate(descr) - - -class IPOption(Option): - "represents the choice of an ip" - __slots__ = ('_private_only', '_allow_reserved') - _opt_type = 'ip' - - def __init__(self, name, doc, default=None, default_multi=None, - requires=None, multi=False, callback=None, - callback_params=None, validator=None, validator_params=None, - properties=None, private_only=False, allow_reserved=False, - warnings_only=False): - self._private_only = private_only - self._allow_reserved = allow_reserved - super(IPOption, self).__init__(name, doc, default=default, - default_multi=default_multi, - callback=callback, - callback_params=callback_params, - requires=requires, - multi=multi, - validator=validator, - validator_params=validator_params, - properties=properties, - warnings_only=warnings_only) - - def _validate(self, value): - # sometimes an ip term starts with a zero - # but this does not fit in some case, for example bind does not like it - try: - for val in value.split('.'): - if val.startswith("0") and len(val) > 1: - raise ValueError(_('invalid IP')) - except AttributeError: - #if integer for example - raise ValueError(_('invalid IP')) - # 'standard' validation - try: - IP('{0}/32'.format(value)) - except ValueError: - raise ValueError(_('invalid IP')) - - def _second_level_validation(self, value, warnings_only): - ip = IP('{0}/32'.format(value)) - if not self._allow_reserved and ip.iptype() == 'RESERVED': - if warnings_only: - msg = _("IP is in reserved class") - else: - msg = _("invalid IP, mustn't be in reserved class") - raise ValueError(msg) - if self._private_only and not ip.iptype() == 'PRIVATE': - if warnings_only: - msg = _("IP is not in private class") - else: - msg = _("invalid IP, must be in private class") - raise ValueError(msg) - - def _cons_in_network(self, opts, vals, warnings_only): - if len(vals) != 3: - raise ConfigError(_('invalid len for vals')) - if None in vals: - return - ip, network, netmask = vals - if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): - if warnings_only: - msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}' - ' ({5})') - else: - msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with ' - 'netmask {4} ({5})') - raise ValueError(msg.format(ip, opts[0]._name, network, - opts[1]._name, netmask, opts[2]._name)) - - -class PortOption(Option): - """represents the choice of a port - The port numbers are divided into three ranges: - the well-known ports, - the registered ports, - and the dynamic or private ports. - You can actived this three range. - Port number 0 is reserved and can't be used. - see: http://en.wikipedia.org/wiki/Port_numbers - """ - __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value') - _opt_type = 'port' - - def __init__(self, name, doc, default=None, default_multi=None, - requires=None, multi=False, callback=None, - callback_params=None, validator=None, validator_params=None, - properties=None, allow_range=False, allow_zero=False, - allow_wellknown=True, allow_registred=True, - allow_private=False, warnings_only=False): - self._allow_range = allow_range - self._min_value = None - self._max_value = None - ports_min = [0, 1, 1024, 49152] - ports_max = [0, 1023, 49151, 65535] - is_finally = False - for index, allowed in enumerate([allow_zero, - allow_wellknown, - allow_registred, - allow_private]): - if self._min_value is None: - if allowed: - self._min_value = ports_min[index] - elif not allowed: - is_finally = True - elif allowed and is_finally: - raise ValueError(_('inconsistency in allowed range')) - if allowed: - self._max_value = ports_max[index] - - if self._max_value is None: - raise ValueError(_('max value is empty')) - - super(PortOption, self).__init__(name, doc, default=default, - default_multi=default_multi, - callback=callback, - callback_params=callback_params, - requires=requires, - multi=multi, - validator=validator, - validator_params=validator_params, - properties=properties, - warnings_only=warnings_only) - - def _validate(self, value): - if self._allow_range and ":" in str(value): - value = str(value).split(':') - if len(value) != 2: - raise ValueError(_('invalid port, range must have two values ' - 'only')) - if not value[0] < value[1]: - raise ValueError(_('invalid port, first port in range must be' - ' smaller than the second one')) - else: - value = [value] - - for val in value: - try: - int(val) - except ValueError: - raise ValueError(_('invalid port')) - if not self._min_value <= int(val) <= self._max_value: - raise ValueError(_('invalid port, must be an between {0} ' - 'and {1}').format(self._min_value, - self._max_value)) - - -class NetworkOption(Option): - "represents the choice of a network" - __slots__ = tuple() - _opt_type = 'network' - - def _validate(self, value): - try: - IP(value) - except ValueError: - raise ValueError(_('invalid network address')) - - def _second_level_validation(self, value, warnings_only): - ip = IP(value) - if ip.iptype() == 'RESERVED': - if warnings_only: - msg = _("network address is in reserved class") - else: - msg = _("invalid network address, mustn't be in reserved class") - raise ValueError(msg) - - -class NetmaskOption(Option): - "represents the choice of a netmask" - __slots__ = tuple() - _opt_type = 'netmask' - - def _validate(self, value): - try: - IP('0.0.0.0/{0}'.format(value)) - except ValueError: - raise ValueError(_('invalid netmask address')) - - def _cons_network_netmask(self, opts, vals, warnings_only): - #opts must be (netmask, network) options - if None in vals: - return - self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only) - - def _cons_ip_netmask(self, opts, vals, warnings_only): - #opts must be (netmask, ip) options - if None in vals: - return - self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only) - - def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net, - warnings_only): - if len(opts) != 2: - raise ConfigError(_('invalid len for opts')) - msg = None - try: - ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), - make_net=make_net) - #if cidr == 32, ip same has network - if ip.prefixlen() != 32: - try: - IP('{0}/{1}'.format(val_ipnetwork, val_netmask), - make_net=not make_net) - except ValueError: - pass - else: - if make_net: - msg = _("invalid IP {0} ({1}) with netmask {2}," - " this IP is a network") - - except ValueError: - if not make_net: - msg = _('invalid network {0} ({1}) with netmask {2}') - if msg is not None: - raise ValueError(msg.format(val_ipnetwork, opts[1]._name, - val_netmask)) - - -class BroadcastOption(Option): - __slots__ = tuple() - _opt_type = 'broadcast' - - def _validate(self, value): - try: - IP('{0}/32'.format(value)) - except ValueError: - raise ValueError(_('invalid broadcast address')) - - def _cons_broadcast(self, opts, vals, warnings_only): - if len(vals) != 3: - raise ConfigError(_('invalid len for vals')) - if None in vals: - return - broadcast, network, netmask = vals - if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast): - raise ValueError(_('invalid broadcast {0} ({1}) with network {2} ' - '({3}) and netmask {4} ({5})').format( - broadcast, opts[0]._name, network, - opts[1]._name, netmask, opts[2]._name)) - - -class DomainnameOption(Option): - """represents the choice of a domain name - netbios: for MS domain - hostname: to identify the device - domainname: - fqdn: with tld, not supported yet - """ - __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re') - _opt_type = 'domainname' - - def __init__(self, name, doc, default=None, default_multi=None, - requires=None, multi=False, callback=None, - callback_params=None, validator=None, validator_params=None, - properties=None, allow_ip=False, type_='domainname', - warnings_only=False, allow_without_dot=False): - if type_ not in ['netbios', 'hostname', 'domainname']: - raise ValueError(_('unknown type_ {0} for hostname').format(type_)) - self._type = type_ - if allow_ip not in [True, False]: - raise ValueError(_('allow_ip must be a boolean')) - if allow_without_dot not in [True, False]: - raise ValueError(_('allow_without_dot must be a boolean')) - self._allow_ip = allow_ip - self._allow_without_dot = allow_without_dot - end = '' - extrachar = '' - extrachar_mandatory = '' - if self._type != 'netbios': - allow_number = '\d' - else: - allow_number = '' - if self._type == 'netbios': - length = 14 - elif self._type == 'hostname': - length = 62 - elif self._type == 'domainname': - length = 62 - if allow_without_dot is False: - extrachar_mandatory = '\.' - else: - extrachar = '\.' - end = '+[a-z]*' - self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$' - ''.format(allow_number, extrachar, length, - extrachar_mandatory, end)) - super(DomainnameOption, self).__init__(name, doc, default=default, - default_multi=default_multi, - callback=callback, - callback_params=callback_params, - requires=requires, - multi=multi, - validator=validator, - validator_params=validator_params, - properties=properties, - warnings_only=warnings_only) - - def _validate(self, value): - if self._allow_ip is True: - try: - IP('{0}/32'.format(value)) - return - except ValueError: - pass - if self._type == 'domainname' and not self._allow_without_dot and \ - '.' not in value: - raise ValueError(_("invalid domainname, must have dot")) - if len(value) > 255: - raise ValueError(_("invalid domainname's length (max 255)")) - if len(value) < 2: - raise ValueError(_("invalid domainname's length (min 2)")) - if not self._domain_re.search(value): - raise ValueError(_('invalid domainname')) - - -class EmailOption(DomainnameOption): - __slots__ = tuple() - _opt_type = 'email' - username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$") - - def _validate(self, value): - splitted = value.split('@', 1) - try: - username, domain = splitted - except ValueError: - raise ValueError(_('invalid email address, must contains one @' - )) - if not self.username_re.search(username): - raise ValueError(_('invalid username in email address')) - super(EmailOption, self)._validate(domain) - - -class URLOption(DomainnameOption): - __slots__ = tuple() - _opt_type = 'url' - proto_re = re.compile(r'(http|https)://') - path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") - - def _validate(self, value): - match = self.proto_re.search(value) - if not match: - raise ValueError(_('invalid url, must start with http:// or ' - 'https://')) - value = value[len(match.group(0)):] - # get domain/files - splitted = value.split('/', 1) - try: - domain, files = splitted - except ValueError: - domain = value - files = None - # if port in domain - splitted = domain.split(':', 1) - try: - domain, port = splitted - - except ValueError: - domain = splitted[0] - port = 0 - if not 0 <= int(port) <= 65535: - raise ValueError(_('invalid url, port must be an between 0 and ' - '65536')) - # validate domainname - super(URLOption, self)._validate(domain) - # validate file - if files is not None and files != '' and not self.path_re.search(files): - raise ValueError(_('invalid url, must ends with filename')) - - -class UsernameOption(Option): - __slots__ = tuple() - _opt_type = 'username' - #regexp build with 'man 8 adduser' informations - username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") - - def _validate(self, value): - match = self.username_re.search(value) - if not match: - raise ValueError(_('invalid username')) - - -class FilenameOption(Option): - __slots__ = tuple() - _opt_type = 'file' - path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") - - def _validate(self, value): - match = self.path_re.search(value) - if not match: - raise ValueError(_('invalid filename')) - - -class OptionDescription(BaseOption): - """Config's schema (organisation, group) and container of Options - The `OptionsDescription` objects lives in the `tiramisu.config.Config`. - """ - __slots__ = ('_name', '_requires', '_cache_paths', '_group_type', - '_state_group_type', '_properties', '_children', - '_cache_consistencies', '_calc_properties', '__weakref__', - '_readonly', '_impl_informations', '_state_requires', - '_stated', '_state_readonly') - _opt_type = 'optiondescription' - - def __init__(self, name, doc, children, requires=None, properties=None): - """ - :param children: a list of options (including optiondescriptions) - - """ - super(OptionDescription, self).__init__(name, doc, requires, properties) - child_names = [child._name for child in children] - #better performance like this - valid_child = copy(child_names) - valid_child.sort() - old = None - for child in valid_child: - if child == old: - raise ConflictError(_('duplicate option name: ' - '{0}').format(child)) - old = child - self._children = (tuple(child_names), tuple(children)) - self._cache_paths = None - self._cache_consistencies = None - # the group_type is useful for filtering OptionDescriptions in a config - self._group_type = groups.default - - def impl_getdoc(self): - return self.impl_get_information('doc') - - def __getattr__(self, name): - if name in self.__slots__: - return object.__getattribute__(self, name) - try: - return self._children[1][self._children[0].index(name)] - except ValueError: - raise AttributeError(_('unknown Option {0} ' - 'in OptionDescription {1}' - '').format(name, self._name)) - - def impl_getkey(self, config): - return tuple([child.impl_getkey(getattr(config, child._name)) - for child in self.impl_getchildren()]) - - def impl_getpaths(self, include_groups=False, _currpath=None): - """returns a list of all paths in self, recursively - _currpath should not be provided (helps with recursion) - """ - if _currpath is None: - _currpath = [] - paths = [] - for option in self.impl_getchildren(): - attr = option._name - 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_getchildren(self): - return self._children[1] - - def impl_build_cache(self, - cache_path=None, - cache_option=None, - _currpath=None, - _consistencies=None, - force_no_consistencies=False): - if _currpath is None and self._cache_paths is not None: - # cache already set - return - if _currpath is None: - save = True - _currpath = [] - if not force_no_consistencies: - _consistencies = {} - else: - save = False - if cache_path is None: - cache_path = [] - cache_option = [] - for option in self.impl_getchildren(): - attr = option._name - if option in cache_option: - raise ConflictError(_('duplicate option: {0}').format(option)) - - cache_option.append(option) - if not force_no_consistencies: - option._readonly = True - cache_path.append(str('.'.join(_currpath + [attr]))) - if not isinstance(option, OptionDescription): - if not force_no_consistencies and \ - option._consistencies is not None: - for consistency in option._consistencies: - func, all_cons_opts, params = consistency - for opt in all_cons_opts: - _consistencies.setdefault(opt, - []).append((func, - all_cons_opts, - params)) - else: - _currpath.append(attr) - option.impl_build_cache(cache_path, - cache_option, - _currpath, - _consistencies, - force_no_consistencies) - _currpath.pop() - if save: - self._cache_paths = (tuple(cache_option), tuple(cache_path)) - if not force_no_consistencies: - if _consistencies != {}: - self._cache_consistencies = {} - for opt, cons in _consistencies.items(): - if opt not in cache_option: - raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name)) - self._cache_consistencies[opt] = tuple(cons) - self._readonly = True - - def impl_get_opt_by_path(self, path): - try: - return self._cache_paths[0][self._cache_paths[1].index(path)] - except ValueError: - raise AttributeError(_('no option for path {0}').format(path)) - - def impl_get_path_by_opt(self, opt): - try: - return self._cache_paths[1][self._cache_paths[0].index(opt)] - except ValueError: - raise AttributeError(_('no option {0} found').format(opt)) - - # ____________________________________________________________ - def impl_set_group_type(self, group_type): - """sets a given group object to an OptionDescription - - :param group_type: an instance of `GroupType` or `MasterGroupType` - that lives in `setting.groups` - """ - if self._group_type != groups.default: - raise TypeError(_('cannot change group_type if already set ' - '(old {0}, new {1})').format(self._group_type, - group_type)) - if isinstance(group_type, groups.GroupType): - self._group_type = group_type - if isinstance(group_type, groups.MasterGroupType): - #if master (same name has group) is set - #for collect all slaves - slaves = [] - master = None - for child in self.impl_getchildren(): - if isinstance(child, OptionDescription): - raise ValueError(_("master group {0} shall not have " - "a subgroup").format(self._name)) - if isinstance(child, SymLinkOption): - raise ValueError(_("master group {0} shall not have " - "a symlinkoption").format(self._name)) - if not child.impl_is_multi(): - raise ValueError(_("not allowed option {0} " - "in group {1}" - ": this option is not a multi" - "").format(child._name, self._name)) - if child._name == self._name: - child._multitype = multitypes.master - master = child - else: - slaves.append(child) - if master is None: - raise ValueError(_('master group with wrong' - ' master name for {0}' - ).format(self._name)) - if master._callback is not None and master._callback[1] is not None: - for key, callbacks in master._callback[1].items(): - for callbk in callbacks: - if isinstance(callbk, tuple): - if callbk[0] in slaves: - raise ValueError(_("callback of master's option shall " - "not refered a slave's ones")) - master._master_slaves = tuple(slaves) - for child in self.impl_getchildren(): - if child != master: - child._master_slaves = master - child._multitype = multitypes.slave - else: - raise ValueError(_('group_type: {0}' - ' not allowed').format(group_type)) - - def impl_get_group_type(self): - return self._group_type - - def _valid_consistency(self, option, value, context, index): - if self._cache_consistencies is None: - return True - #consistencies is something like [('_cons_not_equal', (opt1, opt2))] - consistencies = self._cache_consistencies.get(option) - if consistencies is not None: - for func, all_cons_opts, params in consistencies: - warnings_only = params.get('warnings_only', False) - #all_cons_opts[0] is the option where func is set - try: - all_cons_opts[0]._launch_consistency(func, option, - value, - context, index, - all_cons_opts, - warnings_only) - except ValueError as err: - if warnings_only: - raise ValueWarning(err.message, option) - else: - raise err - - def _impl_getstate(self, descr=None): - """enables us to export into a dict - :param descr: parent :class:`tiramisu.option.OptionDescription` - """ - if descr is None: - self.impl_build_cache() - descr = self - super(OptionDescription, self)._impl_getstate(descr) - self._state_group_type = str(self._group_type) - for option in self.impl_getchildren(): - option._impl_getstate(descr) - - def __getstate__(self): - """special method to enable the serialization with pickle - """ - stated = True - try: - # the `_state` attribute is a flag that which tells us if - # the serialization can be performed - self._stated - except AttributeError: - # if cannot delete, _impl_getstate never launch - # launch it recursivement - # _stated prevent __getstate__ launch more than one time - # _stated is delete, if re-serialize, re-lauch _impl_getstate - self._impl_getstate() - stated = False - return super(OptionDescription, self).__getstate__(stated) - - def _impl_setstate(self, descr=None): - """enables us to import from a dict - :param descr: parent :class:`tiramisu.option.OptionDescription` - """ - if descr is None: - self._cache_paths = None - self._cache_consistencies = None - self.impl_build_cache(force_no_consistencies=True) - descr = self - self._group_type = getattr(groups, self._state_group_type) - del(self._state_group_type) - super(OptionDescription, self)._impl_setstate(descr) - for option in self.impl_getchildren(): - option._impl_setstate(descr) - - def __setstate__(self, state): - super(OptionDescription, self).__setstate__(state) - try: - self._stated - except AttributeError: - self._impl_setstate() - - def validate_requires_arg(requires, name): """check malformed requirements and tranform dict to internal tuple @@ -1524,41 +777,32 @@ def validate_requires_arg(requires, name): return frozenset(config_action.keys()), tuple(ret) -def validate_callback(callback, callback_params, type_): - if type(callback) != FunctionType: - raise ValueError(_('{0} must be a function').format(type_)) - if callback_params is not None: - if not isinstance(callback_params, dict): - raise ValueError(_('{0}_params must be a dict').format(type_)) - for key, callbacks in callback_params.items(): - if key != '' and len(callbacks) != 1: - raise ValueError(_("{0}_params with key {1} mustn't have " - "length different to 1").format(type_, - key)) - if not isinstance(callbacks, tuple): - raise ValueError(_('{0}_params must be tuple for key "{1}"' - ).format(type_, key)) - for callbk in callbacks: - if isinstance(callbk, tuple): - if len(callbk) == 1: - if callbk != (None,): - raise ValueError(_('{0}_params with length of ' - 'tuple as 1 must only have ' - 'None as first value')) - elif len(callbk) != 2: - raise ValueError(_('{0}_params must only have 1 or 2 ' - 'as length')) - else: - option, force_permissive = callbk - if type_ == 'validator' and not force_permissive: - raise ValueError(_('validator not support tuple')) - if not isinstance(option, Option) and not \ - isinstance(option, SymLinkOption): - raise ValueError(_('{0}_params must have an option' - ' not a {0} for first argument' - ).format(type_, type(option))) - if force_permissive not in [True, False]: - raise ValueError(_('{0}_params must have a boolean' - ' not a {0} for second argument' - ).format(type_, type( - force_permissive))) +class SymLinkOption(BaseOption): + __slots__ = ('_name', '_opt', '_state_opt') + _opt_type = 'symlink' + #not return _opt consistencies + _consistencies = None + + def __init__(self, name, opt): + self._name = name + if not isinstance(opt, Option): + raise ValueError(_('malformed symlinkoption ' + 'must be an option ' + 'for symlink {0}').format(name)) + self._opt = opt + self._readonly = True + + def __getattr__(self, name): + if name in ('_name', '_opt', '_opt_type', '_readonly'): + return object.__getattr__(self, name) + else: + return getattr(self._opt, name) + + def _impl_getstate(self, descr): + super(SymLinkOption, self)._impl_getstate(descr) + self._state_opt = descr.impl_get_path_by_opt(self._opt) + + def _impl_setstate(self, descr): + self._opt = descr.impl_get_opt_by_path(self._state_opt) + del(self._state_opt) + super(SymLinkOption, self)._impl_setstate(descr) diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py new file mode 100644 index 0000000..9e0426d --- /dev/null +++ b/tiramisu/option/masterslave.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +"master slave support" +# Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors) +# +# 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 +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# The original `Config` design model is unproudly borrowed from +# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ +# the whole pypy projet is under MIT licence +# ____________________________________________________________ +from tiramisu.i18n import _ +from tiramisu.setting import log +from tiramisu.error import SlaveError, ConfigError +from .baseoption import SymLinkOption, Option + + +class MasterSlaves(object): + __slots__ = ('master', 'slaves') + + def __init__(self, name, childs): + #if master (same name has group) is set + #for collect all slaves + self.master = None + slaves = [] + for child in childs: + if isinstance(child, SymLinkOption): + raise ValueError(_("master group {0} shall not have " + "a symlinkoption").format(name)) + if not isinstance(child, Option): + raise ValueError(_("master group {0} shall not have " + "a subgroup").format(name)) + if not child.impl_is_multi(): + raise ValueError(_("not allowed option {0} " + "in group {1}" + ": this option is not a multi" + "").format(child._name, name)) + if child._name == name: + self.master = child + else: + slaves.append(child) + if self.master is None: + raise ValueError(_('master group with wrong' + ' master name for {0}' + ).format(name)) + if self.master._callback is not None and self.master._callback[1] is not None: + for key, callbacks in self.master._callback[1].items(): + for callbk in callbacks: + if isinstance(callbk, tuple): + if callbk[0] in slaves: + raise ValueError(_("callback of master's option shall " + "not refered a slave's ones")) + #everything is ok, store references + self.slaves = tuple(slaves) + for child in childs: + child._master_slaves = self + + def is_master(self, opt): + return opt == self.master + + def in_same_group(self, opt): + return opt == self.master or opt in self.slaves + + def reset(self, values): + for slave in self.slaves: + values.reset(slave) + + def pop(self, values, index): + #FIXME pas test de meta ... + for slave in self.slaves: + if not values.is_default_owner(slave, validate_properties=False, + validate_meta=False): + values._get_cached_item(slave, validate=False, + validate_properties=False + ).pop(index, force=True) + pass + + def getitem(self, values, opt, path, validate, force_permissive, + force_properties, validate_properties): + if opt == self.master: + value = values._get_validated_value(opt, path, validate, + force_permissive, + force_properties, + validate_properties) + if validate is True: + masterlen = len(value) + for slave in self.slaves: + try: + slave_path = values._get_opt_path(slave) + slave_value = values._get_validated_value(slave, + slave_path, + False, + False, + None, False, + None) # not undefined + slavelen = len(slave_value) + self.validate_slave_length(masterlen, slavelen, slave._name) + except ConfigError: + pass + return value + else: + value = values._get_validated_value(opt, path, validate, + force_permissive, + force_properties, + validate_properties, + None) # not undefined + return self.get_slave_value(values, opt, value, validate, validate_properties) + + def setitem(self, values, opt, value, path): + if opt == self.master: + masterlen = len(value) + for slave in self.slaves: + slave_path = values._get_opt_path(slave) + slave_value = values._get_validated_value(slave, + slave_path, + False, + False, + None, False, + None) # not undefined + slavelen = len(slave_value) + self.validate_slave_length(masterlen, slavelen, slave._name) + else: + self.validate_slave_length(self.get_length(values), len(value), + opt._name, setitem=True) + + def get_length(self, values, validate=True): + masterp = values._get_opt_path(self.master) + return len(self.getitem(values, self.master, masterp, validate, False, + None, True)) + + def validate_slave_length(self, masterlen, valuelen, name, setitem=False): + if valuelen > masterlen or (valuelen < masterlen and setitem): + log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, ' + 'setitem: {2}'.format(masterlen, valuelen, setitem)) + raise SlaveError(_("invalid len for the slave: {0}" + " which has {1} as master").format( + name, self.master._name)) + + def get_slave_value(self, values, opt, value, validate=True, + validate_properties=True): + """ + if master has length 0: + return [] + if master has length bigger than 0: + if default owner: + if has callback: + if return a list: + list same length as master: return list + list is smaller than master: return list + None + list is greater than master: raise SlaveError + if has default value: + list same length as master: return list + list is smaller than master: return list + None + list is greater than master: raise SlaveError + if has default_multi value: + return default_multi * master's length + if has value: + list same length as master: return list + list is smaller than master: return list + None + list is greater than master: raise SlaveError + """ + #if slave, had values until master's one + masterlen = self.get_length(values, validate) + valuelen = len(value) + if validate: + self.validate_slave_length(masterlen, valuelen, opt._name) + path = values._get_opt_path(opt) + if valuelen < masterlen: + for num in range(0, masterlen - valuelen): + index = valuelen + num + value.append(values._get_validated_value(opt, path, True, + False, None, + validate_properties, + index=index), + setitem=False, + force=True) + return value diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py new file mode 100644 index 0000000..8a0cda6 --- /dev/null +++ b/tiramisu/option/option.py @@ -0,0 +1,525 @@ +# -*- coding: utf-8 -*- +"option types and option description" +# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) +# +# 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 +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# The original `Config` design model is unproudly borrowed from +# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ +# the whole pypy projet is under MIT licence +# ____________________________________________________________ +import re +import sys +from IPy import IP + +from tiramisu.error import ConfigError +from tiramisu.i18n import _ +from .baseoption import Option + + +class ChoiceOption(Option): + """represents a choice out of several objects. + + The option can also have the value ``None`` + """ + + __slots__ = ('_values', '_open_values') + _opt_type = 'string' + + def __init__(self, name, doc, values, default=None, default_multi=None, + requires=None, multi=False, callback=None, + callback_params=None, open_values=False, validator=None, + validator_params=None, properties=None, warnings_only=False): + """ + :param values: is a list of values the option can possibly take + """ + if not isinstance(values, tuple): + raise TypeError(_('values must be a tuple for {0}').format(name)) + self._values = values + if open_values not in (True, False): + raise TypeError(_('open_values must be a boolean for ' + '{0}').format(name)) + self._open_values = open_values + super(ChoiceOption, self).__init__(name, doc, default=default, + default_multi=default_multi, + callback=callback, + callback_params=callback_params, + requires=requires, + multi=multi, + validator=validator, + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) + + def impl_get_values(self): + return self._values + + def impl_is_openvalues(self): + return self._open_values + + def _validate(self, value): + if not self.impl_is_openvalues() and not value in self.impl_get_values(): + raise ValueError(_('value {0} is not permitted, ' + 'only {1} is allowed' + '').format(value, self._values)) + + +class BoolOption(Option): + "represents a choice between ``True`` and ``False``" + __slots__ = tuple() + _opt_type = 'bool' + + def _validate(self, value): + if not isinstance(value, bool): + raise ValueError(_('invalid boolean')) + + +class IntOption(Option): + "represents a choice of an integer" + __slots__ = tuple() + _opt_type = 'int' + + def _validate(self, value): + if not isinstance(value, int): + raise ValueError(_('invalid integer')) + + +class FloatOption(Option): + "represents a choice of a floating point number" + __slots__ = tuple() + _opt_type = 'float' + + def _validate(self, value): + if not isinstance(value, float): + raise ValueError(_('invalid float')) + + +class StrOption(Option): + "represents the choice of a string" + __slots__ = tuple() + _opt_type = 'string' + + def _validate(self, value): + if not isinstance(value, str): + raise ValueError(_('invalid string')) + + +if sys.version_info[0] >= 3: + #UnicodeOption is same as StrOption in python 3+ + class UnicodeOption(StrOption): + __slots__ = tuple() + pass +else: + class UnicodeOption(Option): + "represents the choice of a unicode string" + __slots__ = tuple() + _opt_type = 'unicode' + _empty = u'' + + def _validate(self, value): + if not isinstance(value, unicode): + raise ValueError(_('invalid unicode')) + + +class IPOption(Option): + "represents the choice of an ip" + __slots__ = ('_private_only', '_allow_reserved') + _opt_type = 'ip' + + def __init__(self, name, doc, default=None, default_multi=None, + requires=None, multi=False, callback=None, + callback_params=None, validator=None, validator_params=None, + properties=None, private_only=False, allow_reserved=False, + warnings_only=False): + self._private_only = private_only + self._allow_reserved = allow_reserved + super(IPOption, self).__init__(name, doc, default=default, + default_multi=default_multi, + callback=callback, + callback_params=callback_params, + requires=requires, + multi=multi, + validator=validator, + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) + + def _validate(self, value): + # sometimes an ip term starts with a zero + # but this does not fit in some case, for example bind does not like it + try: + for val in value.split('.'): + if val.startswith("0") and len(val) > 1: + raise ValueError(_('invalid IP')) + except AttributeError: + #if integer for example + raise ValueError(_('invalid IP')) + # 'standard' validation + try: + IP('{0}/32'.format(value)) + except ValueError: + raise ValueError(_('invalid IP')) + + def _second_level_validation(self, value, warnings_only): + ip = IP('{0}/32'.format(value)) + if not self._allow_reserved and ip.iptype() == 'RESERVED': + if warnings_only: + msg = _("IP is in reserved class") + else: + msg = _("invalid IP, mustn't be in reserved class") + raise ValueError(msg) + if self._private_only and not ip.iptype() == 'PRIVATE': + if warnings_only: + msg = _("IP is not in private class") + else: + msg = _("invalid IP, must be in private class") + raise ValueError(msg) + + def _cons_in_network(self, opts, vals, warnings_only): + if len(vals) != 3: + raise ConfigError(_('invalid len for vals')) + if None in vals: + return + ip, network, netmask = vals + if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): + if warnings_only: + msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}' + ' ({5})') + else: + msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with ' + 'netmask {4} ({5})') + raise ValueError(msg.format(ip, opts[0]._name, network, + opts[1]._name, netmask, opts[2]._name)) + + +class PortOption(Option): + """represents the choice of a port + The port numbers are divided into three ranges: + the well-known ports, + the registered ports, + and the dynamic or private ports. + You can actived this three range. + Port number 0 is reserved and can't be used. + see: http://en.wikipedia.org/wiki/Port_numbers + """ + __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value') + _opt_type = 'port' + + def __init__(self, name, doc, default=None, default_multi=None, + requires=None, multi=False, callback=None, + callback_params=None, validator=None, validator_params=None, + properties=None, allow_range=False, allow_zero=False, + allow_wellknown=True, allow_registred=True, + allow_private=False, warnings_only=False): + self._allow_range = allow_range + self._min_value = None + self._max_value = None + ports_min = [0, 1, 1024, 49152] + ports_max = [0, 1023, 49151, 65535] + is_finally = False + for index, allowed in enumerate([allow_zero, + allow_wellknown, + allow_registred, + allow_private]): + if self._min_value is None: + if allowed: + self._min_value = ports_min[index] + elif not allowed: + is_finally = True + elif allowed and is_finally: + raise ValueError(_('inconsistency in allowed range')) + if allowed: + self._max_value = ports_max[index] + + if self._max_value is None: + raise ValueError(_('max value is empty')) + + super(PortOption, self).__init__(name, doc, default=default, + default_multi=default_multi, + callback=callback, + callback_params=callback_params, + requires=requires, + multi=multi, + validator=validator, + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) + + def _validate(self, value): + if self._allow_range and ":" in str(value): + value = str(value).split(':') + if len(value) != 2: + raise ValueError(_('invalid port, range must have two values ' + 'only')) + if not value[0] < value[1]: + raise ValueError(_('invalid port, first port in range must be' + ' smaller than the second one')) + else: + value = [value] + + for val in value: + try: + int(val) + except ValueError: + raise ValueError(_('invalid port')) + if not self._min_value <= int(val) <= self._max_value: + raise ValueError(_('invalid port, must be an between {0} ' + 'and {1}').format(self._min_value, + self._max_value)) + + +class NetworkOption(Option): + "represents the choice of a network" + __slots__ = tuple() + _opt_type = 'network' + + def _validate(self, value): + try: + IP(value) + except ValueError: + raise ValueError(_('invalid network address')) + + def _second_level_validation(self, value, warnings_only): + ip = IP(value) + if ip.iptype() == 'RESERVED': + if warnings_only: + msg = _("network address is in reserved class") + else: + msg = _("invalid network address, mustn't be in reserved class") + raise ValueError(msg) + + +class NetmaskOption(Option): + "represents the choice of a netmask" + __slots__ = tuple() + _opt_type = 'netmask' + + def _validate(self, value): + try: + IP('0.0.0.0/{0}'.format(value)) + except ValueError: + raise ValueError(_('invalid netmask address')) + + def _cons_network_netmask(self, opts, vals, warnings_only): + #opts must be (netmask, network) options + if None in vals: + return + self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only) + + def _cons_ip_netmask(self, opts, vals, warnings_only): + #opts must be (netmask, ip) options + if None in vals: + return + self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only) + + def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net, + warnings_only): + if len(opts) != 2: + raise ConfigError(_('invalid len for opts')) + msg = None + try: + ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), + make_net=make_net) + #if cidr == 32, ip same has network + if ip.prefixlen() != 32: + try: + IP('{0}/{1}'.format(val_ipnetwork, val_netmask), + make_net=not make_net) + except ValueError: + pass + else: + if make_net: + msg = _("invalid IP {0} ({1}) with netmask {2}," + " this IP is a network") + + except ValueError: + if not make_net: + msg = _('invalid network {0} ({1}) with netmask {2}') + if msg is not None: + raise ValueError(msg.format(val_ipnetwork, opts[1]._name, + val_netmask)) + + +class BroadcastOption(Option): + __slots__ = tuple() + _opt_type = 'broadcast' + + def _validate(self, value): + try: + IP('{0}/32'.format(value)) + except ValueError: + raise ValueError(_('invalid broadcast address')) + + def _cons_broadcast(self, opts, vals, warnings_only): + if len(vals) != 3: + raise ConfigError(_('invalid len for vals')) + if None in vals: + return + broadcast, network, netmask = vals + if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast): + raise ValueError(_('invalid broadcast {0} ({1}) with network {2} ' + '({3}) and netmask {4} ({5})').format( + broadcast, opts[0]._name, network, + opts[1]._name, netmask, opts[2]._name)) + + +class DomainnameOption(Option): + """represents the choice of a domain name + netbios: for MS domain + hostname: to identify the device + domainname: + fqdn: with tld, not supported yet + """ + __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re') + _opt_type = 'domainname' + + def __init__(self, name, doc, default=None, default_multi=None, + requires=None, multi=False, callback=None, + callback_params=None, validator=None, validator_params=None, + properties=None, allow_ip=False, type_='domainname', + warnings_only=False, allow_without_dot=False): + if type_ not in ['netbios', 'hostname', 'domainname']: + raise ValueError(_('unknown type_ {0} for hostname').format(type_)) + self._type = type_ + if allow_ip not in [True, False]: + raise ValueError(_('allow_ip must be a boolean')) + if allow_without_dot not in [True, False]: + raise ValueError(_('allow_without_dot must be a boolean')) + self._allow_ip = allow_ip + self._allow_without_dot = allow_without_dot + end = '' + extrachar = '' + extrachar_mandatory = '' + if self._type != 'netbios': + allow_number = '\d' + else: + allow_number = '' + if self._type == 'netbios': + length = 14 + elif self._type == 'hostname': + length = 62 + elif self._type == 'domainname': + length = 62 + if allow_without_dot is False: + extrachar_mandatory = '\.' + else: + extrachar = '\.' + end = '+[a-z]*' + self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$' + ''.format(allow_number, extrachar, length, + extrachar_mandatory, end)) + super(DomainnameOption, self).__init__(name, doc, default=default, + default_multi=default_multi, + callback=callback, + callback_params=callback_params, + requires=requires, + multi=multi, + validator=validator, + validator_params=validator_params, + properties=properties, + warnings_only=warnings_only) + + def _validate(self, value): + if self._allow_ip is True: + try: + IP('{0}/32'.format(value)) + return + except ValueError: + pass + if self._type == 'domainname' and not self._allow_without_dot and \ + '.' not in value: + raise ValueError(_("invalid domainname, must have dot")) + if len(value) > 255: + raise ValueError(_("invalid domainname's length (max 255)")) + if len(value) < 2: + raise ValueError(_("invalid domainname's length (min 2)")) + if not self._domain_re.search(value): + raise ValueError(_('invalid domainname')) + + +class EmailOption(DomainnameOption): + __slots__ = tuple() + _opt_type = 'email' + username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$") + + def _validate(self, value): + splitted = value.split('@', 1) + try: + username, domain = splitted + except ValueError: + raise ValueError(_('invalid email address, must contains one @' + )) + if not self.username_re.search(username): + raise ValueError(_('invalid username in email address')) + super(EmailOption, self)._validate(domain) + + +class URLOption(DomainnameOption): + __slots__ = tuple() + _opt_type = 'url' + proto_re = re.compile(r'(http|https)://') + path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") + + def _validate(self, value): + match = self.proto_re.search(value) + if not match: + raise ValueError(_('invalid url, must start with http:// or ' + 'https://')) + value = value[len(match.group(0)):] + # get domain/files + splitted = value.split('/', 1) + try: + domain, files = splitted + except ValueError: + domain = value + files = None + # if port in domain + splitted = domain.split(':', 1) + try: + domain, port = splitted + + except ValueError: + domain = splitted[0] + port = 0 + if not 0 <= int(port) <= 65535: + raise ValueError(_('invalid url, port must be an between 0 and ' + '65536')) + # validate domainname + super(URLOption, self)._validate(domain) + # validate file + if files is not None and files != '' and not self.path_re.search(files): + raise ValueError(_('invalid url, must ends with filename')) + + +class UsernameOption(Option): + __slots__ = tuple() + _opt_type = 'username' + #regexp build with 'man 8 adduser' informations + username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") + + def _validate(self, value): + match = self.username_re.search(value) + if not match: + raise ValueError(_('invalid username')) + + +class FilenameOption(Option): + __slots__ = tuple() + _opt_type = 'file' + path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") + + def _validate(self, value): + match = self.path_re.search(value) + if not match: + raise ValueError(_('invalid filename')) diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py new file mode 100644 index 0000000..8c26f3b --- /dev/null +++ b/tiramisu/option/optiondescription.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors) +# +# 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 +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# The original `Config` design model is unproudly borrowed from +# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ +# the whole pypy projet is under MIT licence +# ____________________________________________________________ +from copy import copy + +from tiramisu.i18n import _ +from tiramisu.setting import groups, log +from .baseoption import BaseOption +from . import MasterSlaves +from tiramisu.error import ConfigError, ConflictError, ValueWarning + + +class OptionDescription(BaseOption): + """Config's schema (organisation, group) and container of Options + The `OptionsDescription` objects lives in the `tiramisu.config.Config`. + """ + __slots__ = ('_name', '_requires', '_cache_paths', '_group_type', + '_state_group_type', '_properties', '_children', + '_cache_consistencies', '_calc_properties', '__weakref__', + '_readonly', '_impl_informations', '_state_requires', + '_stated', '_state_readonly') + _opt_type = 'optiondescription' + + def __init__(self, name, doc, children, requires=None, properties=None): + """ + :param children: a list of options (including optiondescriptions) + + """ + super(OptionDescription, self).__init__(name, doc, requires, properties) + child_names = [child._name for child in children] + #better performance like this + valid_child = copy(child_names) + valid_child.sort() + old = None + for child in valid_child: + if child == old: + raise ConflictError(_('duplicate option name: ' + '{0}').format(child)) + old = child + self._children = (tuple(child_names), tuple(children)) + self._cache_paths = None + self._cache_consistencies = None + # the group_type is useful for filtering OptionDescriptions in a config + self._group_type = groups.default + + def impl_getdoc(self): + return self.impl_get_information('doc') + + def __getattr__(self, name): + if name in self.__slots__: + return object.__getattribute__(self, name) + try: + return self._children[1][self._children[0].index(name)] + except ValueError: + log.debug('__getattr__', exc_info=True) + raise AttributeError(_('unknown Option {0} ' + 'in OptionDescription {1}' + '').format(name, self._name)) + + def impl_getkey(self, config): + return tuple([child.impl_getkey(getattr(config, child._name)) + for child in self.impl_getchildren()]) + + def impl_getpaths(self, include_groups=False, _currpath=None): + """returns a list of all paths in self, recursively + _currpath should not be provided (helps with recursion) + """ + if _currpath is None: + _currpath = [] + paths = [] + for option in self.impl_getchildren(): + attr = option._name + 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_getchildren(self): + return self._children[1] + + def impl_build_cache(self, + cache_path=None, + cache_option=None, + _currpath=None, + _consistencies=None, + force_no_consistencies=False): + if _currpath is None and self._cache_paths is not None: + # cache already set + return + if _currpath is None: + save = True + _currpath = [] + if not force_no_consistencies: + _consistencies = {} + else: + save = False + if cache_path is None: + cache_path = [] + cache_option = [] + for option in self.impl_getchildren(): + attr = option._name + if option in cache_option: + raise ConflictError(_('duplicate option: {0}').format(option)) + + cache_option.append(option) + if not force_no_consistencies: + option._readonly = True + cache_path.append(str('.'.join(_currpath + [attr]))) + if not isinstance(option, OptionDescription): + if not force_no_consistencies and \ + option._consistencies is not None: + for consistency in option._consistencies: + func, all_cons_opts, params = consistency + for opt in all_cons_opts: + _consistencies.setdefault(opt, + []).append((func, + all_cons_opts, + params)) + else: + _currpath.append(attr) + option.impl_build_cache(cache_path, + cache_option, + _currpath, + _consistencies, + force_no_consistencies) + _currpath.pop() + if save: + self._cache_paths = (tuple(cache_option), tuple(cache_path)) + if not force_no_consistencies: + if _consistencies != {}: + self._cache_consistencies = {} + for opt, cons in _consistencies.items(): + if opt not in cache_option: + raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name)) + self._cache_consistencies[opt] = tuple(cons) + self._readonly = True + + def impl_get_opt_by_path(self, path): + try: + return self._cache_paths[0][self._cache_paths[1].index(path)] + except ValueError: + raise AttributeError(_('no option for path {0}').format(path)) + + def impl_get_path_by_opt(self, opt): + try: + return self._cache_paths[1][self._cache_paths[0].index(opt)] + except ValueError: + raise AttributeError(_('no option {0} found').format(opt)) + + # ____________________________________________________________ + def impl_set_group_type(self, group_type): + """sets a given group object to an OptionDescription + + :param group_type: an instance of `GroupType` or `MasterGroupType` + that lives in `setting.groups` + """ + if self._group_type != groups.default: + raise TypeError(_('cannot change group_type if already set ' + '(old {0}, new {1})').format(self._group_type, + group_type)) + if isinstance(group_type, groups.GroupType): + self._group_type = group_type + if isinstance(group_type, groups.MasterGroupType): + MasterSlaves(self._name, self.impl_getchildren()) + else: + raise ValueError(_('group_type: {0}' + ' not allowed').format(group_type)) + + def impl_get_group_type(self): + return self._group_type + + def _valid_consistency(self, option, value, context, index): + if self._cache_consistencies is None: + return True + #consistencies is something like [('_cons_not_equal', (opt1, opt2))] + consistencies = self._cache_consistencies.get(option) + if consistencies is not None: + for func, all_cons_opts, params in consistencies: + warnings_only = params.get('warnings_only', False) + #all_cons_opts[0] is the option where func is set + try: + all_cons_opts[0]._launch_consistency(func, option, + value, + context, index, + all_cons_opts, + warnings_only) + except ValueError as err: + if warnings_only: + raise ValueWarning(err.message, option) + else: + raise err + + def _impl_getstate(self, descr=None): + """enables us to export into a dict + :param descr: parent :class:`tiramisu.option.OptionDescription` + """ + if descr is None: + self.impl_build_cache() + descr = self + super(OptionDescription, self)._impl_getstate(descr) + self._state_group_type = str(self._group_type) + for option in self.impl_getchildren(): + option._impl_getstate(descr) + + def __getstate__(self): + """special method to enable the serialization with pickle + """ + stated = True + try: + # the `_state` attribute is a flag that which tells us if + # the serialization can be performed + self._stated + except AttributeError: + # if cannot delete, _impl_getstate never launch + # launch it recursivement + # _stated prevent __getstate__ launch more than one time + # _stated is delete, if re-serialize, re-lauch _impl_getstate + self._impl_getstate() + stated = False + return super(OptionDescription, self).__getstate__(stated) + + def _impl_setstate(self, descr=None): + """enables us to import from a dict + :param descr: parent :class:`tiramisu.option.OptionDescription` + """ + if descr is None: + self._cache_paths = None + self._cache_consistencies = None + self.impl_build_cache(force_no_consistencies=True) + descr = self + self._group_type = getattr(groups, self._state_group_type) + del(self._state_group_type) + super(OptionDescription, self)._impl_setstate(descr) + for option in self.impl_getchildren(): + option._impl_setstate(descr) + + def __setstate__(self, state): + super(OptionDescription, self).__setstate__(state) + try: + self._stated + except AttributeError: + self._impl_setstate() diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 95cee47..ded4108 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -101,6 +101,9 @@ rw_remove = set(['permissive', 'everything_frozen', 'mandatory']) log = getLogger('tiramisu') +#FIXME +#import logging +#logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) # ____________________________________________________________ @@ -211,34 +214,12 @@ def populate_owners(): setattr(owners, 'addowner', addowner) -def populate_multitypes(): - """all multi option should have a type, this type is automaticly set do - not touch this - - default - default's multi option set if not master or slave - - master - master's option in a group with master's type, name of this option - should be the same name of the optiondescription - - slave - slave's option in a group with master's type - - """ - setattr(multitypes, 'default', multitypes.DefaultMultiType('default')) - setattr(multitypes, 'master', multitypes.MasterMultiType('master')) - setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave')) - - # ____________________________________________________________ # populate groups, owners and multitypes with default attributes groups = GroupModule() populate_groups() owners = OwnerModule() populate_owners() -multitypes = MultiTypeModule() -populate_multitypes() # ____________________________________________________________ diff --git a/tiramisu/value.py b/tiramisu/value.py index 87b0acd..47e74b6 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -16,11 +16,10 @@ # along with this program. If not, see . # ____________________________________________________________ from time import time -from copy import copy import sys import weakref from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError -from tiramisu.setting import owners, multitypes, expires_time, undefined +from tiramisu.setting import owners, expires_time, undefined from tiramisu.autolib import carry_out_calculation from tiramisu.i18n import _ from tiramisu.option import SymLinkOption @@ -55,36 +54,65 @@ class Values(object): raise ConfigError(_('the context does not exist anymore')) return context - def _getdefault(self, opt): - """ - actually retrieves the default value - - :param opt: the `option.Option()` object - """ - meta = self._getcontext().cfgimpl_get_meta() - if meta is not None: - value = meta.cfgimpl_get_values()[opt] - if isinstance(value, Multi): - value = list(value) - else: - value = opt.impl_getdefault() - if opt.impl_is_multi(): - return copy(value) - else: - return value - - def _getvalue(self, opt, path): + def _getvalue(self, opt, path, is_default, index=undefined): """actually retrieves the value :param opt: the `option.Option()` object :returns: the option's value (or the default value if not set) """ - if not self._p_.hasvalue(path): - # if there is no value - value = self._getdefault(opt) - else: - # if there is a value + setting = self._getcontext().cfgimpl_get_settings() + force_default = 'frozen' in setting[opt] and \ + 'force_default_on_freeze' in setting[opt] + if not is_default and not force_default: value = self._p_.getvalue(path) + if index is not undefined: + try: + return value[index] + except IndexError: + #value is smaller than expected + #so return default value + pass + else: + return value + #so default value + # if value has callback and is not set + if opt.impl_has_callback(): + callback, callback_params = opt._callback + if callback_params is None: + callback_params = {} + value = carry_out_calculation(opt, config=self._getcontext(), + callback=callback, + callback_params=callback_params, + index=index) + try: + if isinstance(value, list) and index is not undefined: + return value[index] + return value + except IndexError: + pass + meta = self._getcontext().cfgimpl_get_meta() + if meta is not None: + #FIXME : problème de longueur si meta + slave + #doit passer de meta à pas meta + #en plus il faut gérer la longueur avec les meta ! + #FIXME SymlinkOption + value = meta.cfgimpl_get_values()[opt] + if isinstance(value, Multi): + if index is not undefined: + value = value[index] + else: + value = list(value) + return value + # now try to get default value + value = opt.impl_getdefault() + if opt.impl_is_multi() and index is not undefined: + if value is None: + value = opt.impl_getdefault_multi() + else: + try: + value = value[index] + except IndexError: + value = opt.impl_getdefault_multi() return value def get_modified_values(self): @@ -121,44 +149,38 @@ class Values(object): opt.impl_validate(opt.impl_getdefault(), context, 'validator' in setting) context.cfgimpl_reset_cache() - if (opt.impl_is_multi() and - opt.impl_get_multitype() == multitypes.master): - for slave in opt.impl_get_master_slaves(): - self.reset(slave) + if opt.impl_is_master_slaves('master'): + opt.impl_get_master_slaves().reset(self) self._p_.resetvalue(path) def _isempty(self, opt, value): "convenience method to know if an option is empty" empty = opt._empty - if (not opt.impl_is_multi() and (value is None or value == empty)) or \ - (opt.impl_is_multi() and (value == [] or - None in value or empty in value)): - return True - return False - - def _getcallback_value(self, opt, index=None, max_len=None): - """ - retrieves a value for the options that have a callback - - :param opt: the `option.Option()` object - :param index: if an option is multi, only calculates the nth value - :type index: int - :returns: a calculated value - """ - callback, callback_params = opt._callback - if callback_params is None: - callback_params = {} - return carry_out_calculation(opt, config=self._getcontext(), - callback=callback, - callback_params=callback_params, - index=index, max_len=max_len) + if value is not undefined: + empty_not_multi = not opt.impl_is_multi() and (value is None or + value == empty) + empty_multi = opt.impl_is_multi() and (value == [] or + None in value or + empty in value) + else: + empty_multi = empty_not_multi = False + return empty_not_multi or empty_multi def __getitem__(self, opt): "enables us to use the pythonic dictionary-like access to values" return self.getitem(opt) - def getitem(self, opt, path=None, validate=True, force_permissive=False, - force_properties=None, validate_properties=True): + def getitem(self, opt, validate=True, force_permissive=False, + force_properties=None): + """ + """ + return self._get_cached_item(opt, validate=validate, + force_permissive=force_permissive, + force_properties=force_properties) + + def _get_cached_item(self, opt, path=None, validate=True, + force_permissive=False, force_properties=None, + validate_properties=True): if path is None: path = self._get_opt_path(opt) ntime = None @@ -170,7 +192,7 @@ class Values(object): if is_cached: if opt.impl_is_multi() and not isinstance(value, Multi): #load value so don't need to validate if is not a Multi - value = Multi(value, self.context, opt, path, validate=False) + value = Multi(value, self.context, opt, path) return value val = self._getitem(opt, path, validate, force_permissive, force_properties, validate_properties) @@ -181,85 +203,82 @@ class Values(object): ntime = int(time()) ntime = ntime + expires_time self._p_.setcache(path, val, ntime) - return val def _getitem(self, opt, path, validate, force_permissive, force_properties, validate_properties): - # options with callbacks + if opt.impl_is_master_slaves(): + return opt.impl_get_master_slaves().getitem(self, opt, path, + validate, + force_permissive, + force_properties, + validate_properties) + else: + return self._get_validated_value(opt, path, validate, + force_permissive, + force_properties, + validate_properties) + + def _get_validated_value(self, opt, path, validate, force_permissive, + force_properties, validate_properties, + index=undefined): + """same has getitem but don't touch the cache""" + #FIXME expliquer la différence entre index == undefined et index == None context = self._getcontext() setting = context.cfgimpl_get_settings() - is_frozen = 'frozen' in setting[opt] - # For calculating properties, we need value (ie for mandatory value). - # If value is calculating with a PropertiesOptionError's option - # _getcallback_value raise a ConfigError. - # We can not raise ConfigError if this option should raise - # PropertiesOptionError too. So we get config_error and raise - # ConfigError if properties did not raise. - config_error = None - force_permissives = None - # if value has callback and is not set - # or frozen with force_default_on_freeze - if opt.impl_has_callback() and ( - self._is_default_owner(opt, path, validate_properties=False) or - (is_frozen and 'force_default_on_freeze' in setting[opt])): - lenmaster = None - no_value_slave = False - if (opt.impl_is_multi() and - opt.impl_get_multitype() == multitypes.slave): - masterp = self._get_opt_path(opt.impl_get_master_slaves()) - mastervalue = context.getattr(masterp, validate=False) - lenmaster = len(mastervalue) - if lenmaster == 0: - value = [] - no_value_slave = True + is_default = self._is_default_owner(opt, path, + validate_properties=False, + validate_meta=False) + try: + if index is None: + gv_index = undefined + else: + gv_index = index + value = self._getvalue(opt, path, is_default, index=gv_index) + config_error = None + except ConfigError as err: + # For calculating properties, we need value (ie for mandatory + # value). + # If value is calculating with a PropertiesOptionError's option + # _getvalue raise a ConfigError. + # We can not raise ConfigError if this option should raise + # PropertiesOptionError too. So we get config_error and raise + # ConfigError if properties did not raise. + # cannot assign config_err directly in python 3.3 + config_error = err + # value is not set, for 'undefined' (cannot set None because of + # mandatory property) + value = undefined - if not no_value_slave: - try: - value = self._getcallback_value(opt, max_len=lenmaster) - except ConfigError as err: - # cannot assign config_err directly in python 3.3 - config_error = err - value = None - # should not raise PropertiesOptionError if option is - # mandatory - force_permissives = set(['mandatory']) - else: - if (opt.impl_is_multi() and - opt.impl_get_multitype() == multitypes.slave): - if not isinstance(value, list): - value = [value for i in range(lenmaster)] - if config_error is None: - if opt.impl_is_multi(): - value = Multi(value, self.context, opt, path, validate) - # frozen and force default - elif is_frozen and 'force_default_on_freeze' in setting[opt]: - value = self._getdefault(opt) + if config_error is None: + if index is undefined: + force_index = None + else: + force_index = index if opt.impl_is_multi(): - value = Multi(value, self.context, opt, path, validate) - else: - value = self._getvalue(opt, path) - if opt.impl_is_multi(): - # load value so don't need to validate if is not a Multi - value = Multi(value, self.context, opt, path, validate=validate) - if config_error is None and validate: - opt.impl_validate(value, context, 'validator' in setting) - if config_error is None and \ - self._is_default_owner(opt, path, validate_properties=False) and \ - 'force_store_value' in setting[opt]: - self.setitem(opt, value, path, is_write=False, - force_permissive=force_permissive) + if index is None and not isinstance(value, list): + value = [] + if force_index is None: + value = Multi(value, self.context, opt, path) + if validate: + opt.impl_validate(value, context, 'validator' in setting, + force_index=force_index) + #FIXME pas de test avec les metas ... + #FIXME et les symlinkoption ... + if is_default and 'force_store_value' in setting[opt]: + self.setitem(opt, value, path, is_write=False, + force_permissive=force_permissive) if validate_properties: - setting.validate_properties(opt, False, False, value=value, path=path, + setting.validate_properties(opt, False, False, value=value, + path=path, force_permissive=force_permissive, - force_properties=force_properties, - force_permissives=force_permissives) + force_properties=force_properties) if config_error is not None: raise config_error return value def __setitem__(self, opt, value): - 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, is_write=True): @@ -270,26 +289,11 @@ class Values(object): opt.impl_validate(value, context, 'validator' in context.cfgimpl_get_settings()) if opt.impl_is_multi(): - value = Multi(value, self.context, opt, path, setitem=True) - # Save old value - if opt.impl_get_multitype() == multitypes.master and \ - self._p_.hasvalue(path): - old_value = self._p_.getvalue(path) - old_owner = self._p_.getowner(path, None) - else: - old_value = undefined - old_owner = undefined + value = Multi(value, self.context, opt, path) + if opt.impl_is_master_slaves(): + opt.impl_get_master_slaves().setitem(self, opt, value, path) self._setvalue(opt, path, value, force_permissive=force_permissive, is_write=is_write) - if opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.master: - try: - value._valid_master() - except Exception, err: - if old_value is not undefined: - self._p_.setvalue(path, old_value, old_owner) - else: - self._p_.resetvalue(path) - raise err def _setvalue(self, opt, path, value, force_permissive=False, force_properties=None, @@ -321,13 +325,15 @@ class Values(object): path = self._get_opt_path(opt) return self._getowner(opt, path, force_permissive=force_permissive) - def _getowner(self, opt, path, validate_properties=True, force_permissive=False): - meta = self._getcontext().cfgimpl_get_meta() + def _getowner(self, opt, path, validate_properties=True, + force_permissive=False, validate_meta=True): if validate_properties: self._getitem(opt, path, True, force_permissive, None, True) owner = self._p_.getowner(path, owners.default) - if owner is owners.default and meta is not None: - owner = meta.cfgimpl_get_values()._getowner(opt, path) + if validate_meta: + meta = self._getcontext().cfgimpl_get_meta() + if owner is owners.default and meta is not None: + owner = meta.cfgimpl_get_values()._getowner(opt, path) return owner def setowner(self, opt, owner): @@ -349,17 +355,22 @@ class Values(object): '').format(path, owner)) self._p_.setowner(path, owner) - def is_default_owner(self, opt, validate_properties=True): + def is_default_owner(self, opt, validate_properties=True, + validate_meta=True): """ :param config: *must* be only the **parent** config (not the toplevel config) :return: boolean """ path = self._get_opt_path(opt) - return self._is_default_owner(opt, path, validate_properties) + return self._is_default_owner(opt, path, + validate_properties=validate_properties, + validate_meta=validate_meta) - def _is_default_owner(self, opt, path, validate_properties=True): - return self._getowner(opt, path, validate_properties) == owners.default + def _is_default_owner(self, opt, path, validate_properties=True, + validate_meta=True): + return self._getowner(opt, path, validate_properties, + validate_meta=validate_meta) == owners.default def reset_cache(self, only_expired): """ @@ -377,7 +388,8 @@ class Values(object): :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) + return self._getcontext().cfgimpl_get_description( + ).impl_get_path_by_opt(opt) # information def set_information(self, key, value): @@ -457,13 +469,11 @@ class Multi(list): that support item notation for the values of multi options""" __slots__ = ('opt', 'path', 'context') - def __init__(self, value, context, opt, path, validate=True, - setitem=False): + def __init__(self, value, context, opt, path): """ :param value: the Multi wraps a list value :param context: the home config that has the values :param opt: the option object that have this Multi value - :param setitem: only if set a value """ if isinstance(value, Multi): raise ValueError(_('{0} is already a Multi ').format(opt._name)) @@ -474,11 +484,6 @@ class Multi(list): self.context = context if not isinstance(value, list): value = [value] - if validate and self.opt.impl_get_multitype() == multitypes.slave: - value = self._valid_slave(value, setitem) - elif not setitem and validate and \ - self.opt.impl_get_multitype() == multitypes.master: - self._valid_master() super(Multi, self).__init__(value) def _getcontext(self): @@ -492,124 +497,73 @@ class Multi(list): raise ConfigError(_('the context does not exist anymore')) return context - def _valid_slave(self, value, setitem): - #if slave, had values until master's one - context = self._getcontext() - values = context.cfgimpl_get_values() - masterp = context.cfgimpl_get_description().impl_get_path_by_opt( - self.opt.impl_get_master_slaves()) - mastervalue = context.getattr(masterp, validate=False) - masterlen = len(mastervalue) - valuelen = len(value) - if valuelen > masterlen or (valuelen < masterlen and setitem): - raise SlaveError(_("invalid len for the slave: {0}" - " which has {1} as master").format( - self.opt._name, masterp)) - elif valuelen < masterlen: - for num in range(0, masterlen - valuelen): - if self.opt.impl_has_callback(): - # if callback add a value, but this value will not change - # anymore automaticly (because this value has owner) - index = value.__len__() - value.append(values._getcallback_value(self.opt, - index=index)) - else: - value.append(self.opt.impl_getdefault_multi()) - #else: same len so do nothing - return value - - def _valid_master(self): - #masterlen = len(value) - values = self._getcontext().cfgimpl_get_values() - for slave in self.opt._master_slaves: - path = values._get_opt_path(slave) - Multi(values._getvalue(slave, path), self.context, slave, path) - def __setitem__(self, index, value): self._validate(value, index) #assume not checking mandatory property super(Multi, self).__setitem__(index, value) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, + self) - def append(self, value=undefined, force=False): + def append(self, value=undefined, force=False, setitem=True): """the list value can be updated (appened) only if the option is a master """ - context = self._getcontext() + values = self._getcontext().cfgimpl_get_values() if not force: - if self.opt.impl_get_multitype() == multitypes.slave: + if self.opt.impl_is_master_slaves('slave'): raise SlaveError(_("cannot append a value on a multi option {0}" " which is a slave").format(self.opt._name)) - elif self.opt.impl_get_multitype() == multitypes.master: - values = context.cfgimpl_get_values() - if value is undefined and self.opt.impl_has_callback(): - value = values._getcallback_value(self.opt) - #Force None il return a list - if isinstance(value, list): - value = None index = self.__len__() if value is undefined: - value = self.opt.impl_getdefault_multi() + try: + value = values._get_validated_value(self.opt, self.path, + True, False, None, True, + index=index) + except IndexError: + value = None self._validate(value, index) super(Multi, self).append(value) - context.cfgimpl_get_values()._setvalue(self.opt, self.path, - self, - validate_properties=not force) - if not force and self.opt.impl_get_multitype() == multitypes.master: - for slave in self.opt.impl_get_master_slaves(): - path = values._get_opt_path(slave) - if not values._is_default_owner(slave, path, - validate_properties=False): - if slave.impl_has_callback(): - dvalue = values._getcallback_value(slave, index=index) - else: - dvalue = slave.impl_getdefault_multi() - old_value = values.getitem(slave, path, validate=False, - validate_properties=False) - if len(old_value) + 1 != self.__len__(): - raise SlaveError(_("invalid len for the slave: {0}" - " which has {1} as master").format( - self.opt._name, self.__len__())) - values.getitem(slave, path, validate=False, - validate_properties=False).append( - dvalue, force=True) + if setitem: + values._setvalue(self.opt, self.path, self, + validate_properties=not force) def sort(self, cmp=None, key=None, reverse=False): - if self.opt.impl_get_multitype() in [multitypes.slave, - multitypes.master]: + if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot sort multi option {0} if master or slave" "").format(self.opt._name)) if sys.version_info[0] >= 3: if cmp is not None: - raise ValueError(_('cmp is not permitted in python v3 or greater')) + raise ValueError(_('cmp is not permitted in python v3 or ' + 'greater')) super(Multi, self).sort(key=key, reverse=reverse) else: super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, + self) def reverse(self): - if self.opt.impl_get_multitype() in [multitypes.slave, - multitypes.master]: + if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot reverse multi option {0} if master or " "slave").format(self.opt._name)) super(Multi, self).reverse() - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, + self) def insert(self, index, obj): - if self.opt.impl_get_multitype() in [multitypes.slave, - multitypes.master]: + if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot insert multi option {0} if master or " "slave").format(self.opt._name)) super(Multi, self).insert(index, obj) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, + self) def extend(self, iterable): - if self.opt.impl_get_multitype() in [multitypes.slave, - multitypes.master]: + if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot extend multi option {0} if master or " "slave").format(self.opt._name)) super(Multi, self).extend(iterable) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, + self) def _validate(self, value, force_index): if value is not None: @@ -634,18 +588,14 @@ class Multi(list): """ context = self._getcontext() if not force: - if self.opt.impl_get_multitype() == multitypes.slave: + if self.opt.impl_is_master_slaves('slave'): raise SlaveError(_("cannot pop a value on a multi option {0}" " which is a slave").format(self.opt._name)) - if self.opt.impl_get_multitype() == multitypes.master: - for slave in self.opt.impl_get_master_slaves(): - values = context.cfgimpl_get_values() - if not values.is_default_owner(slave, validate_properties=False): - #get multi without valid properties - values.getitem(slave, validate=False, - validate_properties=False - ).pop(index, force=True) + if self.opt.impl_is_master_slaves('master'): + self.opt.impl_get_master_slaves().pop( + context.cfgimpl_get_values(), index) #set value without valid properties ret = super(Multi, self).pop(index) - context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force) + context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, + validate_properties=not force) return ret