reduce memory usage
authorEmmanuel Garette <egarette@cadoles.com>
Sat, 25 Oct 2014 20:11:31 +0000 (22:11 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sat, 25 Oct 2014 20:11:31 +0000 (22:11 +0200)
ChangeLog
test/test_config.py
test/test_state.py
tiramisu/option/baseoption.py
tiramisu/option/option.py
tiramisu/option/optiondescription.py
tiramisu/setting.py
tiramisu/storage/dictionary/option.py
tiramisu/storage/sqlalchemy/option.py

index cf73539..02bb016 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -13,7 +13,7 @@ Sun Apr 27 10:32:40 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
 
        * behavior change in ChoiceOption:
        remove open_values, that no sens (no type validation is possible) if
-       you wan't something like open_values, please use a typed option and
+       you want something like open_values, please use a typed option and
        add impl_(s|g)et_information to add proposed values and use it in your
        code
        * add dynamic ChoiceOption:
index 0cefc13..ba16545 100644 (file)
@@ -343,4 +343,4 @@ def test_config_od_function():
         print cfg.impl_get_opt_by_path()
     except AttributeError, err:
         assert str(err) == _('unknown Option {0} in OptionDescription {1}'
-                             '').format('impl_get_opt_by_path', descr._name)
+                             '').format('impl_get_opt_by_path', descr.impl_getname())
index e66eb34..1a5d5bf 100644 (file)
@@ -12,7 +12,7 @@ from py.test import raises
 
 
 def return_value(value=None):
-        return value
+    return value
 
 
 def _get_slots(opt):
@@ -52,14 +52,20 @@ def _diff_opt(opt1, opt2):
         val2 = None
         try:
             val1 = getattr(opt1, attr)
-        except:
+            msg1 = "exists"
+        except Exception, err:
             err1 = True
+            msg1 = "not exists"
 
         try:
             val2 = getattr(opt2, attr)
+            msg2 = "exists"
         except:
             err2 = True
-        assert err1 == err2
+            msg2 = "not exists"
+
+        if not err1 == err2:
+            raise ValueError("{0} {1} before but {2} after for {3}".format(attr, msg1, msg2, opt1.impl_getname()))
         if val1 is None:
             assert val1 == val2
         elif attr == '_children':
@@ -81,19 +87,23 @@ def _diff_opt(opt1, opt2):
                     assert consistency[0] == val2[index][0]
                     for idx, opt in enumerate(consistency[1]):
                         assert opt._name == val2[index][1][idx]._name
-        elif attr == '_callback':
-            assert val1 == val2
-        elif attr == '_callback_params':
-            if val1 is not None:
-                for key, values in val1.items():
-                    for idx, value in enumerate(values):
-                        if isinstance(value, tuple):
-                            assert val1[key][idx][0]._name == val2[key][idx][0]._name
-                            assert val1[key][idx][1] == val2[key][idx][1]
+        elif attr == '_val_call':
+            for idx, v in enumerate(val1):
+                if v is None:
+                    assert val2[idx] is None
+                else:
+                    assert v[0] == val2[idx][0]
+                    if len(v) == 2:
+                        if v[1] is not None:
+                            for key, values in v[1].items():
+                                for i, value in enumerate(values):
+                                    if isinstance(value, tuple):
+                                        assert v[1][key][i][0].impl_getname() == val2[idx][1][key][i][0].impl_getname()
+                                        assert v[1][key][i][1] == val2[idx][1][key][i][1]
+                                    else:
+                                        assert v[1][key][i] == val2[idx][1][key][i]
                         else:
-                            assert val1[key][idx] == val2[key][idx]
-            else:
-                assert val1 == val2
+                            assert v[1] == val2[idx][1]
         elif attr == '_master_slaves':
             assert val1.master.impl_getname() == val2.master.impl_getname()
             sval1 = [opt.impl_getname() for opt in val1.slaves]
index 8eec7fa..3c3f3af 100644 (file)
@@ -19,7 +19,6 @@
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 import re
-from copy import copy
 from types import FunctionType
 import warnings
 
@@ -31,12 +30,8 @@ from tiramisu.storage import get_storages_option
 
 
 StorageBase = get_storages_option('base')
-
-
 submulti = 2
-
-
-allowed_character = '[a-z\d\-_]'
+allowed_character = '[a-zA-Z\d\-_]'
 name_regexp = re.compile(r'^[a-z]{0}*$'.format(allowed_character))
 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
                    'make_dict', 'unwrap_from_path', 'read_only',
@@ -101,86 +96,62 @@ def validate_callback(callback, callback_params, type_):
 class Base(StorageBase):
     __slots__ = tuple()
 
-    def impl_set_callback(self, callback, callback_params=None):
-        if callback is None and callback_params is not None:  # pragma: optional cover
-            raise ValueError(_("params defined for a callback function but "
-                               "no callback defined"
-                               " yet for option {0}").format(
-                                   self.impl_getname()))
-        self._validate_callback(callback, callback_params)
-        if callback is not None:
-            validate_callback(callback, callback_params, 'callback')
-            self._callback = callback
-            if callback_params is None:
-                self._callback_params = {}
-            else:
-                self._callback_params = callback_params
-
     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, warnings_only=False):
