Merge branch 'master' into metaconfig

This commit is contained in:
2013-09-28 22:49:50 +02:00
33 changed files with 2363 additions and 817 deletions

View File

@@ -23,11 +23,9 @@ from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.i18n import _
# ____________________________________________________________
def carry_out_calculation(name,
config,
callback,
callback_params,
index=None):
def carry_out_calculation(name, config, callback, callback_params,
index=None, max_len=None):
"""a function that carries out a calculation for an option's value
:param name: the option name (`opt._name`)
@@ -40,73 +38,161 @@ def carry_out_calculation(name,
:type callback_params: dict
:param index: if an option is multi, only calculates the nth value
:type index: int
"""
#callback, callback_params = option.getcallback()
#if callback_params is None:
# callback_params = {}
tcparams = {}
one_is_multi = False
len_multi = 0
:param max_len: max length for a multi
:type max_len: int
for key, values in callback_params.items():
for value in values:
if type(value) == tuple:
path, check_disabled = value
if config is None:
if check_disabled:
continue
raise ConfigError(_('no config specified but needed'))
The callback_params is a dict. Key is used to build args (if key is '')
and kwargs (otherwise). Values are tuple of:
- values
- tuple with option and boolean's force_permissive (True when don't raise
if PropertiesOptionError)
Values could have multiple values only when key is ''.
* if no callback_params:
=> calculate()
* if callback_params={'': ('yes',)}
=> calculate('yes')
* if callback_params={'value': ('yes',)}
=> calculate(value='yes')
* if callback_params={'': ('yes', 'no')}
=> calculate('yes', 'no')
* if callback_params={'value': ('yes', 'no')}
=> ValueError()
* if callback_params={'': ((opt1, False),)}
- a simple option:
opt1 == 11
=> calculate(11)
- a multi option:
opt1 == [1, 2, 3]
=> calculate(1)
=> calculate(2)
=> calculate(3)
* if callback_params={'value': ((opt1, False),)}
- a simple option:
opt1 == 11
=> calculate(value=11)
- a multi option:
opt1 == [1, 2, 3]
=> calculate(value=1)
=> calculate(value=2)
=> calculate(value=3)
* if callback_params={'': ((opt1, False), (opt2, False))}
- a multi option with a simple option
opt1 == [1, 2, 3]
opt2 == 11
=> calculate(1, 11)
=> calculate(2, 11)
=> calculate(3, 11)
- a multi option with an other multi option but with same length
opt1 == [1, 2, 3]
opt2 == [11, 12, 13]
=> calculate(1, 11)
=> calculate(2, 12)
=> calculate(3, 13)
- a multi option with an other multi option but with different length
opt1 == [1, 2, 3]
opt2 == [11, 12]
=> ConfigError()
- a multi option without value with a simple option
opt1 == []
opt2 == 11
=> []
* if callback_params={'value': ((opt1, False), (opt2, False))}
=> ConfigError()
If index is not None, return a value, otherwise return:
* a list if one parameters have multi option
* a value otherwise
If calculate return list, this list is extend to return value.
"""
tcparams = {}
# if callback_params has a callback, launch several time calculate()
one_is_multi = False
# multi's option should have same value for all option
len_multi = None
for key, callbacks in callback_params.items():
for callbk in callbacks:
if isinstance(callbk, tuple):
# callbk is something link (opt, True|False)
option, force_permissive = callbk
path = config.cfgimpl_get_description().impl_get_path_by_opt(
option)
# get value
try:
opt_value = config._getattr(path, force_permissive=True)
opt = config.unwrap_from_path(path, force_permissive=True)
value = config._getattr(path, force_permissive=True)
except PropertiesOptionError as err:
if check_disabled:
if force_permissive:
continue
raise ConfigError(_('unable to carry out a calculation, '
'option {0} has properties: {1} for: '
'{2}').format(path, err.proptype,
'{2}').format(option._name,
err.proptype,
name))
is_multi = opt.impl_is_multi()
is_multi = option.impl_is_multi()
if is_multi:
if opt_value is not None:
len_value = len(opt_value)
if len_multi != 0 and len_multi != len_value:
raise ConfigError(_('unable to carry out a '
'calculation, option value with'
' multi types must have same '
'length for: {0}').format(name))
len_multi = len_value
len_value = len(value)
if len_multi is not None and len_multi != len_value:
raise ConfigError(_('unable to carry out a '
'calculation, option value with'
' multi types must have same '
'length for: {0}').format(name))
len_multi = len_value
one_is_multi = True
tcparams.setdefault(key, []).append((opt_value, is_multi))
tcparams.setdefault(key, []).append((value, is_multi))
else:
tcparams.setdefault(key, []).append((value, False))
# callbk is a value and not a multi
tcparams.setdefault(key, []).append((callbk, False))
# if one value is a multi, launch several time calculate
# if index is set, return a value
# if no index, return a list
if one_is_multi:
ret = []
if index:
range_ = [index]
if index < len_multi:
range_ = [index]
else:
range_ = []
ret = None
else:
range_ = range(len_multi)
if max_len and max_len < len_multi:
range_ = range(max_len)
else:
range_ = range(len_multi)
for incr in range_:
tcp = {}
params = []
args = []
kwargs = {}
for key, couples in tcparams.items():
for couple in couples:
value, ismulti = couple
if ismulti and value is not None:
if key == '':
params.append(value[incr])
else:
if len(value) > incr:
tcp[key] = value[incr]
else:
tcp[key] = ''
if ismulti:
val = value[incr]
else:
if key == '':
params.append(value)
else:
tcp[key] = value
calc = calculate(name, callback, params, tcp)
val = value
if key == '':
args.append(val)
else:
kwargs[key] = val
calc = calculate(callback, args, kwargs)
if index:
ret = calc
else:
@@ -114,28 +200,28 @@ def carry_out_calculation(name,
ret.extend(calc)
else:
ret.append(calc)
return ret
else:
tcp = {}
params = []
# no value is multi
# return a single value
args = []
kwargs = {}
for key, couples in tcparams.items():
for couple in couples:
# couple[1] (ismulti) is always False
if key == '':
value = couple[0]
params.append(value)
args.append(couple[0])
else:
tcp[key] = couple[0]
return calculate(name, callback, params, tcp)
kwargs[key] = couple[0]
return calculate(callback, args, kwargs)
def calculate(name, callback, params, tcparams):
# FIXME we don't need the option's name down there.
def calculate(callback, args, kwargs):
"""wrapper that launches the 'callback'
:param callback: callback name
:param params: in the callback's arity, the unnamed parameters
:param tcparams: in the callback's arity, the named parameters
:param callback: callback function
:param args: in the callback's arity, the unnamed parameters
:param kwargs: in the callback's arity, the named parameters
"""
return callback(*params, **tcparams)
return callback(*args, **kwargs)

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
"options handler global entry point"
# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software; you can redistribute it and/or modify
@@ -20,17 +19,23 @@
# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
# the whole pypy projet is under MIT licence
# ____________________________________________________________
"options handler global entry point"
import weakref
from tiramisu.error import PropertiesOptionError, ConfigError
from tiramisu.option import OptionDescription, Option, SymLinkOption
from tiramisu.setting import groups, Settings, default_encoding
from tiramisu.storage import get_storages
from tiramisu.value import Values
from tiramisu.storage import get_storages, get_storage, set_storage, \
_impl_getstate_setting
from tiramisu.value import Values, Multi
from tiramisu.i18n import _
class SubConfig(object):
"sub configuration management entry"
"""Sub configuration management entry.
Tree if OptionDescription's responsability. SubConfig are generated
on-demand. A Config is also a SubConfig.
Root Config is call context below
"""
__slots__ = ('_impl_context', '_impl_descr', '_impl_path')
def __init__(self, descr, context, subpath=None):
@@ -55,6 +60,7 @@ class SubConfig(object):
def cfgimpl_reset_cache(self, only_expired=False, only=('values',
'settings')):
"remove cache (in context)"
self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
def cfgimpl_get_home_by_path(self, path, force_permissive=False,
@@ -179,7 +185,9 @@ class SubConfig(object):
homeconfig, name = self.cfgimpl_get_home_by_path(name)
return homeconfig.__setattr__(name, value)
child = getattr(self.cfgimpl_get_description(), name)
if not isinstance(child, SymLinkOption):
if isinstance(child, OptionDescription):
raise TypeError(_("can't assign to an OptionDescription"))
elif not isinstance(child, SymLinkOption):
if self._impl_path is None:
path = name
else:
@@ -293,12 +301,13 @@ class SubConfig(object):
return True
try:
value = getattr(self, path)
if value == byvalue:
return True
if isinstance(value, Multi):
return byvalue in value
else:
return value == byvalue
except PropertiesOptionError: # a property is a restriction
# upon the access of the value
pass
return False
return False
def _filter_by_type():
if bytype is None:
@@ -323,15 +332,15 @@ class SubConfig(object):
continue
if not _filter_by_value():
continue
if not _filter_by_type():
continue
#remove option with propertyerror, ...
if check_properties:
if byvalue is None and check_properties:
try:
value = getattr(self, path)
except PropertiesOptionError:
# a property restricts the access of the value
continue
if not _filter_by_type():
continue
if type_ == 'value':
retval = value
elif type_ == 'path':
@@ -521,7 +530,7 @@ class CommonConfig(SubConfig):
# ____________________________________________________________
class Config(CommonConfig):
"main configuration management entry"
__slots__ = ('__weakref__', )
__slots__ = ('__weakref__', '_impl_test')
def __init__(self, descr, session_id=None, persistent=False):
""" Configuration option management master class
@@ -542,6 +551,43 @@ class Config(CommonConfig):
super(Config, self).__init__(descr, weakref.ref(self))
self._impl_build_all_paths()
self._impl_meta = None
#undocumented option used only in test script
self._impl_test = False
def __getstate__(self):
if self._impl_meta is not None:
raise ConfigError('cannot serialize Config with meta')
slots = set()
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['_impl_context', '__weakref__'])
state = {}
for slot in slots:
try:
state[slot] = getattr(self, slot)
except AttributeError:
pass
storage = self._impl_values._p_._storage
if not storage.serializable:
raise ConfigError('this storage is not serialisable, could be a '
'none persistent storage')
state['_storage'] = {'session_id': storage.session_id,
'persistent': storage.persistent}
state['_impl_setting'] = _impl_getstate_setting()
return state
def __setstate__(self, state):
for key, value in state.items():
if key not in ['_storage', '_impl_setting']:
setattr(self, key, value)
set_storage(**state['_impl_setting'])
self._impl_context = weakref.ref(self)
self._impl_settings.context = weakref.ref(self)
self._impl_values.context = weakref.ref(self)
storage = get_storage(test=self._impl_test, **state['_storage'])
self._impl_values._impl_setstate(storage)
self._impl_settings._impl_setstate(storage)
def cfgimpl_reset_cache(self,
only_expired=False,

View File

@@ -61,3 +61,35 @@ class SlaveError(Exception):
class ConstError(TypeError):
"no uniq value in _NameSpace"
pass
#Warning
class ValueWarning(UserWarning):
"""Option could warn user and not raise ValueError.
Example:
>>> import warnings
>>> from tiramisu.error import ValueWarning
>>> from tiramisu.option import StrOption, OptionDescription
>>> from tiramisu.config import Config
>>> warnings.simplefilter("always", ValueWarning)
>>> def a(val):
... raise ValueError('pouet')
...
>>> s=StrOption('s', '', validator=a, warnings_only=True)
>>> o=OptionDescription('o', '', [s])
>>> c=Config(o)
>>> c.s = 'val'
StrOption:0: ValueWarning: invalid value val for option s: pouet
>>> with warnings.catch_warnings(record=True) as w:
... c.s = 'val'
...
>>> w[0].message.opt == s
True
>>> print str(w[0].message)
invalid value val for option s: pouet
"""
def __init__(self, msg, opt):
self.opt = opt
super(ValueWarning, self).__init__(msg)

View File

@@ -25,8 +25,9 @@ import sys
from copy import copy, deepcopy
from types import FunctionType
from IPy import IP
import warnings
from tiramisu.error import ConflictError
from tiramisu.error import ConfigError, ConflictError, ValueWarning
from tiramisu.setting import groups, multitypes
from tiramisu.i18n import _
from tiramisu.autolib import carry_out_calculation
@@ -60,9 +61,8 @@ class BaseOption(object):
__setattr__ method
"""
__slots__ = ('_name', '_requires', '_properties', '_readonly',
'_consistencies', '_calc_properties', '_impl_informations',
'_state_consistencies', '_state_readonly', '_state_requires',
'_stated')
'_calc_properties', '_impl_informations',
'_state_readonly', '_state_requires', '_stated')
def __init__(self, name, doc, requires, properties):
if not valid_name(name):
@@ -72,7 +72,6 @@ class BaseOption(object):
self.impl_set_information('doc', doc)
self._calc_properties, self._requires = validate_requires_arg(
requires, self._name)
self._consistencies = None
if properties is None:
properties = tuple()
if not isinstance(properties, tuple):
@@ -97,8 +96,7 @@ class BaseOption(object):
"frozen" (which has noting to do with the high level "freeze"
propertie or "read_only" property)
"""
if not name.startswith('_state') and \
name not in ('_cache_paths', '_consistencies'):
if not name.startswith('_state') and not name.startswith('_cache'):
is_readonly = False
# never change _name
if name == '_name':
@@ -108,15 +106,12 @@ class BaseOption(object):
is_readonly = True
except:
pass
try:
if self._readonly is True:
if value is True:
# already readonly and try to re set readonly
# don't raise, just exit
return
is_readonly = True
except AttributeError:
pass
elif name != '_readonly':
try:
if self._readonly is True:
is_readonly = True
except AttributeError:
self._readonly = False
if is_readonly:
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format(
@@ -148,56 +143,6 @@ class BaseOption(object):
raise ValueError(_("information's item not found: {0}").format(
key))
# serialize/unserialize
def _impl_convert_consistencies(self, descr, load=False):
"""during serialization process, many things have to be done.
one of them is the localisation of the options.
The paths are set once for all.
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._consistencies is None:
self._state_consistencies = None
elif load and self._state_consistencies is None:
self._consistencies = None
del(self._state_consistencies)
else:
if load:
consistencies = self._state_consistencies
else:
consistencies = self._consistencies
if isinstance(consistencies, list):
new_value = []
for consistency in consistencies:
if load:
new_value.append((consistency[0],
descr.impl_get_opt_by_path(
consistency[1])))
else:
new_value.append((consistency[0],
descr.impl_get_path_by_opt(
consistency[1])))
else:
new_value = {}
for key, _consistencies in consistencies.items():
new_value[key] = []
for key_cons, _cons in _consistencies:
_list_cons = []
for _con in _cons:
if load:
_list_cons.append(descr.impl_get_opt_by_path(_con))
else:
_list_cons.append(descr.impl_get_path_by_opt(_con))
new_value[key].append((key_cons, tuple(_list_cons)))
if load:
del(self._state_consistencies)
self._consistencies = new_value
else:
self._state_consistencies = new_value
def _impl_convert_requires(self, descr, load=False):
"""export of the requires during the serialization process
@@ -240,12 +185,10 @@ class BaseOption(object):
:param descr: the parent :class:`tiramisu.option.OptionDescription`
"""
self._stated = True
self._impl_convert_consistencies(descr)
self._impl_convert_requires(descr)
try:
self._state_readonly = self._readonly
except AttributeError:
pass
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr)
self._state_readonly = self._readonly
def __getstate__(self, stated=True):
"""special method to enable the serialization with pickle
@@ -265,7 +208,8 @@ class BaseOption(object):
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['_cache_paths', '__weakref__'])
slots -= frozenset(['_cache_paths', '_cache_consistencies',
'__weakref__'])
states = {}
for slot in slots:
# remove variable if save variable converted
@@ -292,8 +236,9 @@ class BaseOption(object):
:type descr: :class:`tiramisu.option.OptionDescription`
"""
self._impl_convert_consistencies(descr, load=True)
self._impl_convert_requires(descr, load=True)
for func in dir(self):
if func.startswith('_impl_convert_'):
getattr(self, func)(descr, load=True)
try:
self._readonly = self._state_readonly
del(self._state_readonly)
@@ -322,13 +267,15 @@ class Option(BaseOption):
Reminder: an Option object is **not** a container for the value.
"""
__slots__ = ('_multi', '_validator', '_default_multi', '_default',
'_callback', '_multitype', '_master_slaves', '__weakref__')
'_state_callback', '_callback', '_multitype',
'_consistencies', '_warnings_only', '_master_slaves',
'_state_consistencies', '__weakref__')
_empty = ''
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=None,
properties=None):
callback_params=None, validator=None, validator_params=None,
properties=None, warnings_only=False):
"""
:param name: the option's name
:param doc: the option's description
@@ -344,18 +291,17 @@ class Option(BaseOption):
:param callback_params: the callback's parameter
:param validator: the name of a function which stands for a custom
validation of the value
:param validator_args: the validator's parameters
:param validator_params: the validator's parameters
:param properties: tuple of default properties
:param warnings_only: _validator and _consistencies don't raise if True
Values()._warning contain message
"""
super(Option, self).__init__(name, doc, requires, properties)
self._multi = multi
if validator is not None:
if type(validator) != FunctionType:
raise TypeError(_("validator must be a function"))
if validator_args is None:
validator_args = {}
self._validator = (validator, validator_args)
validate_callback(validator, validator_params, 'validator')
self._validator = (validator, validator_params)
else:
self._validator = None
if not self._multi and default_multi is not None:
@@ -377,11 +323,7 @@ class Option(BaseOption):
"no callback defined"
" yet for option {0}").format(name))
if callback is not None:
if type(callback) != FunctionType:
raise ValueError('callback must be a function')
if callback_params is not None and \
not isinstance(callback_params, dict):
raise ValueError('callback_params must be a dict')
validate_callback(callback, callback_params, 'callback')
self._callback = (callback, callback_params)
else:
self._callback = None
@@ -390,101 +332,124 @@ class Option(BaseOption):
default = []
self._multitype = multitypes.default
self._default_multi = default_multi
self._warnings_only = warnings_only
self.impl_validate(default)
self._default = default
self._consistencies = None
def _launch_consistency(self, func, opt, vals, context, index, opt_):
def _launch_consistency(self, func, option, value, context, index,
all_cons_opts):
"""Launch consistency now
:param func: function name, this name should start with _cons_
:type func: `str`
:param option: option that value is changing
:type option: `tiramisu.option.Option`
:param value: new value of this option
:param context: Config's context, if None, check default value instead
:type context: `tiramisu.config.Config`
:param index: only for multi option, consistency should be launch for
specified index
:type index: `int`
:param all_cons_opts: all options concerne by this consistency
:type all_cons_opts: `list` of `tiramisu.option.Option`
"""
if context is not None:
descr = context.cfgimpl_get_description()
if opt is self:
#values are for self, search opt_ values
values = vals
if context is not None:
path = descr.impl_get_path_by_opt(opt_)
values_ = context._getattr(path, validate=False)
else:
values_ = opt_.impl_getdefault()
if index is not None:
#value is not already set, could be higher
try:
values_ = values_[index]
except IndexError:
values_ = None
else:
#values are for opt_, search self values
values_ = vals
if context is not None:
path = descr.impl_get_path_by_opt(self)
values = context._getattr(path, validate=False)
else:
values = self.impl_getdefault()
if index is not None:
#value is not already set, could be higher
try:
values = values[index]
except IndexError:
values = None
if index is None and self.impl_is_multi():
for index in range(0, len(values)):
try:
value = values[index]
value_ = values_[index]
except IndexError:
value = None
value_ = None
if None not in (value, value_):
getattr(self, func)(opt_._name, value, value_)
else:
if None not in (values, values_):
getattr(self, func)(opt_._name, values, values_)
#option is also in all_cons_opts
if option not in all_cons_opts:
raise ConfigError(_('option not in all_cons_opts'))
def impl_validate(self, value, context=None, validate=True):
all_cons_vals = []
for opt in all_cons_opts:
#get value
if option == opt:
opt_value = value
else:
#if context, calculate value, otherwise get default value
if context is not None:
opt_value = context._getattr(
descr.impl_get_path_by_opt(opt), validate=False)
else:
opt_value = opt.impl_getdefault()
#append value
if not self.impl_is_multi() or option == opt:
all_cons_vals.append(opt_value)
else:
#value is not already set, could be higher index
try:
all_cons_vals.append(opt_value[index])
except IndexError:
#so return if no value
return
getattr(self, func)(all_cons_opts, all_cons_vals)
def impl_validate(self, value, context=None, validate=True,
force_index=None):
"""
:param value: the option's value
:param context: Config's context
:type context: :class:`tiramisu.config.Config`
:param validate: if true enables ``self._validator`` validation
:type validate: boolean
:param force_no_multi: if multi, value has to be a list
not if force_no_multi is True
:type force_no_multi: boolean
"""
if not validate:
return
def val_validator(val):
if self._validator is not None:
callback_params = deepcopy(self._validator[1])
callback_params.setdefault('', []).insert(0, val)
return carry_out_calculation(self._name, config=context,
callback=self._validator[0],
callback_params=callback_params)
else:
return True
if self._validator[1] is not None:
validator_params = deepcopy(self._validator[1])
if '' in validator_params:
lst = list(validator_params[''])
lst.insert(0, val)
validator_params[''] = tuple(lst)
else:
validator_params[''] = (val,)
else:
validator_params = {'': (val,)}
# Raise ValueError if not valid
carry_out_calculation(self._name, config=context,
callback=self._validator[0],
callback_params=validator_params)
def do_validation(_value, _index=None):
if _value is None:
return True
if not val_validator(_value):
raise ValueError(_("invalid value {0} "
"for option {1} for object {2}"
).format(_value,
self._name,
self.__class__.__name__))
return
# option validation
self._validate(_value)
try:
self._validate(_value)
# valid with self._validator
val_validator(_value)
# if not context launch consistency validation
if context is not None:
descr._valid_consistency(self, _value, context, _index)
self._second_level_validation(_value)
except ValueError as err:
raise ValueError(_("invalid value {0} for option {1}: {2}"
"").format(_value, self._name, err))
if context is not None:
descr._valid_consistency(self, _value, context, _index)
msg = _("invalid value {0} for option {1}: {2}").format(
_value, self._name, err)
if self._warnings_only:
warnings.warn_explicit(ValueWarning(msg, self),
ValueWarning,
self.__class__.__name__, 0)
else:
raise ValueError(msg)
# generic calculation
if context is not None:
descr = context.cfgimpl_get_description()
if not self._multi:
do_validation(value)
if not self._multi or force_index is not None:
do_validation(value, force_index)
else:
if not isinstance(value, list):
raise ValueError(_("invalid value {0} for option {1} "
"which must be a list").format(value,
raise ValueError(_("which must be a list").format(value,
self._name))
for index in range(0, len(value)):
val = value[index]
for index, val in enumerate(value):
do_validation(val, index)
def impl_getdefault(self, default_multi=False):
@@ -529,29 +494,133 @@ class Option(BaseOption):
def impl_is_multi(self):
return self._multi
def impl_add_consistency(self, func, opt):
def impl_add_consistency(self, func, *other_opts):
"""Add consistency means that value will be validate with other_opts
option's values.
:param func: function's name
:type func: `str`
:param other_opts: options used to validate value
:type other_opts: `list` of `tiramisu.option.Option`
"""
if self._consistencies is None:
self._consistencies = []
if not isinstance(opt, Option):
raise ValueError('consistency must be set with an option')
if self is opt:
raise ValueError('cannot add consistency with itself')
if self.impl_is_multi() != opt.impl_is_multi():
raise ValueError('options in consistency'
' should be multi in two sides')
for opt in other_opts:
if not isinstance(opt, Option):
raise ConfigError(_('consistency should be set with an option'))
if self is opt:
raise ConfigError(_('cannot add consistency with itself'))
if self.impl_is_multi() != opt.impl_is_multi():
raise ConfigError(_('every options in consistency should be '
'multi or none'))
func = '_cons_{0}'.format(func)
self._launch_consistency(func,
self,
self.impl_getdefault(),
None, None, opt)
self._consistencies.append((func, opt))
all_cons_opts = tuple([self] + list(other_opts))
value = self.impl_getdefault()
if value is not None:
if self.impl_is_multi():
for idx, val in enumerate(value):
self._launch_consistency(func, self, val, None,
idx, all_cons_opts)
else:
self._launch_consistency(func, self, value, None,
None, all_cons_opts)
self._consistencies.append((func, all_cons_opts))
self.impl_validate(self.impl_getdefault())
def _cons_not_equal(self, optname, value, value_):
if value == value_:
raise ValueError(_("invalid value {0} for option {1} "
"must be different as {2} option"
"").format(value, self._name, optname))
def _cons_not_equal(self, opts, vals):
for idx_inf, val_inf in enumerate(vals):
for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
if val_inf == val_sup is not None:
raise ValueError(_("same value for {0} and {1}").format(
opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
def _impl_convert_callbacks(self, descr, load=False):
if not load and self._callback is None:
self._state_callback = None
elif load and self._state_callback is None:
self._callback = None
del(self._state_callback)
else:
if load:
callback, callback_params = self._state_callback
else:
callback, callback_params = self._callback
if callback_params is not None:
cllbck_prms = {}
for key, values in callback_params.items():
vls = []
for value in values:
if isinstance(value, tuple):
if load:
value = (descr.impl_get_opt_by_path(value[0]),
value[1])
else:
value = (descr.impl_get_path_by_opt(value[0]),
value[1])
vls.append(value)
cllbck_prms[key] = tuple(vls)
else:
cllbck_prms = None
if load:
del(self._state_callback)
self._callback = (callback, cllbck_prms)
else:
self._state_callback = (callback, cllbck_prms)
# serialize/unserialize
def _impl_convert_consistencies(self, descr, load=False):
"""during serialization process, many things have to be done.
one of them is the localisation of the options.
The paths are set once for all.
:type descr: :class:`tiramisu.option.OptionDescription`
:param load: `True` if we are at the init of the option description
:type load: bool
"""
if not load and self._consistencies is None:
self._state_consistencies = None
elif load and self._state_consistencies is None:
self._consistencies = None
del(self._state_consistencies)
else:
if load:
consistencies = self._state_consistencies
else:
consistencies = self._consistencies
if isinstance(consistencies, list):
new_value = []
for consistency in consistencies:
values = []
for obj in consistency[1]:
if load:
values.append(descr.impl_get_opt_by_path(obj))
else:
values.append(descr.impl_get_path_by_opt(obj))
new_value.append((consistency[0], tuple(values)))
else:
new_value = {}
for key, _consistencies in consistencies.items():
new_value[key] = []
for key_cons, _cons in _consistencies:
_list_cons = []
for _con in _cons:
if load:
_list_cons.append(
descr.impl_get_opt_by_path(_con))
else:
_list_cons.append(
descr.impl_get_path_by_opt(_con))
new_value[key].append((key_cons, tuple(_list_cons)))
if load:
del(self._state_consistencies)
self._consistencies = new_value
else:
self._state_consistencies = new_value
def _second_level_validation(self, value):
pass
class ChoiceOption(Option):
@@ -566,7 +635,7 @@ class ChoiceOption(Option):
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_args=None, properties=()):
validator_params=None, properties=None, warnings_only=False):
"""
:param values: is a list of values the option can possibly take
"""
@@ -584,8 +653,9 @@ class ChoiceOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
properties=properties)
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def impl_get_values(self):
return self._values
@@ -662,7 +732,7 @@ class SymLinkOption(BaseOption):
__slots__ = ('_name', '_opt', '_state_opt')
_opt_type = 'symlink'
#not return _opt consistencies
_consistencies = {}
_consistencies = None
def __init__(self, name, opt):
self._name = name
@@ -688,23 +758,19 @@ class SymLinkOption(BaseOption):
del(self._state_opt)
super(SymLinkOption, self)._impl_setstate(descr)
def _impl_convert_consistencies(self, descr, load=False):
if load:
del(self._state_consistencies)
else:
self._state_consistencies = None
class IPOption(Option):
"represents the choice of an ip"
__slots__ = ('_only_private',)
__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_args=None,
properties=None, only_private=False):
self._only_private = only_private
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,
@@ -712,14 +778,21 @@ class IPOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
properties=properties)
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
try:
IP('{0}/32'.format(value))
except ValueError:
raise ValueError(_('invalid IP {0}').format(self._name))
def _second_level_validation(self, value):
ip = IP('{0}/32'.format(value))
if ip.iptype() == 'RESERVED':
if not self._allow_reserved and ip.iptype() == 'RESERVED':
raise ValueError(_("IP mustn't not be in reserved class"))
if self._only_private and not ip.iptype() == 'PRIVATE':
if self._private_only and not ip.iptype() == 'PRIVATE':
raise ValueError(_("IP must be in private class"))
@@ -738,10 +811,10 @@ class PortOption(Option):
def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_args=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):
allow_private=False, warnings_only=False):
self._allow_range = allow_range
self._min_value = None
self._max_value = None
@@ -772,8 +845,9 @@ class PortOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
properties=properties)
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
if self._allow_range and ":" in str(value):
@@ -798,9 +872,15 @@ class NetworkOption(Option):
_opt_type = 'network'
def _validate(self, value):
try:
IP(value)
except ValueError:
raise ValueError(_('invalid network address {0}').format(self._name))
def _second_level_validation(self, value):
ip = IP(value)
if ip.iptype() == 'RESERVED':
raise ValueError(_("network mustn't not be in reserved class"))
raise ValueError(_("network shall not be in reserved class"))
class NetmaskOption(Option):
@@ -809,18 +889,26 @@ class NetmaskOption(Option):
_opt_type = 'netmask'
def _validate(self, value):
IP('0.0.0.0/{0}'.format(value))
try:
IP('0.0.0.0/{0}'.format(value))
except ValueError:
raise ValueError(_('invalid netmask address {0}').format(self._name))
def _cons_network_netmask(self, optname, value, value_):
def _cons_network_netmask(self, opts, vals):
#opts must be (netmask, network) options
self.__cons_netmask(optname, value, value_, False)
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], False)
def _cons_ip_netmask(self, optname, value, value_):
def _cons_ip_netmask(self, opts, vals):
#opts must be (netmask, ip) options
self.__cons_netmask(optname, value, value_, True)
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], True)
#def __cons_netmask(self, opt, value, context, index, opts, make_net):
def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
if len(opts) != 2:
raise ConfigError(_('invalid len for opts'))
msg = None
try:
ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
@@ -846,23 +934,48 @@ class NetmaskOption(Option):
else:
msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
if msg is not None:
raise ValueError(msg.format(val_ipnetwork, optname,
raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
val_netmask, self._name))
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 {0}').format(self._name))
def _cons_broadcast(self, opts, vals):
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"
"""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')
_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_args=None,
properties=None, allow_ip=False, type_='domainname'):
#netbios: for MS domain
#hostname: to identify the device
#domainname:
#fqdn: with tld, not supported yet
callback_params=None, validator=None, validator_params=None,
properties=None, allow_ip=False, type_='domainname',
warnings_only=False):
if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_))
self._type = type_
@@ -876,8 +989,9 @@ class DomainnameOption(Option):
requires=requires,
multi=multi,
validator=validator,
validator_args=validator_args,
properties=properties)
validator_params=validator_params,
properties=properties,
warnings_only=warnings_only)
def _validate(self, value):
if self._allow_ip is True:
@@ -915,9 +1029,9 @@ class OptionDescription(BaseOption):
"""
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_state_group_type', '_properties', '_children',
'_consistencies', '_calc_properties', '__weakref__',
'_cache_consistencies', '_calc_properties', '__weakref__',
'_readonly', '_impl_informations', '_state_requires',
'_state_consistencies', '_stated', '_state_readonly')
'_stated', '_state_readonly')
_opt_type = 'optiondescription'
def __init__(self, name, doc, children, requires=None, properties=None):
@@ -938,6 +1052,7 @@ class OptionDescription(BaseOption):
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
@@ -1011,12 +1126,11 @@ class OptionDescription(BaseOption):
if not force_no_consistencies and \
option._consistencies is not None:
for consistency in option._consistencies:
func, opt = consistency
opts = (option, opt)
_consistencies.setdefault(opt,
[]).append((func, opts))
_consistencies.setdefault(option,
[]).append((func, opts))
func, all_cons_opts = consistency
for opt in all_cons_opts:
_consistencies.setdefault(opt,
[]).append((func,
all_cons_opts))
else:
_currpath.append(attr)
option.impl_build_cache(cache_path,
@@ -1028,7 +1142,12 @@ class OptionDescription(BaseOption):
if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path))
if not force_no_consistencies:
self._consistencies = _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):
@@ -1099,17 +1218,18 @@ class OptionDescription(BaseOption):
def impl_get_group_type(self):
return self._group_type
def _valid_consistency(self, opt, value, context=None, index=None):
consistencies = self._consistencies.get(opt)
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 consistency in consistencies:
opt_ = consistency[1]
ret = opt_[0]._launch_consistency(consistency[0],
opt,
value,
context,
index,
opt_[1])
for func, all_cons_opts in consistencies:
#all_cons_opts[0] is the option where func is set
ret = all_cons_opts[0]._launch_consistency(func, option,
value,
context, index,
all_cons_opts)
if ret is False:
return False
return True
@@ -1149,6 +1269,7 @@ class OptionDescription(BaseOption):
"""
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)
@@ -1252,3 +1373,34 @@ def validate_requires_arg(requires, name):
require[3], require[4], require[5]))
ret.append(tuple(ret_action))
return frozenset(config_action.keys()), tuple(ret)
def validate_callback(callback, callback_params, type_):
if type(callback) != FunctionType:
raise ValueError(_('{0} should be a function').format(type_))
if callback_params is not None:
if not isinstance(callback_params, dict):
raise ValueError(_('{0}_params should 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} should not have '
'length different to 1').format(type_,
key))
if not isinstance(callbacks, tuple):
raise ValueError(_('{0}_params should be tuple for key "{1}"'
).format(type_, key))
for callbk in callbacks:
if isinstance(callbk, tuple):
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 should have an option '
'not a {0} for first argument'
).format(type_, type(option)))
if force_permissive not in [True, False]:
raise ValueError(_('{0}_params should have a boolean'
' not a {0} for second argument'
).format(type_, type(
force_permissive)))

View File

@@ -105,7 +105,7 @@ rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
# ____________________________________________________________
class _NameSpace:
class _NameSpace(object):
"""convenient class that emulates a module
and builds constants (that is, unique names)
when attribute is added, we cannot delete it
@@ -385,13 +385,17 @@ class Settings(object):
#____________________________________________________________
def validate_properties(self, opt_or_descr, is_descr, is_write, path,
value=None, force_permissive=False,
force_properties=None):
force_properties=None, force_permissives=None):
"""
validation upon the properties related to `opt_or_descr`
:param opt_or_descr: an option or an option description object
:param force_permissive: behaves as if the permissive property
was present
:param force_properties: set() with properties that is force to add
in global properties
:param force_permissives: set() with permissives that is force to add
in global permissives
:param is_descr: we have to know if we are in an option description,
just because the mandatory property
doesn't exist here
@@ -408,6 +412,8 @@ class Settings(object):
self_properties = copy(self._getproperties())
if force_permissive is True or 'permissive' in self_properties:
properties -= self._p_.getpermissive()
if force_permissives is not None:
properties -= force_permissives
# global properties
if force_properties is not None:
@@ -585,3 +591,23 @@ class Settings(object):
:returns: path
"""
return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)
def get_modified_properties(self):
return self._p_.get_modified_properties()
def get_modified_permissives(self):
return self._p_.get_modified_permissives()
def __getstate__(self):
return {'_p_': self._p_, '_owner': str(self._owner)}
def _impl_setstate(self, storage):
self._p_._storage = storage
def __setstate__(self, states):
self._p_ = states['_p_']
try:
self._owner = getattr(owners, states['_owner'])
except AttributeError:
owners.addowner(states['_owner'])
self._owner = getattr(owners, states['_owner'])

