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 import autopath
from py.test import raises from py.test import raises
from tiramisu.setting import groups
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.setting import groups, owners
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription, SymLinkOption StrOption, OptionDescription, SymLinkOption
from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError 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'] 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(): def test_callback_master_and_slaves_master():
val1 = StrOption('val1', "", multi=True, callback=return_val) val1 = StrOption('val1', "", multi=True, callback=return_val)
val2 = StrOption('val2', "", multi=True) val2 = StrOption('val2', "", multi=True)
@ -541,8 +552,6 @@ def test_callback_master_and_slaves_slave_cal():
cfg.val3 = ['val1'] cfg.val3 = ['val1']
assert cfg.val1.val1 == ['val1'] assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val'] assert cfg.val1.val2 == ['val']
assert cfg.val1.val1 == ['val1']
assert cfg.val1.val2 == ['val']
del(cfg.val1.val1) del(cfg.val1.val1)
cfg.val1.val2 = ['val'] cfg.val1.val2 = ['val']
cfg.val3 = ['val1', 'val2'] 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.val1 == ['val', 'val']
assert cfg.val1.val2 == ['val2', 'val2'] assert cfg.val1.val2 == ['val2', 'val2']
cfg.val3.pop(1) cfg.val3.pop(1)
# # cannot remove slave's value because master is calculated # cannot remove slave's value because master is calculated
# # so raise # so raise
raises(SlaveError, "cfg.val1.val1") raises(SlaveError, "cfg.val1.val1")
raises(SlaveError, "cfg.val1.val2") raises(SlaveError, "cfg.val1.val2")
cfg.val3 = ['val', 'val'] cfg.val3 = ['val', 'val']
@ -585,6 +594,88 @@ def test_callback_master_and_slaves_slave_cal2():
assert cfg.val1.val2 == ['val2', 'val2'] 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(): def test_callback_master_and_slaves_slave_list():
val1 = StrOption('val1', "", multi=True) val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_list) val2 = StrOption('val2', "", multi=True, callback=return_list)
@ -593,20 +684,20 @@ def test_callback_master_and_slaves_slave_list():
maconfig = OptionDescription('rootconfig', '', [interface1]) maconfig = OptionDescription('rootconfig', '', [interface1])
cfg = Config(maconfig) cfg = Config(maconfig)
cfg.read_write() 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'] cfg.val1.val1 = ['val1', 'val2']
assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['val', 'val'] assert cfg.val1.val2 == ['val', 'val']
cfg.val1.val1 = ['val1']
#wrong len #wrong len
raises(SlaveError, 'cfg.val1.val2') raises(SlaveError, "cfg.val1.val1 = ['val1']")
def test_callback_master_and_slaves_value(): def test_callback_master_and_slaves_value():
val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
val1 = StrOption('val1', "", multi=True) val1 = StrOption('val1', "", multi=True)
val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)}) val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)}) 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),)}) val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)})
val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)}) val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)})
interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6]) interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6])
@ -614,27 +705,24 @@ def test_callback_master_and_slaves_value():
maconfig = OptionDescription('rootconfig', '', [interface1, val4]) maconfig = OptionDescription('rootconfig', '', [interface1, val4])
cfg = Config(maconfig) cfg = Config(maconfig)
cfg.read_write() cfg.read_write()
assert cfg.val1.val1 == [] cfg.val4 == ['val10', 'val11']
assert cfg.val1.val2 == [] raises(SlaveError, "cfg.val1.val1")
assert cfg.val1.val3 == [] raises(SlaveError, "cfg.val1.val2")
assert cfg.val1.val5 == [] raises(SlaveError, "cfg.val1.val3")
assert cfg.val1.val6 == [] raises(SlaveError, "cfg.val1.val5")
raises(SlaveError, "cfg.val1.val6")
# #
cfg.val1.val1 = ['val1'] #default calculation has greater length
assert cfg.val1.val1 == ['val1'] raises(SlaveError, "cfg.val1.val1 = ['val1']")
assert cfg.val1.val2 == ['val1']
assert cfg.val1.val3 == ['yes']
assert cfg.val1.val5 == ['val10']
assert cfg.val1.val6 == ['val10']
# #
cfg.val1.val1.append('val2') cfg.val1.val1 = ['val1', 'val2']
assert cfg.val1.val1 == ['val1', 'val2'] assert cfg.val1.val1 == ['val1', 'val2']
assert cfg.val1.val2 == ['val1', 'val2'] assert cfg.val1.val2 == ['val1', 'val2']
assert cfg.val1.val3 == ['yes', 'yes'] assert cfg.val1.val3 == ['yes', 'yes']
assert cfg.val1.val5 == ['val10', 'val11'] assert cfg.val1.val5 == ['val10', 'val11']
assert cfg.val1.val6 == ['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.val1 == ['val1', 'val2', 'val3']
assert cfg.val1.val2 == ['val1', 'val2', 'val3'] assert cfg.val1.val2 == ['val1', 'val2', 'val3']
assert cfg.val1.val3 == ['yes', 'yes', 'yes'] 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] assert cfg.val4.val6 == ['no', None]
cfg.val1.val1 = ['yes', 'yes', 'yes'] cfg.val1.val1 = ['yes', 'yes', 'yes']
cfg.val1.val2 = ['no', 'no', 'no'] cfg.val1.val2 = ['no', 'no', 'no']
assert cfg.val4.val4 == ['val10', 'val11'] raises(SlaveError, "cfg.val4.val4")
assert cfg.val4.val5 == ['yes', 'yes'] raises(SlaveError, "cfg.val4.val5")
assert cfg.val4.val6 == ['no', 'no'] 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(): def test_callback_different_type():
@ -757,6 +852,19 @@ def test_callback_two_disabled():
raises(PropertiesOptionError, 'cfg.od2.opt2') 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(): def test_callback_calculating_disabled():
opt1 = BoolOption('opt1', '', properties=('disabled',)) opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}) opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
@ -779,6 +887,17 @@ def test_callback_calculating_mandatory():
raises(ConfigError, 'cfg.od2.opt2') 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(): def test_callback_two_disabled_multi():
opt1 = BoolOption('opt1', '', properties=('disabled',)) opt1 = BoolOption('opt1', '', properties=('disabled',))
opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True) 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.impl_add_consistency('broadcast', a)
c = Config(od) c = Config(od)
c.a = ['192.168.1.0'] 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']") raises(ConfigError, "c.c = ['192.168.1.255']")