+                 properties=None, warnings_only=False, extra=None):
         if not valid_name(name):  # pragma: optional cover
             raise ValueError(_("invalid name: {0} for option").format(name))
-        self._name = name
-        self._readonly = False
-        self._informations = {}
-        self.impl_set_information('doc', doc)
         if requires is not None:
             self._calc_properties, self._requires = validate_requires_arg(
-                requires, self._name)
-        else:
-            self._calc_properties = frozenset()
-            self._requires = []
+                requires, name)
+        #else:
+        #    self._calc_properties = frozenset()
+        #    self._requires = []
         if not multi and default_multi is not None:  # pragma: optional cover
             raise ValueError(_("a default_multi is set whereas multi is False"
                              " in option: {0}").format(name))
-        if default_multi is not None:
-            try:
-                self._validate(default_multi)
-            except ValueError as err:  # pragma: optional cover
-                raise ValueError(_("invalid default_multi value {0} "
-                                   "for option {1}: {2}").format(
-                                       str(default_multi), name, err))
         if multi is True:
-            self._multi = 0
+            _multi = 0
         elif multi is False:
-            self._multi = 1
+            _multi = 1
         elif multi is submulti:
-            self._multi = submulti
-        if self._multi != 1:
-            if default is None:
-                default = []
-            self._default_multi = default_multi
+            _multi = submulti
         if properties is None:
             properties = tuple()
         if not isinstance(properties, tuple):  # pragma: optional cover
             raise TypeError(_('invalid properties type {0} for {1},'
                             ' must be a tuple').format(
                                 type(properties),
-                                self._name))
+                                name))
         if validator is not None:
             validate_callback(validator, validator_params, 'validator')
-            self._validator = validator
-            if validator_params is not None:
-                self._validator_params = validator_params
-        if self._calc_properties != frozenset([]) and properties is not tuple():  # pragma: optional cover
-            set_forbidden_properties = self._calc_properties & set(properties)
+            self._set_validator(validator, validator_params)
+        if self.impl_get_calc_properties() != frozenset([]) and properties is not tuple():  # pragma: optional cover
+            set_forbidden_properties = self.impl_get_calc_properties() & set(properties)
             if set_forbidden_properties != frozenset():
                 raise ValueError('conflict: properties already set in '
                                  'requirement {0}'.format(
                                      list(set_forbidden_properties)))
-        if multi and default is None:
-            self._default = []
-        else:
-            self._default = default
+        super(Base, self).__init__(name, _multi, warnings_only, doc, extra)
+        self._set_default_values(default, default_multi)
         if callback is not False:
             self.impl_set_callback(callback, callback_params)
         self._properties = properties