View File

@@ -68,7 +68,7 @@ class StorageType(object):
storage_type = StorageType()
def set_storage(name, **args):
def set_storage(name, **kwargs):
"""Change storage's configuration
:params name: is the storage name. If storage is already set, cannot
@@ -77,16 +77,31 @@ def set_storage(name, **args):
Other attributes are differents according to the selected storage's name
"""
storage_type.set(name)
settings = storage_type.get().Setting()
for option, value in args.items():
setting = storage_type.get().setting
for option, value in kwargs.items():
try:
getattr(settings, option)
setattr(settings, option, value)
getattr(setting, option)
setattr(setting, option, value)
except AttributeError:
raise ValueError(_('option {0} not already exists in storage {1}'
'').format(option, name))
def _impl_getstate_setting():
setting = storage_type.get().setting
state = {'name': storage_type.storage_type}
for var in dir(setting):
if not var.startswith('_'):
state[var] = getattr(setting, var)
return state
def get_storage(session_id, persistent, test):
"""all used when __setstate__ a Config
"""
return storage_type.get().Storage(session_id, persistent, test)
def get_storages(context, session_id, persistent):
def gen_id(config):
return str(id(config)) + str(time())

View File

@@ -26,6 +26,6 @@ use it. But if something goes wrong, you will lost your modifications.
"""
from .value import Values
from .setting import Settings
from .storage import Setting, Storage, list_sessions, delete_session
from .storage import setting, Storage, list_sessions, delete_session
__all__ = (Setting, Values, Settings, Storage, list_sessions, delete_session)
__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session)

View File

@@ -17,7 +17,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ____________________________________________________________
from ..cache import Cache
from ..util import Cache
class Settings(Cache):
@@ -50,12 +50,21 @@ class Settings(Cache):
except KeyError:
pass
def get_properties(self, context):
return self._properties
# permissive
def setpermissive(self, path, permissive):
self._permissives[path] = frozenset(permissive)
def getpermissive(self, path=None):
return self._permissives.get(path, frozenset())
def get_modified_properties(self):
"""return all modified settings in a dictionary
example: {'path1': set(['prop1', 'prop2'])}
"""
return self._properties
def get_modified_permissives(self):
"""return all modified permissives in a dictionary
example: {'path1': set(['perm1', 'perm2'])}
"""
return self._permissives

View File

@@ -18,9 +18,10 @@
# ____________________________________________________________
from tiramisu.i18n import _
from tiramisu.error import ConfigError
from ..util import SerializeObject
class Setting(object):
class Setting(SerializeObject):
"""Dictionary storage has no particular setting.
"""
pass
@@ -39,15 +40,18 @@ def delete_session(session_id):
class Storage(object):
__slots__ = ('session_id', 'values', 'settings')
__slots__ = ('session_id', 'persistent')
storage = 'dictionary'
#if object could be serializable
serializable = True
def __init__(self, session_id, persistent):
if session_id in _list_sessions:
def __init__(self, session_id, persistent, test=False):
if not test and session_id in _list_sessions:
raise ValueError(_('session already used'))
if persistent:
raise ValueError(_('a dictionary cannot be persistent'))
self.session_id = session_id
self.persistent = persistent
_list_sessions.append(self.session_id)
def __del__(self):

View File

@@ -18,7 +18,7 @@
#
# ____________________________________________________________
from ..cache import Cache
from ..util import Cache
class Values(Cache):

View File

@@ -24,6 +24,6 @@ You should not configure differents Configs with same session_id.
"""
from .value import Values
from .setting import Settings
from .storage import Setting, Storage, list_sessions, delete_session
from .storage import setting, Storage, list_sessions, delete_session
__all__ = (Setting, Values, Settings, Storage, list_sessions, delete_session)
__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session)