View File

@ -242,6 +242,30 @@ def test_requires_transitive():
assert props == ['disabled'] 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(): def test_requires_transitive_bis():
a = BoolOption('activate_service', '', True) a = BoolOption('activate_service', '', True)
abis = BoolOption('activate_service_bis', '', 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" "enables us to carry out a calculation and return an option's value"
from tiramisu.error import PropertiesOptionError, ConfigError from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.setting import multitypes
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.setting import undefined
# ____________________________________________________________ # ____________________________________________________________
def carry_out_calculation(option, config, callback, callback_params, 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 """a function that carries out a calculation for an option's value
:param name: the option :param name: the option
@ -38,8 +38,6 @@ def carry_out_calculation(option, config, callback, callback_params,
:type callback_params: dict :type callback_params: dict
:param index: if an option is multi, only calculates the nth value :param index: if an option is multi, only calculates the nth value
:type index: int :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 '') The callback_params is a dict. Key is used to build args (if key is '')
and kwargs (otherwise). Values are tuple of: 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))} * if callback_params={'value': ((opt1, False), (opt2, False))}
=> raises ValueError() => 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 list if one parameters have multi option
* a value otherwise * a value otherwise
@ -154,15 +152,8 @@ def carry_out_calculation(option, config, callback, callback_params,
).impl_get_path_by_opt(opt) ).impl_get_path_by_opt(opt)
# get value # get value
try: 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, value = config.getattr(path, force_permissive=True,
validate=validate) validate=False)
# convert to list, not modifie this multi # convert to list, not modifie this multi
if value.__class__.__name__ == 'Multi': if value.__class__.__name__ == 'Multi':
value = list(value) value = list(value)
@ -175,20 +166,13 @@ def carry_out_calculation(option, config, callback, callback_params,
err.proptype, err.proptype,
option._name)) option._name))
is_multi = False if opt.impl_is_master_slaves() and \
if opt.impl_is_multi(): opt.impl_get_master_slaves().in_same_group(option):
#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:
len_multi = len(value) len_multi = len(value)
one_is_multi = True one_is_multi = True
is_multi = True
else:
is_multi = False
tcparams.setdefault(key, []).append((value, is_multi)) tcparams.setdefault(key, []).append((value, is_multi))
else: else:
# callbk is a value and not a multi # 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 no index, return a list
if one_is_multi: if one_is_multi:
ret = [] ret = []
if index is not None: if index is not undefined:
range_ = [index] range_ = [index]
else: else:
range_ = range(len_multi) range_ = range(len_multi)
@ -218,7 +202,7 @@ def carry_out_calculation(option, config, callback, callback_params,
else: else:
kwargs[key] = val kwargs[key] = val
calc = calculate(callback, args, kwargs) calc = calculate(callback, args, kwargs)
if index is not None: if index is not undefined:
ret = calc ret = calc
else: else:
ret.append(calc) ret.append(calc)
@ -237,11 +221,7 @@ def carry_out_calculation(option, config, callback, callback_params,
kwargs[key] = couple[0] kwargs[key] = couple[0]
ret = calculate(callback, args, kwargs) ret = calculate(callback, args, kwargs)
if callback_params != {}: if callback_params != {}:
if isinstance(ret, list) and max_len: if isinstance(ret, list) and index is not undefined:
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 len(ret) < index + 1: if len(ret) < index + 1:
ret = None ret = None
else: else:

View File

@ -228,6 +228,7 @@ class SubConfig(object):
:return: option's value if name is an option name, OptionDescription :return: option's value if name is an option name, OptionDescription
otherwise otherwise
""" """
#FIXME force_properties vraiment utile maintenant ?
# attribute access by passing a path, # attribute access by passing a path,
# for instance getattr(self, "creole.general.family.adresse_ip_eth0") # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
if '.' in name: if '.' in name:
@ -243,6 +244,8 @@ class SubConfig(object):
else: else:
subpath = self._impl_path + '.' + name subpath = self._impl_path + '.' + name
# symlink options # symlink options
#FIXME a gerer plutot dans l'option ca ...
#FIXME je n'en sais rien en fait ... :/
if isinstance(opt_or_descr, SymLinkOption): if isinstance(opt_or_descr, SymLinkOption):
context = self._cfgimpl_get_context() context = self._cfgimpl_get_context()
path = context.cfgimpl_get_description().impl_get_path_by_opt( path = context.cfgimpl_get_description().impl_get_path_by_opt(
@ -257,7 +260,7 @@ class SubConfig(object):
force_properties=force_properties) force_properties=force_properties)
return SubConfig(opt_or_descr, self._impl_context, subpath) return SubConfig(opt_or_descr, self._impl_context, subpath)
else: else:
return self.cfgimpl_get_values().getitem( return self.cfgimpl_get_values()._get_cached_item(
opt_or_descr, path=subpath, opt_or_descr, path=subpath,
validate=validate, validate=validate,
force_properties=force_properties, 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 -*- # -*- coding: utf-8 -*-
"option types and option description" # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
# #
# This program is free software: you can redistribute it and/or modify it # This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the # under the terms of the GNU Lesser General Public License as published by the
@ -20,16 +19,14 @@
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
import re import re
import sys
from copy import copy, deepcopy from copy import copy, deepcopy
from types import FunctionType from types import FunctionType
from IPy import IP
import warnings import warnings
from tiramisu.error import ConfigError, ConflictError, ValueWarning
from tiramisu.setting import groups, multitypes, log
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.setting import log
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
from tiramisu.error import ConfigError, ValueWarning
name_regexp = re.compile(r'^\d+') name_regexp = re.compile(r'^\d+')
forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
@ -48,6 +45,46 @@ def valid_name(name):
return True return True
else: else:
return False 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. Reminder: an Option object is **not** a container for the value.
""" """
__slots__ = ('_multi', '_validator', '_default_multi', '_default', __slots__ = ('_multi', '_validator', '_default_multi', '_default',
'_state_callback', '_callback', '_multitype', '_state_callback', '_callback', '_consistencies',
'_consistencies', '_warnings_only', '_master_slaves', '_warnings_only', '_master_slaves', '_state_consistencies',
'_state_consistencies', '__weakref__') '__weakref__')
_empty = '' _empty = ''
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
@ -327,7 +364,6 @@ class Option(BaseOption):
if self._multi: if self._multi:
if default is None: if default is None:
default = [] default = []
self._multitype = multitypes.default
self._default_multi = default_multi self._default_multi = default_multi
self._warnings_only = warnings_only self._warnings_only = warnings_only
self.impl_validate(default) self.impl_validate(default)
@ -390,9 +426,9 @@ class Option(BaseOption):
:type context: :class:`tiramisu.config.Config` :type context: :class:`tiramisu.config.Config`
:param validate: if true enables ``self._validator`` validation :param validate: if true enables ``self._validator`` validation
:type validate: boolean :type validate: boolean
:param force_no_multi: if multi, value has to be a list :param force_index: if multi, value has to be a list
not if force_no_multi is True not if force_index is not None
:type force_no_multi: boolean :type force_index: integer
""" """
if not validate: if not validate:
return return
@ -421,6 +457,8 @@ class Option(BaseOption):
try: try:
self._validate(_value) self._validate(_value)
except ValueError as err: 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}' raise ValueError(_('invalid value for option {0}: {1}'
'').format(self._name, err)) '').format(self._name, err))
error = None error = None
@ -476,14 +514,28 @@ class Option(BaseOption):
def impl_getdefault(self): def impl_getdefault(self):
"accessing the default value" "accessing the default value"
if isinstance(self._default, list):
return copy(self._default)
return self._default return self._default
def impl_getdefault_multi(self): def impl_getdefault_multi(self):
"accessing the default value for a multi" "accessing the default value for a multi"
return self._default_multi return self._default_multi
def impl_get_multitype(self): def impl_is_master_slaves(self, type_='both'):
return self._multitype """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): def impl_get_master_slaves(self):
return self._master_slaves return self._master_slaves
@ -632,805 +684,6 @@ class Option(BaseOption):
pass 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): def validate_requires_arg(requires, name):
"""check malformed requirements """check malformed requirements
and tranform dict to internal tuple and tranform dict to internal tuple
@ -1524,41 +777,32 @@ def validate_requires_arg(requires, name):
return frozenset(config_action.keys()), tuple(ret) return frozenset(config_action.keys()), tuple(ret)
def validate_callback(callback, callback_params, type_): class SymLinkOption(BaseOption):
if type(callback) != FunctionType: __slots__ = ('_name', '_opt', '_state_opt')
raise ValueError(_('{0} must be a function').format(type_)) _opt_type = 'symlink'
if callback_params is not None: #not return _opt consistencies
if not isinstance(callback_params, dict): _consistencies = None
raise ValueError(_('{0}_params must be a dict').format(type_))
for key, callbacks in callback_params.items(): def __init__(self, name, opt):
if key != '' and len(callbacks) != 1: self._name = name
raise ValueError(_("{0}_params with key {1} mustn't have " if not isinstance(opt, Option):
"length different to 1").format(type_, raise ValueError(_('malformed symlinkoption '
key)) 'must be an option '
if not isinstance(callbacks, tuple): 'for symlink {0}').format(name))
raise ValueError(_('{0}_params must be tuple for key "{1}"' self._opt = opt
).format(type_, key)) self._readonly = True
for callbk in callbacks:
if isinstance(callbk, tuple): def __getattr__(self, name):
if len(callbk) == 1: if name in ('_name', '_opt', '_opt_type', '_readonly'):
if callbk != (None,): return object.__getattr__(self, name)
raise ValueError(_('{0}_params with length of ' else:
'tuple as 1 must only have ' return getattr(self._opt, name)
'None as first value'))
elif len(callbk) != 2: def _impl_getstate(self, descr):
raise ValueError(_('{0}_params must only have 1 or 2 ' super(SymLinkOption, self)._impl_getstate(descr)
'as length')) self._state_opt = descr.impl_get_path_by_opt(self._opt)
else:
option, force_permissive = callbk def _impl_setstate(self, descr):
if type_ == 'validator' and not force_permissive: self._opt = descr.impl_get_opt_by_path(self._state_opt)
raise ValueError(_('validator not support tuple')) del(self._state_opt)
if not isinstance(option, Option) and not \ super(SymLinkOption, self)._impl_setstate(descr)
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)))

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') 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) 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 # populate groups, owners and multitypes with default attributes
groups = GroupModule() groups = GroupModule()
populate_groups() populate_groups()
owners = OwnerModule() owners = OwnerModule()
populate_owners() populate_owners()
multitypes = MultiTypeModule()
populate_multitypes()
# ____________________________________________________________ # ____________________________________________________________