-        self._warnings_only = warnings_only
-        ret = super(Base, self).__init__()
-        self.impl_validate(self._default)
-        return ret
+
+    def impl_set_callback(self, callback, callback_params=None):
+        if callback is None and callback_params is not None:  # pragma: optional cover
+            raise ValueError(_("params defined for a callback function but "
+                               "no callback defined"
+                               " yet for option {0}").format(
+                                   self.impl_getname()))
+        if self.impl_get_callback()[0] is not None:
+            raise ConfigError(_("a callback is already set for option {0}, "
+                                "cannot set another one's".format(self.impl_getname())))
+        self._validate_callback(callback, callback_params)
+        if callback is not None:
+            validate_callback(callback, callback_params, 'callback')
+            self._set_callback(callback, callback_params)
 
     def impl_is_optiondescription(self):
         return self.__class__.__name__ in ['OptionDescription',
@@ -199,29 +170,6 @@ class BaseOption(Base):
     """
     __slots__ = tuple()
 
-    # information
-    def impl_set_information(self, key, value):
-        """updates the information's attribute
-        (which is a dictionary)
-
-        :param key: information's key (ex: "help", "doc"
-        :param value: information's value (ex: "the help string")
-        """
-        self._informations[key] = value
-
-    def impl_get_information(self, key, default=undefined):
-        """retrieves one information's item
-
-        :param key: the item string (ex: "help")
-        """
-        if key in self._informations:
-            return self._informations[key]
-        elif default is not undefined:  # pragma: optional cover
-            return default
-        else:  # pragma: optional cover
-            raise ValueError(_("information's item not found: {0}").format(
-                key))
-
     # ____________________________________________________________
     # serialize object
     def _impl_convert_requires(self, descr, load=False):
@@ -231,16 +179,15 @@ class BaseOption(Base):
         :param load: `True` if we are at the init of the option description
         :type load: bool
         """
-        if not load and self._requires is None:
+        if not load and self.impl_getrequires() is None:
             self._state_requires = None
         elif load and self._state_requires is None:
-            self._requires = None
             del(self._state_requires)
         else:
             if load:
                 _requires = self._state_requires
             else:
-                _requires = self._requires
+                _requires = self.impl_getrequires()
             new_value = []
             for requires in _requires:
                 new_requires = []
@@ -254,7 +201,8 @@ class BaseOption(Base):
                 new_value.append(tuple(new_requires))
             if load:
                 del(self._state_requires)
-                self._requires = new_value
+                if new_value != []:
+                    self._requires = new_value
             else:
                 self._state_requires = new_value
 
@@ -262,12 +210,10 @@ class BaseOption(Base):
         if self.__class__.__name__ == 'OptionDescription' or \
                 isinstance(self, SymLinkOption):
             return
-        if not load and self._callback is None:
+        if not load and self.impl_get_callback() is None:
             self._state_callback = None
             self._state_callback_params = {}
         elif load and self._state_callback is None:
-            self._callback = None
-            self._callback_params = {}
             del(self._state_callback)
             del(self._state_callback_params)
         else:
@@ -275,8 +221,7 @@ class BaseOption(Base):
                 callback = self._state_callback
                 callback_params = self._state_callback_params
             else:
-                callback = self._callback
-                callback_params = self._callback_params
+                callback, callback_params = self.impl_get_callback()
                 self._state_callback_params = {}
             if callback_params is not None:
                 cllbck_prms = {}
@@ -298,8 +243,7 @@ class BaseOption(Base):
             if load:
                 del(self._state_callback)
                 del(self._state_callback_params)
-                self._callback = callback
-                self._callback_params = cllbck_prms
+                self._set_callback(callback, cllbck_prms)
             else:
                 self._state_callback = callback
                 self._state_callback_params = cllbck_prms
@@ -311,11 +255,11 @@ class BaseOption(Base):
 
         :param descr: the parent :class:`tiramisu.option.OptionDescription`
         """
+        #super(BaseOption, self)._impl_getstate()
         self._stated = True
         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
@@ -366,8 +310,6 @@ class BaseOption(Base):
             if func.startswith('_impl_convert_'):
                 getattr(self, func)(descr, load=True)
         try:
-            self._readonly = self._state_readonly
-            del(self._state_readonly)
             del(self._stated)
         except AttributeError:  # pragma: optional cover
             pass
@@ -401,7 +343,7 @@ class BaseOption(Base):
             # never change _name
             if name == '_name':
                 try:
-                    if self._name is not None:
+                    if self.impl_getname() is not None:
                         #so _name is already set
                         is_readonly = True
                 except (KeyError, AttributeError):
@@ -412,30 +354,16 @@ class BaseOption(Base):
                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
                                        " read-only").format(
                                            self.__class__.__name__,
-                                           self._name,
+                                           self.impl_getname(),
                                            name))
         super(BaseOption, self).__setattr__(name, value)
 
-    def impl_is_readonly(self):
-        try:
-            if self._readonly is True:
-                return True
-        except AttributeError:
-            pass
-        return False
-
-    def impl_getname(self):
-        return self._name
-
     def impl_getpath(self, context):
         return context.cfgimpl_get_description().impl_get_path_by_opt(self)
 
-    def impl_get_callback(self):
-        return self._callback, self._callback_params
-
     def impl_has_callback(self):
         "to know if a callback has been defined or not"
-        return self._callback is not None
+        return self.impl_get_callback()[0] is not None
 
     def _is_subdyn(self):
         try:
@@ -464,40 +392,6 @@ class Option(OnlyOption):
     __slots__ = tuple()
     _empty = ''
 
-    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, warnings_only=False):
-        """
-        :param name: the option's name
-        :param doc: the option's description
-        :param default: specifies the default value of the option,
-                        for a multi : ['bla', 'bla', 'bla']
-        :param default_multi: 'bla' (used in case of a reset to default only at
-                        a given index)
-        :param requires: is a list of names of options located anywhere
-                         in the configuration.
-        :param multi: if true, the option's value is a list
-        :param callback: the name of a function. If set, the function's output
-                         is responsible of the option's value
-        :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_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, default, default_multi,
-                                     requires, multi, callback,
-                                     callback_params, validator,
-                                     validator_params, properties,
-                                     warnings_only)
-
-    def impl_getrequires(self):
-        return self._requires
-
     def _launch_consistency(self, func, option, value, context, index,
                             submulti_index, all_cons_opts, warnings_only):
         """Launch consistency now
@@ -581,24 +475,25 @@ class Option(OnlyOption):
             current_opt = self
 
         def val_validator(val):
-            if self._validator is not None:
-                if self._validator_params is not None:
-                    validator_params = {}
-                    for val_param, values in self._validator_params.items():
-                        validator_params[val_param] = values
+            validator, validator_params = self.impl_get_validator()
+            if validator is not None:
+                if validator_params != {}:
+                    validator_params_ = {}
+                    for val_param, values in validator_params.items():
+                        validator_params_[val_param] = values
                     #inject value in calculation
-                    if '' in validator_params:
-                        lst = list(validator_params[''])
+                    if '' in validator_params_:
+                        lst = list(validator_params_[''])
                         lst.insert(0, val)
-                        validator_params[''] = tuple(lst)
+                        validator_params_[''] = tuple(lst)
                     else:
-                        validator_params[''] = (val,)
+                        validator_params_[''] = (val,)
                 else:
-                    validator_params = {'': (val,)}
+                    validator_params_ = {'': (val,)}
                 # Raise ValueError if not valid
                 carry_out_calculation(self, config=context,
-                                      callback=self._validator,
-                                      callback_params=validator_params)
+                                      callback=validator,
+                                      callback_params=validator_params_)
 
         def do_validation(_value, _index, submulti_index):
             if _value is None:
@@ -622,11 +517,11 @@ class Option(OnlyOption):
                 if context is not undefined:
                     descr._valid_consistency(current_opt, _value, context,
                                              _index, submulti_index)
-                self._second_level_validation(_value, self._warnings_only)
+                self._second_level_validation(_value, self._is_warnings_only())
             except ValueError as error:
                 log.debug(_('do_validation for {0}: error in value').format(
                     self.impl_getname()), exc_info=True)
-                if self._warnings_only:
+                if self._is_warnings_only():
                     warning = error
                     error = None
             except ValueWarning as warning:
@@ -655,7 +550,7 @@ class Option(OnlyOption):
                                        self.__class__.__name__, 0)
             elif error:
                 raise ValueError(_("invalid value for option {0}: {1}").format(
-                    self._name, error))
+                    self.impl_getname(), error))
 
         # generic calculation
         if context is not undefined:
@@ -690,16 +585,6 @@ class Option(OnlyOption):
                 else:
                     do_validation(val, idx, force_submulti_index)
 
-    def impl_getdefault(self):
-        "accessing the default value"
-        if isinstance(self._default, list):
-            return copy(self._default)
-        return self._default
-
-    def impl_getdefault_multi(self):
-        "accessing the default value for a multi"
-        return self._default_multi
-
     def impl_is_master_slaves(self, type_='both'):
         """FIXME
         """
@@ -720,9 +605,9 @@ class Option(OnlyOption):
 
     def impl_is_empty_by_default(self):
         "no default value has been set yet"
-        if ((not self.impl_is_multi() and self._default is None) or
-                (self.impl_is_multi() and (self._default == []
-                                           or None in self._default))):
+        if ((not self.impl_is_multi() and self.impl_getdefault() is None) or
+                (self.impl_is_multi() and (self.impl_getdefault() == []
+                                           or None in self.impl_getdefault()))):
             return True
         return False
 
@@ -733,12 +618,6 @@ class Option(OnlyOption):
     #def impl_getkey(self, value):
     #    return value
 
-    def impl_is_multi(self):
-        return self._multi == 0 or self._multi is submulti
-
-    def impl_is_submulti(self):
-        return self._multi is submulti
-
     def impl_add_consistency(self, func, *other_opts, **params):
         """Add consistency means that value will be validate with other_opts
         option's values.
