split tiramisu/option.py and add MasterSlaves object

This commit is contained in:
Emmanuel Garette 2014-04-12 11:53:58 +02:00
parent d1e86f6d48
commit cd4d3527c7
13 changed files with 1478 additions and 1176 deletions

10
ChangeLog Normal file
View File

@ -0,0 +1,10 @@
Sat Apr 12 11:37:27 CEST 2014 Emmanuel Garette <egarette@cadoles.com>
* 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

View File

@ -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)

View File

@ -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']")

View File

@ -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)

View File

@ -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:

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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

525
tiramisu/option/option.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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'))

View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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()

View File

@ -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()
# ____________________________________________________________

View File

@ -16,11 +16,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________
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