View File

@ -16,11 +16,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
from time import time from time import time
from copy import copy
import sys import sys
import weakref import weakref
from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError 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.autolib import carry_out_calculation
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.option import SymLinkOption from tiramisu.option import SymLinkOption
@ -55,36 +54,65 @@ class Values(object):
raise ConfigError(_('the context does not exist anymore')) raise ConfigError(_('the context does not exist anymore'))
return context return context
def _getdefault(self, opt): def _getvalue(self, opt, path, is_default, index=undefined):
"""
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):
"""actually retrieves the value """actually retrieves the value
:param opt: the `option.Option()` object :param opt: the `option.Option()` object
:returns: the option's value (or the default value if not set) :returns: the option's value (or the default value if not set)
""" """
if not self._p_.hasvalue(path): setting = self._getcontext().cfgimpl_get_settings()
# if there is no value force_default = 'frozen' in setting[opt] and \
value = self._getdefault(opt) 'force_default_on_freeze' in setting[opt]
else: if not is_default and not force_default:
# if there is a value
value = self._p_.getvalue(path) 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 return value
def get_modified_values(self): def get_modified_values(self):
@ -121,44 +149,38 @@ class Values(object):
opt.impl_validate(opt.impl_getdefault(), opt.impl_validate(opt.impl_getdefault(),
context, 'validator' in setting) context, 'validator' in setting)
context.cfgimpl_reset_cache() context.cfgimpl_reset_cache()
if (opt.impl_is_multi() and if opt.impl_is_master_slaves('master'):
opt.impl_get_multitype() == multitypes.master): opt.impl_get_master_slaves().reset(self)
for slave in opt.impl_get_master_slaves():
self.reset(slave)
self._p_.resetvalue(path) self._p_.resetvalue(path)
def _isempty(self, opt, value): def _isempty(self, opt, value):
"convenience method to know if an option is empty" "convenience method to know if an option is empty"
empty = opt._empty empty = opt._empty
if (not opt.impl_is_multi() and (value is None or value == empty)) or \ if value is not undefined:
(opt.impl_is_multi() and (value == [] or empty_not_multi = not opt.impl_is_multi() and (value is None or
None in value or empty in value)): value == empty)
return True empty_multi = opt.impl_is_multi() and (value == [] or
return False None in value or
empty in value)
def _getcallback_value(self, opt, index=None, max_len=None): else:
""" empty_multi = empty_not_multi = False
retrieves a value for the options that have a callback return empty_not_multi or empty_multi
: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)
def __getitem__(self, opt): def __getitem__(self, opt):
"enables us to use the pythonic dictionary-like access to values" "enables us to use the pythonic dictionary-like access to values"
return self.getitem(opt) return self.getitem(opt)
def getitem(self, opt, path=None, validate=True, force_permissive=False, def getitem(self, opt, validate=True, force_permissive=False,
force_properties=None, validate_properties=True): 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: if path is None:
path = self._get_opt_path(opt) path = self._get_opt_path(opt)
ntime = None ntime = None
@ -170,7 +192,7 @@ class Values(object):
if is_cached: if is_cached:
if opt.impl_is_multi() and not isinstance(value, Multi): if opt.impl_is_multi() and not isinstance(value, Multi):
#load value so don't need to validate if is not a 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 return value
val = self._getitem(opt, path, validate, force_permissive, val = self._getitem(opt, path, validate, force_permissive,
force_properties, validate_properties) force_properties, validate_properties)
@ -181,85 +203,82 @@ class Values(object):
ntime = int(time()) ntime = int(time())
ntime = ntime + expires_time ntime = ntime + expires_time
self._p_.setcache(path, val, ntime) self._p_.setcache(path, val, ntime)
return val return val
def _getitem(self, opt, path, validate, force_permissive, force_properties, def _getitem(self, opt, path, validate, force_permissive, force_properties,
validate_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() context = self._getcontext()
setting = context.cfgimpl_get_settings() setting = context.cfgimpl_get_settings()
is_frozen = 'frozen' in setting[opt] is_default = self._is_default_owner(opt, path,
# For calculating properties, we need value (ie for mandatory value). validate_properties=False,
# If value is calculating with a PropertiesOptionError's option validate_meta=False)
# _getcallback_value raise a ConfigError. try:
# We can not raise ConfigError if this option should raise if index is None:
# PropertiesOptionError too. So we get config_error and raise gv_index = undefined
# ConfigError if properties did not raise. else:
config_error = None gv_index = index
force_permissives = None value = self._getvalue(opt, path, is_default, index=gv_index)
# if value has callback and is not set config_error = None
# or frozen with force_default_on_freeze except ConfigError as err:
if opt.impl_has_callback() and ( # For calculating properties, we need value (ie for mandatory
self._is_default_owner(opt, path, validate_properties=False) or # value).
(is_frozen and 'force_default_on_freeze' in setting[opt])): # If value is calculating with a PropertiesOptionError's option
lenmaster = None # _getvalue raise a ConfigError.
no_value_slave = False # We can not raise ConfigError if this option should raise
if (opt.impl_is_multi() and # PropertiesOptionError too. So we get config_error and raise
opt.impl_get_multitype() == multitypes.slave): # ConfigError if properties did not raise.
masterp = self._get_opt_path(opt.impl_get_master_slaves()) # cannot assign config_err directly in python 3.3
mastervalue = context.getattr(masterp, validate=False) config_error = err
lenmaster = len(mastervalue) # value is not set, for 'undefined' (cannot set None because of
if lenmaster == 0: # mandatory property)
value = [] value = undefined
no_value_slave = True
if not no_value_slave: if config_error is None:
try: if index is undefined:
value = self._getcallback_value(opt, max_len=lenmaster) force_index = None
except ConfigError as err: else:
# cannot assign config_err directly in python 3.3 force_index = index
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 opt.impl_is_multi(): if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate) if index is None and not isinstance(value, list):
else: value = []
value = self._getvalue(opt, path) if force_index is None:
if opt.impl_is_multi(): value = Multi(value, self.context, opt, path)
# load value so don't need to validate if is not a Multi if validate:
value = Multi(value, self.context, opt, path, validate=validate) opt.impl_validate(value, context, 'validator' in setting,
if config_error is None and validate: force_index=force_index)
opt.impl_validate(value, context, 'validator' in setting) #FIXME pas de test avec les metas ...
if config_error is None and \ #FIXME et les symlinkoption ...
self._is_default_owner(opt, path, validate_properties=False) and \ if is_default and 'force_store_value' in setting[opt]:
'force_store_value' in setting[opt]: self.setitem(opt, value, path, is_write=False,
self.setitem(opt, value, path, is_write=False, force_permissive=force_permissive)
force_permissive=force_permissive)
if validate_properties: 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_permissive=force_permissive,
force_properties=force_properties, force_properties=force_properties)
force_permissives=force_permissives)
if config_error is not None: if config_error is not None:
raise config_error raise config_error
return value return value
def __setitem__(self, opt, 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, def setitem(self, opt, value, path, force_permissive=False,
is_write=True): is_write=True):
@ -270,26 +289,11 @@ class Values(object):
opt.impl_validate(value, context, opt.impl_validate(value, context,
'validator' in context.cfgimpl_get_settings()) 'validator' in context.cfgimpl_get_settings())
if opt.impl_is_multi(): if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, setitem=True) value = Multi(value, self.context, opt, path)
# Save old value if opt.impl_is_master_slaves():
if opt.impl_get_multitype() == multitypes.master and \ opt.impl_get_master_slaves().setitem(self, opt, value, path)
self._p_.hasvalue(path):
old_value = self._p_.getvalue(path)
old_owner = self._p_.getowner(path, None)
else:
old_value = undefined
old_owner = undefined
self._setvalue(opt, path, value, force_permissive=force_permissive, self._setvalue(opt, path, value, force_permissive=force_permissive,
is_write=is_write) 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, def _setvalue(self, opt, path, value, force_permissive=False,
force_properties=None, force_properties=None,
@ -321,13 +325,15 @@ class Values(object):
path = self._get_opt_path(opt) path = self._get_opt_path(opt)
return self._getowner(opt, path, force_permissive=force_permissive) return self._getowner(opt, path, force_permissive=force_permissive)
def _getowner(self, opt, path, validate_properties=True, force_permissive=False): def _getowner(self, opt, path, validate_properties=True,
meta = self._getcontext().cfgimpl_get_meta() force_permissive=False, validate_meta=True):
if validate_properties: if validate_properties:
self._getitem(opt, path, True, force_permissive, None, True) self._getitem(opt, path, True, force_permissive, None, True)
owner = self._p_.getowner(path, owners.default) owner = self._p_.getowner(path, owners.default)
if owner is owners.default and meta is not None: if validate_meta:
owner = meta.cfgimpl_get_values()._getowner(opt, path) 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 return owner
def setowner(self, opt, owner): def setowner(self, opt, owner):
@ -349,17 +355,22 @@ class Values(object):
'').format(path, owner)) '').format(path, owner))
self._p_.setowner(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 :param config: *must* be only the **parent** config
(not the toplevel config) (not the toplevel config)
:return: boolean :return: boolean
""" """
path = self._get_opt_path(opt) 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): def _is_default_owner(self, opt, path, validate_properties=True,
return self._getowner(opt, path, validate_properties) == owners.default validate_meta=True):
return self._getowner(opt, path, validate_properties,
validate_meta=validate_meta) == owners.default
def reset_cache(self, only_expired): def reset_cache(self, only_expired):
""" """
@ -377,7 +388,8 @@ class Values(object):
:param opt: the `option.Option` object :param opt: the `option.Option` object
:returns: a string with points like "gc.dummy.my_option" :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 # information
def set_information(self, key, value): def set_information(self, key, value):
@ -457,13 +469,11 @@ class Multi(list):
that support item notation for the values of multi options""" that support item notation for the values of multi options"""
__slots__ = ('opt', 'path', 'context') __slots__ = ('opt', 'path', 'context')
def __init__(self, value, context, opt, path, validate=True, def __init__(self, value, context, opt, path):
setitem=False):
""" """
:param value: the Multi wraps a list value :param value: the Multi wraps a list value
:param context: the home config that has the values :param context: the home config that has the values
:param opt: the option object that have this Multi value :param opt: the option object that have this Multi value
:param setitem: only if set a value
""" """
if isinstance(value, Multi): if isinstance(value, Multi):
raise ValueError(_('{0} is already a Multi ').format(opt._name)) raise ValueError(_('{0} is already a Multi ').format(opt._name))
@ -474,11 +484,6 @@ class Multi(list):
self.context = context self.context = context
if not isinstance(value, list): if not isinstance(value, list):
value = [value] 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) super(Multi, self).__init__(value)
def _getcontext(self): def _getcontext(self):
@ -492,124 +497,73 @@ class Multi(list):
raise ConfigError(_('the context does not exist anymore')) raise ConfigError(_('the context does not exist anymore'))
return context 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): def __setitem__(self, index, value):
self._validate(value, index) self._validate(value, index)
#assume not checking mandatory property #assume not checking mandatory property
super(Multi, self).__setitem__(index, value) 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) """the list value can be updated (appened)
only if the option is a master only if the option is a master
""" """
context = self._getcontext() values = self._getcontext().cfgimpl_get_values()
if not force: 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}" raise SlaveError(_("cannot append a value on a multi option {0}"
" which is a slave").format(self.opt._name)) " which is a slave").format(self.opt._name))
elif self.opt.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__() index = self.__len__()
if value is undefined: 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) self._validate(value, index)
super(Multi, self).append(value) super(Multi, self).append(value)
context.cfgimpl_get_values()._setvalue(self.opt, self.path, if setitem:
self, values._setvalue(self.opt, self.path, self,
validate_properties=not force) 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)
def sort(self, cmp=None, key=None, reverse=False): def sort(self, cmp=None, key=None, reverse=False):
if self.opt.impl_get_multitype() in [multitypes.slave, if self.opt.impl_is_master_slaves():
multitypes.master]:
raise SlaveError(_("cannot sort multi option {0} if master or slave" raise SlaveError(_("cannot sort multi option {0} if master or slave"
"").format(self.opt._name)) "").format(self.opt._name))
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
if cmp is not None: 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) super(Multi, self).sort(key=key, reverse=reverse)
else: else:
super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse) 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): def reverse(self):
if self.opt.impl_get_multitype() in [multitypes.slave, if self.opt.impl_is_master_slaves():
multitypes.master]:
raise SlaveError(_("cannot reverse multi option {0} if master or " raise SlaveError(_("cannot reverse multi option {0} if master or "
"slave").format(self.opt._name)) "slave").format(self.opt._name))
super(Multi, self).reverse() 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): def insert(self, index, obj):
if self.opt.impl_get_multitype() in [multitypes.slave, if self.opt.impl_is_master_slaves():
multitypes.master]:
raise SlaveError(_("cannot insert multi option {0} if master or " raise SlaveError(_("cannot insert multi option {0} if master or "
"slave").format(self.opt._name)) "slave").format(self.opt._name))
super(Multi, self).insert(index, obj) 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): def extend(self, iterable):
if self.opt.impl_get_multitype() in [multitypes.slave, if self.opt.impl_is_master_slaves():
multitypes.master]:
raise SlaveError(_("cannot extend multi option {0} if master or " raise SlaveError(_("cannot extend multi option {0} if master or "
"slave").format(self.opt._name)) "slave").format(self.opt._name))
super(Multi, self).extend(iterable) 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): def _validate(self, value, force_index):
if value is not None: if value is not None:
@ -634,18 +588,14 @@ class Multi(list):
""" """
context = self._getcontext() context = self._getcontext()
if not force: 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}" raise SlaveError(_("cannot pop a value on a multi option {0}"
" which is a slave").format(self.opt._name)) " which is a slave").format(self.opt._name))
if self.opt.impl_get_multitype() == multitypes.master: if self.opt.impl_is_master_slaves('master'):
for slave in self.opt.impl_get_master_slaves(): self.opt.impl_get_master_slaves().pop(
values = context.cfgimpl_get_values() context.cfgimpl_get_values(), index)
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)
#set value without valid properties #set value without valid properties
ret = super(Multi, self).pop(index) 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 return ret