@@ -753,7 +632,7 @@ class Option(OnlyOption):
             raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
                                    " read-only").format(
                                        self.__class__.__name__,
-                                       self._name))
+                                       self.impl_getname()))
         warnings_only = params.get('warnings_only', False)
         if self._is_subdyn():
             dynod = self._impl_getsubdyn()
@@ -821,16 +700,15 @@ class Option(OnlyOption):
         :param load: `True` if we are at the init of the option description
         :type load: bool
         """
-        if not load and self._consistencies is None:
+        if not load and self._get_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
+                consistencies = self._get_consistencies()
             new_value = []
             for consistency in consistencies:
                 values = []
@@ -842,7 +720,8 @@ class Option(OnlyOption):
                 new_value.append((consistency[0], tuple(values), consistency[2]))
             if load:
                 del(self._state_consistencies)
-                self._consistencies = new_value
+                for new_val in new_value:
+                    self._add_consistency(new_val[0], new_val[1], new_val[2])
             else:
                 self._state_consistencies = new_value
 
@@ -854,14 +733,14 @@ class Option(OnlyOption):
 
     def _validate_callback(self, callback, callback_params):
         try:
-            default_multi = self._default_multi
+            default_multi = self.impl_getdefault_multi()
         except AttributeError:
             default_multi = None
-        if callback is not None and ((self._multi == 1 and
-                                      (self._default is not None or
+        if callback is not None and ((not self.impl_is_multi() and
+                                      (self.impl_getdefault() is not None or
                                        default_multi is not None))
-                                     or (self._multi != 1 and
-                                         (self._default != [] or
+                                     or (self.impl_is_multi() and
+                                         (self.impl_getdefault() != [] or
                                           default_multi is not None))
                                      ):  # pragma: optional cover
             raise ValueError(_("default value not allowed if option: {0} "
@@ -885,7 +764,7 @@ def validate_requires_arg(requires, name):
     # start parsing all requires given by user (has dict)
     # transforme it to a tuple
     for require in requires:
-        if not type(require) == dict:  # pragma: optional cover
+        if not isinstance(require, dict):  # pragma: optional cover
             raise ValueError(_("malformed requirements type for option:"
                                " {0}, must be a dict").format(name))
         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
@@ -963,46 +842,57 @@ def validate_requires_arg(requires, name):
 
 
 class SymLinkOption(OnlyOption):
-    __slots__ = ('_opt', '_state_opt')
+    __slots__ = ('_opt', '_state_opt', '_readonly')
 
     def __init__(self, name, opt):
-        self._name = name
         if not isinstance(opt, Option):  # pragma: optional cover
             raise ValueError(_('malformed symlinkoption '
                                'must be an option '
                                'for symlink {0}').format(name))
         self._opt = opt
-        self._readonly = True
-        super(Base, self).__init__()
+        self._set_readonly()
+        super(Base, self).__init__(name, undefined, undefined, undefined, undefined)
 
     def __getattr__(self, name, context=undefined):
-        if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath'):
+        if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath', '_name', '_state_opt'):
             return object.__getattr__(self, name)
         else:
             return getattr(self._opt, name)
 
     def _impl_getstate(self, descr):
-        super(SymLinkOption, self)._impl_getstate(descr)
+        self._stated = True
         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)
+        try:
+            del(self._stated)
+        except AttributeError:  # pragma: optional cover
+            pass
+        self._set_readonly()
 
     def impl_get_information(self, key, default=undefined):
         return self._opt.impl_get_information(key, default)
 
-#FIXME utile tout ca ? c'est un peu de la duplication ...
+    def _set_readonly(self):
+        self._readonly = True
+
+    def impl_is_readonly(self):
+        try:
+            return self._readonly
+        except AttributeError:
+            return False
+
     def impl_getproperties(self):
         return self._opt._properties
 
     def impl_get_callback(self):
-        return self._opt._callback, self._opt._callback_params
+        return self._opt.impl_get_callback()
 
     def impl_has_callback(self):
         "to know if a callback has been defined or not"
-        return self._opt._callback is not None
+        return self._opt.impl_has_callback()
 
     def _is_subdyn(self):
         try:
index 1f5df9d..687a5a8 100644 (file)
@@ -155,8 +155,8 @@ class IPOption(Option):
                  callback_params=None, validator=None, validator_params=None,
                  properties=None, private_only=False, allow_reserved=False,
                  warnings_only=False):
-        self._extra = {'_private_only': private_only,
-                       '_allow_reserved': allow_reserved}
+        extra = {'_private_only': private_only,
+                 '_allow_reserved': allow_reserved}
         super(IPOption, self).__init__(name, doc, default=default,
                                        default_multi=default_multi,
                                        callback=callback,
@@ -166,7 +166,8 @@ class IPOption(Option):
                                        validator=validator,
                                        validator_params=validator_params,
                                        properties=properties,
-                                       warnings_only=warnings_only)
+                                       warnings_only=warnings_only,
+                                       extra=extra)
 
     def _validate(self, value, context=undefined):
         # sometimes an ip term starts with a zero
@@ -186,13 +187,13 @@ class IPOption(Option):
 
     def _second_level_validation(self, value, warnings_only):
         ip = IP('{0}/32'.format(value))
-        if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED':  # pragma: optional cover
+        if not self._get_extra('_allow_reserved') and ip.iptype() == 'RESERVED':  # pragma: optional cover
             if warnings_only:
                 msg = _("IP is in reserved class")
             else:
                 msg = _("invalid IP, mustn't be in reserved class")
             raise ValueError(msg)
-        if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE':  # pragma: optional cover
+        if self._get_extra('_private_only') and not ip.iptype() == 'PRIVATE':  # pragma: optional cover
             if warnings_only:
                 msg = _("IP is not in private class")
             else:
@@ -257,7 +258,6 @@ class PortOption(Option):
         if extra['_max_value'] is None:
             raise ValueError(_('max value is empty'))  # pragma: optional cover
 
-        self._extra = extra
         super(PortOption, self).__init__(name, doc, default=default,
                                          default_multi=default_multi,
                                          callback=callback,
@@ -267,10 +267,11 @@ class PortOption(Option):
                                          validator=validator,
                                          validator_params=validator_params,
                                          properties=properties,
-                                         warnings_only=warnings_only)
+                                         warnings_only=warnings_only,
+                                         extra=extra)
 
     def _validate(self, value, context=undefined):
-        if self._extra['_allow_range'] and ":" in str(value):  # pragma: optional cover
+        if self._get_extra('_allow_range') and ":" in str(value):  # pragma: optional cover
             value = str(value).split(':')
             if len(value) != 2:
                 raise ValueError(_('invalid port, range must have two values '
@@ -286,10 +287,10 @@ class PortOption(Option):
                 val = int(val)
             except ValueError:  # pragma: optional cover
                 raise ValueError(_('invalid port'))
-            if not self._extra['_min_value'] <= val <= self._extra['_max_value']:  # pragma: optional cover
+            if not self._get_extra('_min_value') <= val <= self._get_extra('_max_value'):  # pragma: optional cover
                 raise ValueError(_('invalid port, must be an between {0} '
-                                   'and {1}').format(self._extra['_min_value'],
-                                                     self._extra['_max_value']))
+                                   'and {1}').format(self._get_extra('_min_value'),
+                                                     self._get_extra('_max_value')))
 
 
 class NetworkOption(Option):
@@ -400,34 +401,34 @@ class DomainnameOption(Option):
                  warnings_only=False, allow_without_dot=False):
         if type_ not in ['netbios', 'hostname', 'domainname']:
             raise ValueError(_('unknown type_ {0} for hostname').format(type_))  # pragma: optional cover
-        self._extra = {'_dom_type': type_}
+        extra = {'_dom_type': type_}
         if allow_ip not in [True, False]:
             raise ValueError(_('allow_ip must be a boolean'))  # pragma: optional cover
         if allow_without_dot not in [True, False]:
             raise ValueError(_('allow_without_dot must be a boolean'))  # pragma: optional cover
-        self._extra['_allow_ip'] = allow_ip
-        self._extra['_allow_without_dot'] = allow_without_dot
+        extra['_allow_ip'] = allow_ip
+        extra['_allow_without_dot'] = allow_without_dot
         end = ''
         extrachar = ''
         extrachar_mandatory = ''
-        if self._extra['_dom_type'] != 'netbios':
+        if extra['_dom_type'] != 'netbios':
             allow_number = '\d'
         else:
             allow_number = ''  # pragma: optional cover
-        if self._extra['_dom_type'] == 'netbios':
+        if extra['_dom_type'] == 'netbios':
             length = 14  # pragma: optional cover
-        elif self._extra['_dom_type'] == 'hostname':
+        elif extra['_dom_type'] == 'hostname':
             length = 62  # pragma: optional cover
-        elif self._extra['_dom_type'] == 'domainname':
+        elif extra['_dom_type'] == 'domainname':
             length = 62
             if allow_without_dot is False:
                 extrachar_mandatory = '\.'
             else:
                 extrachar = '\.'  # pragma: optional cover
             end = '+[a-z]*'
-        self._extra['_domain_re'] = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
-                                               ''.format(allow_number, extrachar, length,
-                                                         extrachar_mandatory, end))
+        extra['_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,
@@ -437,23 +438,25 @@ class DomainnameOption(Option):
                                                validator=validator,
                                                validator_params=validator_params,
                                                properties=properties,
-                                               warnings_only=warnings_only)
+                                               warnings_only=warnings_only,
+                                               extra=extra)
 
     def _validate(self, value, context=undefined):
-        if self._extra['_allow_ip'] is True:  # pragma: optional cover
+        if self._get_extra('_allow_ip') is True:  # pragma: optional cover
             try:
                 IP('{0}/32'.format(value))
                 return
             except ValueError:
                 pass
-        if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \
+        if self._get_extra('_dom_type') == 'domainname' and \
+                not self._get_extra('_allow_without_dot') and \
                 '.' not in value:  # pragma: optional cover
             raise ValueError(_("invalid domainname, must have dot"))
         if len(value) > 255:
             raise ValueError(_("invalid domainname's length (max 255)"))  # pragma: optional cover
         if len(value) < 2:
             raise ValueError(_("invalid domainname's length (min 2)"))  # pragma: optional cover
-        if not self._extra['_domain_re'].search(value):
+        if not self._get_extra('_domain_re').search(value):
             raise ValueError(_('invalid domainname'))  # pragma: optional cover
 
 
index f9e9401..fa8bdcc 100644 (file)
@@ -79,9 +79,6 @@ class OptionDescription(BaseOption, StorageOptionDescription):
         self._group_type = groups.default
         self._is_build_cache = False
 
-    def impl_getrequires(self):
-        return self._requires
-
     def impl_getdoc(self):
         return self.impl_get_information('doc')
 
@@ -141,11 +138,11 @@ class OptionDescription(BaseOption, StorageOptionDescription):
             if option._get_id() in cache_option:  # pragma: optional cover
                 raise ConflictError(_('duplicate option: {0}').format(option))
             cache_option.append(option._get_id())
-            option._readonly = True
+            option._set_readonly()
             if isinstance(option, OptionDescription):
                 option.impl_validate_options(cache_option)
         if init:
-            self._readonly = True
+            self._set_readonly()
 
     # ____________________________________________________________
     def impl_set_group_type(self, group_type):
@@ -281,7 +278,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
 
     def _impl_get_dynchild(self, child, suffix):
         name = child.impl_getname() + suffix
-        path = self._name + suffix + '.' + name
+        path = self.impl_getname() + suffix + '.' + name
         if isinstance(child, OptionDescription):
             return SynDynOptionDescription(child, name, path, suffix)
         else:
@@ -289,7 +286,7 @@ class OptionDescription(BaseOption, StorageOptionDescription):
 
     def _impl_getchildren(self, dyn=True, context=undefined):
         for child in self._impl_st_getchildren(context):
-            cname = child._name
+            cname = child.impl_getname()
             if dyn and child.impl_is_dynoptiondescription():
                 path = cname
                 for value in child._impl_get_suffixes(context):
index d2435f2..eae628e 100644 (file)
@@ -251,7 +251,7 @@ class Property(object):
         :type propname: string
         """
         if self._opt is not None and self._opt.impl_getrequires() is not None \