View File

@@ -30,22 +30,22 @@ class Settings(Sqlite3DB):
permissives_table += 'primary key, permissives text)'
# should init cache too
super(Settings, self).__init__(storage)
self.storage.execute(settings_table, commit=False)
self.storage.execute(permissives_table)
self._storage.execute(settings_table, commit=False)
self._storage.execute(permissives_table)
# propertives
def setproperties(self, path, properties):
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM property WHERE path = ?", (path,),
False)
self.storage.execute("INSERT INTO property(path, properties) VALUES "
"(?, ?)", (path,
self._sqlite_encode(properties)))
self._storage.execute("DELETE FROM property WHERE path = ?", (path,),
False)
self._storage.execute("INSERT INTO property(path, properties) VALUES "
"(?, ?)", (path,
self._sqlite_encode(properties)))
def getproperties(self, path, default_properties):
path = self._sqlite_encode_path(path)
value = self.storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,))
value = self._storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,))
if value is None:
return set(default_properties)
else:
@@ -53,42 +53,53 @@ class Settings(Sqlite3DB):
def hasproperties(self, path):
path = self._sqlite_encode_path(path)
return self.storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,)) is not None
return self._storage.select("SELECT properties FROM property WHERE "
"path = ?", (path,)) is not None
def reset_all_propertives(self):
self.storage.execute("DELETE FROM property")
self._storage.execute("DELETE FROM property")
def reset_properties(self, path):
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM property WHERE path = ?", (path,))
def get_properties(self, context):
"""return all properties in a dictionary
"""
ret = {}
for path, properties in self.storage.select("SELECT * FROM property",
only_one=False):
path = self._sqlite_decode_path(path)
properties = self._sqlite_decode(properties)
ret[path] = properties
return ret
self._storage.execute("DELETE FROM property WHERE path = ?", (path,))
# permissive
def setpermissive(self, path, permissive):
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
False)
self.storage.execute("INSERT INTO permissive(path, permissives) "
"VALUES (?, ?)", (path,
self._sqlite_encode(permissive)
))
self._storage.execute("DELETE FROM permissive WHERE path = ?", (path,),
False)
self._storage.execute("INSERT INTO permissive(path, permissives) "
"VALUES (?, ?)", (path,
self._sqlite_encode(permissive)
))
def getpermissive(self, path='_none'):
permissives = self.storage.select("SELECT permissives FROM "
"permissive WHERE path = ?",
(path,))
permissives = self._storage.select("SELECT permissives FROM "
"permissive WHERE path = ?",
(path,))
if permissives is None:
return frozenset()
else:
return frozenset(self._sqlite_decode(permissives[0]))
def get_modified_properties(self):
"""return all modified settings in a dictionary
example: {'path1': set(['prop1', 'prop2'])}
"""
ret = {}
for path, properties in self._storage.select("SELECT * FROM property",
only_one=False):
path = self._sqlite_decode_path(path)
ret[path] = self._sqlite_decode(properties)
return ret
def get_modified_permissives(self):
"""return all modified permissives in a dictionary
example: {'path1': set(['perm1', 'perm2'])}
"""
ret = {}
for path, permissives in self._storage.select("SELECT * FROM permissive",
only_one=False):
path = self._sqlite_decode_path(path)
ret[path] = self._sqlite_decode(permissives)
return ret