-                and propname in self._opt._calc_properties:  # pragma: optional cover
+                and propname in self._opt.impl_get_calc_properties():  # pragma: optional cover
             raise ValueError(_('cannot append {0} property for option {1}: '
                                'this property is calculated').format(
                                    propname, self._opt.impl_getname()))
@@ -573,13 +573,13 @@ class Settings(object):
         :param path: the option's path in the config
         :type path: str
         """
-        if opt._requires is None:
+        if opt.impl_getrequires() == []:
             return frozenset()
 
         # filters the callbacks
         calc_properties = set()
         context = self._getcontext()
-        for requires in opt._requires:
+        for requires in opt.impl_getrequires():
             for require in requires:
                 option, expected, action, inverse, \
                     transitive, same_action = require
index 3cb61e7..f58826e 100644 (file)
@@ -25,49 +25,188 @@ from tiramisu.error import ConfigError
 #____________________________________________________________
 #
 # Base
+#('_name', '_informations', '_multi', '_multitype', '_warnings_only', '_extra', '_readonly', '_subdyn)
 class StorageBase(object):
-    __slots__ = ('_name', '_requires', '_properties', '_readonly',
-                 '_calc_properties', '_informations',
-                 '_state_readonly', '_state_requires', '_stated',
-                 '_multi', '_validator', '_validator_params',
-                 '_default', '_default_multi', '_state_callback', '_callback',
-                 '_state_callback_params', '_callback_params', '_multitype',
-                 '_consistencies', '_warnings_only', '_master_slaves',
-                 '_state_consistencies', '_extra', '_subdyn', '__weakref__',
-                 '_state_master_slaves', '_choice_values',
-                 '_choice_values_params')
-
-    def __init__(self):
+    __slots__ = ('_name',
+                 '_informations',
+                 '_multi',
+                 '_extra',
+                 '_warnings_only',
+                 #valeur
+                 '_default',
+                 '_default_multi',
+                 #calcul
+                 '_subdyn',
+                 '_requires',
+                 '_properties',
+                 '_calc_properties',
+                 '_val_call',
+                 #'_validator',
+                 #'_validator_params',
+                 #'_callback',
+                 #'_callback_params',
+                 '_consistencies',
+                 '_master_slaves',
+                 '_choice_values',
+                 '_choice_values_params',
+                 #autre
+                 '_state_master_slaves',
+                 '_state_callback',
+                 '_state_callback_params',
+                 '_state_requires',
+                 '_stated',
+                 '_state_consistencies',
+                 '_state_informations',
+                 '_state_readonly',
+                 '__weakref__'
+                 )
+
+    def __init__(self, name, multi, warnings_only, doc, extra):
+        self._name = name
+        if doc is not undefined:
+            self._informations = {'doc': doc}
+            if multi != 1:
+                self._multi = multi
+            if extra is not None:
+                self._extra = extra
+        if warnings_only is True:
+            self._warnings_only = warnings_only
+
+    def _set_default_values(self, default, default_multi):
+        if self.impl_is_multi() and default is None:
+                default = []
+        self.impl_validate(default)
+        if ((self.impl_is_multi() and default != tuple()) or
+                (not self.impl_is_multi() and default is not None)):
+            if self.impl_is_multi():
+                default = tuple(default)
+            self._default = default
+
+        if self.impl_is_multi() and default_multi is not None:
+            try:
+                self._validate(default_multi)
+            except ValueError as err:  # pragma: optional cover
+                raise ValueError(_("invalid default_multi value {0} "
+                                   "for option {1}: {2}").format(
+                                       str(default_multi),
+                                       self.impl_getname(), err))
+            self._default_multi = default_multi
+
+    # information
+    def impl_set_information(self, key, value):
+        """updates the information's attribute
+        (which is a dictionary)
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._informations[key] = value
+
+    def impl_get_information(self, key, default=undefined):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        error = False
+        dico = self._informations
+        if dico is None or isinstance(dico, str) or isinstance(dico, unicode):
+            if key == 'doc':
+                return dico
+            error = True
+        elif isinstance(dico, tuple):
+            try:
+                return dico[1][dico[0].index(key)]
+            except AttributeError:
+                if default is not undefined:
+                    return default
+                error = True
+        else:
+            if default is not undefined:
+                return self._informations.get(key, default)
+            try:
+                return self._informations[key]
+            except KeyError:  # pragma: optional cover
+                error = True
+        if error:
+            raise ValueError(_("information's item not found: {0}").format(
+                key))
+
+    def _add_consistency(self, func, all_cons_opts, params):
+        cons = (func, all_cons_opts, params)
         try:
-            self._subdyn
+            self._consistencies.append(cons)
         except AttributeError:
-            self._subdyn = None
+            self._consistencies = [cons]
+
+    def _get_consistencies(self):
         try:
-            self._consistencies
+            return self._consistencies
         except AttributeError:
-            self._consistencies = []
+            return tuple()
+
+    def _set_callback(self, callback, callback_params):
+        if callback_params is None or callback_params == {}:
+            val_call = (callback,)
+        else:
+            val_call = tuple([callback, callback_params])
         try:
-            self._callback
+            self._val_call = (self._val_call[0], val_call)
         except AttributeError:
-            self._callback = None
+            self._val_call = (None, val_call)
+
+    def impl_get_callback(self):
+        default = (None, {})
         try:
-            self._callback_params
-        except AttributeError:
-            self._callback_params = {}
+            call = self._val_call[1]
+        except (AttributeError, IndexError):
+            ret_call = default
+        else:
+            if call is None:
+                ret_call = default
+            else:
+                if len(call) == 1:
+                    ret_call = (call[0], default[1])
+                else:
+                    ret_call = call
+        return ret_call
+
+    def impl_get_calc_properties(self):
         try:
-            self._validator
+            return self._calc_properties
         except AttributeError:
-            self._validator = None
+            return frozenset()
+
+    def impl_getrequires(self):
         try:
-            self._validator_params
+            return self._requires
         except AttributeError:
-            self._validator_params = None
+            return []
 
-    def _add_consistency(self, func, all_cons_opts, params):
-        self._consistencies.append((func, all_cons_opts, params))
+    def _set_validator(self, validator, validator_params):
+        if validator_params is None:
+            val_call = (validator,)
+        else:
+            val_call = (validator, validator_params)
+        try:
+            self._val_call = (val_call, self._val_call[1])
+        except (AttributeError, IndexError):
+            self._val_call = (val_call, None)
 
-    def _get_consistencies(self):
-        return self._consistencies
+    def impl_get_validator(self):
+        default = (None, {})
+        try:
+            val = self._val_call[0]
+        except (AttributeError, IndexError):
+            ret_val = default
+        else:
+            if val is None:
+                ret_val = default
+            else:
+                if len(val) == 1:
+                    ret_val = (val[0], default[1])
+                else:
+                    ret_val = val
+        return ret_val
 
     def _get_id(self):
         return id(self)
@@ -75,9 +214,103 @@ class StorageBase(object):
     def _impl_getsubdyn(self):
         return self._subdyn
 
+    def _set_readonly(self):
+        if not self.impl_is_readonly():
+            dico = self._informations
+            if not (dico is None or isinstance(dico, str) or isinstance(dico, unicode)):
+                keys = tuple(dico.keys())
+                if keys == ('doc',):
+                    dico = dico['doc']
+                else:
+                    dico = tuple([tuple(dico.keys()), tuple(dico.values())])
+            self._informations = dico
+            try:
+                extra = self._extra
+                self._extra = tuple([tuple(extra.keys()), tuple(extra.values())])
+            except AttributeError:
+                pass
+
     def _impl_setsubdyn(self, subdyn):
         self._subdyn = subdyn
 