View File

@@ -17,8 +17,11 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ____________________________________________________________
from cPickle import loads, dumps
from ..cache import Cache
try:
from cPickle import loads, dumps
except ImportError:
from pickle import loads, dumps
from ..util import Cache
class Sqlite3DB(Cache):

View File

@@ -22,9 +22,10 @@ from os import unlink
from os.path import basename, splitext, join
import sqlite3
from glob import glob
from ..util import SerializeObject
class Setting(object):
class Setting(SerializeObject):
""":param extension: database file extension (by default: db)
:param dir_database: root database directory (by default: /tmp)
"""
@@ -52,13 +53,17 @@ def delete_session(session_id):
class Storage(object):
__slots__ = ('_conn', '_cursor', 'persistent', '_session_id')
__slots__ = ('_conn', '_cursor', 'persistent', 'session_id', 'serializable')
storage = 'sqlite3'
def __init__(self, session_id, persistent):
def __init__(self, session_id, persistent, test=False):
self.persistent = persistent
self._session_id = session_id
self._conn = sqlite3.connect(_gen_filename(self._session_id))
if self.persistent:
self.serializable = True
else:
self.serializable = False
self.session_id = session_id
self._conn = sqlite3.connect(_gen_filename(self.session_id))
self._conn.text_factory = str
self._cursor = self._conn.cursor()
@@ -80,4 +85,4 @@ class Storage(object):
self._cursor.close()
self._conn.close()
if not self.persistent:
delete_session(self._session_id)
delete_session(self.session_id)