+    def _impl_convert_informations(self, descr, load=False):
+        if not load:
+            infos = self._informations
+            if isinstance(infos, tuple):
+                self._state_informations = {}
+                for key, value in infos:
+                    self._state_informations[key] = value
+            elif isinstance(infos, str) or isinstance(infos, unicode):
+                self._state_informations = {'doc': infos}
+            else:
+                self._state_informations = infos
+            self._state_readonly = self.impl_is_readonly()
+        else:
+            try:
+                self._informations = self._state_informations
+                del(self._state_informations)
+            except AttributeError:
+                pass
+            if self._state_readonly:
+                self._set_readonly()
+            del(self._state_readonly)
+
+    def impl_is_readonly(self):
+        try:
+            return not isinstance(self._informations, dict)
+        except AttributeError:
+            return False
+
+    def impl_getname(self):
+        return self._name
+
+    def impl_is_multi(self):
+        try:
+            _multi = self._multi
+        except AttributeError:
+            _multi = 1
+        return _multi == 0 or _multi == 2
+
+    def impl_is_submulti(self):
+        try:
+            _multi = self._multi
+        except IndexError:
+            _multi = 1
+        return _multi == 2
+
+    def _get_extra(self, key):
+        extra = self._extra
+        if isinstance(extra, tuple):
+            return extra[1][extra[0].index(key)]
+        else:
+            return extra[key]
+
+    def _is_warnings_only(self):
+        try:
+            return self._warnings_only
+        except:
+            return False
+
+    def impl_getdefault(self):
+        "accessing the default value"
+        try:
+            ret = self._default
+            if self.impl_is_multi():
+                return list(ret)
+            return ret
+        except AttributeError:
+            if self.impl_is_multi():
+                return []
+            return None
+
+    def impl_getdefault_multi(self):
+        "accessing the default value for a multi"
+        if self.impl_is_multi():
+            try:
+                return self._default_multi
+            except (AttributeError, IndexError):
+                pass
+
     def commit(self):
         pass
 
@@ -86,7 +319,8 @@ class StorageOptionDescription(StorageBase):
     __slots__ = ('_children', '_cache_paths', '_cache_consistencies',
                  '_group_type', '_is_build_cache', '_state_group_type')
 
-    def __init__(self):
+    def __init__(self, name, multi, warnings_only, doc, extra):
+        super(StorageOptionDescription, self).__init__(name, multi, warnings_only, doc, None)
         self._cache_paths = None
 
     def _add_children(self, child_names, children):
@@ -128,7 +362,7 @@ class StorageOptionDescription(StorageBase):
             cache_path = []
             cache_option = []
         for option in self._impl_getchildren(dyn=False):
-            attr = option._name
+            attr = option.impl_getname()
             path = str('.'.join(_currpath + [attr]))
             cache_option.append(option)
             cache_path.append(path)
@@ -272,7 +506,7 @@ class StorageOptionDescription(StorageBase):
         if error:
             raise AttributeError(_('unknown Option {0} '
                                    'in OptionDescription {1}'
-                                   '').format(name, self._name))
+                                   '').format(name, self.impl_getname()))
 
     def _get_force_store_value(self):
         #FIXME faire des tests (notamment pas ajouter à un config)
index a2c103f..6578189 100644 (file)
@@ -254,7 +254,6 @@ class _Base(SqlAlchemyBase):
     _reqs = relationship("_Require", collection_class=list)
     _requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
     _multi = Column(Integer)
-    _multitype = Column(String)
     ######
     _callback = Column(PickleType)
     _call_params = relationship('_CallbackParam',