View File

@@ -32,11 +32,11 @@ class Values(Sqlite3DB):
super(Values, self).__init__(storage)
values_table = 'CREATE TABLE IF NOT EXISTS value(path text primary '
values_table += 'key, value text, owner text)'
self.storage.execute(values_table, commit=False)
self._storage.execute(values_table, commit=False)
informations_table = 'CREATE TABLE IF NOT EXISTS information(key text primary '
informations_table += 'key, value text)'
self.storage.execute(informations_table)
for owner in self.storage.select("SELECT DISTINCT owner FROM value", tuple(), False):
self._storage.execute(informations_table)
for owner in self._storage.select("SELECT DISTINCT owner FROM value", tuple(), False):
try:
getattr(owners, owner[0])
except AttributeError:
@@ -44,7 +44,7 @@ class Values(Sqlite3DB):
# sqlite
def _sqlite_select(self, path):
return self.storage.select("SELECT value FROM value WHERE path = ?",
return self._storage.select("SELECT value FROM value WHERE path = ?",
(path,))
# value
@@ -54,7 +54,7 @@ class Values(Sqlite3DB):
"""
self.resetvalue(path)
path = self._sqlite_encode_path(path)
self.storage.execute("INSERT INTO value(path, value, owner) VALUES "
self._storage.execute("INSERT INTO value(path, value, owner) VALUES "
"(?, ?, ?)", (path, self._sqlite_encode(value),
str(owner)))
@@ -76,14 +76,14 @@ class Values(Sqlite3DB):
"""remove value means delete value in storage
"""
path = self._sqlite_encode_path(path)
self.storage.execute("DELETE FROM value WHERE path = ?", (path,))
self._storage.execute("DELETE FROM value WHERE path = ?", (path,))
def get_modified_values(self):
"""return all values in a dictionary
example: {option1: (owner, 'value1'), option2: (owner, 'value2')}
"""
ret = {}
for path, value, owner in self.storage.select("SELECT * FROM value",
for path, value, owner in self._storage.select("SELECT * FROM value",
only_one=False):
path = self._sqlite_decode_path(path)
owner = getattr(owners, owner)
@@ -97,7 +97,7 @@ class Values(Sqlite3DB):
"""change owner for an option
"""
path = self._sqlite_encode_path(path)
self.storage.execute("UPDATE value SET owner = ? WHERE path = ?",
self._storage.execute("UPDATE value SET owner = ? WHERE path = ?",
(str(owner), path))
def getowner(self, path, default):
@@ -105,7 +105,7 @@ class Values(Sqlite3DB):
return: owner object
"""
path = self._sqlite_encode_path(path)
owner = self.storage.select("SELECT owner FROM value WHERE path = ?",
owner = self._storage.select("SELECT owner FROM value WHERE path = ?",
(path,))
if owner is None:
return default
@@ -125,9 +125,9 @@ class Values(Sqlite3DB):
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
self.storage.execute("DELETE FROM information WHERE key = ?", (key,),
self._storage.execute("DELETE FROM information WHERE key = ?", (key,),
False)
self.storage.execute("INSERT INTO information(key, value) VALUES "
self._storage.execute("INSERT INTO information(key, value) VALUES "
"(?, ?)", (key, self._sqlite_encode(value)))
def get_information(self, key):
@@ -135,7 +135,7 @@ class Values(Sqlite3DB):
:param key: the item string (ex: "help")
"""
value = self.storage.select("SELECT value FROM information WHERE key = ?",
value = self._storage.select("SELECT value FROM information WHERE key = ?",
(key,))
if value is None:
raise ValueError("not found")

View File

@@ -17,15 +17,65 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ____________________________________________________________
from tiramisu.setting import owners
class SerializeObject(object):
def __getstate__(self):
ret = {}
for key in dir(self):
if not key.startswith('__'):
ret[key] = getattr(self, key)
return ret
class Cache(object):
__slots__ = ('_cache', 'storage')
__slots__ = ('_cache', '_storage')
key_is_path = False
def __init__(self, storage):
self._cache = {}
self.storage = storage
self._storage = storage
def __getstate__(self):
slots = set()
for subclass in self.__class__.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
slots -= frozenset(['__weakref__', '_storage'])
states = {}
for slot in slots:
try:
value = getattr(self, slot)
#value has owners object, need 'str()' it
if slot == '_values':
_value = {}
for key, values in value.items():
vals = list(values)
vals[0] = str(vals[0])
_value[key] = tuple(vals)
states[slot] = _value
else:
states[slot] = value
except AttributeError:
pass
return states
def __setstate__(self, states):
for key, value in states.items():
#value has owners object, need to reconstruct it
if key == '_values':
_value = {}
for key_, values_ in value.items():
vals = list(values_)
try:
vals[0] = getattr(owners, vals[0])
except AttributeError:
owners.addowner(vals[0])
vals[0] = getattr(owners, vals[0])
_value[key_] = tuple(vals)
value = _value
setattr(self, key, value)
def setcache(self, path, val, time):
self._cache[path] = (val, time)

View File

@@ -106,7 +106,8 @@ class Values(object):
path = self._get_opt_path(opt)
if self._p_.hasvalue(path):
setting = self.context().cfgimpl_get_settings()
opt.impl_validate(opt.impl_getdefault(), self.context(),
opt.impl_validate(opt.impl_getdefault(),
self.context(),
'validator' in setting)
self.context().cfgimpl_reset_cache()
if (opt.impl_is_multi() and
@@ -124,7 +125,7 @@ class Values(object):
return True
return False
def _getcallback_value(self, opt, index=None):
def _getcallback_value(self, opt, index=None, max_len=None):
"""
retrieves a value for the options that have a callback
@@ -139,7 +140,7 @@ class Values(object):
return carry_out_calculation(opt._name, config=self.context(),
callback=callback,
callback_params=callback_params,
index=index)
index=index, max_len=max_len)
def __getitem__(self, opt):
"enables us to use the pythonic dictionary-like access to values"
@@ -177,11 +178,20 @@ class Values(object):
# options with callbacks
setting = self.context().cfgimpl_get_settings()
is_frozen = 'frozen' in setting[opt]
# For calculating properties, we need value (ie for mandatory value).
# If value is calculating with a PropertiesOptionError's option
# _getcallback_value raise a ConfigError.
# We can not raise ConfigError if this option should raise
# PropertiesOptionError too. So we get config_error and raise
# ConfigError if properties did not raise.
config_error = None
force_permissives = None
# if value is callback and is not set
# or frozen with force_default_on_freeze
if opt.impl_has_callback() and (
self._is_default_owner(path) or
(is_frozen and 'force_default_on_freeze' in setting[opt])):
lenmaster = None
no_value_slave = False
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
@@ -193,15 +203,25 @@ class Values(object):
no_value_slave = True
if not no_value_slave:
value = self._getcallback_value(opt)
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 opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate)
# suppress value if already set
self.reset(opt, path)
try:
value = self._getcallback_value(opt, max_len=lenmaster)
except ConfigError as err:
# cannot assign config_err directly in python 3.3
config_error = err
value = None
# should not raise PropertiesOptionError if option is
# mandatory
force_permissives = set(['mandatory'])
else:
if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.slave):
if not isinstance(value, list):
value = [value for i in range(lenmaster)]
if config_error is None:
if opt.impl_is_multi():
value = Multi(value, self.context, opt, path, validate)
# suppress value if already set
self.reset(opt, path)
# frozen and force default
elif is_frozen and 'force_default_on_freeze' in setting[opt]:
value = self._getdefault(opt)
@@ -209,15 +229,18 @@ class Values(object):
value = Multi(value, self.context, opt, path, validate)
else:
value = self._getvalue(opt, path, validate)
if validate:
if config_error is None and validate:
opt.impl_validate(value, self.context(), 'validator' in setting)
if self._is_default_owner(path) and \
if config_error is None and self._is_default_owner(path) and \
'force_store_value' in setting[opt]:
self.setitem(opt, value, path, is_write=False)
if validate_properties:
setting.validate_properties(opt, False, False, value=value, path=path,
force_permissive=force_permissive,
force_properties=force_properties)
force_properties=force_properties,
force_permissives=force_permissives)
if config_error is not None:
raise config_error
return value
def __setitem__(self, opt, value):
@@ -231,7 +254,7 @@ class Values(object):
opt.impl_validate(value, self.context(),
'validator' in self.context().cfgimpl_get_settings())
if opt.impl_is_multi() and not isinstance(value, Multi):
value = Multi(value, self.context, opt, path)
value = Multi(value, self.context, opt, path, setitem=True)
self._setvalue(opt, path, value, force_permissive=force_permissive,
is_write=is_write)
@@ -339,6 +362,15 @@ class Values(object):
raise ValueError(_("information's item"
" not found: {0}").format(key))
def __getstate__(self):
return {'_p_': self._p_}
def _impl_setstate(self, storage):
self._p_._storage = storage
def __setstate__(self, states):
self._p_ = states['_p_']
# ____________________________________________________________
# multi types
@@ -349,11 +381,13 @@ class Multi(list):
that support item notation for the values of multi options"""
__slots__ = ('opt', 'path', 'context')
def __init__(self, value, context, opt, path, validate=True):
def __init__(self, value, context, opt, path, validate=True,
setitem=False):
"""
:param value: the Multi wraps a list value
:param context: the home config that has the values
:param opt: the option object that have this Multi value
:param setitem: only if set a value
"""
self.opt = opt
self.path = path
@@ -363,27 +397,35 @@ class Multi(list):
if not isinstance(value, list):
value = [value]
if validate and self.opt.impl_get_multitype() == multitypes.slave:
value = self._valid_slave(value)
elif self.opt.impl_get_multitype() == multitypes.master:
value = self._valid_slave(value, setitem)
elif validate and self.opt.impl_get_multitype() == multitypes.master:
self._valid_master(value)
super(Multi, self).__init__(value)
def _valid_slave(self, value):
def _valid_slave(self, value, setitem):
#if slave, had values until master's one
values = self.context().cfgimpl_get_values()
masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt(
self.opt.impl_get_master_slaves())
mastervalue = getattr(self.context(), masterp)
masterlen = len(mastervalue)
valuelen = len(value)
is_default_owner = not values._is_default_owner(self.path) or setitem
if valuelen > masterlen or (valuelen < masterlen and
not self.context().cfgimpl_get_values(
)._is_default_owner(self.path)):
is_default_owner):
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):
value.append(self.opt.impl_getdefault_multi())
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
@@ -401,13 +443,22 @@ class Multi(list):
self.opt._name, slave._name))
elif len(value_slave) < masterlen:
for num in range(0, masterlen - len(value_slave)):
value_slave.append(slave.impl_getdefault_multi(),
force=True)
if slave.impl_has_callback():
# if callback add a value, but this value will not
# change anymore automaticly (because this value
# has owner)
index = value_slave.__len__()
value_slave.append(
values._getcallback_value(slave, index=index),
force=True)
else:
value_slave.append(slave.impl_getdefault_multi(),
force=True)
def __setitem__(self, key, value):
self._validate(value)
def __setitem__(self, index, value):
self._validate(value, index)
#assume not checking mandatory property
super(Multi, self).__setitem__(key, value)
super(Multi, self).__setitem__(index, value)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
def append(self, value, force=False):
@@ -425,15 +476,17 @@ class Multi(list):
#Force None il return a list
if isinstance(value, list):
value = None
self._validate(value)
index = self.__len__()
self._validate(value, index)
super(Multi, self).append(value)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path,
self,
validate_properties=not force)
if not force and self.opt.impl_get_multitype() == multitypes.master:
for slave in self.opt.impl_get_master_slaves():
path = values._get_opt_path(slave)
if not values._is_default_owner(path):
if slave.impl_has_callback():
index = self.__len__() - 1
dvalue = values._getcallback_value(slave, index=index)
else:
dvalue = slave.impl_getdefault_multi()
@@ -485,22 +538,26 @@ class Multi(list):
super(Multi, self).extend(iterable)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
def _validate(self, value):
def _validate(self, value, force_index):
if value is not None:
try:
self.opt._validate(value)
self.opt.impl_validate(value, context=self.context(),
force_index=force_index)
except ValueError as err:
raise ValueError(_("invalid value {0} "
"for option {1}: {2}"
"").format(str(value),
self.opt._name, err))
def pop(self, key, force=False):
def pop(self, index, force=False):
"""the list value can be updated (poped)
only if the option is a master
:param key: index of the element to pop
:return: the requested element
:param index: remove item a index
:type index: int
:param force: force pop item (withoud check master/slave)
:type force: boolean
:returns: item at index
"""
if not force:
if self.opt.impl_get_multitype() == multitypes.slave:
@@ -513,8 +570,8 @@ class Multi(list):
#get multi without valid properties
values.getitem(slave,
validate_properties=False
).pop(key, force=True)
).pop(index, force=True)
#set value without valid properties
ret = super(Multi, self).pop(key)
ret = super(Multi, self).pop(index)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
return ret